github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/transform_diff.go (about)

     1  package durgaform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/eliastor/durgaform/internal/dag"
     8  	"github.com/eliastor/durgaform/internal/plans"
     9  	"github.com/eliastor/durgaform/internal/states"
    10  	"github.com/eliastor/durgaform/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][]GraphNodeConfigResource{}
    40  	for _, node := range g.Vertices() {
    41  		rn, ok := node.(GraphNodeConfigResource)
    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  			// For a no-op change we don't take any action but we still
    69  			// run any condition checks associated with the object, to
    70  			// make sure that they still hold when considering the
    71  			// results of other changes.
    72  			update = true
    73  		case plans.Delete:
    74  			delete = true
    75  		case plans.DeleteThenCreate, plans.CreateThenDelete:
    76  			update = true
    77  			delete = true
    78  			createBeforeDestroy = (rc.Action == plans.CreateThenDelete)
    79  		default:
    80  			update = true
    81  		}
    82  
    83  		if dk != states.NotDeposed && update {
    84  			diags = diags.Append(tfdiags.Sourceless(
    85  				tfdiags.Error,
    86  				"Invalid planned change for deposed object",
    87  				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 Durgaform.", addr, dk),
    88  			))
    89  			continue
    90  		}
    91  
    92  		// If we're going to do a create_before_destroy Replace operation then
    93  		// we need to allocate a DeposedKey to use to retain the
    94  		// not-yet-destroyed prior object, so that the delete node can destroy
    95  		// _that_ rather than the newly-created node, which will be current
    96  		// by the time the delete node is visited.
    97  		if update && delete && createBeforeDestroy {
    98  			// In this case, variable dk will be the _pre-assigned_ DeposedKey
    99  			// that must be used if the update graph node deposes the current
   100  			// instance, which will then align with the same key we pass
   101  			// into the destroy node to ensure we destroy exactly the deposed
   102  			// object we expect.
   103  			if state != nil {
   104  				ris := state.ResourceInstance(addr)
   105  				if ris == nil {
   106  					// Should never happen, since we don't plan to replace an
   107  					// instance that doesn't exist yet.
   108  					diags = diags.Append(tfdiags.Sourceless(
   109  						tfdiags.Error,
   110  						"Invalid planned change",
   111  						fmt.Sprintf("The plan contains a replace change for %s, which doesn't exist yet. This is a bug in Durgaform.", addr),
   112  					))
   113  					continue
   114  				}
   115  
   116  				// Allocating a deposed key separately from using it can be racy
   117  				// in general, but we assume here that nothing except the apply
   118  				// node we instantiate below will actually make new deposed objects
   119  				// in practice, and so the set of already-used keys will not change
   120  				// between now and then.
   121  				dk = ris.FindUnusedDeposedKey()
   122  			} else {
   123  				// If we have no state at all yet then we can use _any_
   124  				// DeposedKey.
   125  				dk = states.NewDeposedKey()
   126  			}
   127  		}
   128  
   129  		if update {
   130  			// All actions except destroying the node type chosen by t.Concrete
   131  			abstract := NewNodeAbstractResourceInstance(addr)
   132  			var node dag.Vertex = abstract
   133  			if f := t.Concrete; f != nil {
   134  				node = f(abstract)
   135  			}
   136  
   137  			if createBeforeDestroy {
   138  				// We'll attach our pre-allocated DeposedKey to the node if
   139  				// it supports that. NodeApplyableResourceInstance is the
   140  				// specific concrete node type we are looking for here really,
   141  				// since that's the only node type that might depose objects.
   142  				if dn, ok := node.(GraphNodeDeposer); ok {
   143  					dn.SetPreallocatedDeposedKey(dk)
   144  				}
   145  				log.Printf("[TRACE] DiffTransformer: %s will be represented by %s, deposing prior object to %s", addr, dag.VertexName(node), dk)
   146  			} else {
   147  				log.Printf("[TRACE] DiffTransformer: %s will be represented by %s", addr, dag.VertexName(node))
   148  			}
   149  
   150  			g.Add(node)
   151  			rsrcAddr := addr.ContainingResource().String()
   152  			for _, rsrcNode := range resourceNodes[rsrcAddr] {
   153  				g.Connect(dag.BasicEdge(node, rsrcNode))
   154  			}
   155  		}
   156  
   157  		if delete {
   158  			// Destroying always uses a destroy-specific node type, though
   159  			// which one depends on whether we're destroying a current object
   160  			// or a deposed object.
   161  			var node GraphNodeResourceInstance
   162  			abstract := NewNodeAbstractResourceInstance(addr)
   163  			if dk == states.NotDeposed {
   164  				node = &NodeDestroyResourceInstance{
   165  					NodeAbstractResourceInstance: abstract,
   166  					DeposedKey:                   dk,
   167  				}
   168  			} else {
   169  				node = &NodeDestroyDeposedResourceInstanceObject{
   170  					NodeAbstractResourceInstance: abstract,
   171  					DeposedKey:                   dk,
   172  				}
   173  			}
   174  			if dk == states.NotDeposed {
   175  				log.Printf("[TRACE] DiffTransformer: %s will be represented for destruction by %s", addr, dag.VertexName(node))
   176  			} else {
   177  				log.Printf("[TRACE] DiffTransformer: %s deposed object %s will be represented for destruction by %s", addr, dk, dag.VertexName(node))
   178  			}
   179  			g.Add(node)
   180  		}
   181  
   182  	}
   183  
   184  	log.Printf("[TRACE] DiffTransformer complete")
   185  
   186  	return diags.Err()
   187  }