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 }