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 }