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  }