]>
Commit | Line | Data |
---|---|---|
1 | package terraform | |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | ||
7 | "github.com/hashicorp/terraform/plans" | |
8 | "github.com/hashicorp/terraform/providers" | |
9 | ||
10 | "github.com/hashicorp/terraform/states" | |
11 | ||
12 | "github.com/hashicorp/terraform/addrs" | |
13 | "github.com/hashicorp/terraform/dag" | |
14 | "github.com/hashicorp/terraform/tfdiags" | |
15 | ) | |
16 | ||
17 | // NodeRefreshableManagedResource represents a resource that is expanabled into | |
18 | // NodeRefreshableManagedResourceInstance. Resource count orphans are also added. | |
19 | type NodeRefreshableManagedResource struct { | |
20 | *NodeAbstractResource | |
21 | } | |
22 | ||
23 | var ( | |
24 | _ GraphNodeSubPath = (*NodeRefreshableManagedResource)(nil) | |
25 | _ GraphNodeDynamicExpandable = (*NodeRefreshableManagedResource)(nil) | |
26 | _ GraphNodeReferenceable = (*NodeRefreshableManagedResource)(nil) | |
27 | _ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil) | |
28 | _ GraphNodeResource = (*NodeRefreshableManagedResource)(nil) | |
29 | _ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil) | |
30 | ) | |
31 | ||
32 | // GraphNodeDynamicExpandable | |
33 | func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) { | |
34 | var diags tfdiags.Diagnostics | |
35 | ||
36 | count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx) | |
37 | diags = diags.Append(countDiags) | |
38 | if countDiags.HasErrors() { | |
39 | return nil, diags.Err() | |
40 | } | |
41 | ||
42 | // Next we need to potentially rename an instance address in the state | |
43 | // if we're transitioning whether "count" is set at all. | |
44 | fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1) | |
45 | ||
46 | // Our graph transformers require access to the full state, so we'll | |
47 | // temporarily lock it while we work on this. | |
48 | state := ctx.State().Lock() | |
49 | defer ctx.State().Unlock() | |
50 | ||
51 | // The concrete resource factory we'll use | |
52 | concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex { | |
53 | // Add the config and state since we don't do that via transforms | |
54 | a.Config = n.Config | |
55 | a.ResolvedProvider = n.ResolvedProvider | |
56 | ||
57 | return &NodeRefreshableManagedResourceInstance{ | |
58 | NodeAbstractResourceInstance: a, | |
59 | } | |
60 | } | |
61 | ||
62 | // Start creating the steps | |
63 | steps := []GraphTransformer{ | |
64 | // Expand the count. | |
65 | &ResourceCountTransformer{ | |
66 | Concrete: concreteResource, | |
67 | Schema: n.Schema, | |
68 | Count: count, | |
69 | Addr: n.ResourceAddr(), | |
70 | }, | |
71 | ||
72 | // Add the count orphans to make sure these resources are accounted for | |
73 | // during a scale in. | |
74 | &OrphanResourceCountTransformer{ | |
75 | Concrete: concreteResource, | |
76 | Count: count, | |
77 | Addr: n.ResourceAddr(), | |
78 | State: state, | |
79 | }, | |
80 | ||
81 | // Attach the state | |
82 | &AttachStateTransformer{State: state}, | |
83 | ||
84 | // Targeting | |
85 | &TargetsTransformer{Targets: n.Targets}, | |
86 | ||
87 | // Connect references so ordering is correct | |
88 | &ReferenceTransformer{}, | |
89 | ||
90 | // Make sure there is a single root | |
91 | &RootTransformer{}, | |
92 | } | |
93 | ||
94 | // Build the graph | |
95 | b := &BasicGraphBuilder{ | |
96 | Steps: steps, | |
97 | Validate: true, | |
98 | Name: "NodeRefreshableManagedResource", | |
99 | } | |
100 | ||
101 | graph, diags := b.Build(ctx.Path()) | |
102 | return graph, diags.ErrWithWarnings() | |
103 | } | |
104 | ||
105 | // NodeRefreshableManagedResourceInstance represents a resource that is "applyable": | |
106 | // it is ready to be applied and is represented by a diff. | |
107 | type NodeRefreshableManagedResourceInstance struct { | |
108 | *NodeAbstractResourceInstance | |
109 | } | |
110 | ||
111 | var ( | |
112 | _ GraphNodeSubPath = (*NodeRefreshableManagedResourceInstance)(nil) | |
113 | _ GraphNodeReferenceable = (*NodeRefreshableManagedResourceInstance)(nil) | |
114 | _ GraphNodeReferencer = (*NodeRefreshableManagedResourceInstance)(nil) | |
115 | _ GraphNodeDestroyer = (*NodeRefreshableManagedResourceInstance)(nil) | |
116 | _ GraphNodeResource = (*NodeRefreshableManagedResourceInstance)(nil) | |
117 | _ GraphNodeResourceInstance = (*NodeRefreshableManagedResourceInstance)(nil) | |
118 | _ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResourceInstance)(nil) | |
119 | _ GraphNodeAttachResourceState = (*NodeRefreshableManagedResourceInstance)(nil) | |
120 | _ GraphNodeEvalable = (*NodeRefreshableManagedResourceInstance)(nil) | |
121 | ) | |
122 | ||
123 | // GraphNodeDestroyer | |
124 | func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance { | |
125 | addr := n.ResourceInstanceAddr() | |
126 | return &addr | |
127 | } | |
128 | ||
129 | // GraphNodeEvalable | |
130 | func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode { | |
131 | addr := n.ResourceInstanceAddr() | |
132 | ||
133 | // Eval info is different depending on what kind of resource this is | |
134 | switch addr.Resource.Resource.Mode { | |
135 | case addrs.ManagedResourceMode: | |
136 | if n.ResourceState == nil { | |
137 | log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s has no existing state to refresh", addr) | |
138 | return n.evalTreeManagedResourceNoState() | |
139 | } | |
140 | log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s will be refreshed", addr) | |
141 | return n.evalTreeManagedResource() | |
142 | ||
143 | case addrs.DataResourceMode: | |
144 | // Get the data source node. If we don't have a configuration | |
145 | // then it is an orphan so we destroy it (remove it from the state). | |
146 | var dn GraphNodeEvalable | |
147 | if n.Config != nil { | |
148 | dn = &NodeRefreshableDataResourceInstance{ | |
149 | NodeAbstractResourceInstance: n.NodeAbstractResourceInstance, | |
150 | } | |
151 | } else { | |
152 | dn = &NodeDestroyableDataResourceInstance{ | |
153 | NodeAbstractResourceInstance: n.NodeAbstractResourceInstance, | |
154 | } | |
155 | } | |
156 | ||
157 | return dn.EvalTree() | |
158 | default: | |
159 | panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode)) | |
160 | } | |
161 | } | |
162 | ||
163 | func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode { | |
164 | addr := n.ResourceInstanceAddr() | |
165 | ||
166 | // Declare a bunch of variables that are used for state during | |
167 | // evaluation. Most of this are written to by-address below. | |
168 | var provider providers.Interface | |
169 | var providerSchema *ProviderSchema | |
170 | var state *states.ResourceInstanceObject | |
171 | ||
172 | // This happened during initial development. All known cases were | |
173 | // fixed and tested but as a sanity check let's assert here. | |
174 | if n.ResourceState == nil { | |
175 | err := fmt.Errorf( | |
176 | "No resource state attached for addr: %s\n\n"+ | |
177 | "This is a bug. Please report this to Terraform with your configuration\n"+ | |
178 | "and state attached. Please be careful to scrub any sensitive information.", | |
179 | addr) | |
180 | return &EvalReturnError{Error: &err} | |
181 | } | |
182 | ||
183 | return &EvalSequence{ | |
184 | Nodes: []EvalNode{ | |
185 | &EvalGetProvider{ | |
186 | Addr: n.ResolvedProvider, | |
187 | Output: &provider, | |
188 | Schema: &providerSchema, | |
189 | }, | |
190 | ||
191 | &EvalReadState{ | |
192 | Addr: addr.Resource, | |
193 | Provider: &provider, | |
194 | ProviderSchema: &providerSchema, | |
195 | ||
196 | Output: &state, | |
197 | }, | |
198 | ||
199 | &EvalRefresh{ | |
200 | Addr: addr.Resource, | |
201 | ProviderAddr: n.ResolvedProvider, | |
202 | Provider: &provider, | |
203 | ProviderSchema: &providerSchema, | |
204 | State: &state, | |
205 | Output: &state, | |
206 | }, | |
207 | ||
208 | &EvalWriteState{ | |
209 | Addr: addr.Resource, | |
210 | ProviderAddr: n.ResolvedProvider, | |
211 | ProviderSchema: &providerSchema, | |
212 | State: &state, | |
213 | }, | |
214 | }, | |
215 | } | |
216 | } | |
217 | ||
218 | // evalTreeManagedResourceNoState produces an EvalSequence for refresh resource | |
219 | // nodes that don't have state attached. An example of where this functionality | |
220 | // is useful is when a resource that already exists in state is being scaled | |
221 | // out, ie: has its resource count increased. In this case, the scaled out node | |
222 | // needs to be available to other nodes (namely data sources) that may depend | |
223 | // on it for proper interpolation, or confusing "index out of range" errors can | |
224 | // occur. | |
225 | // | |
226 | // The steps in this sequence are very similar to the steps carried out in | |
227 | // plan, but nothing is done with the diff after it is created - it is dropped, | |
228 | // and its changes are not counted in the UI. | |
229 | func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode { | |
230 | addr := n.ResourceInstanceAddr() | |
231 | ||
232 | // Declare a bunch of variables that are used for state during | |
233 | // evaluation. Most of this are written to by-address below. | |
234 | var provider providers.Interface | |
235 | var providerSchema *ProviderSchema | |
236 | var change *plans.ResourceInstanceChange | |
237 | var state *states.ResourceInstanceObject | |
238 | ||
239 | return &EvalSequence{ | |
240 | Nodes: []EvalNode{ | |
241 | &EvalGetProvider{ | |
242 | Addr: n.ResolvedProvider, | |
243 | Output: &provider, | |
244 | Schema: &providerSchema, | |
245 | }, | |
246 | ||
247 | &EvalReadState{ | |
248 | Addr: addr.Resource, | |
249 | Provider: &provider, | |
250 | ProviderSchema: &providerSchema, | |
251 | ||
252 | Output: &state, | |
253 | }, | |
254 | ||
255 | &EvalDiff{ | |
256 | Addr: addr.Resource, | |
257 | Config: n.Config, | |
258 | Provider: &provider, | |
259 | ProviderAddr: n.ResolvedProvider, | |
260 | ProviderSchema: &providerSchema, | |
261 | State: &state, | |
262 | OutputChange: &change, | |
263 | OutputState: &state, | |
264 | Stub: true, | |
265 | }, | |
266 | ||
267 | &EvalWriteState{ | |
268 | Addr: addr.Resource, | |
269 | ProviderAddr: n.ResolvedProvider, | |
270 | ProviderSchema: &providerSchema, | |
271 | State: &state, | |
272 | }, | |
273 | ||
274 | // We must also save the planned change, so that expressions in | |
275 | // other nodes, such as provider configurations and data resources, | |
276 | // can work with the planned new value. | |
277 | // | |
278 | // This depends on the fact that Context.Refresh creates a | |
279 | // temporary new empty changeset for the duration of its graph | |
280 | // walk, and so this recorded change will be discarded immediately | |
281 | // after the refresh walk completes. | |
282 | &EvalWriteDiff{ | |
283 | Addr: addr.Resource, | |
284 | Change: &change, | |
285 | ProviderSchema: &providerSchema, | |
286 | }, | |
287 | }, | |
288 | } | |
289 | } |