diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/lang/eval.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/lang/eval.go | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/lang/eval.go b/vendor/github.com/hashicorp/terraform/lang/eval.go new file mode 100644 index 0000000..a3fb363 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/eval.go | |||
@@ -0,0 +1,477 @@ | |||
1 | package lang | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "log" | ||
6 | "strconv" | ||
7 | |||
8 | "github.com/hashicorp/hcl2/ext/dynblock" | ||
9 | "github.com/hashicorp/hcl2/hcl" | ||
10 | "github.com/hashicorp/hcl2/hcldec" | ||
11 | "github.com/hashicorp/terraform/addrs" | ||
12 | "github.com/hashicorp/terraform/configs/configschema" | ||
13 | "github.com/hashicorp/terraform/lang/blocktoattr" | ||
14 | "github.com/hashicorp/terraform/tfdiags" | ||
15 | "github.com/zclconf/go-cty/cty" | ||
16 | "github.com/zclconf/go-cty/cty/convert" | ||
17 | ) | ||
18 | |||
19 | // ExpandBlock expands any "dynamic" blocks present in the given body. The | ||
20 | // result is a body with those blocks expanded, ready to be evaluated with | ||
21 | // EvalBlock. | ||
22 | // | ||
23 | // If the returned diagnostics contains errors then the result may be | ||
24 | // incomplete or invalid. | ||
25 | func (s *Scope) ExpandBlock(body hcl.Body, schema *configschema.Block) (hcl.Body, tfdiags.Diagnostics) { | ||
26 | spec := schema.DecoderSpec() | ||
27 | |||
28 | traversals := dynblock.ExpandVariablesHCLDec(body, spec) | ||
29 | refs, diags := References(traversals) | ||
30 | |||
31 | ctx, ctxDiags := s.EvalContext(refs) | ||
32 | diags = diags.Append(ctxDiags) | ||
33 | |||
34 | return dynblock.Expand(body, ctx), diags | ||
35 | } | ||
36 | |||
37 | // EvalBlock evaluates the given body using the given block schema and returns | ||
38 | // a cty object value representing its contents. The type of the result conforms | ||
39 | // to the implied type of the given schema. | ||
40 | // | ||
41 | // This function does not automatically expand "dynamic" blocks within the | ||
42 | // body. If that is desired, first call the ExpandBlock method to obtain | ||
43 | // an expanded body to pass to this method. | ||
44 | // | ||
45 | // If the returned diagnostics contains errors then the result may be | ||
46 | // incomplete or invalid. | ||
47 | func (s *Scope) EvalBlock(body hcl.Body, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { | ||
48 | spec := schema.DecoderSpec() | ||
49 | |||
50 | refs, diags := ReferencesInBlock(body, schema) | ||
51 | |||
52 | ctx, ctxDiags := s.EvalContext(refs) | ||
53 | diags = diags.Append(ctxDiags) | ||
54 | if diags.HasErrors() { | ||
55 | // We'll stop early if we found problems in the references, because | ||
56 | // it's likely evaluation will produce redundant copies of the same errors. | ||
57 | return cty.UnknownVal(schema.ImpliedType()), diags | ||
58 | } | ||
59 | |||
60 | // HACK: In order to remain compatible with some assumptions made in | ||
61 | // Terraform v0.11 and earlier about the approximate equivalence of | ||
62 | // attribute vs. block syntax, we do a just-in-time fixup here to allow | ||
63 | // any attribute in the schema that has a list-of-objects or set-of-objects | ||
64 | // kind to potentially be populated instead by one or more nested blocks | ||
65 | // whose type is the attribute name. | ||
66 | body = blocktoattr.FixUpBlockAttrs(body, schema) | ||
67 | |||
68 | val, evalDiags := hcldec.Decode(body, spec, ctx) | ||
69 | diags = diags.Append(evalDiags) | ||
70 | |||
71 | return val, diags | ||
72 | } | ||
73 | |||
74 | // EvalExpr evaluates a single expression in the receiving context and returns | ||
75 | // the resulting value. The value will be converted to the given type before | ||
76 | // it is returned if possible, or else an error diagnostic will be produced | ||
77 | // describing the conversion error. | ||
78 | // | ||
79 | // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion | ||
80 | // and just obtain the returned value directly. | ||
81 | // | ||
82 | // If the returned diagnostics contains errors then the result may be | ||
83 | // incomplete, but will always be of the requested type. | ||
84 | func (s *Scope) EvalExpr(expr hcl.Expression, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) { | ||
85 | refs, diags := ReferencesInExpr(expr) | ||
86 | |||
87 | ctx, ctxDiags := s.EvalContext(refs) | ||
88 | diags = diags.Append(ctxDiags) | ||
89 | if diags.HasErrors() { | ||
90 | // We'll stop early if we found problems in the references, because | ||
91 | // it's likely evaluation will produce redundant copies of the same errors. | ||
92 | return cty.UnknownVal(wantType), diags | ||
93 | } | ||
94 | |||
95 | val, evalDiags := expr.Value(ctx) | ||
96 | diags = diags.Append(evalDiags) | ||
97 | |||
98 | if wantType != cty.DynamicPseudoType { | ||
99 | var convErr error | ||
100 | val, convErr = convert.Convert(val, wantType) | ||
101 | if convErr != nil { | ||
102 | val = cty.UnknownVal(wantType) | ||
103 | diags = diags.Append(&hcl.Diagnostic{ | ||
104 | Severity: hcl.DiagError, | ||
105 | Summary: "Incorrect value type", | ||
106 | Detail: fmt.Sprintf("Invalid expression value: %s.", tfdiags.FormatError(convErr)), | ||
107 | Subject: expr.Range().Ptr(), | ||
108 | }) | ||
109 | } | ||
110 | } | ||
111 | |||
112 | return val, diags | ||
113 | } | ||
114 | |||
115 | // EvalReference evaluates the given reference in the receiving scope and | ||
116 | // returns the resulting value. The value will be converted to the given type before | ||
117 | // it is returned if possible, or else an error diagnostic will be produced | ||
118 | // describing the conversion error. | ||
119 | // | ||
120 | // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion | ||
121 | // and just obtain the returned value directly. | ||
122 | // | ||
123 | // If the returned diagnostics contains errors then the result may be | ||
124 | // incomplete, but will always be of the requested type. | ||
125 | func (s *Scope) EvalReference(ref *addrs.Reference, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) { | ||
126 | var diags tfdiags.Diagnostics | ||
127 | |||
128 | // We cheat a bit here and just build an EvalContext for our requested | ||
129 | // reference with the "self" address overridden, and then pull the "self" | ||
130 | // result out of it to return. | ||
131 | ctx, ctxDiags := s.evalContext([]*addrs.Reference{ref}, ref.Subject) | ||
132 | diags = diags.Append(ctxDiags) | ||
133 | val := ctx.Variables["self"] | ||
134 | if val == cty.NilVal { | ||
135 | val = cty.DynamicVal | ||
136 | } | ||
137 | |||
138 | var convErr error | ||
139 | val, convErr = convert.Convert(val, wantType) | ||
140 | if convErr != nil { | ||
141 | val = cty.UnknownVal(wantType) | ||
142 | diags = diags.Append(&hcl.Diagnostic{ | ||
143 | Severity: hcl.DiagError, | ||
144 | Summary: "Incorrect value type", | ||
145 | Detail: fmt.Sprintf("Invalid expression value: %s.", tfdiags.FormatError(convErr)), | ||
146 | Subject: ref.SourceRange.ToHCL().Ptr(), | ||
147 | }) | ||
148 | } | ||
149 | |||
150 | return val, diags | ||
151 | } | ||
152 | |||
153 | // EvalContext constructs a HCL expression evaluation context whose variable | ||
154 | // scope contains sufficient values to satisfy the given set of references. | ||
155 | // | ||
156 | // Most callers should prefer to use the evaluation helper methods that | ||
157 | // this type offers, but this is here for less common situations where the | ||
158 | // caller will handle the evaluation calls itself. | ||
159 | func (s *Scope) EvalContext(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) { | ||
160 | return s.evalContext(refs, s.SelfAddr) | ||
161 | } | ||
162 | |||
163 | func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceable) (*hcl.EvalContext, tfdiags.Diagnostics) { | ||
164 | if s == nil { | ||
165 | panic("attempt to construct EvalContext for nil Scope") | ||
166 | } | ||
167 | |||
168 | var diags tfdiags.Diagnostics | ||
169 | vals := make(map[string]cty.Value) | ||
170 | funcs := s.Functions() | ||
171 | ctx := &hcl.EvalContext{ | ||
172 | Variables: vals, | ||
173 | Functions: funcs, | ||
174 | } | ||
175 | |||
176 | if len(refs) == 0 { | ||
177 | // Easy path for common case where there are no references at all. | ||
178 | return ctx, diags | ||
179 | } | ||
180 | |||
181 | // First we'll do static validation of the references. This catches things | ||
182 | // early that might otherwise not get caught due to unknown values being | ||
183 | // present in the scope during planning. | ||
184 | if staticDiags := s.Data.StaticValidateReferences(refs, selfAddr); staticDiags.HasErrors() { | ||
185 | diags = diags.Append(staticDiags) | ||
186 | return ctx, diags | ||
187 | } | ||
188 | |||
189 | // The reference set we are given has not been de-duped, and so there can | ||
190 | // be redundant requests in it for two reasons: | ||
191 | // - The same item is referenced multiple times | ||
192 | // - Both an item and that item's container are separately referenced. | ||
193 | // We will still visit every reference here and ask our data source for | ||
194 | // it, since that allows us to gather a full set of any errors and | ||
195 | // warnings, but once we've gathered all the data we'll then skip anything | ||
196 | // that's redundant in the process of populating our values map. | ||
197 | dataResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{} | ||
198 | managedResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{} | ||
199 | wholeModules := map[string]map[addrs.InstanceKey]cty.Value{} | ||
200 | moduleOutputs := map[string]map[addrs.InstanceKey]map[string]cty.Value{} | ||
201 | inputVariables := map[string]cty.Value{} | ||
202 | localValues := map[string]cty.Value{} | ||
203 | pathAttrs := map[string]cty.Value{} | ||
204 | terraformAttrs := map[string]cty.Value{} | ||
205 | countAttrs := map[string]cty.Value{} | ||
206 | var self cty.Value | ||
207 | |||
208 | for _, ref := range refs { | ||
209 | rng := ref.SourceRange | ||
210 | isSelf := false | ||
211 | |||
212 | rawSubj := ref.Subject | ||
213 | if rawSubj == addrs.Self { | ||
214 | if selfAddr == nil { | ||
215 | diags = diags.Append(&hcl.Diagnostic{ | ||
216 | Severity: hcl.DiagError, | ||
217 | Summary: `Invalid "self" reference`, | ||
218 | // This detail message mentions some current practice that | ||
219 | // this codepath doesn't really "know about". If the "self" | ||
220 | // object starts being supported in more contexts later then | ||
221 | // we'll need to adjust this message. | ||
222 | Detail: `The "self" object is not available in this context. This object can be used only in resource provisioner and connection blocks.`, | ||
223 | Subject: ref.SourceRange.ToHCL().Ptr(), | ||
224 | }) | ||
225 | continue | ||
226 | } | ||
227 | |||
228 | // Treat "self" as an alias for the configured self address. | ||
229 | rawSubj = selfAddr | ||
230 | isSelf = true | ||
231 | |||
232 | if rawSubj == addrs.Self { | ||
233 | // Programming error: the self address cannot alias itself. | ||
234 | panic("scope SelfAddr attempting to alias itself") | ||
235 | } | ||
236 | } | ||
237 | |||
238 | // This type switch must cover all of the "Referenceable" implementations | ||
239 | // in package addrs. | ||
240 | switch subj := rawSubj.(type) { | ||
241 | |||
242 | case addrs.ResourceInstance: | ||
243 | var into map[string]map[string]map[addrs.InstanceKey]cty.Value | ||
244 | switch subj.Resource.Mode { | ||
245 | case addrs.ManagedResourceMode: | ||
246 | into = managedResources | ||
247 | case addrs.DataResourceMode: | ||
248 | into = dataResources | ||
249 | default: | ||
250 | panic(fmt.Errorf("unsupported ResourceMode %s", subj.Resource.Mode)) | ||
251 | } | ||
252 | |||
253 | val, valDiags := normalizeRefValue(s.Data.GetResourceInstance(subj, rng)) | ||
254 | diags = diags.Append(valDiags) | ||
255 | |||
256 | r := subj.Resource | ||
257 | if into[r.Type] == nil { | ||
258 | into[r.Type] = make(map[string]map[addrs.InstanceKey]cty.Value) | ||
259 | } | ||
260 | if into[r.Type][r.Name] == nil { | ||
261 | into[r.Type][r.Name] = make(map[addrs.InstanceKey]cty.Value) | ||
262 | } | ||
263 | into[r.Type][r.Name][subj.Key] = val | ||
264 | if isSelf { | ||
265 | self = val | ||
266 | } | ||
267 | |||
268 | case addrs.ModuleCallInstance: | ||
269 | val, valDiags := normalizeRefValue(s.Data.GetModuleInstance(subj, rng)) | ||
270 | diags = diags.Append(valDiags) | ||
271 | |||
272 | if wholeModules[subj.Call.Name] == nil { | ||
273 | wholeModules[subj.Call.Name] = make(map[addrs.InstanceKey]cty.Value) | ||
274 | } | ||
275 | wholeModules[subj.Call.Name][subj.Key] = val | ||
276 | if isSelf { | ||
277 | self = val | ||
278 | } | ||
279 | |||
280 | case addrs.ModuleCallOutput: | ||
281 | val, valDiags := normalizeRefValue(s.Data.GetModuleInstanceOutput(subj, rng)) | ||
282 | diags = diags.Append(valDiags) | ||
283 | |||
284 | callName := subj.Call.Call.Name | ||
285 | callKey := subj.Call.Key | ||
286 | if moduleOutputs[callName] == nil { | ||
287 | moduleOutputs[callName] = make(map[addrs.InstanceKey]map[string]cty.Value) | ||
288 | } | ||
289 | if moduleOutputs[callName][callKey] == nil { | ||
290 | moduleOutputs[callName][callKey] = make(map[string]cty.Value) | ||
291 | } | ||
292 | moduleOutputs[callName][callKey][subj.Name] = val | ||
293 | if isSelf { | ||
294 | self = val | ||
295 | } | ||
296 | |||
297 | case addrs.InputVariable: | ||
298 | val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng)) | ||
299 | diags = diags.Append(valDiags) | ||
300 | inputVariables[subj.Name] = val | ||
301 | if isSelf { | ||
302 | self = val | ||
303 | } | ||
304 | |||
305 | case addrs.LocalValue: | ||
306 | val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng)) | ||
307 | diags = diags.Append(valDiags) | ||
308 | localValues[subj.Name] = val | ||
309 | if isSelf { | ||
310 | self = val | ||
311 | } | ||
312 | |||
313 | case addrs.PathAttr: | ||
314 | val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng)) | ||
315 | diags = diags.Append(valDiags) | ||
316 | pathAttrs[subj.Name] = val | ||
317 | if isSelf { | ||
318 | self = val | ||
319 | } | ||
320 | |||
321 | case addrs.TerraformAttr: | ||
322 | val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng)) | ||
323 | diags = diags.Append(valDiags) | ||
324 | terraformAttrs[subj.Name] = val | ||
325 | if isSelf { | ||
326 | self = val | ||
327 | } | ||
328 | |||
329 | case addrs.CountAttr: | ||
330 | val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng)) | ||
331 | diags = diags.Append(valDiags) | ||
332 | countAttrs[subj.Name] = val | ||
333 | if isSelf { | ||
334 | self = val | ||
335 | } | ||
336 | |||
337 | default: | ||
338 | // Should never happen | ||
339 | panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj)) | ||
340 | } | ||
341 | } | ||
342 | |||
343 | for k, v := range buildResourceObjects(managedResources) { | ||
344 | vals[k] = v | ||
345 | } | ||
346 | vals["data"] = cty.ObjectVal(buildResourceObjects(dataResources)) | ||
347 | vals["module"] = cty.ObjectVal(buildModuleObjects(wholeModules, moduleOutputs)) | ||
348 | vals["var"] = cty.ObjectVal(inputVariables) | ||
349 | vals["local"] = cty.ObjectVal(localValues) | ||
350 | vals["path"] = cty.ObjectVal(pathAttrs) | ||
351 | vals["terraform"] = cty.ObjectVal(terraformAttrs) | ||
352 | vals["count"] = cty.ObjectVal(countAttrs) | ||
353 | if self != cty.NilVal { | ||
354 | vals["self"] = self | ||
355 | } | ||
356 | |||
357 | return ctx, diags | ||
358 | } | ||
359 | |||
360 | func buildResourceObjects(resources map[string]map[string]map[addrs.InstanceKey]cty.Value) map[string]cty.Value { | ||
361 | vals := make(map[string]cty.Value) | ||
362 | for typeName, names := range resources { | ||
363 | nameVals := make(map[string]cty.Value) | ||
364 | for name, keys := range names { | ||
365 | nameVals[name] = buildInstanceObjects(keys) | ||
366 | } | ||
367 | vals[typeName] = cty.ObjectVal(nameVals) | ||
368 | } | ||
369 | return vals | ||
370 | } | ||
371 | |||
372 | func buildModuleObjects(wholeModules map[string]map[addrs.InstanceKey]cty.Value, moduleOutputs map[string]map[addrs.InstanceKey]map[string]cty.Value) map[string]cty.Value { | ||
373 | vals := make(map[string]cty.Value) | ||
374 | |||
375 | for name, keys := range wholeModules { | ||
376 | vals[name] = buildInstanceObjects(keys) | ||
377 | } | ||
378 | |||
379 | for name, keys := range moduleOutputs { | ||
380 | if _, exists := wholeModules[name]; exists { | ||
381 | // If we also have a whole module value for this name then we'll | ||
382 | // skip this since the individual outputs are embedded in that result. | ||
383 | continue | ||
384 | } | ||
385 | |||
386 | // The shape of this collection isn't compatible with buildInstanceObjects, | ||
387 | // but rather than replicating most of the buildInstanceObjects logic | ||
388 | // here we'll instead first transform the structure to be what that | ||
389 | // function expects and then use it. This is a little wasteful, but | ||
390 | // we do not expect this these maps to be large and so the extra work | ||
391 | // here should not hurt too much. | ||
392 | flattened := make(map[addrs.InstanceKey]cty.Value, len(keys)) | ||
393 | for k, vals := range keys { | ||
394 | flattened[k] = cty.ObjectVal(vals) | ||
395 | } | ||
396 | vals[name] = buildInstanceObjects(flattened) | ||
397 | } | ||
398 | |||
399 | return vals | ||
400 | } | ||
401 | |||
402 | func buildInstanceObjects(keys map[addrs.InstanceKey]cty.Value) cty.Value { | ||
403 | if val, exists := keys[addrs.NoKey]; exists { | ||
404 | // If present, a "no key" value supersedes all other values, | ||
405 | // since they should be embedded inside it. | ||
406 | return val | ||
407 | } | ||
408 | |||
409 | // If we only have individual values then we need to construct | ||
410 | // either a list or a map, depending on what sort of keys we | ||
411 | // have. | ||
412 | haveInt := false | ||
413 | haveString := false | ||
414 | maxInt := 0 | ||
415 | |||
416 | for k := range keys { | ||
417 | switch tk := k.(type) { | ||
418 | case addrs.IntKey: | ||
419 | haveInt = true | ||
420 | if int(tk) > maxInt { | ||
421 | maxInt = int(tk) | ||
422 | } | ||
423 | case addrs.StringKey: | ||
424 | haveString = true | ||
425 | } | ||
426 | } | ||
427 | |||
428 | // We should either have ints or strings and not both, but | ||
429 | // if we have both then we'll prefer strings and let the | ||
430 | // language interpreter try to convert the int keys into | ||
431 | // strings in a map. | ||
432 | switch { | ||
433 | case haveString: | ||
434 | vals := make(map[string]cty.Value) | ||
435 | for k, v := range keys { | ||
436 | switch tk := k.(type) { | ||
437 | case addrs.StringKey: | ||
438 | vals[string(tk)] = v | ||
439 | case addrs.IntKey: | ||
440 | sk := strconv.Itoa(int(tk)) | ||
441 | vals[sk] = v | ||
442 | } | ||
443 | } | ||
444 | return cty.ObjectVal(vals) | ||
445 | case haveInt: | ||
446 | // We'll make a tuple that is long enough for our maximum | ||
447 | // index value. It doesn't matter if we end up shorter than | ||
448 | // the number of instances because if length(...) were | ||
449 | // being evaluated we would've got a NoKey reference and | ||
450 | // thus not ended up in this codepath at all. | ||
451 | vals := make([]cty.Value, maxInt+1) | ||
452 | for i := range vals { | ||
453 | if v, exists := keys[addrs.IntKey(i)]; exists { | ||
454 | vals[i] = v | ||
455 | } else { | ||
456 | // Just a placeholder, since nothing will access this anyway | ||
457 | vals[i] = cty.DynamicVal | ||
458 | } | ||
459 | } | ||
460 | return cty.TupleVal(vals) | ||
461 | default: | ||
462 | // Should never happen because there are no other key types. | ||
463 | log.Printf("[ERROR] strange makeInstanceObjects call with no supported key types") | ||
464 | return cty.EmptyObjectVal | ||
465 | } | ||
466 | } | ||
467 | |||
468 | func normalizeRefValue(val cty.Value, diags tfdiags.Diagnostics) (cty.Value, tfdiags.Diagnostics) { | ||
469 | if diags.HasErrors() { | ||
470 | // If there are errors then we will force an unknown result so that | ||
471 | // we can still evaluate and catch type errors but we'll avoid | ||
472 | // producing redundant re-statements of the same errors we've already | ||
473 | // dealt with here. | ||
474 | return cty.UnknownVal(val.Type()), diags | ||
475 | } | ||
476 | return val, diags | ||
477 | } | ||