]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/transform_diff.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / transform_diff.go
1 package terraform
2
3 import (
4 "fmt"
5 "log"
6
7 "github.com/hashicorp/terraform/dag"
8 "github.com/hashicorp/terraform/plans"
9 "github.com/hashicorp/terraform/states"
10 "github.com/hashicorp/terraform/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 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))
184 }
185 }
186
187 }
188
189 log.Printf("[TRACE] DiffTransformer complete")
190
191 return diags.Err()
192 }