]>
Commit | Line | Data |
---|---|---|
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 | } |