7 "github.com/hashicorp/terraform/dag"
8 "github.com/hashicorp/terraform/plans"
9 "github.com/hashicorp/terraform/states"
10 "github.com/hashicorp/terraform/tfdiags"
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
18 Changes *plans.Changes
21 func (t *DiffTransformer) Transform(g *Graph) error {
22 if t.Changes == nil || len(t.Changes.Resources) == 0 {
27 // Go through all the modules in the diff.
28 log.Printf("[TRACE] DiffTransformer starting")
30 var diags tfdiags.Diagnostics
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)
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 {
52 addr := rn.ResourceAddr().String()
53 resourceNodes[addr] = append(resourceNodes[addr], rn)
56 for _, rc := range changes.Resources {
60 log.Printf("[TRACE] DiffTransformer: found %s change for %s %s", rc.Action, addr, dk)
62 // Depending on the action we'll need some different combinations of
63 // nodes, because destroying uses a special node type separate from
65 var update, delete, createBeforeDestroy bool
71 case plans.DeleteThenCreate, plans.CreateThenDelete:
74 createBeforeDestroy = (rc.Action == plans.CreateThenDelete)
79 if dk != states.NotDeposed && update {
80 diags = diags.Append(tfdiags.Sourceless(
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),
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
100 ris := state.ResourceInstance(addr)
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(
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),
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()
119 // If we have no state at all yet then we can use _any_
121 dk = states.NewDeposedKey()
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 {
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)
141 log.Printf("[TRACE] DiffTransformer: %s will be represented by %s, deposing prior object to %s", addr, dag.VertexName(node), dk)
143 log.Printf("[TRACE] DiffTransformer: %s will be represented by %s", addr, dag.VertexName(node))
147 rsrcAddr := addr.ContainingResource().String()
148 for _, rsrcNode := range resourceNodes[rsrcAddr] {
149 g.Connect(dag.BasicEdge(node, rsrcNode))
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,
164 node.(*NodeDestroyResourceInstance).ModifyCreateBeforeDestroy(createBeforeDestroy)
166 node = &NodeDestroyDeposedResourceInstanceObject{
167 NodeAbstractResourceInstance: abstract,
171 if dk == states.NotDeposed {
172 log.Printf("[TRACE] DiffTransformer: %s will be represented for destruction by %s", addr, dag.VertexName(node))
174 log.Printf("[TRACE] DiffTransformer: %s deposed object %s will be represented for destruction by %s", addr, dk, dag.VertexName(node))
177 rsrcAddr := addr.ContainingResource().String()
178 for _, rsrcNode := range resourceNodes[rsrcAddr] {
179 // We connect this edge "forwards" (even though destroy dependencies
180 // are often inverted) because evaluating the resource node
181 // after the destroy node could cause an unnecessary husk of
182 // a resource state to be re-added.
183 g.Connect(dag.BasicEdge(node, rsrcNode))
189 log.Printf("[TRACE] DiffTransformer complete")