]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "strings" | |
6 | ||
7 | "github.com/hashicorp/go-multierror" | |
8 | "github.com/hashicorp/terraform/config" | |
9 | "github.com/hashicorp/terraform/dag" | |
10 | ) | |
11 | ||
12 | // GraphSemanticChecker is the interface that semantic checks across | |
13 | // the entire Terraform graph implement. | |
14 | // | |
15 | // The graph should NOT be modified by the semantic checker. | |
16 | type GraphSemanticChecker interface { | |
17 | Check(*dag.Graph) error | |
18 | } | |
19 | ||
20 | // UnorderedSemanticCheckRunner is an implementation of GraphSemanticChecker | |
21 | // that runs a list of SemanticCheckers against the vertices of the graph | |
22 | // in no specified order. | |
23 | type UnorderedSemanticCheckRunner struct { | |
24 | Checks []SemanticChecker | |
25 | } | |
26 | ||
27 | func (sc *UnorderedSemanticCheckRunner) Check(g *dag.Graph) error { | |
28 | var err error | |
29 | for _, v := range g.Vertices() { | |
30 | for _, check := range sc.Checks { | |
31 | if e := check.Check(g, v); e != nil { | |
32 | err = multierror.Append(err, e) | |
33 | } | |
34 | } | |
35 | } | |
36 | ||
37 | return err | |
38 | } | |
39 | ||
40 | // SemanticChecker is the interface that semantic checks across the | |
41 | // Terraform graph implement. Errors are accumulated. Even after an error | |
42 | // is returned, child vertices in the graph will still be visited. | |
43 | // | |
44 | // The graph should NOT be modified by the semantic checker. | |
45 | // | |
46 | // The order in which vertices are visited is left unspecified, so the | |
47 | // semantic checks should not rely on that. | |
48 | type SemanticChecker interface { | |
49 | Check(*dag.Graph, dag.Vertex) error | |
50 | } | |
51 | ||
52 | // smcUserVariables does all the semantic checks to verify that the | |
53 | // variables given satisfy the configuration itself. | |
54 | func smcUserVariables(c *config.Config, vs map[string]interface{}) []error { | |
55 | var errs []error | |
56 | ||
57 | cvs := make(map[string]*config.Variable) | |
58 | for _, v := range c.Variables { | |
59 | cvs[v.Name] = v | |
60 | } | |
61 | ||
62 | // Check that all required variables are present | |
63 | required := make(map[string]struct{}) | |
64 | for _, v := range c.Variables { | |
65 | if v.Required() { | |
66 | required[v.Name] = struct{}{} | |
67 | } | |
68 | } | |
69 | for k, _ := range vs { | |
70 | delete(required, k) | |
71 | } | |
72 | if len(required) > 0 { | |
73 | for k, _ := range required { | |
74 | errs = append(errs, fmt.Errorf( | |
75 | "Required variable not set: %s", k)) | |
76 | } | |
77 | } | |
78 | ||
79 | // Check that types match up | |
80 | for name, proposedValue := range vs { | |
81 | // Check for "map.key" fields. These stopped working with Terraform | |
82 | // 0.7 but we do this to surface a better error message informing | |
83 | // the user what happened. | |
84 | if idx := strings.Index(name, "."); idx > 0 { | |
85 | key := name[:idx] | |
86 | if _, ok := cvs[key]; ok { | |
87 | errs = append(errs, fmt.Errorf( | |
88 | "%s: Overriding map keys with the format `name.key` is no "+ | |
89 | "longer allowed. You may still override keys by setting "+ | |
90 | "`name = { key = value }`. The maps will be merged. This "+ | |
91 | "behavior appeared in 0.7.0.", name)) | |
92 | continue | |
93 | } | |
94 | } | |
95 | ||
96 | schema, ok := cvs[name] | |
97 | if !ok { | |
98 | continue | |
99 | } | |
100 | ||
101 | declaredType := schema.Type() | |
102 | ||
103 | switch declaredType { | |
104 | case config.VariableTypeString: | |
105 | switch proposedValue.(type) { | |
106 | case string: | |
107 | continue | |
108 | } | |
109 | case config.VariableTypeMap: | |
110 | switch v := proposedValue.(type) { | |
111 | case map[string]interface{}: | |
112 | continue | |
113 | case []map[string]interface{}: | |
114 | // if we have a list of 1 map, it will get coerced later as needed | |
115 | if len(v) == 1 { | |
116 | continue | |
117 | } | |
118 | } | |
119 | case config.VariableTypeList: | |
120 | switch proposedValue.(type) { | |
121 | case []interface{}: | |
122 | continue | |
123 | } | |
124 | } | |
125 | errs = append(errs, fmt.Errorf("variable %s should be type %s, got %s", | |
126 | name, declaredType.Printable(), hclTypeName(proposedValue))) | |
127 | } | |
128 | ||
129 | // TODO(mitchellh): variables that are unknown | |
130 | ||
131 | return errs | |
132 | } |