github.com/pulumi/terraform@v1.4.0/pkg/lang/eval.go (about) 1 package lang 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/ext/dynblock" 8 "github.com/hashicorp/hcl/v2/hcldec" 9 "github.com/pulumi/terraform/pkg/addrs" 10 "github.com/pulumi/terraform/pkg/configs/configschema" 11 "github.com/pulumi/terraform/pkg/instances" 12 "github.com/pulumi/terraform/pkg/lang/blocktoattr" 13 "github.com/pulumi/terraform/pkg/tfdiags" 14 "github.com/zclconf/go-cty/cty" 15 "github.com/zclconf/go-cty/cty/convert" 16 ) 17 18 // ExpandBlock expands any "dynamic" blocks present in the given body. The 19 // result is a body with those blocks expanded, ready to be evaluated with 20 // EvalBlock. 21 // 22 // If the returned diagnostics contains errors then the result may be 23 // incomplete or invalid. 24 func (s *Scope) ExpandBlock(body hcl.Body, schema *configschema.Block) (hcl.Body, tfdiags.Diagnostics) { 25 spec := schema.DecoderSpec() 26 27 traversals := dynblock.ExpandVariablesHCLDec(body, spec) 28 refs, diags := References(traversals) 29 30 ctx, ctxDiags := s.EvalContext(refs) 31 diags = diags.Append(ctxDiags) 32 33 return dynblock.Expand(body, ctx), diags 34 } 35 36 // EvalBlock evaluates the given body using the given block schema and returns 37 // a cty object value representing its contents. The type of the result conforms 38 // to the implied type of the given schema. 39 // 40 // This function does not automatically expand "dynamic" blocks within the 41 // body. If that is desired, first call the ExpandBlock method to obtain 42 // an expanded body to pass to this method. 43 // 44 // If the returned diagnostics contains errors then the result may be 45 // incomplete or invalid. 46 func (s *Scope) EvalBlock(body hcl.Body, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { 47 spec := schema.DecoderSpec() 48 49 refs, diags := ReferencesInBlock(body, schema) 50 51 ctx, ctxDiags := s.EvalContext(refs) 52 diags = diags.Append(ctxDiags) 53 if diags.HasErrors() { 54 // We'll stop early if we found problems in the references, because 55 // it's likely evaluation will produce redundant copies of the same errors. 56 return cty.UnknownVal(schema.ImpliedType()), diags 57 } 58 59 // HACK: In order to remain compatible with some assumptions made in 60 // Terraform v0.11 and earlier about the approximate equivalence of 61 // attribute vs. block syntax, we do a just-in-time fixup here to allow 62 // any attribute in the schema that has a list-of-objects or set-of-objects 63 // kind to potentially be populated instead by one or more nested blocks 64 // whose type is the attribute name. 65 body = blocktoattr.FixUpBlockAttrs(body, schema) 66 67 val, evalDiags := hcldec.Decode(body, spec, ctx) 68 diags = diags.Append(evalDiags) 69 70 return val, diags 71 } 72 73 // EvalSelfBlock evaluates the given body only within the scope of the provided 74 // object and instance key data. References to the object must use self, and the 75 // key data will only contain count.index or each.key. The static values for 76 // terraform and path will also be available in this context. 77 func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschema.Block, keyData instances.RepetitionData) (cty.Value, tfdiags.Diagnostics) { 78 var diags tfdiags.Diagnostics 79 80 spec := schema.DecoderSpec() 81 82 vals := make(map[string]cty.Value) 83 vals["self"] = self 84 85 if !keyData.CountIndex.IsNull() { 86 vals["count"] = cty.ObjectVal(map[string]cty.Value{ 87 "index": keyData.CountIndex, 88 }) 89 } 90 if !keyData.EachKey.IsNull() { 91 vals["each"] = cty.ObjectVal(map[string]cty.Value{ 92 "key": keyData.EachKey, 93 }) 94 } 95 96 refs, refDiags := References(hcldec.Variables(body, spec)) 97 diags = diags.Append(refDiags) 98 99 terraformAttrs := map[string]cty.Value{} 100 pathAttrs := map[string]cty.Value{} 101 102 // We could always load the static values for Path and Terraform values, 103 // but we want to parse the references so that we can get source ranges for 104 // user diagnostics. 105 for _, ref := range refs { 106 // we already loaded the self value 107 if ref.Subject == addrs.Self { 108 continue 109 } 110 111 switch subj := ref.Subject.(type) { 112 case addrs.PathAttr: 113 val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, ref.SourceRange)) 114 diags = diags.Append(valDiags) 115 pathAttrs[subj.Name] = val 116 117 case addrs.TerraformAttr: 118 val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, ref.SourceRange)) 119 diags = diags.Append(valDiags) 120 terraformAttrs[subj.Name] = val 121 122 case addrs.CountAttr, addrs.ForEachAttr: 123 // each and count have already been handled. 124 125 default: 126 // This should have been caught in validation, but point the user 127 // to the correct location in case something slipped through. 128 diags = diags.Append(&hcl.Diagnostic{ 129 Severity: hcl.DiagError, 130 Summary: `Invalid reference`, 131 Detail: fmt.Sprintf("The reference to %q is not valid in this context", ref.Subject), 132 Subject: ref.SourceRange.ToHCL().Ptr(), 133 }) 134 } 135 } 136 137 vals["path"] = cty.ObjectVal(pathAttrs) 138 vals["terraform"] = cty.ObjectVal(terraformAttrs) 139 140 ctx := &hcl.EvalContext{ 141 Variables: vals, 142 Functions: s.Functions(), 143 } 144 145 val, decDiags := hcldec.Decode(body, schema.DecoderSpec(), ctx) 146 diags = diags.Append(decDiags) 147 return val, diags 148 } 149 150 // EvalExpr evaluates a single expression in the receiving context and returns 151 // the resulting value. The value will be converted to the given type before 152 // it is returned if possible, or else an error diagnostic will be produced 153 // describing the conversion error. 154 // 155 // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion 156 // and just obtain the returned value directly. 157 // 158 // If the returned diagnostics contains errors then the result may be 159 // incomplete, but will always be of the requested type. 160 func (s *Scope) EvalExpr(expr hcl.Expression, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) { 161 refs, diags := ReferencesInExpr(expr) 162 163 ctx, ctxDiags := s.EvalContext(refs) 164 diags = diags.Append(ctxDiags) 165 if diags.HasErrors() { 166 // We'll stop early if we found problems in the references, because 167 // it's likely evaluation will produce redundant copies of the same errors. 168 return cty.UnknownVal(wantType), diags 169 } 170 171 val, evalDiags := expr.Value(ctx) 172 diags = diags.Append(evalDiags) 173 174 if wantType != cty.DynamicPseudoType { 175 var convErr error 176 val, convErr = convert.Convert(val, wantType) 177 if convErr != nil { 178 val = cty.UnknownVal(wantType) 179 diags = diags.Append(&hcl.Diagnostic{ 180 Severity: hcl.DiagError, 181 Summary: "Incorrect value type", 182 Detail: fmt.Sprintf("Invalid expression value: %s.", tfdiags.FormatError(convErr)), 183 Subject: expr.Range().Ptr(), 184 Expression: expr, 185 EvalContext: ctx, 186 }) 187 } 188 } 189 190 return val, diags 191 } 192 193 // EvalReference evaluates the given reference in the receiving scope and 194 // returns the resulting value. The value will be converted to the given type before 195 // it is returned if possible, or else an error diagnostic will be produced 196 // describing the conversion error. 197 // 198 // Pass an expected type of cty.DynamicPseudoType to skip automatic conversion 199 // and just obtain the returned value directly. 200 // 201 // If the returned diagnostics contains errors then the result may be 202 // incomplete, but will always be of the requested type. 203 func (s *Scope) EvalReference(ref *addrs.Reference, wantType cty.Type) (cty.Value, tfdiags.Diagnostics) { 204 var diags tfdiags.Diagnostics 205 206 // We cheat a bit here and just build an EvalContext for our requested 207 // reference with the "self" address overridden, and then pull the "self" 208 // result out of it to return. 209 ctx, ctxDiags := s.evalContext([]*addrs.Reference{ref}, ref.Subject) 210 diags = diags.Append(ctxDiags) 211 val := ctx.Variables["self"] 212 if val == cty.NilVal { 213 val = cty.DynamicVal 214 } 215 216 var convErr error 217 val, convErr = convert.Convert(val, wantType) 218 if convErr != nil { 219 val = cty.UnknownVal(wantType) 220 diags = diags.Append(&hcl.Diagnostic{ 221 Severity: hcl.DiagError, 222 Summary: "Incorrect value type", 223 Detail: fmt.Sprintf("Invalid expression value: %s.", tfdiags.FormatError(convErr)), 224 Subject: ref.SourceRange.ToHCL().Ptr(), 225 }) 226 } 227 228 return val, diags 229 } 230 231 // EvalContext constructs a HCL expression evaluation context whose variable 232 // scope contains sufficient values to satisfy the given set of references. 233 // 234 // Most callers should prefer to use the evaluation helper methods that 235 // this type offers, but this is here for less common situations where the 236 // caller will handle the evaluation calls itself. 237 func (s *Scope) EvalContext(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) { 238 return s.evalContext(refs, s.SelfAddr) 239 } 240 241 func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceable) (*hcl.EvalContext, tfdiags.Diagnostics) { 242 if s == nil { 243 panic("attempt to construct EvalContext for nil Scope") 244 } 245 246 var diags tfdiags.Diagnostics 247 vals := make(map[string]cty.Value) 248 funcs := s.Functions() 249 ctx := &hcl.EvalContext{ 250 Variables: vals, 251 Functions: funcs, 252 } 253 254 if len(refs) == 0 { 255 // Easy path for common case where there are no references at all. 256 return ctx, diags 257 } 258 259 // First we'll do static validation of the references. This catches things 260 // early that might otherwise not get caught due to unknown values being 261 // present in the scope during planning. 262 staticDiags := s.Data.StaticValidateReferences(refs, selfAddr) 263 diags = diags.Append(staticDiags) 264 if staticDiags.HasErrors() { 265 return ctx, diags 266 } 267 268 // The reference set we are given has not been de-duped, and so there can 269 // be redundant requests in it for two reasons: 270 // - The same item is referenced multiple times 271 // - Both an item and that item's container are separately referenced. 272 // We will still visit every reference here and ask our data source for 273 // it, since that allows us to gather a full set of any errors and 274 // warnings, but once we've gathered all the data we'll then skip anything 275 // that's redundant in the process of populating our values map. 276 dataResources := map[string]map[string]cty.Value{} 277 managedResources := map[string]map[string]cty.Value{} 278 wholeModules := map[string]cty.Value{} 279 inputVariables := map[string]cty.Value{} 280 localValues := map[string]cty.Value{} 281 pathAttrs := map[string]cty.Value{} 282 terraformAttrs := map[string]cty.Value{} 283 countAttrs := map[string]cty.Value{} 284 forEachAttrs := map[string]cty.Value{} 285 var self cty.Value 286 287 for _, ref := range refs { 288 rng := ref.SourceRange 289 290 rawSubj := ref.Subject 291 if rawSubj == addrs.Self { 292 if selfAddr == nil { 293 diags = diags.Append(&hcl.Diagnostic{ 294 Severity: hcl.DiagError, 295 Summary: `Invalid "self" reference`, 296 // This detail message mentions some current practice that 297 // this codepath doesn't really "know about". If the "self" 298 // object starts being supported in more contexts later then 299 // we'll need to adjust this message. 300 Detail: `The "self" object is not available in this context. This object can be used only in resource provisioner, connection, and postcondition blocks.`, 301 Subject: ref.SourceRange.ToHCL().Ptr(), 302 }) 303 continue 304 } 305 306 if selfAddr == addrs.Self { 307 // Programming error: the self address cannot alias itself. 308 panic("scope SelfAddr attempting to alias itself") 309 } 310 311 // self can only be used within a resource instance 312 subj := selfAddr.(addrs.ResourceInstance) 313 314 val, valDiags := normalizeRefValue(s.Data.GetResource(subj.ContainingResource(), rng)) 315 316 diags = diags.Append(valDiags) 317 318 // Self is an exception in that it must always resolve to a 319 // particular instance. We will still insert the full resource into 320 // the context below. 321 var hclDiags hcl.Diagnostics 322 // We should always have a valid self index by this point, but in 323 // the case of an error, self may end up as a cty.DynamicValue. 324 switch k := subj.Key.(type) { 325 case addrs.IntKey: 326 self, hclDiags = hcl.Index(val, cty.NumberIntVal(int64(k)), ref.SourceRange.ToHCL().Ptr()) 327 diags = diags.Append(hclDiags) 328 case addrs.StringKey: 329 self, hclDiags = hcl.Index(val, cty.StringVal(string(k)), ref.SourceRange.ToHCL().Ptr()) 330 diags = diags.Append(hclDiags) 331 default: 332 self = val 333 } 334 continue 335 } 336 337 // This type switch must cover all of the "Referenceable" implementations 338 // in package addrs, however we are removing the possibility of 339 // Instances beforehand. 340 switch addr := rawSubj.(type) { 341 case addrs.ResourceInstance: 342 rawSubj = addr.ContainingResource() 343 case addrs.ModuleCallInstance: 344 rawSubj = addr.Call 345 case addrs.ModuleCallInstanceOutput: 346 rawSubj = addr.Call.Call 347 } 348 349 switch subj := rawSubj.(type) { 350 case addrs.Resource: 351 var into map[string]map[string]cty.Value 352 switch subj.Mode { 353 case addrs.ManagedResourceMode: 354 into = managedResources 355 case addrs.DataResourceMode: 356 into = dataResources 357 default: 358 panic(fmt.Errorf("unsupported ResourceMode %s", subj.Mode)) 359 } 360 361 val, valDiags := normalizeRefValue(s.Data.GetResource(subj, rng)) 362 diags = diags.Append(valDiags) 363 364 r := subj 365 if into[r.Type] == nil { 366 into[r.Type] = make(map[string]cty.Value) 367 } 368 into[r.Type][r.Name] = val 369 370 case addrs.ModuleCall: 371 val, valDiags := normalizeRefValue(s.Data.GetModule(subj, rng)) 372 diags = diags.Append(valDiags) 373 wholeModules[subj.Name] = val 374 375 case addrs.InputVariable: 376 val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng)) 377 diags = diags.Append(valDiags) 378 inputVariables[subj.Name] = val 379 380 case addrs.LocalValue: 381 val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng)) 382 diags = diags.Append(valDiags) 383 localValues[subj.Name] = val 384 385 case addrs.PathAttr: 386 val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng)) 387 diags = diags.Append(valDiags) 388 pathAttrs[subj.Name] = val 389 390 case addrs.TerraformAttr: 391 val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng)) 392 diags = diags.Append(valDiags) 393 terraformAttrs[subj.Name] = val 394 395 case addrs.CountAttr: 396 val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng)) 397 diags = diags.Append(valDiags) 398 countAttrs[subj.Name] = val 399 400 case addrs.ForEachAttr: 401 val, valDiags := normalizeRefValue(s.Data.GetForEachAttr(subj, rng)) 402 diags = diags.Append(valDiags) 403 forEachAttrs[subj.Name] = val 404 405 default: 406 // Should never happen 407 panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj)) 408 } 409 } 410 411 // Managed resources are exposed in two different locations. The primary 412 // is at the top level where the resource type name is the root of the 413 // traversal, but we also expose them under "resource" as an escaping 414 // technique if we add a reserved name in a future language edition which 415 // conflicts with someone's existing provider. 416 for k, v := range buildResourceObjects(managedResources) { 417 vals[k] = v 418 } 419 vals["resource"] = cty.ObjectVal(buildResourceObjects(managedResources)) 420 421 vals["data"] = cty.ObjectVal(buildResourceObjects(dataResources)) 422 vals["module"] = cty.ObjectVal(wholeModules) 423 vals["var"] = cty.ObjectVal(inputVariables) 424 vals["local"] = cty.ObjectVal(localValues) 425 vals["path"] = cty.ObjectVal(pathAttrs) 426 vals["terraform"] = cty.ObjectVal(terraformAttrs) 427 vals["count"] = cty.ObjectVal(countAttrs) 428 vals["each"] = cty.ObjectVal(forEachAttrs) 429 if self != cty.NilVal { 430 vals["self"] = self 431 } 432 433 return ctx, diags 434 } 435 436 func buildResourceObjects(resources map[string]map[string]cty.Value) map[string]cty.Value { 437 vals := make(map[string]cty.Value) 438 for typeName, nameVals := range resources { 439 vals[typeName] = cty.ObjectVal(nameVals) 440 } 441 return vals 442 } 443 444 func normalizeRefValue(val cty.Value, diags tfdiags.Diagnostics) (cty.Value, tfdiags.Diagnostics) { 445 if diags.HasErrors() { 446 // If there are errors then we will force an unknown result so that 447 // we can still evaluate and catch type errors but we'll avoid 448 // producing redundant re-statements of the same errors we've already 449 // dealt with here. 450 return cty.UnknownVal(val.Type()), diags 451 } 452 return val, diags 453 }