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"
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
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()
28 traversals := dynblock.ExpandVariablesHCLDec(body, spec)
29 refs, diags := References(traversals)
31 ctx, ctxDiags := s.EvalContext(refs)
32 diags = diags.Append(ctxDiags)
34 return dynblock.Expand(body, ctx), diags
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.
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.
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()
50 refs, diags := ReferencesInBlock(body, schema)
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
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)
68 val, evalDiags := hcldec.Decode(body, spec, ctx)
69 diags = diags.Append(evalDiags)
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.
79 // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion
80 // and just obtain the returned value directly.
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)
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
95 val, evalDiags := expr.Value(ctx)
96 diags = diags.Append(evalDiags)
98 if wantType != cty.DynamicPseudoType {
100 val, convErr = convert.Convert(val, wantType)
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(),
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.
120 // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion
121 // and just obtain the returned value directly.
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
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 {
139 val, convErr = convert.Convert(val, wantType)
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(),
153 // EvalContext constructs a HCL expression evaluation context whose variable
154 // scope contains sufficient values to satisfy the given set of references.
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)
163 func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceable) (*hcl.EvalContext, tfdiags.Diagnostics) {
165 panic("attempt to construct EvalContext for nil Scope")
168 var diags tfdiags.Diagnostics
169 vals := make(map[string]cty.Value)
170 funcs := s.Functions()
171 ctx := &hcl.EvalContext{
177 // Easy path for common case where there are no references at all.
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)
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{}
208 for _, ref := range refs {
209 rng := ref.SourceRange
212 rawSubj := ref.Subject
213 if rawSubj == addrs.Self {
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(),
228 // Treat "self" as an alias for the configured self address.
232 if rawSubj == addrs.Self {
233 // Programming error: the self address cannot alias itself.
234 panic("scope SelfAddr attempting to alias itself")
238 // This type switch must cover all of the "Referenceable" implementations
240 switch subj := rawSubj.(type) {
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:
250 panic(fmt.Errorf("unsupported ResourceMode %s", subj.Resource.Mode))
253 val, valDiags := normalizeRefValue(s.Data.GetResourceInstance(subj, rng))
254 diags = diags.Append(valDiags)
257 if into[r.Type] == nil {
258 into[r.Type] = make(map[string]map[addrs.InstanceKey]cty.Value)
260 if into[r.Type][r.Name] == nil {
261 into[r.Type][r.Name] = make(map[addrs.InstanceKey]cty.Value)
263 into[r.Type][r.Name][subj.Key] = val
268 case addrs.ModuleCallInstance:
269 val, valDiags := normalizeRefValue(s.Data.GetModuleInstance(subj, rng))
270 diags = diags.Append(valDiags)
272 if wholeModules[subj.Call.Name] == nil {
273 wholeModules[subj.Call.Name] = make(map[addrs.InstanceKey]cty.Value)
275 wholeModules[subj.Call.Name][subj.Key] = val
280 case addrs.ModuleCallOutput:
281 val, valDiags := normalizeRefValue(s.Data.GetModuleInstanceOutput(subj, rng))
282 diags = diags.Append(valDiags)
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)
289 if moduleOutputs[callName][callKey] == nil {
290 moduleOutputs[callName][callKey] = make(map[string]cty.Value)
292 moduleOutputs[callName][callKey][subj.Name] = val
297 case addrs.InputVariable:
298 val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng))
299 diags = diags.Append(valDiags)
300 inputVariables[subj.Name] = val
305 case addrs.LocalValue:
306 val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng))
307 diags = diags.Append(valDiags)
308 localValues[subj.Name] = val
314 val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng))
315 diags = diags.Append(valDiags)
316 pathAttrs[subj.Name] = val
321 case addrs.TerraformAttr:
322 val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng))
323 diags = diags.Append(valDiags)
324 terraformAttrs[subj.Name] = val
329 case addrs.CountAttr:
330 val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng))
331 diags = diags.Append(valDiags)
332 countAttrs[subj.Name] = val
338 // Should never happen
339 panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj))
343 for k, v := range buildResourceObjects(managedResources) {
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 {
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)
367 vals[typeName] = cty.ObjectVal(nameVals)
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)
375 for name, keys := range wholeModules {
376 vals[name] = buildInstanceObjects(keys)
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.
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)
396 vals[name] = buildInstanceObjects(flattened)
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.
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
416 for k := range keys {
417 switch tk := k.(type) {
420 if int(tk) > maxInt {
423 case addrs.StringKey:
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
434 vals := make(map[string]cty.Value)
435 for k, v := range keys {
436 switch tk := k.(type) {
437 case addrs.StringKey:
440 sk := strconv.Itoa(int(tk))
444 return cty.ObjectVal(vals)
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 {
456 // Just a placeholder, since nothing will access this anyway
457 vals[i] = cty.DynamicVal
460 return cty.TupleVal(vals)
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
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
474 return cty.UnknownVal(val.Type()), diags