]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package config |
2 | ||
3 | import ( | |
4 | "bytes" | |
5 | "encoding/gob" | |
15c0b25d AP |
6 | "errors" |
7 | "strconv" | |
bae9f6d2 JC |
8 | "sync" |
9 | ||
15c0b25d AP |
10 | "github.com/zclconf/go-cty/cty" |
11 | "github.com/zclconf/go-cty/cty/convert" | |
12 | ||
13 | hcl2 "github.com/hashicorp/hcl2/hcl" | |
bae9f6d2 JC |
14 | "github.com/hashicorp/hil" |
15 | "github.com/hashicorp/hil/ast" | |
16 | "github.com/mitchellh/copystructure" | |
17 | "github.com/mitchellh/reflectwalk" | |
18 | ) | |
19 | ||
20 | // UnknownVariableValue is a sentinel value that can be used | |
21 | // to denote that the value of a variable is unknown at this time. | |
22 | // RawConfig uses this information to build up data about | |
23 | // unknown keys. | |
24 | const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66" | |
25 | ||
26 | // RawConfig is a structure that holds a piece of configuration | |
27 | // where the overall structure is unknown since it will be used | |
28 | // to configure a plugin or some other similar external component. | |
29 | // | |
30 | // RawConfigs can be interpolated with variables that come from | |
31 | // other resources, user variables, etc. | |
32 | // | |
33 | // RawConfig supports a query-like interface to request | |
34 | // information from deep within the structure. | |
35 | type RawConfig struct { | |
15c0b25d AP |
36 | Key string |
37 | ||
38 | // Only _one_ of Raw and Body may be populated at a time. | |
39 | // | |
40 | // In the normal case, Raw is populated and Body is nil. | |
41 | // | |
42 | // When the experimental HCL2 parsing mode is enabled, "Body" | |
43 | // is populated and RawConfig serves only to transport the hcl2.Body | |
44 | // through the rest of Terraform core so we can ultimately decode it | |
45 | // once its schema is known. | |
46 | // | |
47 | // Once we transition to HCL2 as the primary representation, RawConfig | |
48 | // should be removed altogether and the hcl2.Body should be passed | |
49 | // around directly. | |
50 | ||
51 | Raw map[string]interface{} | |
52 | Body hcl2.Body | |
53 | ||
bae9f6d2 JC |
54 | Interpolations []ast.Node |
55 | Variables map[string]InterpolatedVariable | |
56 | ||
57 | lock sync.Mutex | |
58 | config map[string]interface{} | |
59 | unknownKeys []string | |
60 | } | |
61 | ||
62 | // NewRawConfig creates a new RawConfig structure and populates the | |
63 | // publicly readable struct fields. | |
64 | func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) { | |
65 | result := &RawConfig{Raw: raw} | |
66 | if err := result.init(); err != nil { | |
67 | return nil, err | |
68 | } | |
69 | ||
70 | return result, nil | |
71 | } | |
72 | ||
15c0b25d AP |
73 | // NewRawConfigHCL2 creates a new RawConfig that is serving as a capsule |
74 | // to transport a hcl2.Body. In this mode, the publicly-readable struct | |
75 | // fields are not populated since all operations should instead be diverted | |
76 | // to the HCL2 body. | |
77 | // | |
78 | // For a RawConfig object constructed with this function, the only valid use | |
79 | // is to later retrieve the Body value and call its own methods. Callers | |
80 | // may choose to set and then later handle the Key field, in a manner | |
81 | // consistent with how it is handled by the Value method, but the Value | |
82 | // method itself must not be used. | |
83 | // | |
84 | // This is an experimental codepath to be used only by the HCL2 config loader. | |
85 | // Non-experimental parsing should _always_ use NewRawConfig to produce a | |
86 | // fully-functional RawConfig object. | |
87 | func NewRawConfigHCL2(body hcl2.Body) *RawConfig { | |
88 | return &RawConfig{ | |
89 | Body: body, | |
90 | } | |
91 | } | |
92 | ||
bae9f6d2 JC |
93 | // RawMap returns a copy of the RawConfig.Raw map. |
94 | func (r *RawConfig) RawMap() map[string]interface{} { | |
95 | r.lock.Lock() | |
96 | defer r.lock.Unlock() | |
97 | ||
98 | m := make(map[string]interface{}) | |
99 | for k, v := range r.Raw { | |
100 | m[k] = v | |
101 | } | |
102 | return m | |
103 | } | |
104 | ||
105 | // Copy returns a copy of this RawConfig, uninterpolated. | |
106 | func (r *RawConfig) Copy() *RawConfig { | |
107 | if r == nil { | |
108 | return nil | |
109 | } | |
110 | ||
111 | r.lock.Lock() | |
112 | defer r.lock.Unlock() | |
113 | ||
15c0b25d AP |
114 | if r.Body != nil { |
115 | return NewRawConfigHCL2(r.Body) | |
116 | } | |
117 | ||
bae9f6d2 JC |
118 | newRaw := make(map[string]interface{}) |
119 | for k, v := range r.Raw { | |
120 | newRaw[k] = v | |
121 | } | |
122 | ||
123 | result, err := NewRawConfig(newRaw) | |
124 | if err != nil { | |
125 | panic("copy failed: " + err.Error()) | |
126 | } | |
127 | ||
128 | result.Key = r.Key | |
129 | return result | |
130 | } | |
131 | ||
132 | // Value returns the value of the configuration if this configuration | |
133 | // has a Key set. If this does not have a Key set, nil will be returned. | |
134 | func (r *RawConfig) Value() interface{} { | |
135 | if c := r.Config(); c != nil { | |
136 | if v, ok := c[r.Key]; ok { | |
137 | return v | |
138 | } | |
139 | } | |
140 | ||
141 | r.lock.Lock() | |
142 | defer r.lock.Unlock() | |
143 | return r.Raw[r.Key] | |
144 | } | |
145 | ||
146 | // Config returns the entire configuration with the variables | |
147 | // interpolated from any call to Interpolate. | |
148 | // | |
149 | // If any interpolated variables are unknown (value set to | |
150 | // UnknownVariableValue), the first non-container (map, slice, etc.) element | |
151 | // will be removed from the config. The keys of unknown variables | |
152 | // can be found using the UnknownKeys function. | |
153 | // | |
154 | // By pruning out unknown keys from the configuration, the raw | |
155 | // structure will always successfully decode into its ultimate | |
156 | // structure using something like mapstructure. | |
157 | func (r *RawConfig) Config() map[string]interface{} { | |
158 | r.lock.Lock() | |
159 | defer r.lock.Unlock() | |
160 | return r.config | |
161 | } | |
162 | ||
163 | // Interpolate uses the given mapping of variable values and uses | |
164 | // those as the values to replace any variables in this raw | |
165 | // configuration. | |
166 | // | |
167 | // Any prior calls to Interpolate are replaced with this one. | |
168 | // | |
169 | // If a variable key is missing, this will panic. | |
170 | func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error { | |
171 | r.lock.Lock() | |
172 | defer r.lock.Unlock() | |
173 | ||
174 | config := langEvalConfig(vs) | |
175 | return r.interpolate(func(root ast.Node) (interface{}, error) { | |
176 | // None of the variables we need are computed, meaning we should | |
177 | // be able to properly evaluate. | |
178 | result, err := hil.Eval(root, config) | |
179 | if err != nil { | |
180 | return "", err | |
181 | } | |
182 | ||
183 | return result.Value, nil | |
184 | }) | |
185 | } | |
186 | ||
187 | // Merge merges another RawConfig into this one (overriding any conflicting | |
188 | // values in this config) and returns a new config. The original config | |
189 | // is not modified. | |
190 | func (r *RawConfig) Merge(other *RawConfig) *RawConfig { | |
191 | r.lock.Lock() | |
192 | defer r.lock.Unlock() | |
193 | ||
194 | // Merge the raw configurations | |
195 | raw := make(map[string]interface{}) | |
196 | for k, v := range r.Raw { | |
197 | raw[k] = v | |
198 | } | |
199 | for k, v := range other.Raw { | |
200 | raw[k] = v | |
201 | } | |
202 | ||
203 | // Create the result | |
204 | result, err := NewRawConfig(raw) | |
205 | if err != nil { | |
206 | panic(err) | |
207 | } | |
208 | ||
209 | // Merge the interpolated results | |
210 | result.config = make(map[string]interface{}) | |
211 | for k, v := range r.config { | |
212 | result.config[k] = v | |
213 | } | |
214 | for k, v := range other.config { | |
215 | result.config[k] = v | |
216 | } | |
217 | ||
218 | // Build the unknown keys | |
219 | if len(r.unknownKeys) > 0 || len(other.unknownKeys) > 0 { | |
220 | unknownKeys := make(map[string]struct{}) | |
221 | for _, k := range r.unknownKeys { | |
222 | unknownKeys[k] = struct{}{} | |
223 | } | |
224 | for _, k := range other.unknownKeys { | |
225 | unknownKeys[k] = struct{}{} | |
226 | } | |
227 | ||
228 | result.unknownKeys = make([]string, 0, len(unknownKeys)) | |
229 | for k, _ := range unknownKeys { | |
230 | result.unknownKeys = append(result.unknownKeys, k) | |
231 | } | |
232 | } | |
233 | ||
234 | return result | |
235 | } | |
236 | ||
237 | func (r *RawConfig) init() error { | |
238 | r.lock.Lock() | |
239 | defer r.lock.Unlock() | |
240 | ||
241 | r.config = r.Raw | |
242 | r.Interpolations = nil | |
243 | r.Variables = nil | |
244 | ||
245 | fn := func(node ast.Node) (interface{}, error) { | |
246 | r.Interpolations = append(r.Interpolations, node) | |
247 | vars, err := DetectVariables(node) | |
248 | if err != nil { | |
249 | return "", err | |
250 | } | |
251 | ||
252 | for _, v := range vars { | |
253 | if r.Variables == nil { | |
254 | r.Variables = make(map[string]InterpolatedVariable) | |
255 | } | |
256 | ||
257 | r.Variables[v.FullKey()] = v | |
258 | } | |
259 | ||
260 | return "", nil | |
261 | } | |
262 | ||
263 | walker := &interpolationWalker{F: fn} | |
264 | if err := reflectwalk.Walk(r.Raw, walker); err != nil { | |
265 | return err | |
266 | } | |
267 | ||
268 | return nil | |
269 | } | |
270 | ||
271 | func (r *RawConfig) interpolate(fn interpolationWalkerFunc) error { | |
15c0b25d AP |
272 | if r.Body != nil { |
273 | // For RawConfigs created for the HCL2 experiement, callers must | |
274 | // use the HCL2 Body API directly rather than interpolating via | |
275 | // the RawConfig. | |
276 | return errors.New("this feature is not yet supported under the HCL2 experiment") | |
277 | } | |
278 | ||
bae9f6d2 JC |
279 | config, err := copystructure.Copy(r.Raw) |
280 | if err != nil { | |
281 | return err | |
282 | } | |
283 | r.config = config.(map[string]interface{}) | |
284 | ||
285 | w := &interpolationWalker{F: fn, Replace: true} | |
286 | err = reflectwalk.Walk(r.config, w) | |
287 | if err != nil { | |
288 | return err | |
289 | } | |
290 | ||
291 | r.unknownKeys = w.unknownKeys | |
292 | return nil | |
293 | } | |
294 | ||
295 | func (r *RawConfig) merge(r2 *RawConfig) *RawConfig { | |
296 | if r == nil && r2 == nil { | |
297 | return nil | |
298 | } | |
299 | ||
300 | if r == nil { | |
301 | r = &RawConfig{} | |
302 | } | |
303 | ||
304 | rawRaw, err := copystructure.Copy(r.Raw) | |
305 | if err != nil { | |
306 | panic(err) | |
307 | } | |
308 | ||
309 | raw := rawRaw.(map[string]interface{}) | |
310 | if r2 != nil { | |
311 | for k, v := range r2.Raw { | |
312 | raw[k] = v | |
313 | } | |
314 | } | |
315 | ||
316 | result, err := NewRawConfig(raw) | |
317 | if err != nil { | |
318 | panic(err) | |
319 | } | |
320 | ||
321 | return result | |
322 | } | |
323 | ||
15c0b25d AP |
324 | // couldBeInteger is a helper that determines if the represented value could |
325 | // result in an integer. | |
326 | // | |
327 | // This function only works for RawConfigs that have "Key" set, meaning that | |
328 | // a single result can be produced. Calling this function will overwrite | |
329 | // the Config and Value results to be a test value. | |
330 | // | |
331 | // This function is conservative. If there is some doubt about whether the | |
332 | // result could be an integer -- for example, if it depends on a variable | |
333 | // whose type we don't know yet -- it will still return true. | |
334 | func (r *RawConfig) couldBeInteger() bool { | |
335 | if r.Key == "" { | |
336 | // un-keyed RawConfigs can never produce numbers | |
337 | return false | |
338 | } | |
339 | if r.Body == nil { | |
340 | // Normal path: using the interpolator in this package | |
341 | // Interpolate with a fixed number to verify that its a number. | |
342 | r.interpolate(func(root ast.Node) (interface{}, error) { | |
343 | // Execute the node but transform the AST so that it returns | |
344 | // a fixed value of "5" for all interpolations. | |
345 | result, err := hil.Eval( | |
346 | hil.FixedValueTransform( | |
347 | root, &ast.LiteralNode{Value: "5", Typex: ast.TypeString}), | |
348 | nil) | |
349 | if err != nil { | |
350 | return "", err | |
351 | } | |
352 | ||
353 | return result.Value, nil | |
354 | }) | |
355 | _, err := strconv.ParseInt(r.Value().(string), 0, 0) | |
356 | return err == nil | |
357 | } else { | |
358 | // HCL2 experiment path: using the HCL2 API via shims | |
359 | // | |
360 | // This path catches fewer situations because we have to assume all | |
361 | // variables are entirely unknown in HCL2, rather than the assumption | |
362 | // above that all variables can be numbers because names like "var.foo" | |
363 | // are considered a single variable rather than an attribute access. | |
364 | // This is fine in practice, because we get a definitive answer | |
365 | // during the graph walk when we have real values to work with. | |
366 | attrs, diags := r.Body.JustAttributes() | |
367 | if diags.HasErrors() { | |
368 | // This body is not just a single attribute with a value, so | |
369 | // this can't be a number. | |
370 | return false | |
371 | } | |
372 | attr, hasAttr := attrs[r.Key] | |
373 | if !hasAttr { | |
374 | return false | |
375 | } | |
376 | result, diags := hcl2EvalWithUnknownVars(attr.Expr) | |
377 | if diags.HasErrors() { | |
378 | // We'll conservatively assume that this error is a result of | |
379 | // us not being ready to fully-populate the scope, and catch | |
380 | // any further problems during the main graph walk. | |
381 | return true | |
382 | } | |
383 | ||
384 | // If the result is convertable to number then we'll allow it. | |
385 | // We do this because an unknown string is optimistically convertable | |
386 | // to number (might be "5") but a _known_ string "hello" is not. | |
387 | _, err := convert.Convert(result, cty.Number) | |
388 | return err == nil | |
389 | } | |
390 | } | |
391 | ||
bae9f6d2 JC |
392 | // UnknownKeys returns the keys of the configuration that are unknown |
393 | // because they had interpolated variables that must be computed. | |
394 | func (r *RawConfig) UnknownKeys() []string { | |
395 | r.lock.Lock() | |
396 | defer r.lock.Unlock() | |
397 | return r.unknownKeys | |
398 | } | |
399 | ||
400 | // See GobEncode | |
401 | func (r *RawConfig) GobDecode(b []byte) error { | |
402 | var data gobRawConfig | |
403 | err := gob.NewDecoder(bytes.NewReader(b)).Decode(&data) | |
404 | if err != nil { | |
405 | return err | |
406 | } | |
407 | ||
408 | r.Key = data.Key | |
409 | r.Raw = data.Raw | |
410 | ||
411 | return r.init() | |
412 | } | |
413 | ||
414 | // GobEncode is a custom Gob encoder to use so that we only include the | |
415 | // raw configuration. Interpolated variables and such are lost and the | |
416 | // tree of interpolated variables is recomputed on decode, since it is | |
417 | // referentially transparent. | |
418 | func (r *RawConfig) GobEncode() ([]byte, error) { | |
419 | r.lock.Lock() | |
420 | defer r.lock.Unlock() | |
421 | ||
422 | data := gobRawConfig{ | |
423 | Key: r.Key, | |
424 | Raw: r.Raw, | |
425 | } | |
426 | ||
427 | var buf bytes.Buffer | |
428 | if err := gob.NewEncoder(&buf).Encode(data); err != nil { | |
429 | return nil, err | |
430 | } | |
431 | ||
432 | return buf.Bytes(), nil | |
433 | } | |
434 | ||
435 | type gobRawConfig struct { | |
436 | Key string | |
437 | Raw map[string]interface{} | |
438 | } | |
439 | ||
440 | // langEvalConfig returns the evaluation configuration we use to execute. | |
441 | func langEvalConfig(vs map[string]ast.Variable) *hil.EvalConfig { | |
442 | funcMap := make(map[string]ast.Function) | |
443 | for k, v := range Funcs() { | |
444 | funcMap[k] = v | |
445 | } | |
446 | funcMap["lookup"] = interpolationFuncLookup(vs) | |
447 | funcMap["keys"] = interpolationFuncKeys(vs) | |
448 | funcMap["values"] = interpolationFuncValues(vs) | |
449 | ||
450 | return &hil.EvalConfig{ | |
451 | GlobalScope: &ast.BasicScope{ | |
452 | VarMap: vs, | |
453 | FuncMap: funcMap, | |
454 | }, | |
455 | } | |
456 | } |