github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_reference.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 tofu 7 8 import ( 9 "fmt" 10 "log" 11 "sort" 12 13 "github.com/hashicorp/hcl/v2" 14 15 "github.com/opentofu/opentofu/internal/addrs" 16 "github.com/opentofu/opentofu/internal/configs/configschema" 17 "github.com/opentofu/opentofu/internal/dag" 18 "github.com/opentofu/opentofu/internal/lang" 19 ) 20 21 // GraphNodeReferenceable must be implemented by any node that represents 22 // a OpenTofu thing that can be referenced (resource, module, etc.). 23 // 24 // Even if the thing has no name, this should return an empty list. By 25 // implementing this and returning a non-nil result, you say that this CAN 26 // be referenced and other methods of referencing may still be possible (such 27 // as by path!) 28 type GraphNodeReferenceable interface { 29 GraphNodeModulePath 30 31 // ReferenceableAddrs returns a list of addresses through which this can be 32 // referenced. 33 ReferenceableAddrs() []addrs.Referenceable 34 } 35 36 // GraphNodeReferencer must be implemented by nodes that reference other 37 // OpenTofu items and therefore depend on them. 38 type GraphNodeReferencer interface { 39 GraphNodeModulePath 40 41 // References returns a list of references made by this node, which 42 // include both a referenced address and source location information for 43 // the reference. 44 References() []*addrs.Reference 45 } 46 47 // GraphNodeRootReferencer is implemented by nodes that reference the root 48 // module, for example module imports 49 type GraphNodeRootReferencer interface { 50 GraphNodeReferencer 51 52 RootReferences() []*addrs.Reference 53 } 54 55 type GraphNodeAttachDependencies interface { 56 GraphNodeConfigResource 57 AttachDependencies([]addrs.ConfigResource) 58 } 59 60 // graphNodeDependsOn is implemented by resources that need to expose any 61 // references set via DependsOn in their configuration. 62 type graphNodeDependsOn interface { 63 GraphNodeReferencer 64 DependsOn() []*addrs.Reference 65 } 66 67 // graphNodeAttachDataResourceDependsOn records all resources that are transitively 68 // referenced through depends_on in the configuration. This is used by data 69 // resources to determine if they can be read during the plan, or if they need 70 // to be further delayed until apply. 71 // We can only use an addrs.ConfigResource address here, because modules are 72 // not yet expended in the graph. While this will cause some extra data 73 // resources to show in the plan when their depends_on references may be in 74 // unrelated module instances, the fact that it only happens when there are any 75 // resource updates pending means we can still avoid the problem of the 76 // "perpetual diff" 77 type graphNodeAttachDataResourceDependsOn interface { 78 GraphNodeConfigResource 79 graphNodeDependsOn 80 81 // AttachDataResourceDependsOn stores the discovered dependencies in the 82 // resource node for evaluation later. 83 // 84 // The force parameter indicates that even if there are no dependencies, 85 // force the data source to act as though there are for refresh purposes. 86 // This is needed because yet-to-be-created resources won't be in the 87 // initial refresh graph, but may still be referenced through depends_on. 88 AttachDataResourceDependsOn(deps []addrs.ConfigResource, force bool) 89 } 90 91 // GraphNodeReferenceOutside is an interface that can optionally be implemented. 92 // A node that implements it can specify that its own referenceable addresses 93 // and/or the addresses it references are in a different module than the 94 // node itself. 95 // 96 // Any referenceable addresses returned by ReferenceableAddrs are interpreted 97 // relative to the returned selfPath. 98 // 99 // Any references returned by References are interpreted relative to the 100 // returned referencePath. 101 // 102 // It is valid but not required for either of these paths to match what is 103 // returned by method Path, though if both match the main Path then there 104 // is no reason to implement this method. 105 // 106 // The primary use-case for this is the nodes representing module input 107 // variables, since their expressions are resolved in terms of their calling 108 // module, but they are still referenced from their own module. 109 type GraphNodeReferenceOutside interface { 110 // ReferenceOutside returns a path in which any references from this node 111 // are resolved. 112 ReferenceOutside() (selfPath, referencePath addrs.Module) 113 } 114 115 // ReferenceTransformer is a GraphTransformer that connects all the 116 // nodes that reference each other in order to form the proper ordering. 117 type ReferenceTransformer struct{} 118 119 func (t *ReferenceTransformer) Transform(g *Graph) error { 120 // Build a reference map so we can efficiently look up the references 121 vs := g.Vertices() 122 m := NewReferenceMap(vs) 123 124 // Find the things that reference things and connect them 125 for _, v := range vs { 126 if _, ok := v.(GraphNodeDestroyer); ok { 127 // destroy nodes references are not connected, since they can only 128 // use their own state. 129 continue 130 } 131 132 parents := m.References(v) 133 parentsDbg := make([]string, len(parents)) 134 for i, v := range parents { 135 parentsDbg[i] = dag.VertexName(v) 136 } 137 log.Printf( 138 "[DEBUG] ReferenceTransformer: %q references: %v", 139 dag.VertexName(v), parentsDbg) 140 141 for _, parent := range parents { 142 // A destroy plan relies solely on the state, so we only need to 143 // ensure that temporary values are connected to get the evaluation 144 // order correct. Any references to destroy nodes will cause 145 // cycles, because they are connected in reverse order. 146 if _, ok := parent.(GraphNodeDestroyer); ok { 147 continue 148 } 149 150 if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(v, parent) { 151 g.Connect(dag.BasicEdge(v, parent)) 152 } else { 153 log.Printf("[TRACE] ReferenceTransformer: skipping %s => %s inter-module-instance dependency", dag.VertexName(v), dag.VertexName(parent)) 154 } 155 } 156 157 if len(parents) > 0 { 158 continue 159 } 160 } 161 162 return nil 163 } 164 165 type depMap map[string]addrs.ConfigResource 166 167 // add stores the vertex if it represents a resource in the 168 // graph. 169 func (m depMap) add(v dag.Vertex) { 170 // we're only concerned with resources which may have changes that 171 // need to be applied. 172 switch v := v.(type) { 173 case GraphNodeResourceInstance: 174 instAddr := v.ResourceInstanceAddr() 175 addr := instAddr.ContainingResource().Config() 176 m[addr.String()] = addr 177 case GraphNodeConfigResource: 178 addr := v.ResourceAddr() 179 m[addr.String()] = addr 180 } 181 } 182 183 // attachDataResourceDependsOnTransformer records all resources transitively 184 // referenced through a configuration depends_on. 185 type attachDataResourceDependsOnTransformer struct { 186 } 187 188 func (t attachDataResourceDependsOnTransformer) Transform(g *Graph) error { 189 // First we need to make a map of referenceable addresses to their vertices. 190 // This is very similar to what's done in ReferenceTransformer, but we keep 191 // implementation separate as they may need to change independently. 192 vertices := g.Vertices() 193 refMap := NewReferenceMap(vertices) 194 195 for _, v := range vertices { 196 depender, ok := v.(graphNodeAttachDataResourceDependsOn) 197 if !ok { 198 continue 199 } 200 201 // Only data need to attach depends_on, so they can determine if they 202 // are eligible to be read during plan. 203 if depender.ResourceAddr().Resource.Mode != addrs.DataResourceMode { 204 continue 205 } 206 207 // depMap will only add resource references then dedupe 208 deps := make(depMap) 209 dependsOnDeps, fromModule := refMap.dependsOn(g, depender) 210 for _, dep := range dependsOnDeps { 211 // any the dependency 212 deps.add(dep) 213 } 214 215 res := make([]addrs.ConfigResource, 0, len(deps)) 216 for _, d := range deps { 217 res = append(res, d) 218 } 219 220 log.Printf("[TRACE] attachDataDependenciesTransformer: %s depends on %s", depender.ResourceAddr(), res) 221 depender.AttachDataResourceDependsOn(res, fromModule) 222 } 223 224 return nil 225 } 226 227 // AttachDependenciesTransformer records all resource dependencies for each 228 // instance, and attaches the addresses to the node itself. Managed resource 229 // will record these in the state for proper ordering of destroy operations. 230 type AttachDependenciesTransformer struct { 231 } 232 233 func (t AttachDependenciesTransformer) Transform(g *Graph) error { 234 for _, v := range g.Vertices() { 235 attacher, ok := v.(GraphNodeAttachDependencies) 236 if !ok { 237 continue 238 } 239 selfAddr := attacher.ResourceAddr() 240 241 ans, err := g.Ancestors(v) 242 if err != nil { 243 return err 244 } 245 246 // dedupe addrs when there's multiple instances involved, or 247 // multiple paths in the un-reduced graph 248 depMap := map[string]addrs.ConfigResource{} 249 for _, d := range ans { 250 var addr addrs.ConfigResource 251 252 switch d := d.(type) { 253 case GraphNodeResourceInstance: 254 instAddr := d.ResourceInstanceAddr() 255 addr = instAddr.ContainingResource().Config() 256 case GraphNodeConfigResource: 257 addr = d.ResourceAddr() 258 default: 259 continue 260 } 261 262 if addr.Equal(selfAddr) { 263 continue 264 } 265 depMap[addr.String()] = addr 266 } 267 268 deps := make([]addrs.ConfigResource, 0, len(depMap)) 269 for _, d := range depMap { 270 deps = append(deps, d) 271 } 272 sort.Slice(deps, func(i, j int) bool { 273 return deps[i].String() < deps[j].String() 274 }) 275 276 log.Printf("[TRACE] AttachDependenciesTransformer: %s depends on %s", attacher.ResourceAddr(), deps) 277 attacher.AttachDependencies(deps) 278 } 279 280 return nil 281 } 282 283 func isDependableResource(v dag.Vertex) bool { 284 switch v.(type) { 285 case GraphNodeResourceInstance: 286 return true 287 case GraphNodeConfigResource: 288 return true 289 } 290 return false 291 } 292 293 // ReferenceMap is a structure that can be used to efficiently check 294 // for references on a graph, mapping internal reference keys (as produced by 295 // the mapKey method) to one or more vertices that are identified by each key. 296 type ReferenceMap map[string][]dag.Vertex 297 298 // References returns the set of vertices that the given vertex refers to, 299 // and any referenced addresses that do not have corresponding vertices. 300 func (m ReferenceMap) References(v dag.Vertex) []dag.Vertex { 301 rn, ok := v.(GraphNodeReferencer) 302 if !ok { 303 return nil 304 } 305 306 var matches []dag.Vertex 307 308 if rrn, ok := rn.(GraphNodeRootReferencer); ok { 309 for _, ref := range rrn.RootReferences() { 310 matches = append(matches, m.addReference(addrs.RootModule, v, ref)...) 311 } 312 } 313 314 for _, ref := range rn.References() { 315 matches = append(matches, m.addReference(vertexReferencePath(v), v, ref)...) 316 } 317 318 return matches 319 } 320 321 // addReferences returns the set of vertices that the given reference requires 322 // within a given module. It additionally excludes the current vertex. 323 func (m ReferenceMap) addReference(path addrs.Module, current dag.Vertex, ref *addrs.Reference) []dag.Vertex { 324 var matches []dag.Vertex 325 326 subject := ref.Subject 327 328 key := m.mapKey(path, subject) 329 if _, exists := m[key]; !exists { 330 // If what we were looking for was a ResourceInstance then we 331 // might be in a resource-oriented graph rather than an 332 // instance-oriented graph, and so we'll see if we have the 333 // resource itself instead. 334 switch ri := subject.(type) { 335 case addrs.ResourceInstance: 336 subject = ri.ContainingResource() 337 case addrs.ResourceInstancePhase: 338 subject = ri.ContainingResource() 339 case addrs.ModuleCallInstanceOutput: 340 subject = ri.ModuleCallOutput() 341 case addrs.ModuleCallInstance: 342 subject = ri.Call 343 case addrs.ProviderFunction: 344 return nil 345 default: 346 log.Printf("[INFO] ReferenceTransformer: reference not found: %q", subject) 347 return nil 348 } 349 key = m.mapKey(path, subject) 350 } 351 vertices := m[key] 352 for _, rv := range vertices { 353 // don't include self-references 354 if rv == current { 355 continue 356 } 357 matches = append(matches, rv) 358 } 359 return matches 360 } 361 362 // dependsOn returns the set of vertices that the given vertex refers to from 363 // the configured depends_on. The bool return value indicates if depends_on was 364 // found in a parent module configuration. 365 func (m ReferenceMap) dependsOn(g *Graph, depender graphNodeDependsOn) ([]dag.Vertex, bool) { 366 var res []dag.Vertex 367 fromModule := false 368 369 refs := depender.DependsOn() 370 371 // get any implied dependencies for data sources 372 refs = append(refs, m.dataDependsOn(depender)...) 373 374 // This is where we record that a module has depends_on configured. 375 if _, ok := depender.(*nodeExpandModule); ok && len(refs) > 0 { 376 fromModule = true 377 } 378 379 for _, ref := range refs { 380 subject := ref.Subject 381 382 key := m.referenceMapKey(depender, subject) 383 vertices, ok := m[key] 384 if !ok { 385 // the ReferenceMap generates all possible keys, so any warning 386 // here is probably not useful for this implementation. 387 continue 388 } 389 for _, rv := range vertices { 390 // don't include self-references 391 if rv == depender { 392 continue 393 } 394 res = append(res, rv) 395 396 // Check any ancestors for transitive dependencies when we're 397 // not pointed directly at a resource. We can't be much more 398 // precise here, since in order to maintain our guarantee that data 399 // sources will wait for explicit dependencies, if those dependencies 400 // happen to be a module, output, or variable, we have to find some 401 // upstream managed resource in order to check for a planned 402 // change. 403 if _, ok := rv.(GraphNodeConfigResource); !ok { 404 ans, _ := g.Ancestors(rv) 405 for _, v := range ans { 406 if isDependableResource(v) { 407 res = append(res, v) 408 } 409 } 410 } 411 } 412 } 413 414 parentDeps, fromParentModule := m.parentModuleDependsOn(g, depender) 415 res = append(res, parentDeps...) 416 417 return res, fromModule || fromParentModule 418 } 419 420 // Return extra depends_on references if this is a data source. 421 // For data sources we implicitly treat references to managed resources as 422 // depends_on entries. If a data source references a managed resource, even if 423 // that reference is resolvable, it stands to reason that the user intends for 424 // the data source to require that resource in some way. 425 func (m ReferenceMap) dataDependsOn(depender graphNodeDependsOn) []*addrs.Reference { 426 var refs []*addrs.Reference 427 if n, ok := depender.(GraphNodeConfigResource); ok && 428 n.ResourceAddr().Resource.Mode == addrs.DataResourceMode { 429 for _, r := range depender.References() { 430 431 var resAddr addrs.Resource 432 switch s := r.Subject.(type) { 433 case addrs.Resource: 434 resAddr = s 435 case addrs.ResourceInstance: 436 resAddr = s.Resource 437 r.Subject = resAddr 438 case addrs.ProviderFunction: 439 continue 440 } 441 442 if resAddr.Mode != addrs.ManagedResourceMode { 443 // We only want to wait on directly referenced managed resources. 444 // Data sources have no external side effects, so normal 445 // references to them in the config will suffice for proper 446 // ordering. 447 continue 448 } 449 450 refs = append(refs, r) 451 } 452 } 453 return refs 454 } 455 456 // parentModuleDependsOn returns the set of vertices that a data sources parent 457 // module references through the module call's depends_on. The bool return 458 // value indicates if depends_on was found in a parent module configuration. 459 func (m ReferenceMap) parentModuleDependsOn(g *Graph, depender graphNodeDependsOn) ([]dag.Vertex, bool) { 460 var res []dag.Vertex 461 fromModule := false 462 463 // Look for containing modules with DependsOn. 464 // This should be connected directly to the module node, so we only need to 465 // look one step away. 466 for _, v := range g.DownEdges(depender) { 467 // we're only concerned with module expansion nodes here. 468 mod, ok := v.(*nodeExpandModule) 469 if !ok { 470 continue 471 } 472 473 deps, fromParentModule := m.dependsOn(g, mod) 474 for _, dep := range deps { 475 // add the dependency 476 res = append(res, dep) 477 478 // and check any transitive resource dependencies for more resources 479 ans, _ := g.Ancestors(dep) 480 for _, v := range ans { 481 if isDependableResource(v) { 482 res = append(res, v) 483 } 484 } 485 } 486 fromModule = fromModule || fromParentModule 487 } 488 489 return res, fromModule 490 } 491 492 func (m *ReferenceMap) mapKey(path addrs.Module, addr addrs.Referenceable) string { 493 return fmt.Sprintf("%s|%s", path.String(), addr.String()) 494 } 495 496 // vertexReferenceablePath returns the path in which the given vertex can be 497 // referenced. This is the path that its results from ReferenceableAddrs 498 // are considered to be relative to. 499 // 500 // Only GraphNodeModulePath implementations can be referenced, so this method will 501 // panic if the given vertex does not implement that interface. 502 func vertexReferenceablePath(v dag.Vertex) addrs.Module { 503 sp, ok := v.(GraphNodeModulePath) 504 if !ok { 505 // Only nodes with paths can participate in a reference map. 506 panic(fmt.Errorf("vertexMapKey on vertex type %T which doesn't implement GraphNodeModulePath", sp)) 507 } 508 509 if outside, ok := v.(GraphNodeReferenceOutside); ok { 510 // Vertex is referenced from a different module than where it was 511 // declared. 512 path, _ := outside.ReferenceOutside() 513 return path 514 } 515 516 // Vertex is referenced from the same module as where it was declared. 517 return sp.ModulePath() 518 } 519 520 // vertexReferencePath returns the path in which references _from_ the given 521 // vertex must be interpreted. 522 // 523 // Only GraphNodeModulePath implementations can have references, so this method 524 // will panic if the given vertex does not implement that interface. 525 func vertexReferencePath(v dag.Vertex) addrs.Module { 526 sp, ok := v.(GraphNodeModulePath) 527 if !ok { 528 // Only nodes with paths can participate in a reference map. 529 panic(fmt.Errorf("vertexReferencePath on vertex type %T which doesn't implement GraphNodeModulePath", v)) 530 } 531 532 if outside, ok := v.(GraphNodeReferenceOutside); ok { 533 // Vertex makes references to objects in a different module than where 534 // it was declared. 535 _, path := outside.ReferenceOutside() 536 return path 537 } 538 539 // Vertex makes references to objects in the same module as where it 540 // was declared. 541 return sp.ModulePath() 542 } 543 544 // referenceMapKey produces keys for the "edges" map. "referrer" is the vertex 545 // that the reference is from, and "addr" is the address of the object being 546 // referenced. 547 // 548 // The result is an opaque string that includes both the address of the given 549 // object and the address of the module instance that object belongs to. 550 // 551 // Only GraphNodeModulePath implementations can be referrers, so this method will 552 // panic if the given vertex does not implement that interface. 553 func (m *ReferenceMap) referenceMapKey(referrer dag.Vertex, addr addrs.Referenceable) string { 554 path := vertexReferencePath(referrer) 555 return m.mapKey(path, addr) 556 } 557 558 // NewReferenceMap is used to create a new reference map for the 559 // given set of vertices. 560 func NewReferenceMap(vs []dag.Vertex) ReferenceMap { 561 // Build the lookup table 562 m := make(ReferenceMap) 563 for _, v := range vs { 564 // We're only looking for referenceable nodes 565 rn, ok := v.(GraphNodeReferenceable) 566 if !ok { 567 continue 568 } 569 570 path := vertexReferenceablePath(v) 571 572 // Go through and cache them 573 for _, addr := range rn.ReferenceableAddrs() { 574 key := m.mapKey(path, addr) 575 m[key] = append(m[key], v) 576 } 577 } 578 579 return m 580 } 581 582 // ReferencesFromConfig returns the references that a configuration has 583 // based on the interpolated variables in a configuration. 584 func ReferencesFromConfig(body hcl.Body, schema *configschema.Block) []*addrs.Reference { 585 if body == nil { 586 return nil 587 } 588 refs, _ := lang.ReferencesInBlock(addrs.ParseRef, body, schema) 589 return refs 590 }