github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/terraform/node_resource_destroy_deposed.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/cycloidio/terraform/addrs"
     8  	"github.com/cycloidio/terraform/dag"
     9  	"github.com/cycloidio/terraform/plans"
    10  	"github.com/cycloidio/terraform/states"
    11  	"github.com/cycloidio/terraform/tfdiags"
    12  )
    13  
    14  // ConcreteResourceInstanceDeposedNodeFunc is a callback type used to convert
    15  // an abstract resource instance to a concrete one of some type that has
    16  // an associated deposed object key.
    17  type ConcreteResourceInstanceDeposedNodeFunc func(*NodeAbstractResourceInstance, states.DeposedKey) dag.Vertex
    18  
    19  type GraphNodeDeposedResourceInstanceObject interface {
    20  	DeposedInstanceObjectKey() states.DeposedKey
    21  }
    22  
    23  // NodePlanDeposedResourceInstanceObject represents deposed resource
    24  // instance objects during plan. These are distinct from the primary object
    25  // for each resource instance since the only valid operation to do with them
    26  // is to destroy them.
    27  //
    28  // This node type is also used during the refresh walk to ensure that the
    29  // record of a deposed object is up-to-date before we plan to destroy it.
    30  type NodePlanDeposedResourceInstanceObject struct {
    31  	*NodeAbstractResourceInstance
    32  	DeposedKey states.DeposedKey
    33  
    34  	// skipRefresh indicates that we should skip refreshing individual instances
    35  	skipRefresh bool
    36  
    37  	// skipPlanChanges indicates we should skip trying to plan change actions
    38  	// for any instances.
    39  	skipPlanChanges bool
    40  }
    41  
    42  var (
    43  	_ GraphNodeDeposedResourceInstanceObject = (*NodePlanDeposedResourceInstanceObject)(nil)
    44  	_ GraphNodeConfigResource                = (*NodePlanDeposedResourceInstanceObject)(nil)
    45  	_ GraphNodeResourceInstance              = (*NodePlanDeposedResourceInstanceObject)(nil)
    46  	_ GraphNodeReferenceable                 = (*NodePlanDeposedResourceInstanceObject)(nil)
    47  	_ GraphNodeReferencer                    = (*NodePlanDeposedResourceInstanceObject)(nil)
    48  	_ GraphNodeExecutable                    = (*NodePlanDeposedResourceInstanceObject)(nil)
    49  	_ GraphNodeProviderConsumer              = (*NodePlanDeposedResourceInstanceObject)(nil)
    50  	_ GraphNodeProvisionerConsumer           = (*NodePlanDeposedResourceInstanceObject)(nil)
    51  )
    52  
    53  func (n *NodePlanDeposedResourceInstanceObject) Name() string {
    54  	return fmt.Sprintf("%s (deposed %s)", n.ResourceInstanceAddr().String(), n.DeposedKey)
    55  }
    56  
    57  func (n *NodePlanDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
    58  	return n.DeposedKey
    59  }
    60  
    61  // GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
    62  func (n *NodePlanDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
    63  	// Deposed objects don't participate in references.
    64  	return nil
    65  }
    66  
    67  // GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
    68  func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference {
    69  	// We don't evaluate configuration for deposed objects, so they effectively
    70  	// make no references.
    71  	return nil
    72  }
    73  
    74  // GraphNodeEvalable impl.
    75  func (n *NodePlanDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
    76  	log.Printf("[TRACE] NodePlanDeposedResourceInstanceObject: planning %s deposed object %s", n.Addr, n.DeposedKey)
    77  
    78  	// Read the state for the deposed resource instance
    79  	state, err := n.readResourceInstanceStateDeposed(ctx, n.Addr, n.DeposedKey)
    80  	diags = diags.Append(err)
    81  	if diags.HasErrors() {
    82  		return diags
    83  	}
    84  
    85  	// Note any upgrades that readResourceInstanceState might've done in the
    86  	// prevRunState, so that it'll conform to current schema.
    87  	diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, state, prevRunState))
    88  	if diags.HasErrors() {
    89  		return diags
    90  	}
    91  	// Also the refreshState, because that should still reflect schema upgrades
    92  	// even if not refreshing.
    93  	diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, state, refreshState))
    94  	if diags.HasErrors() {
    95  		return diags
    96  	}
    97  
    98  	// We don't refresh during the planDestroy walk, since that is only adding
    99  	// the destroy changes to the plan and the provider will not be configured
   100  	// at this point. The other nodes use separate types for plan and destroy,
   101  	// while deposed instances are always a destroy operation, so the logic
   102  	// here is a bit overloaded.
   103  	if !n.skipRefresh && op != walkPlanDestroy {
   104  		// Refresh this object even though it is going to be destroyed, in
   105  		// case it's already been deleted outside of Terraform. If this is a
   106  		// normal plan, providers expect a Read request to remove missing
   107  		// resources from the plan before apply, and may not handle a missing
   108  		// resource during Delete correctly. If this is a simple refresh,
   109  		// Terraform is expected to remove the missing resource from the state
   110  		// entirely
   111  		refreshedState, refreshDiags := n.refresh(ctx, n.DeposedKey, state)
   112  		diags = diags.Append(refreshDiags)
   113  		if diags.HasErrors() {
   114  			return diags
   115  		}
   116  
   117  		diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, refreshedState, refreshState))
   118  		if diags.HasErrors() {
   119  			return diags
   120  		}
   121  
   122  		// If we refreshed then our subsequent planning should be in terms of
   123  		// the new object, not the original object.
   124  		state = refreshedState
   125  	}
   126  
   127  	if !n.skipPlanChanges {
   128  		var change *plans.ResourceInstanceChange
   129  		change, destroyPlanDiags := n.planDestroy(ctx, state, n.DeposedKey)
   130  		diags = diags.Append(destroyPlanDiags)
   131  		if diags.HasErrors() {
   132  			return diags
   133  		}
   134  
   135  		// NOTE: We don't check prevent_destroy for deposed objects, even
   136  		// though we would do so here for a "current" object, because
   137  		// if we've reached a point where an object is already deposed then
   138  		// we've already planned and partially-executed a create_before_destroy
   139  		// replace and we would've checked prevent_destroy at that point. We're
   140  		// now just need to get the deposed object destroyed, because there
   141  		// should be a new object already serving as its replacement.
   142  
   143  		diags = diags.Append(n.writeChange(ctx, change, n.DeposedKey))
   144  		if diags.HasErrors() {
   145  			return diags
   146  		}
   147  
   148  		diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, nil, workingState))
   149  	} else {
   150  		// The working state should at least be updated with the result
   151  		// of upgrading and refreshing from above.
   152  		diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, state, workingState))
   153  	}
   154  
   155  	return diags
   156  }
   157  
   158  // NodeDestroyDeposedResourceInstanceObject represents deposed resource
   159  // instance objects during apply. Nodes of this type are inserted by
   160  // DiffTransformer when the planned changeset contains "delete" changes for
   161  // deposed instance objects, and its only supported operation is to destroy
   162  // and then forget the associated object.
   163  type NodeDestroyDeposedResourceInstanceObject struct {
   164  	*NodeAbstractResourceInstance
   165  	DeposedKey states.DeposedKey
   166  }
   167  
   168  var (
   169  	_ GraphNodeDeposedResourceInstanceObject = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   170  	_ GraphNodeConfigResource                = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   171  	_ GraphNodeResourceInstance              = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   172  	_ GraphNodeDestroyer                     = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   173  	_ GraphNodeDestroyerCBD                  = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   174  	_ GraphNodeReferenceable                 = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   175  	_ GraphNodeReferencer                    = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   176  	_ GraphNodeExecutable                    = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   177  	_ GraphNodeProviderConsumer              = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   178  	_ GraphNodeProvisionerConsumer           = (*NodeDestroyDeposedResourceInstanceObject)(nil)
   179  )
   180  
   181  func (n *NodeDestroyDeposedResourceInstanceObject) Name() string {
   182  	return fmt.Sprintf("%s (destroy deposed %s)", n.ResourceInstanceAddr(), n.DeposedKey)
   183  }
   184  
   185  func (n *NodeDestroyDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
   186  	return n.DeposedKey
   187  }
   188  
   189  // GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
   190  func (n *NodeDestroyDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
   191  	// Deposed objects don't participate in references.
   192  	return nil
   193  }
   194  
   195  // GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
   196  func (n *NodeDestroyDeposedResourceInstanceObject) References() []*addrs.Reference {
   197  	// We don't evaluate configuration for deposed objects, so they effectively
   198  	// make no references.
   199  	return nil
   200  }
   201  
   202  // GraphNodeDestroyer
   203  func (n *NodeDestroyDeposedResourceInstanceObject) DestroyAddr() *addrs.AbsResourceInstance {
   204  	addr := n.ResourceInstanceAddr()
   205  	return &addr
   206  }
   207  
   208  // GraphNodeDestroyerCBD
   209  func (n *NodeDestroyDeposedResourceInstanceObject) CreateBeforeDestroy() bool {
   210  	// A deposed instance is always CreateBeforeDestroy by definition, since
   211  	// we use deposed only to handle create-before-destroy.
   212  	return true
   213  }
   214  
   215  // GraphNodeDestroyerCBD
   216  func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v bool) error {
   217  	if !v {
   218  		// Should never happen: deposed instances are _always_ create_before_destroy.
   219  		return fmt.Errorf("can't deactivate create_before_destroy for a deposed instance")
   220  	}
   221  	return nil
   222  }
   223  
   224  // GraphNodeExecutable impl.
   225  func (n *NodeDestroyDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
   226  	var change *plans.ResourceInstanceChange
   227  
   228  	// Read the state for the deposed resource instance
   229  	state, err := n.readResourceInstanceStateDeposed(ctx, n.Addr, n.DeposedKey)
   230  	if err != nil {
   231  		return diags.Append(err)
   232  	}
   233  
   234  	if state == nil {
   235  		diags = diags.Append(fmt.Errorf("missing deposed state for %s (%s)", n.Addr, n.DeposedKey))
   236  		return diags
   237  	}
   238  
   239  	change, destroyPlanDiags := n.planDestroy(ctx, state, n.DeposedKey)
   240  	diags = diags.Append(destroyPlanDiags)
   241  	if diags.HasErrors() {
   242  		return diags
   243  	}
   244  
   245  	// Call pre-apply hook
   246  	diags = diags.Append(n.preApplyHook(ctx, change))
   247  	if diags.HasErrors() {
   248  		return diags
   249  	}
   250  
   251  	// we pass a nil configuration to apply because we are destroying
   252  	state, applyDiags := n.apply(ctx, state, change, nil, false)
   253  	diags = diags.Append(applyDiags)
   254  	// don't return immediately on errors, we need to handle the state
   255  
   256  	// Always write the resource back to the state deposed. If it
   257  	// was successfully destroyed it will be pruned. If it was not, it will
   258  	// be caught on the next run.
   259  	writeDiags := n.writeResourceInstanceState(ctx, state)
   260  	diags.Append(writeDiags)
   261  	if diags.HasErrors() {
   262  		return diags
   263  	}
   264  
   265  	diags = diags.Append(n.postApplyHook(ctx, state, diags.Err()))
   266  
   267  	return diags.Append(updateStateHook(ctx))
   268  }
   269  
   270  // GraphNodeDeposer is an optional interface implemented by graph nodes that
   271  // might create a single new deposed object for a specific associated resource
   272  // instance, allowing a caller to optionally pre-allocate a DeposedKey for
   273  // it.
   274  type GraphNodeDeposer interface {
   275  	// SetPreallocatedDeposedKey will be called during graph construction
   276  	// if a particular node must use a pre-allocated deposed key if/when it
   277  	// "deposes" the current object of its associated resource instance.
   278  	SetPreallocatedDeposedKey(key states.DeposedKey)
   279  }
   280  
   281  // graphNodeDeposer is an embeddable implementation of GraphNodeDeposer.
   282  // Embed it in a node type to get automatic support for it, and then access
   283  // the field PreallocatedDeposedKey to access any pre-allocated key.
   284  type graphNodeDeposer struct {
   285  	PreallocatedDeposedKey states.DeposedKey
   286  }
   287  
   288  func (n *graphNodeDeposer) SetPreallocatedDeposedKey(key states.DeposedKey) {
   289  	n.PreallocatedDeposedKey = key
   290  }
   291  
   292  func (n *NodeDestroyDeposedResourceInstanceObject) writeResourceInstanceState(ctx EvalContext, obj *states.ResourceInstanceObject) error {
   293  	absAddr := n.Addr
   294  	key := n.DeposedKey
   295  	state := ctx.State()
   296  
   297  	if key == states.NotDeposed {
   298  		// should never happen
   299  		return fmt.Errorf("can't save deposed object for %s without a deposed key; this is a bug in Terraform that should be reported", absAddr)
   300  	}
   301  
   302  	if obj == nil {
   303  		// No need to encode anything: we'll just write it directly.
   304  		state.SetResourceInstanceDeposed(absAddr, key, nil, n.ResolvedProvider)
   305  		log.Printf("[TRACE] writeResourceInstanceStateDeposed: removing state object for %s deposed %s", absAddr, key)
   306  		return nil
   307  	}
   308  
   309  	_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
   310  	if err != nil {
   311  		return err
   312  	}
   313  	if providerSchema == nil {
   314  		// Should never happen, unless our state object is nil
   315  		panic("writeResourceInstanceStateDeposed used with no ProviderSchema object")
   316  	}
   317  
   318  	schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource)
   319  	if schema == nil {
   320  		// It shouldn't be possible to get this far in any real scenario
   321  		// without a schema, but we might end up here in contrived tests that
   322  		// fail to set up their world properly.
   323  		return fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr)
   324  	}
   325  	src, err := obj.Encode(schema.ImpliedType(), currentVersion)
   326  	if err != nil {
   327  		return fmt.Errorf("failed to encode %s in state: %s", absAddr, err)
   328  	}
   329  
   330  	log.Printf("[TRACE] writeResourceInstanceStateDeposed: writing state object for %s deposed %s", absAddr, key)
   331  	state.SetResourceInstanceDeposed(absAddr, key, src, n.ResolvedProvider)
   332  	return nil
   333  }