github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/node_resource_abstract.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sort"
     7  
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/lang"
    13  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    14  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    15  )
    16  
    17  // ConcreteResourceNodeFunc is a callback type used to convert an
    18  // abstract resource to a concrete one of some type.
    19  type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex
    20  
    21  // GraphNodeResource is implemented by any nodes that represent a resource.
    22  // The type of operation cannot be assumed, only that this node represents
    23  // the given resource.
    24  type GraphNodeResource interface {
    25  	ResourceAddr() addrs.AbsResource
    26  }
    27  
    28  // ConcreteResourceInstanceNodeFunc is a callback type used to convert an
    29  // abstract resource instance to a concrete one of some type.
    30  type ConcreteResourceInstanceNodeFunc func(*NodeAbstractResourceInstance) dag.Vertex
    31  
    32  // GraphNodeResourceInstance is implemented by any nodes that represent
    33  // a resource instance. A single resource may have multiple instances if,
    34  // for example, the "count" or "for_each" argument is used for it in
    35  // configuration.
    36  type GraphNodeResourceInstance interface {
    37  	ResourceInstanceAddr() addrs.AbsResourceInstance
    38  }
    39  
    40  // NodeAbstractResource represents a resource that has no associated
    41  // operations. It registers all the interfaces for a resource that common
    42  // across multiple operation types.
    43  type NodeAbstractResource struct {
    44  	Addr addrs.AbsResource // Addr is the address for this resource
    45  
    46  	// The fields below will be automatically set using the Attach
    47  	// interfaces if you're running those transforms, but also be explicitly
    48  	// set if you already have that information.
    49  
    50  	Schema        *configschema.Block // Schema for processing the configuration body
    51  	SchemaVersion uint64              // Schema version of "Schema", as decided by the provider
    52  	Config        *configs.Resource   // Config is the resource in the config
    53  
    54  	ProvisionerSchemas map[string]*configschema.Block
    55  
    56  	Targets []addrs.Targetable // Set from GraphNodeTargetable
    57  
    58  	// The address of the provider this resource will use
    59  	ResolvedProvider addrs.AbsProviderConfig
    60  }
    61  
    62  var (
    63  	_ GraphNodeSubPath                 = (*NodeAbstractResource)(nil)
    64  	_ GraphNodeReferenceable           = (*NodeAbstractResource)(nil)
    65  	_ GraphNodeReferencer              = (*NodeAbstractResource)(nil)
    66  	_ GraphNodeProviderConsumer        = (*NodeAbstractResource)(nil)
    67  	_ GraphNodeProvisionerConsumer     = (*NodeAbstractResource)(nil)
    68  	_ GraphNodeResource                = (*NodeAbstractResource)(nil)
    69  	_ GraphNodeAttachResourceConfig    = (*NodeAbstractResource)(nil)
    70  	_ GraphNodeAttachResourceSchema    = (*NodeAbstractResource)(nil)
    71  	_ GraphNodeAttachProvisionerSchema = (*NodeAbstractResource)(nil)
    72  	_ GraphNodeTargetable              = (*NodeAbstractResource)(nil)
    73  	_ dag.GraphNodeDotter              = (*NodeAbstractResource)(nil)
    74  )
    75  
    76  // NewNodeAbstractResource creates an abstract resource graph node for
    77  // the given absolute resource address.
    78  func NewNodeAbstractResource(addr addrs.AbsResource) *NodeAbstractResource {
    79  	return &NodeAbstractResource{
    80  		Addr: addr,
    81  	}
    82  }
    83  
    84  // NodeAbstractResourceInstance represents a resource instance with no
    85  // associated operations. It embeds NodeAbstractResource but additionally
    86  // contains an instance key, used to identify one of potentially many
    87  // instances that were created from a resource in configuration, e.g. using
    88  // the "count" or "for_each" arguments.
    89  type NodeAbstractResourceInstance struct {
    90  	NodeAbstractResource
    91  	InstanceKey addrs.InstanceKey
    92  
    93  	// The fields below will be automatically set using the Attach
    94  	// interfaces if you're running those transforms, but also be explicitly
    95  	// set if you already have that information.
    96  
    97  	ResourceState *states.Resource
    98  }
    99  
   100  var (
   101  	_ GraphNodeSubPath                 = (*NodeAbstractResourceInstance)(nil)
   102  	_ GraphNodeReferenceable           = (*NodeAbstractResourceInstance)(nil)
   103  	_ GraphNodeReferencer              = (*NodeAbstractResourceInstance)(nil)
   104  	_ GraphNodeProviderConsumer        = (*NodeAbstractResourceInstance)(nil)
   105  	_ GraphNodeProvisionerConsumer     = (*NodeAbstractResourceInstance)(nil)
   106  	_ GraphNodeResource                = (*NodeAbstractResourceInstance)(nil)
   107  	_ GraphNodeResourceInstance        = (*NodeAbstractResourceInstance)(nil)
   108  	_ GraphNodeAttachResourceState     = (*NodeAbstractResourceInstance)(nil)
   109  	_ GraphNodeAttachResourceConfig    = (*NodeAbstractResourceInstance)(nil)
   110  	_ GraphNodeAttachResourceSchema    = (*NodeAbstractResourceInstance)(nil)
   111  	_ GraphNodeAttachProvisionerSchema = (*NodeAbstractResourceInstance)(nil)
   112  	_ GraphNodeTargetable              = (*NodeAbstractResourceInstance)(nil)
   113  	_ dag.GraphNodeDotter              = (*NodeAbstractResourceInstance)(nil)
   114  )
   115  
   116  // NewNodeAbstractResourceInstance creates an abstract resource instance graph
   117  // node for the given absolute resource instance address.
   118  func NewNodeAbstractResourceInstance(addr addrs.AbsResourceInstance) *NodeAbstractResourceInstance {
   119  	// Due to the fact that we embed NodeAbstractResource, the given address
   120  	// actually ends up split between the resource address in the embedded
   121  	// object and the InstanceKey field in our own struct. The
   122  	// ResourceInstanceAddr method will stick these back together again on
   123  	// request.
   124  	return &NodeAbstractResourceInstance{
   125  		NodeAbstractResource: NodeAbstractResource{
   126  			Addr: addr.ContainingResource(),
   127  		},
   128  		InstanceKey: addr.Resource.Key,
   129  	}
   130  }
   131  
   132  func (n *NodeAbstractResource) Name() string {
   133  	return n.ResourceAddr().String()
   134  }
   135  
   136  func (n *NodeAbstractResourceInstance) Name() string {
   137  	return n.ResourceInstanceAddr().String()
   138  }
   139  
   140  // GraphNodeSubPath
   141  func (n *NodeAbstractResource) Path() addrs.ModuleInstance {
   142  	return n.Addr.Module
   143  }
   144  
   145  // GraphNodeReferenceable
   146  func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable {
   147  	return []addrs.Referenceable{n.Addr.Resource}
   148  }
   149  
   150  // GraphNodeReferenceable
   151  func (n *NodeAbstractResourceInstance) ReferenceableAddrs() []addrs.Referenceable {
   152  	addr := n.ResourceInstanceAddr()
   153  	return []addrs.Referenceable{
   154  		addr.Resource,
   155  
   156  		// A resource instance can also be referenced by the address of its
   157  		// containing resource, so that e.g. a reference to aws_instance.foo
   158  		// would match both aws_instance.foo[0] and aws_instance.foo[1].
   159  		addr.ContainingResource().Resource,
   160  	}
   161  }
   162  
   163  // GraphNodeReferencer
   164  func (n *NodeAbstractResource) References() []*addrs.Reference {
   165  	// If we have a config then we prefer to use that.
   166  	if c := n.Config; c != nil {
   167  		var result []*addrs.Reference
   168  
   169  		for _, traversal := range c.DependsOn {
   170  			ref, err := addrs.ParseRef(traversal)
   171  			if err != nil {
   172  				// We ignore this here, because this isn't a suitable place to return
   173  				// errors. This situation should be caught and rejected during
   174  				// validation.
   175  				log.Printf("[ERROR] Can't parse %#v from depends_on as reference: %s", traversal, err)
   176  				continue
   177  			}
   178  
   179  			result = append(result, ref)
   180  		}
   181  
   182  		if n.Schema == nil {
   183  			// Should never happens, but we'll log if it does so that we can
   184  			// see this easily when debugging.
   185  			log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name())
   186  		}
   187  
   188  		refs, _ := lang.ReferencesInExpr(c.Count)
   189  		result = append(result, refs...)
   190  		refs, _ = lang.ReferencesInExpr(c.ForEach)
   191  		result = append(result, refs...)
   192  		refs, _ = lang.ReferencesInBlock(c.Config, n.Schema)
   193  		result = append(result, refs...)
   194  		if c.Managed != nil {
   195  			for _, p := range c.Managed.Provisioners {
   196  				if p.When != configs.ProvisionerWhenCreate {
   197  					continue
   198  				}
   199  				if p.Connection != nil {
   200  					refs, _ = lang.ReferencesInBlock(p.Connection.Config, connectionBlockSupersetSchema)
   201  					result = append(result, refs...)
   202  				}
   203  
   204  				schema := n.ProvisionerSchemas[p.Type]
   205  				if schema == nil {
   206  					log.Printf("[WARN] no schema for provisioner %q is attached to %s, so provisioner block references cannot be detected", p.Type, n.Name())
   207  				}
   208  				refs, _ = lang.ReferencesInBlock(p.Config, schema)
   209  				result = append(result, refs...)
   210  			}
   211  		}
   212  		return result
   213  	}
   214  
   215  	// Otherwise, we have no references.
   216  	return nil
   217  }
   218  
   219  // GraphNodeReferencer
   220  func (n *NodeAbstractResourceInstance) References() []*addrs.Reference {
   221  	// If we have a configuration attached then we'll delegate to our
   222  	// embedded abstract resource, which knows how to extract dependencies
   223  	// from configuration.
   224  	if n.Config != nil {
   225  		if n.Schema == nil {
   226  			// We'll produce a log message about this out here so that
   227  			// we can include the full instance address, since the equivalent
   228  			// message in NodeAbstractResource.References cannot see it.
   229  			log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name())
   230  			return nil
   231  		}
   232  		return n.NodeAbstractResource.References()
   233  	}
   234  
   235  	// Otherwise, if we have state then we'll use the values stored in state
   236  	// as a fallback.
   237  	if rs := n.ResourceState; rs != nil {
   238  		if s := rs.Instance(n.InstanceKey); s != nil {
   239  			// State is still storing dependencies as old-style strings, so we'll
   240  			// need to do a little work here to massage this to the form we now
   241  			// want.
   242  			var result []*addrs.Reference
   243  
   244  			// It is (apparently) possible for s.Current to be nil. This proved
   245  			// difficult to reproduce, so we will fix the symptom here and hope
   246  			// to find the root cause another time.
   247  			//
   248  			// https://github.com/hashicorp/terraform-plugin-sdk/issues/21407
   249  			if s.Current == nil {
   250  				log.Printf("[WARN] no current state found for %s", n.Name())
   251  			} else {
   252  				for _, addr := range s.Current.Dependencies {
   253  					if addr == nil {
   254  						// Should never happen; indicates a bug in the state loader
   255  						panic(fmt.Sprintf("dependencies for current object on %s contains nil address", n.ResourceInstanceAddr()))
   256  					}
   257  
   258  					// This is a little weird: we need to manufacture an addrs.Reference
   259  					// with a fake range here because the state isn't something we can
   260  					// make source references into.
   261  					result = append(result, &addrs.Reference{
   262  						Subject: addr,
   263  						SourceRange: tfdiags.SourceRange{
   264  							Filename: "(state file)",
   265  						},
   266  					})
   267  				}
   268  			}
   269  			return result
   270  		}
   271  	}
   272  
   273  	// If we have neither config nor state then we have no references.
   274  	return nil
   275  }
   276  
   277  // StateReferences returns the dependencies to put into the state for
   278  // this resource.
   279  func (n *NodeAbstractResourceInstance) StateReferences() []addrs.Referenceable {
   280  	selfAddrs := n.ReferenceableAddrs()
   281  
   282  	// Since we don't include the source location references in our
   283  	// results from this method, we'll also filter out duplicates:
   284  	// there's no point in listing the same object twice without
   285  	// that additional context.
   286  	seen := map[string]struct{}{}
   287  
   288  	// Pretend that we've already "seen" all of our own addresses so that we
   289  	// won't record self-references in the state. This can arise if, for
   290  	// example, a provisioner for a resource refers to the resource itself,
   291  	// which is valid (since provisioners always run after apply) but should
   292  	// not create an explicit dependency edge.
   293  	for _, selfAddr := range selfAddrs {
   294  		seen[selfAddr.String()] = struct{}{}
   295  		if riAddr, ok := selfAddr.(addrs.ResourceInstance); ok {
   296  			seen[riAddr.ContainingResource().String()] = struct{}{}
   297  		}
   298  	}
   299  
   300  	depsRaw := n.References()
   301  	deps := make([]addrs.Referenceable, 0, len(depsRaw))
   302  	for _, d := range depsRaw {
   303  		subj := d.Subject
   304  		if mco, isOutput := subj.(addrs.ModuleCallOutput); isOutput {
   305  			// For state dependencies, we simplify outputs to just refer
   306  			// to the module as a whole. It's not really clear why we do this,
   307  			// but this logic is preserved from before the 0.12 rewrite of
   308  			// this function.
   309  			subj = mco.Call
   310  		}
   311  
   312  		k := subj.String()
   313  		if _, exists := seen[k]; exists {
   314  			continue
   315  		}
   316  		seen[k] = struct{}{}
   317  		switch tr := subj.(type) {
   318  		case addrs.ResourceInstance:
   319  			deps = append(deps, tr)
   320  		case addrs.Resource:
   321  			deps = append(deps, tr)
   322  		case addrs.ModuleCallInstance:
   323  			deps = append(deps, tr)
   324  		default:
   325  			// No other reference types are recorded in the state.
   326  		}
   327  	}
   328  
   329  	// We'll also sort them, since that'll avoid creating changes in the
   330  	// serialized state that make no semantic difference.
   331  	sort.Slice(deps, func(i, j int) bool {
   332  		// Simple string-based sort because we just care about consistency,
   333  		// not user-friendliness.
   334  		return deps[i].String() < deps[j].String()
   335  	})
   336  
   337  	return deps
   338  }
   339  
   340  func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) {
   341  	n.ResolvedProvider = p
   342  }
   343  
   344  // GraphNodeProviderConsumer
   345  func (n *NodeAbstractResource) ProvidedBy() (addrs.AbsProviderConfig, bool) {
   346  	// If we have a config we prefer that above all else
   347  	if n.Config != nil {
   348  		relAddr := n.Config.ProviderConfigAddr()
   349  		return relAddr.Absolute(n.Path()), false
   350  	}
   351  
   352  	// Use our type and containing module path to guess a provider configuration address
   353  	return n.Addr.Resource.DefaultProviderConfig().Absolute(n.Addr.Module), false
   354  }
   355  
   356  // GraphNodeProviderConsumer
   357  func (n *NodeAbstractResourceInstance) ProvidedBy() (addrs.AbsProviderConfig, bool) {
   358  	// If we have a config we prefer that above all else
   359  	if n.Config != nil {
   360  		relAddr := n.Config.ProviderConfigAddr()
   361  		return relAddr.Absolute(n.Path()), false
   362  	}
   363  
   364  	// If we have state, then we will use the provider from there
   365  	if n.ResourceState != nil {
   366  		// An address from the state must match exactly, since we must ensure
   367  		// we refresh/destroy a resource with the same provider configuration
   368  		// that created it.
   369  		return n.ResourceState.ProviderConfig, true
   370  	}
   371  
   372  	// Use our type and containing module path to guess a provider configuration address
   373  	return n.Addr.Resource.DefaultProviderConfig().Absolute(n.Path()), false
   374  }
   375  
   376  // GraphNodeProvisionerConsumer
   377  func (n *NodeAbstractResource) ProvisionedBy() []string {
   378  	// If we have no configuration, then we have no provisioners
   379  	if n.Config == nil || n.Config.Managed == nil {
   380  		return nil
   381  	}
   382  
   383  	// Build the list of provisioners we need based on the configuration.
   384  	// It is okay to have duplicates here.
   385  	result := make([]string, len(n.Config.Managed.Provisioners))
   386  	for i, p := range n.Config.Managed.Provisioners {
   387  		result[i] = p.Type
   388  	}
   389  
   390  	return result
   391  }
   392  
   393  // GraphNodeProvisionerConsumer
   394  func (n *NodeAbstractResource) AttachProvisionerSchema(name string, schema *configschema.Block) {
   395  	if n.ProvisionerSchemas == nil {
   396  		n.ProvisionerSchemas = make(map[string]*configschema.Block)
   397  	}
   398  	n.ProvisionerSchemas[name] = schema
   399  }
   400  
   401  // GraphNodeResource
   402  func (n *NodeAbstractResource) ResourceAddr() addrs.AbsResource {
   403  	return n.Addr
   404  }
   405  
   406  // GraphNodeResourceInstance
   407  func (n *NodeAbstractResourceInstance) ResourceInstanceAddr() addrs.AbsResourceInstance {
   408  	return n.NodeAbstractResource.Addr.Instance(n.InstanceKey)
   409  }
   410  
   411  // GraphNodeAddressable, TODO: remove, used by target, should unify
   412  func (n *NodeAbstractResource) ResourceAddress() *ResourceAddress {
   413  	return NewLegacyResourceAddress(n.Addr)
   414  }
   415  
   416  // GraphNodeTargetable
   417  func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) {
   418  	n.Targets = targets
   419  }
   420  
   421  // GraphNodeAttachResourceState
   422  func (n *NodeAbstractResourceInstance) AttachResourceState(s *states.Resource) {
   423  	n.ResourceState = s
   424  }
   425  
   426  // GraphNodeAttachResourceConfig
   427  func (n *NodeAbstractResource) AttachResourceConfig(c *configs.Resource) {
   428  	n.Config = c
   429  }
   430  
   431  // GraphNodeAttachResourceSchema impl
   432  func (n *NodeAbstractResource) AttachResourceSchema(schema *configschema.Block, version uint64) {
   433  	n.Schema = schema
   434  	n.SchemaVersion = version
   435  }
   436  
   437  // GraphNodeDotter impl.
   438  func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
   439  	return &dag.DotNode{
   440  		Name: name,
   441  		Attrs: map[string]string{
   442  			"label": n.Name(),
   443  			"shape": "box",
   444  		},
   445  	}
   446  }