]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | "reflect" | |
7 | "strconv" | |
8 | "strings" | |
9 | ||
10 | "github.com/hashicorp/terraform/config" | |
11 | "github.com/hashicorp/terraform/config/module" | |
12 | "github.com/hashicorp/terraform/helper/hilmapstructure" | |
13 | ) | |
14 | ||
15 | // EvalTypeCheckVariable is an EvalNode which ensures that the variable | |
16 | // values which are assigned as inputs to a module (including the root) | |
17 | // match the types which are either declared for the variables explicitly | |
18 | // or inferred from the default values. | |
19 | // | |
20 | // In order to achieve this three things are required: | |
21 | // - a map of the proposed variable values | |
22 | // - the configuration tree of the module in which the variable is | |
23 | // declared | |
24 | // - the path to the module (so we know which part of the tree to | |
25 | // compare the values against). | |
26 | type EvalTypeCheckVariable struct { | |
27 | Variables map[string]interface{} | |
28 | ModulePath []string | |
29 | ModuleTree *module.Tree | |
30 | } | |
31 | ||
32 | func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) { | |
33 | currentTree := n.ModuleTree | |
34 | for _, pathComponent := range n.ModulePath[1:] { | |
35 | currentTree = currentTree.Children()[pathComponent] | |
36 | } | |
37 | targetConfig := currentTree.Config() | |
38 | ||
39 | prototypes := make(map[string]config.VariableType) | |
40 | for _, variable := range targetConfig.Variables { | |
41 | prototypes[variable.Name] = variable.Type() | |
42 | } | |
43 | ||
44 | // Only display a module in an error message if we are not in the root module | |
45 | modulePathDescription := fmt.Sprintf(" in module %s", strings.Join(n.ModulePath[1:], ".")) | |
46 | if len(n.ModulePath) == 1 { | |
47 | modulePathDescription = "" | |
48 | } | |
49 | ||
50 | for name, declaredType := range prototypes { | |
51 | proposedValue, ok := n.Variables[name] | |
52 | if !ok { | |
53 | // This means the default value should be used as no overriding value | |
54 | // has been set. Therefore we should continue as no check is necessary. | |
55 | continue | |
56 | } | |
57 | ||
58 | if proposedValue == config.UnknownVariableValue { | |
59 | continue | |
60 | } | |
61 | ||
62 | switch declaredType { | |
63 | case config.VariableTypeString: | |
64 | switch proposedValue.(type) { | |
65 | case string: | |
66 | continue | |
67 | default: | |
68 | return nil, fmt.Errorf("variable %s%s should be type %s, got %s", | |
69 | name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue)) | |
70 | } | |
71 | case config.VariableTypeMap: | |
72 | switch proposedValue.(type) { | |
73 | case map[string]interface{}: | |
74 | continue | |
75 | default: | |
76 | return nil, fmt.Errorf("variable %s%s should be type %s, got %s", | |
77 | name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue)) | |
78 | } | |
79 | case config.VariableTypeList: | |
80 | switch proposedValue.(type) { | |
81 | case []interface{}: | |
82 | continue | |
83 | default: | |
84 | return nil, fmt.Errorf("variable %s%s should be type %s, got %s", | |
85 | name, modulePathDescription, declaredType.Printable(), hclTypeName(proposedValue)) | |
86 | } | |
87 | default: | |
88 | return nil, fmt.Errorf("variable %s%s should be type %s, got type string", | |
89 | name, modulePathDescription, declaredType.Printable()) | |
90 | } | |
91 | } | |
92 | ||
93 | return nil, nil | |
94 | } | |
95 | ||
96 | // EvalSetVariables is an EvalNode implementation that sets the variables | |
97 | // explicitly for interpolation later. | |
98 | type EvalSetVariables struct { | |
99 | Module *string | |
100 | Variables map[string]interface{} | |
101 | } | |
102 | ||
103 | // TODO: test | |
104 | func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) { | |
105 | ctx.SetVariables(*n.Module, n.Variables) | |
106 | return nil, nil | |
107 | } | |
108 | ||
109 | // EvalVariableBlock is an EvalNode implementation that evaluates the | |
110 | // given configuration, and uses the final values as a way to set the | |
111 | // mapping. | |
112 | type EvalVariableBlock struct { | |
113 | Config **ResourceConfig | |
114 | VariableValues map[string]interface{} | |
115 | } | |
116 | ||
117 | func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) { | |
118 | // Clear out the existing mapping | |
119 | for k, _ := range n.VariableValues { | |
120 | delete(n.VariableValues, k) | |
121 | } | |
122 | ||
123 | // Get our configuration | |
124 | rc := *n.Config | |
125 | for k, v := range rc.Config { | |
126 | vKind := reflect.ValueOf(v).Type().Kind() | |
127 | ||
128 | switch vKind { | |
129 | case reflect.Slice: | |
130 | var vSlice []interface{} | |
131 | if err := hilmapstructure.WeakDecode(v, &vSlice); err == nil { | |
132 | n.VariableValues[k] = vSlice | |
133 | continue | |
134 | } | |
135 | case reflect.Map: | |
136 | var vMap map[string]interface{} | |
137 | if err := hilmapstructure.WeakDecode(v, &vMap); err == nil { | |
138 | n.VariableValues[k] = vMap | |
139 | continue | |
140 | } | |
141 | default: | |
142 | var vString string | |
143 | if err := hilmapstructure.WeakDecode(v, &vString); err == nil { | |
144 | n.VariableValues[k] = vString | |
145 | continue | |
146 | } | |
147 | } | |
148 | ||
149 | return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k) | |
150 | } | |
151 | ||
152 | for _, path := range rc.ComputedKeys { | |
153 | log.Printf("[DEBUG] Setting Unknown Variable Value for computed key: %s", path) | |
154 | err := n.setUnknownVariableValueForPath(path) | |
155 | if err != nil { | |
156 | return nil, err | |
157 | } | |
158 | } | |
159 | ||
160 | return nil, nil | |
161 | } | |
162 | ||
163 | func (n *EvalVariableBlock) setUnknownVariableValueForPath(path string) error { | |
164 | pathComponents := strings.Split(path, ".") | |
165 | ||
166 | if len(pathComponents) < 1 { | |
167 | return fmt.Errorf("No path comoponents in %s", path) | |
168 | } | |
169 | ||
170 | if len(pathComponents) == 1 { | |
171 | // Special case the "top level" since we know the type | |
172 | if _, ok := n.VariableValues[pathComponents[0]]; !ok { | |
173 | n.VariableValues[pathComponents[0]] = config.UnknownVariableValue | |
174 | } | |
175 | return nil | |
176 | } | |
177 | ||
178 | // Otherwise find the correct point in the tree and then set to unknown | |
179 | var current interface{} = n.VariableValues[pathComponents[0]] | |
180 | for i := 1; i < len(pathComponents); i++ { | |
181 | switch tCurrent := current.(type) { | |
182 | case []interface{}: | |
183 | index, err := strconv.Atoi(pathComponents[i]) | |
184 | if err != nil { | |
185 | return fmt.Errorf("Cannot convert %s to slice index in path %s", | |
186 | pathComponents[i], path) | |
187 | } | |
188 | current = tCurrent[index] | |
189 | case []map[string]interface{}: | |
190 | index, err := strconv.Atoi(pathComponents[i]) | |
191 | if err != nil { | |
192 | return fmt.Errorf("Cannot convert %s to slice index in path %s", | |
193 | pathComponents[i], path) | |
194 | } | |
195 | current = tCurrent[index] | |
196 | case map[string]interface{}: | |
197 | if val, hasVal := tCurrent[pathComponents[i]]; hasVal { | |
198 | current = val | |
199 | continue | |
200 | } | |
201 | ||
202 | tCurrent[pathComponents[i]] = config.UnknownVariableValue | |
203 | break | |
204 | } | |
205 | } | |
206 | ||
207 | return nil | |
208 | } | |
209 | ||
210 | // EvalCoerceMapVariable is an EvalNode implementation that recognizes a | |
211 | // specific ambiguous HCL parsing situation and resolves it. In HCL parsing, a | |
212 | // bare map literal is indistinguishable from a list of maps w/ one element. | |
213 | // | |
214 | // We take all the same inputs as EvalTypeCheckVariable above, since we need | |
215 | // both the target type and the proposed value in order to properly coerce. | |
216 | type EvalCoerceMapVariable struct { | |
217 | Variables map[string]interface{} | |
218 | ModulePath []string | |
219 | ModuleTree *module.Tree | |
220 | } | |
221 | ||
222 | // Eval implements the EvalNode interface. See EvalCoerceMapVariable for | |
223 | // details. | |
224 | func (n *EvalCoerceMapVariable) Eval(ctx EvalContext) (interface{}, error) { | |
225 | currentTree := n.ModuleTree | |
226 | for _, pathComponent := range n.ModulePath[1:] { | |
227 | currentTree = currentTree.Children()[pathComponent] | |
228 | } | |
229 | targetConfig := currentTree.Config() | |
230 | ||
231 | prototypes := make(map[string]config.VariableType) | |
232 | for _, variable := range targetConfig.Variables { | |
233 | prototypes[variable.Name] = variable.Type() | |
234 | } | |
235 | ||
236 | for name, declaredType := range prototypes { | |
237 | if declaredType != config.VariableTypeMap { | |
238 | continue | |
239 | } | |
240 | ||
241 | proposedValue, ok := n.Variables[name] | |
242 | if !ok { | |
243 | continue | |
244 | } | |
245 | ||
246 | if list, ok := proposedValue.([]interface{}); ok && len(list) == 1 { | |
247 | if m, ok := list[0].(map[string]interface{}); ok { | |
248 | log.Printf("[DEBUG] EvalCoerceMapVariable: "+ | |
249 | "Coercing single element list into map: %#v", m) | |
250 | n.Variables[name] = m | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | return nil, nil | |
256 | } | |
257 | ||
258 | // hclTypeName returns the name of the type that would represent this value in | |
259 | // a config file, or falls back to the Go type name if there's no corresponding | |
260 | // HCL type. This is used for formatted output, not for comparing types. | |
261 | func hclTypeName(i interface{}) string { | |
262 | switch k := reflect.Indirect(reflect.ValueOf(i)).Kind(); k { | |
263 | case reflect.Bool: | |
264 | return "boolean" | |
265 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | |
266 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, | |
267 | reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: | |
268 | return "number" | |
269 | case reflect.Array, reflect.Slice: | |
270 | return "list" | |
271 | case reflect.Map: | |
272 | return "map" | |
273 | case reflect.String: | |
274 | return "string" | |
275 | default: | |
276 | // fall back to the Go type if there's no match | |
277 | return k.String() | |
278 | } | |
279 | } |