]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
107c1cdb | 5 | "log" |
bae9f6d2 | 6 | |
107c1cdb ND |
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" | |
9b12e4fe | 13 | "github.com/hashicorp/terraform/dag" |
107c1cdb | 14 | "github.com/hashicorp/terraform/tfdiags" |
bae9f6d2 JC |
15 | ) |
16 | ||
9b12e4fe JC |
17 | // NodeRefreshableManagedResource represents a resource that is expanabled into |
18 | // NodeRefreshableManagedResourceInstance. Resource count orphans are also added. | |
19 | type NodeRefreshableManagedResource struct { | |
107c1cdb | 20 | *NodeAbstractResource |
9b12e4fe JC |
21 | } |
22 | ||
107c1cdb ND |
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 | ||
9b12e4fe JC |
32 | // GraphNodeDynamicExpandable |
33 | func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) { | |
107c1cdb ND |
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() | |
9b12e4fe JC |
40 | } |
41 | ||
107c1cdb ND |
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 | ||
9b12e4fe | 51 | // The concrete resource factory we'll use |
107c1cdb | 52 | concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex { |
9b12e4fe JC |
53 | // Add the config and state since we don't do that via transforms |
54 | a.Config = n.Config | |
15c0b25d | 55 | a.ResolvedProvider = n.ResolvedProvider |
9b12e4fe JC |
56 | |
57 | return &NodeRefreshableManagedResourceInstance{ | |
107c1cdb | 58 | NodeAbstractResourceInstance: a, |
9b12e4fe JC |
59 | } |
60 | } | |
61 | ||
62 | // Start creating the steps | |
63 | steps := []GraphTransformer{ | |
64 | // Expand the count. | |
65 | &ResourceCountTransformer{ | |
66 | Concrete: concreteResource, | |
107c1cdb | 67 | Schema: n.Schema, |
9b12e4fe JC |
68 | Count: count, |
69 | Addr: n.ResourceAddr(), | |
70 | }, | |
71 | ||
9b12e4fe JC |
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 | |
107c1cdb | 85 | &TargetsTransformer{Targets: n.Targets}, |
9b12e4fe JC |
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 | ||
107c1cdb ND |
101 | graph, diags := b.Build(ctx.Path()) |
102 | return graph, diags.ErrWithWarnings() | |
9b12e4fe JC |
103 | } |
104 | ||
105 | // NodeRefreshableManagedResourceInstance represents a resource that is "applyable": | |
bae9f6d2 | 106 | // it is ready to be applied and is represented by a diff. |
9b12e4fe | 107 | type NodeRefreshableManagedResourceInstance struct { |
107c1cdb | 108 | *NodeAbstractResourceInstance |
bae9f6d2 JC |
109 | } |
110 | ||
107c1cdb ND |
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 | ||
bae9f6d2 | 123 | // GraphNodeDestroyer |
107c1cdb ND |
124 | func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance { |
125 | addr := n.ResourceInstanceAddr() | |
126 | return &addr | |
bae9f6d2 JC |
127 | } |
128 | ||
129 | // GraphNodeEvalable | |
9b12e4fe | 130 | func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode { |
107c1cdb ND |
131 | addr := n.ResourceInstanceAddr() |
132 | ||
bae9f6d2 | 133 | // Eval info is different depending on what kind of resource this is |
107c1cdb ND |
134 | switch addr.Resource.Resource.Mode { |
135 | case addrs.ManagedResourceMode: | |
c680a8e1 | 136 | if n.ResourceState == nil { |
107c1cdb | 137 | log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s has no existing state to refresh", addr) |
c680a8e1 RS |
138 | return n.evalTreeManagedResourceNoState() |
139 | } | |
107c1cdb | 140 | log.Printf("[TRACE] NodeRefreshableManagedResourceInstance: %s will be refreshed", addr) |
bae9f6d2 JC |
141 | return n.evalTreeManagedResource() |
142 | ||
107c1cdb | 143 | case addrs.DataResourceMode: |
bae9f6d2 JC |
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{ | |
107c1cdb | 149 | NodeAbstractResourceInstance: n.NodeAbstractResourceInstance, |
bae9f6d2 JC |
150 | } |
151 | } else { | |
107c1cdb ND |
152 | dn = &NodeDestroyableDataResourceInstance{ |
153 | NodeAbstractResourceInstance: n.NodeAbstractResourceInstance, | |
bae9f6d2 JC |
154 | } |
155 | } | |
156 | ||
157 | return dn.EvalTree() | |
158 | default: | |
107c1cdb | 159 | panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode)) |
bae9f6d2 JC |
160 | } |
161 | } | |
162 | ||
9b12e4fe | 163 | func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode { |
107c1cdb | 164 | addr := n.ResourceInstanceAddr() |
bae9f6d2 JC |
165 | |
166 | // Declare a bunch of variables that are used for state during | |
167 | // evaluation. Most of this are written to by-address below. | |
107c1cdb ND |
168 | var provider providers.Interface |
169 | var providerSchema *ProviderSchema | |
170 | var state *states.ResourceInstanceObject | |
bae9f6d2 JC |
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{ | |
107c1cdb | 186 | Addr: n.ResolvedProvider, |
bae9f6d2 | 187 | Output: &provider, |
107c1cdb | 188 | Schema: &providerSchema, |
bae9f6d2 | 189 | }, |
107c1cdb | 190 | |
bae9f6d2 | 191 | &EvalReadState{ |
107c1cdb ND |
192 | Addr: addr.Resource, |
193 | Provider: &provider, | |
194 | ProviderSchema: &providerSchema, | |
195 | ||
bae9f6d2 JC |
196 | Output: &state, |
197 | }, | |
107c1cdb | 198 | |
bae9f6d2 | 199 | &EvalRefresh{ |
107c1cdb ND |
200 | Addr: addr.Resource, |
201 | ProviderAddr: n.ResolvedProvider, | |
202 | Provider: &provider, | |
203 | ProviderSchema: &providerSchema, | |
204 | State: &state, | |
205 | Output: &state, | |
bae9f6d2 | 206 | }, |
107c1cdb | 207 | |
bae9f6d2 | 208 | &EvalWriteState{ |
107c1cdb ND |
209 | Addr: addr.Resource, |
210 | ProviderAddr: n.ResolvedProvider, | |
211 | ProviderSchema: &providerSchema, | |
212 | State: &state, | |
bae9f6d2 JC |
213 | }, |
214 | }, | |
215 | } | |
216 | } | |
c680a8e1 RS |
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 { | |
107c1cdb ND |
230 | addr := n.ResourceInstanceAddr() |
231 | ||
c680a8e1 RS |
232 | // Declare a bunch of variables that are used for state during |
233 | // evaluation. Most of this are written to by-address below. | |
107c1cdb ND |
234 | var provider providers.Interface |
235 | var providerSchema *ProviderSchema | |
236 | var change *plans.ResourceInstanceChange | |
237 | var state *states.ResourceInstanceObject | |
15c0b25d | 238 | |
c680a8e1 RS |
239 | return &EvalSequence{ |
240 | Nodes: []EvalNode{ | |
c680a8e1 | 241 | &EvalGetProvider{ |
107c1cdb | 242 | Addr: n.ResolvedProvider, |
c680a8e1 | 243 | Output: &provider, |
107c1cdb | 244 | Schema: &providerSchema, |
c680a8e1 | 245 | }, |
107c1cdb | 246 | |
c680a8e1 | 247 | &EvalReadState{ |
107c1cdb ND |
248 | Addr: addr.Resource, |
249 | Provider: &provider, | |
250 | ProviderSchema: &providerSchema, | |
251 | ||
c680a8e1 RS |
252 | Output: &state, |
253 | }, | |
107c1cdb | 254 | |
c680a8e1 | 255 | &EvalDiff{ |
107c1cdb ND |
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, | |
c680a8e1 | 265 | }, |
107c1cdb | 266 | |
c680a8e1 | 267 | &EvalWriteState{ |
107c1cdb ND |
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, | |
c680a8e1 RS |
286 | }, |
287 | }, | |
288 | } | |
289 | } |