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

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    11  )
    12  
    13  // DiffTransformer is a GraphTransformer that adds graph nodes representing
    14  // each of the resource changes described in the given Changes object.
    15  type DiffTransformer struct {
    16  	Concrete ConcreteResourceInstanceNodeFunc
    17  	State    *states.State
    18  	Changes  *plans.Changes
    19  }
    20  
    21  func (t *DiffTransformer) Transform(g *Graph) error {
    22  	if t.Changes == nil || len(t.Changes.Resources) == 0 {
    23  		// Nothing to do!
    24  		return nil
    25  	}
    26  
    27  	// Go through all the modules in the diff.
    28  	log.Printf("[TRACE] DiffTransformer starting")
    29  
    30  	var diags tfdiags.Diagnostics
    31  	state := t.State
    32  	changes := t.Changes
    33  
    34  	// DiffTransformer creates resource _instance_ nodes. If there are any
    35  	// whole-resource nodes already in the graph, we must ensure that they
    36  	// get evaluated before any of the corresponding instances by creating
    37  	// dependency edges, so we'll do some prep work here to ensure we'll only
    38  	// create connections to nodes that existed before we started here.
    39  	resourceNodes := map[string][]GraphNodeResource{}
    40  	for _, node := range g.Vertices() {
    41  		rn, ok := node.(GraphNodeResource)
    42  		if !ok {
    43  			continue
    44  		}
    45  		// We ignore any instances that _also_ implement
    46  		// GraphNodeResourceInstance, since in the unlikely event that they
    47  		// do exist we'd probably end up creating cycles by connecting them.
    48  		if _, ok := node.(GraphNodeResourceInstance); ok {
    49  			continue
    50  		}
    51  
    52  		addr := rn.ResourceAddr().String()
    53  		resourceNodes[addr] = append(resourceNodes[addr], rn)
    54  	}
    55  
    56  	for _, rc := range changes.Resources {
    57  		addr := rc.Addr
    58  		dk := rc.DeposedKey
    59  
    60  		log.Printf("[TRACE] DiffTransformer: found %s change for %s %s", rc.Action, addr, dk)
    61  
    62  		// Depending on the action we'll need some different combinations of
    63  		// nodes, because destroying uses a special node type separate from
    64  		// other actions.
    65  		var update, delete, createBeforeDestroy bool
    66  		switch rc.Action {
    67  		case plans.NoOp:
    68  			continue
    69  		case plans.Delete:
    70  			delete = true
    71  		case plans.DeleteThenCreate, plans.CreateThenDelete:
    72  			update = true
    73  			delete = true
    74  			createBeforeDestroy = (rc.Action == plans.CreateThenDelete)
    75  		default:
    76  			update = true
    77  		}
    78  
    79  		if dk != states.NotDeposed && update {
    80  			diags = diags.Append(tfdiags.Sourceless(
    81  				tfdiags.Error,
    82  				"Invalid planned change for deposed object",
    83  				fmt.Sprintf("The plan contains a non-delete change for %s deposed object %s. The only valid action for a deposed object is to destroy it, so this is a bug in Terraform.", addr, dk),
    84  			))
    85  			continue
    86  		}
    87  
    88  		// If we're going to do a create_before_destroy Replace operation then
    89  		// we need to allocate a DeposedKey to use to retain the
    90  		// not-yet-destroyed prior object, so that the delete node can destroy
    91  		// _that_ rather than the newly-created node, which will be current
    92  		// by the time the delete node is visited.
    93  		if update && delete && createBeforeDestroy {
    94  			// In this case, variable dk will be the _pre-assigned_ DeposedKey
    95  			// that must be used if the update graph node deposes the current
    96  			// instance, which will then align with the same key we pass
    97  			// into the destroy node to ensure we destroy exactly the deposed
    98  			// object we expect.
    99  			if state != nil {
   100  				ris := state.ResourceInstance(addr)
   101  				if ris == nil {
   102  					// Should never happen, since we don't plan to replace an
   103  					// instance that doesn't exist yet.
   104  					diags = diags.Append(tfdiags.Sourceless(
   105  						tfdiags.Error,
   106  						"Invalid planned change",
   107  						fmt.Sprintf("The plan contains a replace change for %s, which doesn't exist yet. This is a bug in Terraform.", addr),
   108  					))
   109  					continue
   110  				}
   111  
   112  				// Allocating a deposed key separately from using it can be racy
   113  				// in general, but we assume here that nothing except the apply
   114  				// node we instantiate below will actually make new deposed objects
   115  				// in practice, and so the set of already-used keys will not change
   116  				// between now and then.
   117  				dk = ris.FindUnusedDeposedKey()
   118  			} else {
   119  				// If we have no state at all yet then we can use _any_
   120  				// DeposedKey.
   121  				dk = states.NewDeposedKey()
   122  			}
   123  		}
   124  
   125  		if update {
   126  			// All actions except destroying the node type chosen by t.Concrete
   127  			abstract := NewNodeAbstractResourceInstance(addr)
   128  			var node dag.Vertex = abstract
   129  			if f := t.Concrete; f != nil {
   130  				node = f(abstract)
   131  			}
   132  
   133  			if createBeforeDestroy {
   134  				// We'll attach our pre-allocated DeposedKey to the node if
   135  				// it supports that. NodeApplyableResourceInstance is the
   136  				// specific concrete node type we are looking for here really,
   137  				// since that's the only node type that might depose objects.
   138  				if dn, ok := node.(GraphNodeDeposer); ok {
   139  					dn.SetPreallocatedDeposedKey(dk)
   140  				}
   141  				log.Printf("[TRACE] DiffTransformer: %s will be represented by %s, deposing prior object to %s", addr, dag.VertexName(node), dk)
   142  			} else {
   143  				log.Printf("[TRACE] DiffTransformer: %s will be represented by %s", addr, dag.VertexName(node))
   144  			}
   145  
   146  			g.Add(node)
   147  			rsrcAddr := addr.ContainingResource().String()
   148  			for _, rsrcNode := range resourceNodes[rsrcAddr] {
   149  				g.Connect(dag.BasicEdge(node, rsrcNode))
   150  			}
   151  		}
   152  
   153  		if delete {
   154  			// Destroying always uses a destroy-specific node type, though
   155  			// which one depends on whether we're destroying a current object
   156  			// or a deposed object.
   157  			var node GraphNodeResourceInstance
   158  			abstract := NewNodeAbstractResourceInstance(addr)
   159  			if dk == states.NotDeposed {
   160  				node = &NodeDestroyResourceInstance{
   161  					NodeAbstractResourceInstance: abstract,
   162  					DeposedKey:                   dk,
   163  				}
   164  				node.(*NodeDestroyResourceInstance).ModifyCreateBeforeDestroy(createBeforeDestroy)
   165  			} else {
   166  				node = &NodeDestroyDeposedResourceInstanceObject{
   167  					NodeAbstractResourceInstance: abstract,
   168  					DeposedKey:                   dk,
   169  				}
   170  			}
   171  			if dk == states.NotDeposed {
   172  				log.Printf("[TRACE] DiffTransformer: %s will be represented for destruction by %s", addr, dag.VertexName(node))
   173  			} else {
   174  				log.Printf("[TRACE] DiffTransformer: %s deposed object %s will be represented for destruction by %s", addr, dk, dag.VertexName(node))
   175  			}
   176  			g.Add(node)
   177  		}
   178  
   179  	}
   180  
   181  	log.Printf("[TRACE] DiffTransformer complete")
   182  
   183  	return diags.Err()
   184  }