]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/lang/eval.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / lang / eval.go
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 }