]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/lang/eval.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / lang / eval.go
CommitLineData
107c1cdb
ND
1package lang
2
3import (
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.
25func (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.
47func (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.
84func (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.
125func (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.
159func (s *Scope) EvalContext(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) {
160 return s.evalContext(refs, s.SelfAddr)
161}
162
163func (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{}
863486a6 206 forEachAttrs := map[string]cty.Value{}
107c1cdb
ND
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
863486a6
AG
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
107c1cdb
ND
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)
863486a6 362 vals["each"] = cty.ObjectVal(forEachAttrs)
107c1cdb
ND
363 if self != cty.NilVal {
364 vals["self"] = self
365 }
366
367 return ctx, diags
368}
369
370func 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
382func 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
412func 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
478func 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}