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