github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_diff.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  
    12  	"github.com/opentofu/opentofu/internal/addrs"
    13  	"github.com/opentofu/opentofu/internal/configs"
    14  	"github.com/opentofu/opentofu/internal/dag"
    15  	"github.com/opentofu/opentofu/internal/plans"
    16  	"github.com/opentofu/opentofu/internal/states"
    17  	"github.com/opentofu/opentofu/internal/tfdiags"
    18  )
    19  
    20  // DiffTransformer is a GraphTransformer that adds graph nodes representing
    21  // each of the resource changes described in the given Changes object.
    22  type DiffTransformer struct {
    23  	Concrete ConcreteResourceInstanceNodeFunc
    24  	State    *states.State
    25  	Changes  *plans.Changes
    26  	Config   *configs.Config
    27  }
    28  
    29  // return true if the given resource instance has either Preconditions or
    30  // Postconditions defined in the configuration.
    31  func (t *DiffTransformer) hasConfigConditions(addr addrs.AbsResourceInstance) bool {
    32  	// unit tests may have no config
    33  	if t.Config == nil {
    34  		return false
    35  	}
    36  
    37  	cfg := t.Config.DescendentForInstance(addr.Module)
    38  	if cfg == nil {
    39  		return false
    40  	}
    41  
    42  	res := cfg.Module.ResourceByAddr(addr.ConfigResource().Resource)
    43  	if res == nil {
    44  		return false
    45  	}
    46  
    47  	return len(res.Preconditions) > 0 || len(res.Postconditions) > 0
    48  }
    49  
    50  func (t *DiffTransformer) Transform(g *Graph) error {
    51  	if t.Changes == nil || len(t.Changes.Resources) == 0 {
    52  		// Nothing to do!
    53  		return nil
    54  	}
    55  
    56  	// Go through all the modules in the diff.
    57  	log.Printf("[TRACE] DiffTransformer starting")
    58  
    59  	var diags tfdiags.Diagnostics
    60  	state := t.State
    61  	changes := t.Changes
    62  
    63  	// DiffTransformer creates resource _instance_ nodes. If there are any
    64  	// whole-resource nodes already in the graph, we must ensure that they
    65  	// get evaluated before any of the corresponding instances by creating
    66  	// dependency edges, so we'll do some prep work here to ensure we'll only
    67  	// create connections to nodes that existed before we started here.
    68  	resourceNodes := map[string][]GraphNodeConfigResource{}
    69  	for _, node := range g.Vertices() {
    70  		rn, ok := node.(GraphNodeConfigResource)
    71  		if !ok {
    72  			continue
    73  		}
    74  		// We ignore any instances that _also_ implement
    75  		// GraphNodeResourceInstance, since in the unlikely event that they
    76  		// do exist we'd probably end up creating cycles by connecting them.
    77  		if _, ok := node.(GraphNodeResourceInstance); ok {
    78  			continue
    79  		}
    80  
    81  		addr := rn.ResourceAddr().String()
    82  		resourceNodes[addr] = append(resourceNodes[addr], rn)
    83  	}
    84  
    85  	for _, rc := range changes.Resources {
    86  		addr := rc.Addr
    87  		dk := rc.DeposedKey
    88  
    89  		log.Printf("[TRACE] DiffTransformer: found %s change for %s %s", rc.Action, addr, dk)
    90  
    91  		// Depending on the action we'll need some different combinations of
    92  		// nodes, because destroying uses a special node type separate from
    93  		// other actions.
    94  		var update, delete, forget, createBeforeDestroy bool
    95  		switch rc.Action {
    96  		case plans.NoOp:
    97  			// For a no-op change we don't take any action but we still
    98  			// run any condition checks associated with the object, to
    99  			// make sure that they still hold when considering the
   100  			// results of other changes.
   101  			update = t.hasConfigConditions(addr)
   102  		case plans.Delete:
   103  			delete = true
   104  		case plans.Forget:
   105  			forget = true
   106  		case plans.DeleteThenCreate, plans.CreateThenDelete:
   107  			update = true
   108  			delete = true
   109  			createBeforeDestroy = (rc.Action == plans.CreateThenDelete)
   110  		default:
   111  			update = true
   112  		}
   113  
   114  		// A deposed instance may only have a change of Delete, Forget or NoOp.
   115  		// A NoOp can happen if the provider shows it no longer exists during
   116  		// the most recent ReadResource operation.
   117  		if dk != states.NotDeposed && !(rc.Action == plans.Delete || rc.Action == plans.NoOp || rc.Action == plans.Forget) {
   118  			diags = diags.Append(tfdiags.Sourceless(
   119  				tfdiags.Error,
   120  				"Invalid planned change for deposed object",
   121  				fmt.Sprintf("The plan contains a non-removal change for %s deposed object %s. The only valid actions for a deposed object is to destroy it or forget it, so this is a bug in OpenTofu.", addr, dk),
   122  			))
   123  			continue
   124  		}
   125  
   126  		// If we're going to do a create_before_destroy Replace operation then
   127  		// we need to allocate a DeposedKey to use to retain the
   128  		// not-yet-destroyed prior object, so that the delete node can destroy
   129  		// _that_ rather than the newly-created node, which will be current
   130  		// by the time the delete node is visited.
   131  		if update && delete && createBeforeDestroy {
   132  			// In this case, variable dk will be the _pre-assigned_ DeposedKey
   133  			// that must be used if the update graph node deposes the current
   134  			// instance, which will then align with the same key we pass
   135  			// into the destroy node to ensure we destroy exactly the deposed
   136  			// object we expect.
   137  			if state != nil {
   138  				ris := state.ResourceInstance(addr)
   139  				if ris == nil {
   140  					// Should never happen, since we don't plan to replace an
   141  					// instance that doesn't exist yet.
   142  					diags = diags.Append(tfdiags.Sourceless(
   143  						tfdiags.Error,
   144  						"Invalid planned change",
   145  						fmt.Sprintf("The plan contains a replace change for %s, which doesn't exist yet. This is a bug in OpenTofu.", addr),
   146  					))
   147  					continue
   148  				}
   149  
   150  				// Allocating a deposed key separately from using it can be racy
   151  				// in general, but we assume here that nothing except the apply
   152  				// node we instantiate below will actually make new deposed objects
   153  				// in practice, and so the set of already-used keys will not change
   154  				// between now and then.
   155  				dk = ris.FindUnusedDeposedKey()
   156  			} else {
   157  				// If we have no state at all yet then we can use _any_
   158  				// DeposedKey.
   159  				dk = states.NewDeposedKey()
   160  			}
   161  		}
   162  
   163  		if update {
   164  			// All actions except destroying the node type chosen by t.Concrete
   165  			abstract := NewNodeAbstractResourceInstance(addr)
   166  			var node dag.Vertex = abstract
   167  			if f := t.Concrete; f != nil {
   168  				node = f(abstract)
   169  			}
   170  
   171  			if createBeforeDestroy {
   172  				// We'll attach our pre-allocated DeposedKey to the node if
   173  				// it supports that. NodeApplyableResourceInstance is the
   174  				// specific concrete node type we are looking for here really,
   175  				// since that's the only node type that might depose objects.
   176  				if dn, ok := node.(GraphNodeDeposer); ok {
   177  					dn.SetPreallocatedDeposedKey(dk)
   178  				}
   179  				log.Printf("[TRACE] DiffTransformer: %s will be represented by %s, deposing prior object to %s", addr, dag.VertexName(node), dk)
   180  			} else {
   181  				log.Printf("[TRACE] DiffTransformer: %s will be represented by %s", addr, dag.VertexName(node))
   182  			}
   183  
   184  			g.Add(node)
   185  			rsrcAddr := addr.ContainingResource().String()
   186  			for _, rsrcNode := range resourceNodes[rsrcAddr] {
   187  				g.Connect(dag.BasicEdge(node, rsrcNode))
   188  			}
   189  		}
   190  
   191  		if delete {
   192  			// Destroying always uses a destroy-specific node type, though
   193  			// which one depends on whether we're destroying a current object
   194  			// or a deposed object.
   195  			var node GraphNodeResourceInstance
   196  			abstract := NewNodeAbstractResourceInstance(addr)
   197  			if dk == states.NotDeposed {
   198  				node = &NodeDestroyResourceInstance{
   199  					NodeAbstractResourceInstance: abstract,
   200  					DeposedKey:                   dk,
   201  				}
   202  			} else {
   203  				node = &NodeDestroyDeposedResourceInstanceObject{
   204  					NodeAbstractResourceInstance: abstract,
   205  					DeposedKey:                   dk,
   206  				}
   207  			}
   208  			if dk == states.NotDeposed {
   209  				log.Printf("[TRACE] DiffTransformer: %s will be represented for destruction by %s", addr, dag.VertexName(node))
   210  			} else {
   211  				log.Printf("[TRACE] DiffTransformer: %s deposed object %s will be represented for destruction by %s", addr, dk, dag.VertexName(node))
   212  			}
   213  			g.Add(node)
   214  		}
   215  
   216  		if forget {
   217  			var node GraphNodeResourceInstance
   218  			abstract := NewNodeAbstractResourceInstance(addr)
   219  			if dk == states.NotDeposed {
   220  				node = &NodeForgetResourceInstance{
   221  					NodeAbstractResourceInstance: abstract,
   222  					DeposedKey:                   dk,
   223  				}
   224  				log.Printf("[TRACE] DiffTransformer: %s will be represented for removal from the state by %s", addr, dag.VertexName(node))
   225  			} else {
   226  				node = &NodeForgetDeposedResourceInstanceObject{
   227  					NodeAbstractResourceInstance: abstract,
   228  					DeposedKey:                   dk,
   229  				}
   230  				log.Printf("[TRACE] DiffTransformer: %s deposed object %s will be represented for removal from the state by %s", addr, dk, dag.VertexName(node))
   231  			}
   232  
   233  			g.Add(node)
   234  		}
   235  
   236  	}
   237  
   238  	log.Printf("[TRACE] DiffTransformer complete")
   239  
   240  	return diags.Err()
   241  }