]>
Commit | Line | Data |
---|---|---|
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 | forEachAttrs := map[string]cty.Value{} | |
207 | var self cty.Value | |
208 | ||
209 | for _, ref := range refs { | |
210 | rng := ref.SourceRange | |
211 | isSelf := false | |
212 | ||
213 | rawSubj := ref.Subject | |
214 | if rawSubj == addrs.Self { | |
215 | if selfAddr == nil { | |
216 | diags = diags.Append(&hcl.Diagnostic{ | |
217 | Severity: hcl.DiagError, | |
218 | Summary: `Invalid "self" reference`, | |
219 | // This detail message mentions some current practice that | |
220 | // this codepath doesn't really "know about". If the "self" | |
221 | // object starts being supported in more contexts later then | |
222 | // we'll need to adjust this message. | |
223 | Detail: `The "self" object is not available in this context. This object can be used only in resource provisioner and connection blocks.`, | |
224 | Subject: ref.SourceRange.ToHCL().Ptr(), | |
225 | }) | |
226 | continue | |
227 | } | |
228 | ||
229 | // Treat "self" as an alias for the configured self address. | |
230 | rawSubj = selfAddr | |
231 | isSelf = true | |
232 | ||
233 | if rawSubj == addrs.Self { | |
234 | // Programming error: the self address cannot alias itself. | |
235 | panic("scope SelfAddr attempting to alias itself") | |
236 | } | |
237 | } | |
238 | ||
239 | // This type switch must cover all of the "Referenceable" implementations | |
240 | // in package addrs. | |
241 | switch subj := rawSubj.(type) { | |
242 | ||
243 | case addrs.ResourceInstance: | |
244 | var into map[string]map[string]map[addrs.InstanceKey]cty.Value | |
245 | switch subj.Resource.Mode { | |
246 | case addrs.ManagedResourceMode: | |
247 | into = managedResources | |
248 | case addrs.DataResourceMode: | |
249 | into = dataResources | |
250 | default: | |
251 | panic(fmt.Errorf("unsupported ResourceMode %s", subj.Resource.Mode)) | |
252 | } | |
253 | ||
254 | val, valDiags := normalizeRefValue(s.Data.GetResourceInstance(subj, rng)) | |
255 | diags = diags.Append(valDiags) | |
256 | ||
257 | r := subj.Resource | |
258 | if into[r.Type] == nil { | |
259 | into[r.Type] = make(map[string]map[addrs.InstanceKey]cty.Value) | |
260 | } | |
261 | if into[r.Type][r.Name] == nil { | |
262 | into[r.Type][r.Name] = make(map[addrs.InstanceKey]cty.Value) | |
263 | } | |
264 | into[r.Type][r.Name][subj.Key] = val | |
265 | if isSelf { | |
266 | self = val | |
267 | } | |
268 | ||
269 | case addrs.ModuleCallInstance: | |
270 | val, valDiags := normalizeRefValue(s.Data.GetModuleInstance(subj, rng)) | |
271 | diags = diags.Append(valDiags) | |
272 | ||
273 | if wholeModules[subj.Call.Name] == nil { | |
274 | wholeModules[subj.Call.Name] = make(map[addrs.InstanceKey]cty.Value) | |
275 | } | |
276 | wholeModules[subj.Call.Name][subj.Key] = val | |
277 | if isSelf { | |
278 | self = val | |
279 | } | |
280 | ||
281 | case addrs.ModuleCallOutput: | |
282 | val, valDiags := normalizeRefValue(s.Data.GetModuleInstanceOutput(subj, rng)) | |
283 | diags = diags.Append(valDiags) | |
284 | ||
285 | callName := subj.Call.Call.Name | |
286 | callKey := subj.Call.Key | |
287 | if moduleOutputs[callName] == nil { | |
288 | moduleOutputs[callName] = make(map[addrs.InstanceKey]map[string]cty.Value) | |
289 | } | |
290 | if moduleOutputs[callName][callKey] == nil { | |
291 | moduleOutputs[callName][callKey] = make(map[string]cty.Value) | |
292 | } | |
293 | moduleOutputs[callName][callKey][subj.Name] = val | |
294 | if isSelf { | |
295 | self = val | |
296 | } | |
297 | ||
298 | case addrs.InputVariable: | |
299 | val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng)) | |
300 | diags = diags.Append(valDiags) | |
301 | inputVariables[subj.Name] = val | |
302 | if isSelf { | |
303 | self = val | |
304 | } | |
305 | ||
306 | case addrs.LocalValue: | |
307 | val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng)) | |
308 | diags = diags.Append(valDiags) | |
309 | localValues[subj.Name] = val | |
310 | if isSelf { | |
311 | self = val | |
312 | } | |
313 | ||
314 | case addrs.PathAttr: | |
315 | val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng)) | |
316 | diags = diags.Append(valDiags) | |
317 | pathAttrs[subj.Name] = val | |
318 | if isSelf { | |
319 | self = val | |
320 | } | |
321 | ||
322 | case addrs.TerraformAttr: | |
323 | val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng)) | |
324 | diags = diags.Append(valDiags) | |
325 | terraformAttrs[subj.Name] = val | |
326 | if isSelf { | |
327 | self = val | |
328 | } | |
329 | ||
330 | case addrs.CountAttr: | |
331 | val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng)) | |
332 | diags = diags.Append(valDiags) | |
333 | countAttrs[subj.Name] = val | |
334 | if isSelf { | |
335 | self = val | |
336 | } | |
337 | ||
338 | case addrs.ForEachAttr: | |
339 | val, valDiags := normalizeRefValue(s.Data.GetForEachAttr(subj, rng)) | |
340 | diags = diags.Append(valDiags) | |
341 | forEachAttrs[subj.Name] = val | |
342 | if isSelf { | |
343 | self = val | |
344 | } | |
345 | ||
346 | default: | |
347 | // Should never happen | |
348 | panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj)) | |
349 | } | |
350 | } | |
351 | ||
352 | for k, v := range buildResourceObjects(managedResources) { | |
353 | vals[k] = v | |
354 | } | |
355 | vals["data"] = cty.ObjectVal(buildResourceObjects(dataResources)) | |
356 | vals["module"] = cty.ObjectVal(buildModuleObjects(wholeModules, moduleOutputs)) | |
357 | vals["var"] = cty.ObjectVal(inputVariables) | |
358 | vals["local"] = cty.ObjectVal(localValues) | |
359 | vals["path"] = cty.ObjectVal(pathAttrs) | |
360 | vals["terraform"] = cty.ObjectVal(terraformAttrs) | |
361 | vals["count"] = cty.ObjectVal(countAttrs) | |
362 | vals["each"] = cty.ObjectVal(forEachAttrs) | |
363 | if self != cty.NilVal { | |
364 | vals["self"] = self | |
365 | } | |
366 | ||
367 | return ctx, diags | |
368 | } | |
369 | ||
370 | func buildResourceObjects(resources map[string]map[string]map[addrs.InstanceKey]cty.Value) map[string]cty.Value { | |
371 | vals := make(map[string]cty.Value) | |
372 | for typeName, names := range resources { | |
373 | nameVals := make(map[string]cty.Value) | |
374 | for name, keys := range names { | |
375 | nameVals[name] = buildInstanceObjects(keys) | |
376 | } | |
377 | vals[typeName] = cty.ObjectVal(nameVals) | |
378 | } | |
379 | return vals | |
380 | } | |
381 | ||
382 | func buildModuleObjects(wholeModules map[string]map[addrs.InstanceKey]cty.Value, moduleOutputs map[string]map[addrs.InstanceKey]map[string]cty.Value) map[string]cty.Value { | |
383 | vals := make(map[string]cty.Value) | |
384 | ||
385 | for name, keys := range wholeModules { | |
386 | vals[name] = buildInstanceObjects(keys) | |
387 | } | |
388 | ||
389 | for name, keys := range moduleOutputs { | |
390 | if _, exists := wholeModules[name]; exists { | |
391 | // If we also have a whole module value for this name then we'll | |
392 | // skip this since the individual outputs are embedded in that result. | |
393 | continue | |
394 | } | |
395 | ||
396 | // The shape of this collection isn't compatible with buildInstanceObjects, | |
397 | // but rather than replicating most of the buildInstanceObjects logic | |
398 | // here we'll instead first transform the structure to be what that | |
399 | // function expects and then use it. This is a little wasteful, but | |
400 | // we do not expect this these maps to be large and so the extra work | |
401 | // here should not hurt too much. | |
402 | flattened := make(map[addrs.InstanceKey]cty.Value, len(keys)) | |
403 | for k, vals := range keys { | |
404 | flattened[k] = cty.ObjectVal(vals) | |
405 | } | |
406 | vals[name] = buildInstanceObjects(flattened) | |
407 | } | |
408 | ||
409 | return vals | |
410 | } | |
411 | ||
412 | func buildInstanceObjects(keys map[addrs.InstanceKey]cty.Value) cty.Value { | |
413 | if val, exists := keys[addrs.NoKey]; exists { | |
414 | // If present, a "no key" value supersedes all other values, | |
415 | // since they should be embedded inside it. | |
416 | return val | |
417 | } | |
418 | ||
419 | // If we only have individual values then we need to construct | |
420 | // either a list or a map, depending on what sort of keys we | |
421 | // have. | |
422 | haveInt := false | |
423 | haveString := false | |
424 | maxInt := 0 | |
425 | ||
426 | for k := range keys { | |
427 | switch tk := k.(type) { | |
428 | case addrs.IntKey: | |
429 | haveInt = true | |
430 | if int(tk) > maxInt { | |
431 | maxInt = int(tk) | |
432 | } | |
433 | case addrs.StringKey: | |
434 | haveString = true | |
435 | } | |
436 | } | |
437 | ||
438 | // We should either have ints or strings and not both, but | |
439 | // if we have both then we'll prefer strings and let the | |
440 | // language interpreter try to convert the int keys into | |
441 | // strings in a map. | |
442 | switch { | |
443 | case haveString: | |
444 | vals := make(map[string]cty.Value) | |
445 | for k, v := range keys { | |
446 | switch tk := k.(type) { | |
447 | case addrs.StringKey: | |
448 | vals[string(tk)] = v | |
449 | case addrs.IntKey: | |
450 | sk := strconv.Itoa(int(tk)) | |
451 | vals[sk] = v | |
452 | } | |
453 | } | |
454 | return cty.ObjectVal(vals) | |
455 | case haveInt: | |
456 | // We'll make a tuple that is long enough for our maximum | |
457 | // index value. It doesn't matter if we end up shorter than | |
458 | // the number of instances because if length(...) were | |
459 | // being evaluated we would've got a NoKey reference and | |
460 | // thus not ended up in this codepath at all. | |
461 | vals := make([]cty.Value, maxInt+1) | |
462 | for i := range vals { | |
463 | if v, exists := keys[addrs.IntKey(i)]; exists { | |
464 | vals[i] = v | |
465 | } else { | |
466 | // Just a placeholder, since nothing will access this anyway | |
467 | vals[i] = cty.DynamicVal | |
468 | } | |
469 | } | |
470 | return cty.TupleVal(vals) | |
471 | default: | |
472 | // Should never happen because there are no other key types. | |
473 | log.Printf("[ERROR] strange makeInstanceObjects call with no supported key types") | |
474 | return cty.EmptyObjectVal | |
475 | } | |
476 | } | |
477 | ||
478 | func normalizeRefValue(val cty.Value, diags tfdiags.Diagnostics) (cty.Value, tfdiags.Diagnostics) { | |
479 | if diags.HasErrors() { | |
480 | // If there are errors then we will force an unknown result so that | |
481 | // we can still evaluate and catch type errors but we'll avoid | |
482 | // producing redundant re-statements of the same errors we've already | |
483 | // dealt with here. | |
484 | return cty.UnknownVal(val.Type()), diags | |
485 | } | |
486 | return val, diags | |
487 | } |