github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/node_resource_plan.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/dag"
    10  	"github.com/hashicorp/terraform/internal/states"
    11  	"github.com/hashicorp/terraform/internal/tfdiags"
    12  )
    13  
    14  // nodeExpandPlannableResource handles the first layer of resource
    15  // expansion.  We need this extra layer so DynamicExpand is called twice for
    16  // the resource, the first to expand the Resource for each module instance, and
    17  // the second to expand each ResourceInstance for the expanded Resources.
    18  type nodeExpandPlannableResource struct {
    19  	*NodeAbstractResource
    20  
    21  	// ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD
    22  	// during graph construction, if dependencies require us to force this
    23  	// on regardless of what the configuration says.
    24  	ForceCreateBeforeDestroy *bool
    25  
    26  	// skipRefresh indicates that we should skip refreshing individual instances
    27  	skipRefresh bool
    28  
    29  	// skipPlanChanges indicates we should skip trying to plan change actions
    30  	// for any instances.
    31  	skipPlanChanges bool
    32  
    33  	// forceReplace are resource instance addresses where the user wants to
    34  	// force generating a replace action. This set isn't pre-filtered, so
    35  	// it might contain addresses that have nothing to do with the resource
    36  	// that this node represents, which the node itself must therefore ignore.
    37  	forceReplace []addrs.AbsResourceInstance
    38  
    39  	// We attach dependencies to the Resource during refresh, since the
    40  	// instances are instantiated during DynamicExpand.
    41  	// FIXME: These would be better off converted to a generic Set data
    42  	// structure in the future, as we need to compare for equality and take the
    43  	// union of multiple groups of dependencies.
    44  	dependencies []addrs.ConfigResource
    45  }
    46  
    47  var (
    48  	_ GraphNodeDestroyerCBD         = (*nodeExpandPlannableResource)(nil)
    49  	_ GraphNodeDynamicExpandable    = (*nodeExpandPlannableResource)(nil)
    50  	_ GraphNodeReferenceable        = (*nodeExpandPlannableResource)(nil)
    51  	_ GraphNodeReferencer           = (*nodeExpandPlannableResource)(nil)
    52  	_ GraphNodeConfigResource       = (*nodeExpandPlannableResource)(nil)
    53  	_ GraphNodeAttachResourceConfig = (*nodeExpandPlannableResource)(nil)
    54  	_ GraphNodeAttachDependencies   = (*nodeExpandPlannableResource)(nil)
    55  	_ GraphNodeTargetable           = (*nodeExpandPlannableResource)(nil)
    56  )
    57  
    58  func (n *nodeExpandPlannableResource) Name() string {
    59  	return n.NodeAbstractResource.Name() + " (expand)"
    60  }
    61  
    62  // GraphNodeAttachDependencies
    63  func (n *nodeExpandPlannableResource) AttachDependencies(deps []addrs.ConfigResource) {
    64  	n.dependencies = deps
    65  }
    66  
    67  // GraphNodeDestroyerCBD
    68  func (n *nodeExpandPlannableResource) CreateBeforeDestroy() bool {
    69  	if n.ForceCreateBeforeDestroy != nil {
    70  		return *n.ForceCreateBeforeDestroy
    71  	}
    72  
    73  	// If we have no config, we just assume no
    74  	if n.Config == nil || n.Config.Managed == nil {
    75  		return false
    76  	}
    77  
    78  	return n.Config.Managed.CreateBeforeDestroy
    79  }
    80  
    81  // GraphNodeDestroyerCBD
    82  func (n *nodeExpandPlannableResource) ModifyCreateBeforeDestroy(v bool) error {
    83  	n.ForceCreateBeforeDestroy = &v
    84  	return nil
    85  }
    86  
    87  func (n *nodeExpandPlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
    88  	var g Graph
    89  
    90  	expander := ctx.InstanceExpander()
    91  	moduleInstances := expander.ExpandModule(n.Addr.Module)
    92  
    93  	// Add the current expanded resource to the graph
    94  	for _, module := range moduleInstances {
    95  		resAddr := n.Addr.Resource.Absolute(module)
    96  		g.Add(&NodePlannableResource{
    97  			NodeAbstractResource:     n.NodeAbstractResource,
    98  			Addr:                     resAddr,
    99  			ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy,
   100  			dependencies:             n.dependencies,
   101  			skipRefresh:              n.skipRefresh,
   102  			skipPlanChanges:          n.skipPlanChanges,
   103  			forceReplace:             n.forceReplace,
   104  		})
   105  	}
   106  
   107  	// Lock the state while we inspect it
   108  	state := ctx.State().Lock()
   109  	defer ctx.State().Unlock()
   110  
   111  	var orphans []*states.Resource
   112  	for _, res := range state.Resources(n.Addr) {
   113  		found := false
   114  		for _, m := range moduleInstances {
   115  			if m.Equal(res.Addr.Module) {
   116  				found = true
   117  				break
   118  			}
   119  		}
   120  		// Address form state was not found in the current config
   121  		if !found {
   122  			orphans = append(orphans, res)
   123  		}
   124  	}
   125  
   126  	// The concrete resource factory we'll use for orphans
   127  	concreteResourceOrphan := func(a *NodeAbstractResourceInstance) *NodePlannableResourceInstanceOrphan {
   128  		// Add the config and state since we don't do that via transforms
   129  		a.Config = n.Config
   130  		a.ResolvedProvider = n.ResolvedProvider
   131  		a.Schema = n.Schema
   132  		a.ProvisionerSchemas = n.ProvisionerSchemas
   133  		a.ProviderMetas = n.ProviderMetas
   134  		a.Dependencies = n.dependencies
   135  
   136  		return &NodePlannableResourceInstanceOrphan{
   137  			NodeAbstractResourceInstance: a,
   138  			skipRefresh:                  n.skipRefresh,
   139  			skipPlanChanges:              n.skipPlanChanges,
   140  		}
   141  	}
   142  
   143  	for _, res := range orphans {
   144  		for key := range res.Instances {
   145  			addr := res.Addr.Instance(key)
   146  			abs := NewNodeAbstractResourceInstance(addr)
   147  			abs.AttachResourceState(res)
   148  			n := concreteResourceOrphan(abs)
   149  			g.Add(n)
   150  		}
   151  	}
   152  
   153  	return &g, nil
   154  }
   155  
   156  // NodePlannableResource represents a resource that is "plannable":
   157  // it is ready to be planned in order to create a diff.
   158  type NodePlannableResource struct {
   159  	*NodeAbstractResource
   160  
   161  	Addr addrs.AbsResource
   162  
   163  	// ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD
   164  	// during graph construction, if dependencies require us to force this
   165  	// on regardless of what the configuration says.
   166  	ForceCreateBeforeDestroy *bool
   167  
   168  	// skipRefresh indicates that we should skip refreshing individual instances
   169  	skipRefresh bool
   170  
   171  	// skipPlanChanges indicates we should skip trying to plan change actions
   172  	// for any instances.
   173  	skipPlanChanges bool
   174  
   175  	// forceReplace are resource instance addresses where the user wants to
   176  	// force generating a replace action. This set isn't pre-filtered, so
   177  	// it might contain addresses that have nothing to do with the resource
   178  	// that this node represents, which the node itself must therefore ignore.
   179  	forceReplace []addrs.AbsResourceInstance
   180  
   181  	dependencies []addrs.ConfigResource
   182  }
   183  
   184  var (
   185  	_ GraphNodeModuleInstance       = (*NodePlannableResource)(nil)
   186  	_ GraphNodeDestroyerCBD         = (*NodePlannableResource)(nil)
   187  	_ GraphNodeDynamicExpandable    = (*NodePlannableResource)(nil)
   188  	_ GraphNodeReferenceable        = (*NodePlannableResource)(nil)
   189  	_ GraphNodeReferencer           = (*NodePlannableResource)(nil)
   190  	_ GraphNodeConfigResource       = (*NodePlannableResource)(nil)
   191  	_ GraphNodeAttachResourceConfig = (*NodePlannableResource)(nil)
   192  )
   193  
   194  func (n *NodePlannableResource) Path() addrs.ModuleInstance {
   195  	return n.Addr.Module
   196  }
   197  
   198  func (n *NodePlannableResource) Name() string {
   199  	return n.Addr.String()
   200  }
   201  
   202  // GraphNodeExecutable
   203  func (n *NodePlannableResource) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics {
   204  	var diags tfdiags.Diagnostics
   205  
   206  	if n.Config == nil {
   207  		// Nothing to do, then.
   208  		log.Printf("[TRACE] NodeApplyableResource: no configuration present for %s", n.Name())
   209  		return diags
   210  	}
   211  
   212  	// writeResourceState is responsible for informing the expander of what
   213  	// repetition mode this resource has, which allows expander.ExpandResource
   214  	// to work below.
   215  	moreDiags := n.writeResourceState(ctx, n.Addr)
   216  	diags = diags.Append(moreDiags)
   217  	if moreDiags.HasErrors() {
   218  		return diags
   219  	}
   220  
   221  	// Before we expand our resource into potentially many resource instances,
   222  	// we'll verify that any mention of this resource in n.forceReplace is
   223  	// consistent with the repetition mode of the resource. In other words,
   224  	// we're aiming to catch a situation where naming a particular resource
   225  	// instance would require an instance key but the given address has none.
   226  	expander := ctx.InstanceExpander()
   227  	instanceAddrs := expander.ExpandResource(n.ResourceAddr().Absolute(ctx.Path()))
   228  
   229  	// If there's a number of instances other than 1 then we definitely need
   230  	// an index.
   231  	mustHaveIndex := len(instanceAddrs) != 1
   232  	// If there's only one instance then we might still need an index, if the
   233  	// instance address has one.
   234  	if len(instanceAddrs) == 1 && instanceAddrs[0].Resource.Key != addrs.NoKey {
   235  		mustHaveIndex = true
   236  	}
   237  	if mustHaveIndex {
   238  		for _, candidateAddr := range n.forceReplace {
   239  			if candidateAddr.Resource.Key == addrs.NoKey {
   240  				if n.Addr.Resource.Equal(candidateAddr.Resource.Resource) {
   241  					switch {
   242  					case len(instanceAddrs) == 0:
   243  						// In this case there _are_ no instances to replace, so
   244  						// there isn't any alternative address for us to suggest.
   245  						diags = diags.Append(tfdiags.Sourceless(
   246  							tfdiags.Warning,
   247  							"Incompletely-matched force-replace resource instance",
   248  							fmt.Sprintf(
   249  								"Your force-replace request for %s doesn't match any resource instances because this resource doesn't have any instances.",
   250  								candidateAddr,
   251  							),
   252  						))
   253  					case len(instanceAddrs) == 1:
   254  						diags = diags.Append(tfdiags.Sourceless(
   255  							tfdiags.Warning,
   256  							"Incompletely-matched force-replace resource instance",
   257  							fmt.Sprintf(
   258  								"Your force-replace request for %s doesn't match any resource instances because it lacks an instance key.\n\nTo force replacement of the single declared instance, use the following option instead:\n  -replace=%q",
   259  								candidateAddr, instanceAddrs[0],
   260  							),
   261  						))
   262  					default:
   263  						var possibleValidOptions strings.Builder
   264  						for _, addr := range instanceAddrs {
   265  							fmt.Fprintf(&possibleValidOptions, "\n  -replace=%q", addr)
   266  						}
   267  
   268  						diags = diags.Append(tfdiags.Sourceless(
   269  							tfdiags.Warning,
   270  							"Incompletely-matched force-replace resource instance",
   271  							fmt.Sprintf(
   272  								"Your force-replace request for %s doesn't match any resource instances because it lacks an instance key.\n\nTo force replacement of particular instances, use one or more of the following options instead:%s",
   273  								candidateAddr, possibleValidOptions.String(),
   274  							),
   275  						))
   276  					}
   277  				}
   278  			}
   279  		}
   280  	}
   281  	// NOTE: The actual interpretation of n.forceReplace to produce replace
   282  	// actions is in NodeAbstractResourceInstance.plan, because we must do so
   283  	// on a per-instance basis rather than for the whole resource.
   284  
   285  	return diags
   286  }
   287  
   288  // GraphNodeDestroyerCBD
   289  func (n *NodePlannableResource) CreateBeforeDestroy() bool {
   290  	if n.ForceCreateBeforeDestroy != nil {
   291  		return *n.ForceCreateBeforeDestroy
   292  	}
   293  
   294  	// If we have no config, we just assume no
   295  	if n.Config == nil || n.Config.Managed == nil {
   296  		return false
   297  	}
   298  
   299  	return n.Config.Managed.CreateBeforeDestroy
   300  }
   301  
   302  // GraphNodeDestroyerCBD
   303  func (n *NodePlannableResource) ModifyCreateBeforeDestroy(v bool) error {
   304  	n.ForceCreateBeforeDestroy = &v
   305  	return nil
   306  }
   307  
   308  // GraphNodeDynamicExpandable
   309  func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
   310  	var diags tfdiags.Diagnostics
   311  
   312  	// Our instance expander should already have been informed about the
   313  	// expansion of this resource and of all of its containing modules, so
   314  	// it can tell us which instance addresses we need to process.
   315  	expander := ctx.InstanceExpander()
   316  	instanceAddrs := expander.ExpandResource(n.ResourceAddr().Absolute(ctx.Path()))
   317  
   318  	// Our graph transformers require access to the full state, so we'll
   319  	// temporarily lock it while we work on this.
   320  	state := ctx.State().Lock()
   321  	defer ctx.State().Unlock()
   322  
   323  	// If this is a resource that participates in custom condition checks
   324  	// (i.e. it has preconditions or postconditions) then the check state
   325  	// wants to know the addresses of the checkable objects so that it can
   326  	// treat them as unknown status if we encounter an error before actually
   327  	// visiting the checks.
   328  	if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.NodeAbstractResource.Addr) {
   329  		checkableAddrs := addrs.MakeSet[addrs.Checkable]()
   330  		for _, addr := range instanceAddrs {
   331  			checkableAddrs.Add(addr)
   332  		}
   333  		checkState.ReportCheckableObjects(n.NodeAbstractResource.Addr, checkableAddrs)
   334  	}
   335  
   336  	// The concrete resource factory we'll use
   337  	concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
   338  		// check if this node is being imported first
   339  		for _, importTarget := range n.importTargets {
   340  			if importTarget.Addr.Equal(a.Addr) {
   341  				return &graphNodeImportState{
   342  					Addr:             importTarget.Addr,
   343  					ID:               importTarget.ID,
   344  					ResolvedProvider: n.ResolvedProvider,
   345  				}
   346  			}
   347  		}
   348  
   349  		// Add the config and state since we don't do that via transforms
   350  		a.Config = n.Config
   351  		a.ResolvedProvider = n.ResolvedProvider
   352  		a.Schema = n.Schema
   353  		a.ProvisionerSchemas = n.ProvisionerSchemas
   354  		a.ProviderMetas = n.ProviderMetas
   355  		a.dependsOn = n.dependsOn
   356  		a.Dependencies = n.dependencies
   357  
   358  		return &NodePlannableResourceInstance{
   359  			NodeAbstractResourceInstance: a,
   360  
   361  			// By the time we're walking, we've figured out whether we need
   362  			// to force on CreateBeforeDestroy due to dependencies on other
   363  			// nodes that have it.
   364  			ForceCreateBeforeDestroy: n.CreateBeforeDestroy(),
   365  			skipRefresh:              n.skipRefresh,
   366  			skipPlanChanges:          n.skipPlanChanges,
   367  			forceReplace:             n.forceReplace,
   368  		}
   369  	}
   370  
   371  	// The concrete resource factory we'll use for orphans
   372  	concreteResourceOrphan := func(a *NodeAbstractResourceInstance) dag.Vertex {
   373  		// Add the config and state since we don't do that via transforms
   374  		a.Config = n.Config
   375  		a.ResolvedProvider = n.ResolvedProvider
   376  		a.Schema = n.Schema
   377  		a.ProvisionerSchemas = n.ProvisionerSchemas
   378  		a.ProviderMetas = n.ProviderMetas
   379  
   380  		return &NodePlannableResourceInstanceOrphan{
   381  			NodeAbstractResourceInstance: a,
   382  			skipRefresh:                  n.skipRefresh,
   383  			skipPlanChanges:              n.skipPlanChanges,
   384  		}
   385  	}
   386  
   387  	// Start creating the steps
   388  	steps := []GraphTransformer{
   389  		// Expand the count or for_each (if present)
   390  		&ResourceCountTransformer{
   391  			Concrete:      concreteResource,
   392  			Schema:        n.Schema,
   393  			Addr:          n.ResourceAddr(),
   394  			InstanceAddrs: instanceAddrs,
   395  		},
   396  
   397  		// Add the count/for_each orphans
   398  		&OrphanResourceInstanceCountTransformer{
   399  			Concrete:      concreteResourceOrphan,
   400  			Addr:          n.Addr,
   401  			InstanceAddrs: instanceAddrs,
   402  			State:         state,
   403  		},
   404  
   405  		// Attach the state
   406  		&AttachStateTransformer{State: state},
   407  
   408  		// Targeting
   409  		&TargetsTransformer{Targets: n.Targets},
   410  
   411  		// Connect references so ordering is correct
   412  		&ReferenceTransformer{},
   413  
   414  		// Make sure there is a single root
   415  		&RootTransformer{},
   416  	}
   417  
   418  	// Build the graph
   419  	b := &BasicGraphBuilder{
   420  		Steps: steps,
   421  		Name:  "NodePlannableResource",
   422  	}
   423  	graph, diags := b.Build(ctx.Path())
   424  	return graph, diags.ErrWithWarnings()
   425  }