github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/lang/globalref/analyzer_meta_references.go (about) 1 package globalref 2 3 import ( 4 "github.com/hashicorp/hcl/v2" 5 "github.com/hashicorp/terraform/internal/addrs" 6 "github.com/hashicorp/terraform/internal/configs/configschema" 7 "github.com/hashicorp/terraform/internal/lang" 8 "github.com/zclconf/go-cty/cty" 9 "github.com/zclconf/go-cty/cty/convert" 10 "github.com/zclconf/go-cty/cty/gocty" 11 ) 12 13 // MetaReferences inspects the configuration to find the references contained 14 // within the most specific object that the given address refers to. 15 // 16 // This finds only the direct references in that object, not any indirect 17 // references from those. This is a building block for some other Analyzer 18 // functions that can walk through multiple levels of reference. 19 // 20 // If the given reference refers to something that doesn't exist in the 21 // configuration we're analyzing then MetaReferences will return no 22 // meta-references at all, which is indistinguishable from an existing 23 // object that doesn't refer to anything. 24 func (a *Analyzer) MetaReferences(ref Reference) []Reference { 25 // This function is aiming to encapsulate the fact that a reference 26 // is actually quite a complex notion which includes both a specific 27 // object the reference is to, where each distinct object type has 28 // a very different representation in the configuration, and then 29 // also potentially an attribute or block within the definition of that 30 // object. Our goal is to make all of these different situations appear 31 // mostly the same to the caller, in that all of them can be reduced to 32 // a set of references regardless of which expression or expressions we 33 // derive those from. 34 35 moduleAddr := ref.ModuleAddr() 36 remaining := ref.LocalRef.Remaining 37 38 // Our first task then is to select an appropriate implementation based 39 // on which address type the reference refers to. 40 switch targetAddr := ref.LocalRef.Subject.(type) { 41 case addrs.InputVariable: 42 return a.metaReferencesInputVariable(moduleAddr, targetAddr, remaining) 43 case addrs.LocalValue: 44 return a.metaReferencesLocalValue(moduleAddr, targetAddr, remaining) 45 case addrs.ModuleCallInstanceOutput: 46 return a.metaReferencesOutputValue(moduleAddr, targetAddr, remaining) 47 case addrs.ModuleCallInstance: 48 return a.metaReferencesModuleCall(moduleAddr, targetAddr, remaining) 49 case addrs.ModuleCall: 50 // TODO: It isn't really correct to say that a reference to a module 51 // call is a reference to its no-key instance. Really what we want to 52 // say here is that it's a reference to _all_ instances, or to an 53 // instance with an unknown key, but we don't have any representation 54 // of that. For the moment it's pretty immaterial since most of our 55 // other analysis ignores instance keys anyway, but maybe we'll revisit 56 // this latter to distingish these two cases better. 57 return a.metaReferencesModuleCall(moduleAddr, targetAddr.Instance(addrs.NoKey), remaining) 58 case addrs.CountAttr, addrs.ForEachAttr: 59 if resourceAddr, ok := ref.ResourceInstance(); ok { 60 return a.metaReferencesCountOrEach(resourceAddr.ContainingResource()) 61 } 62 return nil 63 case addrs.ResourceInstance: 64 return a.metaReferencesResourceInstance(moduleAddr, targetAddr, remaining) 65 case addrs.Resource: 66 // TODO: It isn't really correct to say that a reference to a resource 67 // is a reference to its no-key instance. Really what we want to say 68 // here is that it's a reference to _all_ instances, or to an instance 69 // with an unknown key, but we don't have any representation of that. 70 // For the moment it's pretty immaterial since most of our other 71 // analysis ignores instance keys anyway, but maybe we'll revisit this 72 // latter to distingish these two cases better. 73 return a.metaReferencesResourceInstance(moduleAddr, targetAddr.Instance(addrs.NoKey), remaining) 74 default: 75 // For anything we don't explicitly support we'll just return no 76 // references. This includes the reference types that don't really 77 // refer to configuration objects at all, like "path.module", 78 // and so which cannot possibly generate any references. 79 return nil 80 } 81 } 82 83 func (a *Analyzer) metaReferencesInputVariable(calleeAddr addrs.ModuleInstance, addr addrs.InputVariable, remain hcl.Traversal) []Reference { 84 if calleeAddr.IsRoot() { 85 // A root module variable definition can never refer to anything, 86 // because it conceptually exists outside of any module. 87 return nil 88 } 89 90 callerAddr, callAddr := calleeAddr.Call() 91 92 // We need to find the module call inside the caller module. 93 callerCfg := a.ModuleConfig(callerAddr) 94 if callerCfg == nil { 95 return nil 96 } 97 call := callerCfg.ModuleCalls[callAddr.Name] 98 if call == nil { 99 return nil 100 } 101 102 // Now we need to look for an attribute matching the variable name inside 103 // the module block body. 104 body := call.Config 105 schema := &hcl.BodySchema{ 106 Attributes: []hcl.AttributeSchema{ 107 {Name: addr.Name}, 108 }, 109 } 110 // We don't check for errors here because we'll make a best effort to 111 // analyze whatever partial result HCL is able to extract. 112 content, _, _ := body.PartialContent(schema) 113 attr := content.Attributes[addr.Name] 114 if attr == nil { 115 return nil 116 } 117 refs, _ := lang.ReferencesInExpr(attr.Expr) 118 return absoluteRefs(callerAddr, refs) 119 } 120 121 func (a *Analyzer) metaReferencesOutputValue(callerAddr addrs.ModuleInstance, addr addrs.ModuleCallInstanceOutput, remain hcl.Traversal) []Reference { 122 calleeAddr := callerAddr.Child(addr.Call.Call.Name, addr.Call.Key) 123 124 // We need to find the output value declaration inside the callee module. 125 calleeCfg := a.ModuleConfig(calleeAddr) 126 if calleeCfg == nil { 127 return nil 128 } 129 130 oc := calleeCfg.Outputs[addr.Name] 131 if oc == nil { 132 return nil 133 } 134 135 // We don't check for errors here because we'll make a best effort to 136 // analyze whatever partial result HCL is able to extract. 137 refs, _ := lang.ReferencesInExpr(oc.Expr) 138 return absoluteRefs(calleeAddr, refs) 139 } 140 141 func (a *Analyzer) metaReferencesLocalValue(moduleAddr addrs.ModuleInstance, addr addrs.LocalValue, remain hcl.Traversal) []Reference { 142 modCfg := a.ModuleConfig(moduleAddr) 143 if modCfg == nil { 144 return nil 145 } 146 147 local := modCfg.Locals[addr.Name] 148 if local == nil { 149 return nil 150 } 151 152 // We don't check for errors here because we'll make a best effort to 153 // analyze whatever partial result HCL is able to extract. 154 refs, _ := lang.ReferencesInExpr(local.Expr) 155 return absoluteRefs(moduleAddr, refs) 156 } 157 158 func (a *Analyzer) metaReferencesModuleCall(callerAddr addrs.ModuleInstance, addr addrs.ModuleCallInstance, remain hcl.Traversal) []Reference { 159 calleeAddr := callerAddr.Child(addr.Call.Name, addr.Key) 160 161 // What we're really doing here is just rolling up all of the references 162 // from all of this module's output values. 163 calleeCfg := a.ModuleConfig(calleeAddr) 164 if calleeCfg == nil { 165 return nil 166 } 167 168 var ret []Reference 169 for name := range calleeCfg.Outputs { 170 outputAddr := addrs.ModuleCallInstanceOutput{ 171 Call: addr, 172 Name: name, 173 } 174 moreRefs := a.metaReferencesOutputValue(callerAddr, outputAddr, nil) 175 ret = append(ret, moreRefs...) 176 } 177 return ret 178 } 179 180 func (a *Analyzer) metaReferencesCountOrEach(resourceAddr addrs.AbsResource) []Reference { 181 return a.ReferencesFromResourceRepetition(resourceAddr) 182 } 183 184 func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstance, addr addrs.ResourceInstance, remain hcl.Traversal) []Reference { 185 modCfg := a.ModuleConfig(moduleAddr) 186 if modCfg == nil { 187 return nil 188 } 189 190 rc := modCfg.ResourceByAddr(addr.Resource) 191 if rc == nil { 192 return nil 193 } 194 195 // In valid cases we should have the schema for this resource type 196 // available. In invalid cases we might be dealing with partial information, 197 // and so the schema might be nil so we won't be able to return reference 198 // information for this particular situation. 199 providerSchema := a.providerSchemas[rc.Provider] 200 if providerSchema == nil { 201 return nil 202 } 203 204 resourceTypeSchema, _ := providerSchema.SchemaForResourceAddr(addr.Resource) 205 if resourceTypeSchema == nil { 206 return nil 207 } 208 209 // When analyzing the resource configuration to look for references, we'll 210 // make a best effort to narrow down to only a particular sub-portion of 211 // the configuration by following the remaining traversal steps. In the 212 // ideal case this will lead us to a specific expression, but as a 213 // compromise it might lead us to some nested blocks where at least we 214 // can limit our searching only to those. 215 bodies := []hcl.Body{rc.Config} 216 var exprs []hcl.Expression 217 schema := resourceTypeSchema 218 var steppingThrough *configschema.NestedBlock 219 var steppingThroughType string 220 nextStep := func(newBodies []hcl.Body, newExprs []hcl.Expression) { 221 // We append exprs but replace bodies because exprs represent extra 222 // expressions we collected on the path, such as dynamic block for_each, 223 // which can potentially contribute to the final evalcontext, but 224 // bodies never contribute any values themselves, and instead just 225 // narrow down where we're searching. 226 bodies = newBodies 227 exprs = append(exprs, newExprs...) 228 steppingThrough = nil 229 steppingThroughType = "" 230 // Caller must also update "schema" if necessary. 231 } 232 traverseInBlock := func(name string) ([]hcl.Body, []hcl.Expression) { 233 if attr := schema.Attributes[name]; attr != nil { 234 // When we reach a specific attribute we can't traverse any deeper, because attributes are the leaves of the schema. 235 schema = nil 236 return traverseAttr(bodies, name) 237 } else if blockType := schema.BlockTypes[name]; blockType != nil { 238 // We need to take a different action here depending on 239 // the nesting mode of the block type. Some require us 240 // to traverse in two steps in order to select a specific 241 // child block, while others we can just step through 242 // directly. 243 switch blockType.Nesting { 244 case configschema.NestingSingle, configschema.NestingGroup: 245 // There should be only zero or one blocks of this 246 // type, so we can traverse in only one step. 247 schema = &blockType.Block 248 return traverseNestedBlockSingle(bodies, name) 249 case configschema.NestingMap, configschema.NestingList, configschema.NestingSet: 250 steppingThrough = blockType 251 return bodies, exprs // Preserve current selections for the second step 252 default: 253 // The above should be exhaustive, but just in case 254 // we add something new in future we'll bail out 255 // here and conservatively return everything under 256 // the current traversal point. 257 schema = nil 258 return nil, nil 259 } 260 } 261 262 // We'll get here if the given name isn't in the schema at all. If so, 263 // there's nothing else to be done here. 264 schema = nil 265 return nil, nil 266 } 267 Steps: 268 for _, step := range remain { 269 // If we filter out all of our bodies before we finish traversing then 270 // we know we won't find anything else, because all of our subsequent 271 // traversal steps won't have any bodies to search. 272 if len(bodies) == 0 { 273 return nil 274 } 275 // If we no longer have a schema then that suggests we've 276 // traversed as deep as what the schema covers (e.g. we reached 277 // a specific attribute) and so we'll stop early, assuming that 278 // any remaining steps are traversals into an attribute expression 279 // result. 280 if schema == nil { 281 break 282 } 283 284 switch step := step.(type) { 285 286 case hcl.TraverseAttr: 287 switch { 288 case steppingThrough != nil: 289 // If we're stepping through a NestingMap block then 290 // it's valid to use attribute syntax to select one of 291 // the blocks by its label. Other nesting types require 292 // TraverseIndex, so can never be valid. 293 if steppingThrough.Nesting != configschema.NestingMap { 294 nextStep(nil, nil) // bail out 295 continue 296 } 297 nextStep(traverseNestedBlockMap(bodies, steppingThroughType, step.Name)) 298 schema = &steppingThrough.Block 299 default: 300 nextStep(traverseInBlock(step.Name)) 301 if schema == nil { 302 // traverseInBlock determined that we've traversed as 303 // deep as we can with reference to schema, so we'll 304 // stop here and just process whatever's selected. 305 break Steps 306 } 307 } 308 case hcl.TraverseIndex: 309 switch { 310 case steppingThrough != nil: 311 switch steppingThrough.Nesting { 312 case configschema.NestingMap: 313 keyVal, err := convert.Convert(step.Key, cty.String) 314 if err != nil { // Invalid traversal, so can't have any refs 315 nextStep(nil, nil) // bail out 316 continue 317 } 318 nextStep(traverseNestedBlockMap(bodies, steppingThroughType, keyVal.AsString())) 319 schema = &steppingThrough.Block 320 case configschema.NestingList: 321 idxVal, err := convert.Convert(step.Key, cty.Number) 322 if err != nil { // Invalid traversal, so can't have any refs 323 nextStep(nil, nil) // bail out 324 continue 325 } 326 var idx int 327 err = gocty.FromCtyValue(idxVal, &idx) 328 if err != nil { // Invalid traversal, so can't have any refs 329 nextStep(nil, nil) // bail out 330 continue 331 } 332 nextStep(traverseNestedBlockList(bodies, steppingThroughType, idx)) 333 schema = &steppingThrough.Block 334 default: 335 // Note that NestingSet ends up in here because we don't 336 // actually allow traversing into set-backed block types, 337 // and so such a reference would be invalid. 338 nextStep(nil, nil) // bail out 339 continue 340 } 341 default: 342 // When indexing the contents of a block directly we always 343 // interpret the key as a string representing an attribute 344 // name. 345 nameVal, err := convert.Convert(step.Key, cty.String) 346 if err != nil { // Invalid traversal, so can't have any refs 347 nextStep(nil, nil) // bail out 348 continue 349 } 350 nextStep(traverseInBlock(nameVal.AsString())) 351 if schema == nil { 352 // traverseInBlock determined that we've traversed as 353 // deep as we can with reference to schema, so we'll 354 // stop here and just process whatever's selected. 355 break Steps 356 } 357 } 358 default: 359 // We shouldn't get here, because the above cases are exhaustive 360 // for all of the relative traversal types, but we'll be robust in 361 // case HCL adds more in future and just pretend the traversal 362 // ended a bit early if so. 363 break Steps 364 } 365 } 366 367 if steppingThrough != nil { 368 // If we ended in the middle of "stepping through" then we'll conservatively 369 // use the bodies of _all_ nested blocks of the type we were stepping 370 // through, because the recipient of this value could refer to any 371 // of them dynamically. 372 var labelNames []string 373 if steppingThrough.Nesting == configschema.NestingMap { 374 labelNames = []string{"key"} 375 } 376 blocks := findBlocksInBodies(bodies, steppingThroughType, labelNames) 377 for _, block := range blocks { 378 bodies, exprs = blockParts(block) 379 } 380 } 381 382 if len(bodies) == 0 && len(exprs) == 0 { 383 return nil 384 } 385 386 var refs []*addrs.Reference 387 for _, expr := range exprs { 388 moreRefs, _ := lang.ReferencesInExpr(expr) 389 refs = append(refs, moreRefs...) 390 } 391 if schema != nil { 392 for _, body := range bodies { 393 moreRefs, _ := lang.ReferencesInBlock(body, schema) 394 refs = append(refs, moreRefs...) 395 } 396 } 397 return absoluteRefs(addr.Absolute(moduleAddr), refs) 398 } 399 400 func traverseAttr(bodies []hcl.Body, name string) ([]hcl.Body, []hcl.Expression) { 401 if len(bodies) == 0 { 402 return nil, nil 403 } 404 schema := &hcl.BodySchema{ 405 Attributes: []hcl.AttributeSchema{ 406 {Name: name}, 407 }, 408 } 409 // We can find at most one expression per body, because attribute names 410 // are always unique within a body. 411 retExprs := make([]hcl.Expression, 0, len(bodies)) 412 for _, body := range bodies { 413 content, _, _ := body.PartialContent(schema) 414 if attr := content.Attributes[name]; attr != nil && attr.Expr != nil { 415 retExprs = append(retExprs, attr.Expr) 416 } 417 } 418 return nil, retExprs 419 } 420 421 func traverseNestedBlockSingle(bodies []hcl.Body, typeName string) ([]hcl.Body, []hcl.Expression) { 422 if len(bodies) == 0 { 423 return nil, nil 424 } 425 426 blocks := findBlocksInBodies(bodies, typeName, nil) 427 var retBodies []hcl.Body 428 var retExprs []hcl.Expression 429 for _, block := range blocks { 430 moreBodies, moreExprs := blockParts(block) 431 retBodies = append(retBodies, moreBodies...) 432 retExprs = append(retExprs, moreExprs...) 433 } 434 return retBodies, retExprs 435 } 436 437 func traverseNestedBlockMap(bodies []hcl.Body, typeName string, key string) ([]hcl.Body, []hcl.Expression) { 438 if len(bodies) == 0 { 439 return nil, nil 440 } 441 442 blocks := findBlocksInBodies(bodies, typeName, []string{"key"}) 443 var retBodies []hcl.Body 444 var retExprs []hcl.Expression 445 for _, block := range blocks { 446 switch block.Type { 447 case "dynamic": 448 // For dynamic blocks we allow the key to be chosen dynamically 449 // and so we'll just conservatively include all dynamic block 450 // bodies. However, we need to also look for references in some 451 // arguments of the dynamic block itself. 452 argExprs, contentBody := dynamicBlockParts(block.Body) 453 retExprs = append(retExprs, argExprs...) 454 if contentBody != nil { 455 retBodies = append(retBodies, contentBody) 456 } 457 case typeName: 458 if len(block.Labels) == 1 && block.Labels[0] == key && block.Body != nil { 459 retBodies = append(retBodies, block.Body) 460 } 461 } 462 } 463 return retBodies, retExprs 464 } 465 466 func traverseNestedBlockList(bodies []hcl.Body, typeName string, idx int) ([]hcl.Body, []hcl.Expression) { 467 if len(bodies) == 0 { 468 return nil, nil 469 } 470 471 schema := &hcl.BodySchema{ 472 Blocks: []hcl.BlockHeaderSchema{ 473 {Type: typeName, LabelNames: nil}, 474 {Type: "dynamic", LabelNames: []string{"type"}}, 475 }, 476 } 477 var retBodies []hcl.Body 478 var retExprs []hcl.Expression 479 for _, body := range bodies { 480 content, _, _ := body.PartialContent(schema) 481 blocks := content.Blocks 482 483 // A tricky aspect of this scenario is that if there are any "dynamic" 484 // blocks then we can't statically predict how many concrete blocks they 485 // will generate, and so consequently we can't predict the indices of 486 // any statically-defined blocks that might appear after them. 487 firstDynamic := -1 // -1 means "no dynamic blocks" 488 for i, block := range blocks { 489 if block.Type == "dynamic" { 490 firstDynamic = i 491 break 492 } 493 } 494 495 switch { 496 case firstDynamic >= 0 && idx >= firstDynamic: 497 // This is the unfortunate case where the selection could be 498 // any of the blocks from firstDynamic onwards, and so we 499 // need to conservatively include all of them in our result. 500 for _, block := range blocks[firstDynamic:] { 501 moreBodies, moreExprs := blockParts(block) 502 retBodies = append(retBodies, moreBodies...) 503 retExprs = append(retExprs, moreExprs...) 504 } 505 default: 506 // This is the happier case where we can select just a single 507 // static block based on idx. Note that this one is guaranteed 508 // to never be dynamic but we're using blockParts here just 509 // for consistency. 510 moreBodies, moreExprs := blockParts(blocks[idx]) 511 retBodies = append(retBodies, moreBodies...) 512 retExprs = append(retExprs, moreExprs...) 513 } 514 } 515 516 return retBodies, retExprs 517 } 518 519 func findBlocksInBodies(bodies []hcl.Body, typeName string, labelNames []string) []*hcl.Block { 520 // We need to look for both static blocks of the given type, and any 521 // dynamic blocks whose label gives the expected type name. 522 schema := &hcl.BodySchema{ 523 Blocks: []hcl.BlockHeaderSchema{ 524 {Type: typeName, LabelNames: labelNames}, 525 {Type: "dynamic", LabelNames: []string{"type"}}, 526 }, 527 } 528 var blocks []*hcl.Block 529 for _, body := range bodies { 530 // We ignore errors here because we'll just make a best effort to analyze 531 // whatever partial result HCL returns in that case. 532 content, _, _ := body.PartialContent(schema) 533 534 for _, block := range content.Blocks { 535 switch block.Type { 536 case "dynamic": 537 if len(block.Labels) != 1 { // Invalid 538 continue 539 } 540 if block.Labels[0] == typeName { 541 blocks = append(blocks, block) 542 } 543 case typeName: 544 blocks = append(blocks, block) 545 } 546 } 547 } 548 549 // NOTE: The caller still needs to check for dynamic vs. static in order 550 // to do further processing. The callers above all aim to encapsulate 551 // that. 552 return blocks 553 } 554 555 func blockParts(block *hcl.Block) ([]hcl.Body, []hcl.Expression) { 556 switch block.Type { 557 case "dynamic": 558 exprs, contentBody := dynamicBlockParts(block.Body) 559 var bodies []hcl.Body 560 if contentBody != nil { 561 bodies = []hcl.Body{contentBody} 562 } 563 return bodies, exprs 564 default: 565 if block.Body == nil { 566 return nil, nil 567 } 568 return []hcl.Body{block.Body}, nil 569 } 570 } 571 572 func dynamicBlockParts(body hcl.Body) ([]hcl.Expression, hcl.Body) { 573 if body == nil { 574 return nil, nil 575 } 576 577 // This is a subset of the "dynamic" block schema defined by the HCL 578 // dynblock extension, covering only the two arguments that are allowed 579 // to be arbitrary expressions possibly referring elsewhere. 580 schema := &hcl.BodySchema{ 581 Attributes: []hcl.AttributeSchema{ 582 {Name: "for_each"}, 583 {Name: "labels"}, 584 }, 585 Blocks: []hcl.BlockHeaderSchema{ 586 {Type: "content"}, 587 }, 588 } 589 content, _, _ := body.PartialContent(schema) 590 var exprs []hcl.Expression 591 if len(content.Attributes) != 0 { 592 exprs = make([]hcl.Expression, 0, len(content.Attributes)) 593 } 594 for _, attr := range content.Attributes { 595 if attr.Expr != nil { 596 exprs = append(exprs, attr.Expr) 597 } 598 } 599 var contentBody hcl.Body 600 for _, block := range content.Blocks { 601 if block != nil && block.Type == "content" && block.Body != nil { 602 contentBody = block.Body 603 } 604 } 605 return exprs, contentBody 606 }