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