]>
Commit | Line | Data |
---|---|---|
1 | package terraform | |
2 | ||
3 | import ( | |
4 | "github.com/hashicorp/terraform/dag" | |
5 | "github.com/hashicorp/terraform/plans" | |
6 | "github.com/hashicorp/terraform/providers" | |
7 | "github.com/hashicorp/terraform/states" | |
8 | "github.com/hashicorp/terraform/tfdiags" | |
9 | "github.com/zclconf/go-cty/cty" | |
10 | ) | |
11 | ||
12 | // NodeRefreshableDataResource represents a resource that is "refreshable". | |
13 | type NodeRefreshableDataResource struct { | |
14 | *NodeAbstractResource | |
15 | } | |
16 | ||
17 | var ( | |
18 | _ GraphNodeSubPath = (*NodeRefreshableDataResource)(nil) | |
19 | _ GraphNodeDynamicExpandable = (*NodeRefreshableDataResource)(nil) | |
20 | _ GraphNodeReferenceable = (*NodeRefreshableDataResource)(nil) | |
21 | _ GraphNodeReferencer = (*NodeRefreshableDataResource)(nil) | |
22 | _ GraphNodeResource = (*NodeRefreshableDataResource)(nil) | |
23 | _ GraphNodeAttachResourceConfig = (*NodeRefreshableDataResource)(nil) | |
24 | ) | |
25 | ||
26 | // GraphNodeDynamicExpandable | |
27 | func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) { | |
28 | var diags tfdiags.Diagnostics | |
29 | ||
30 | count, countKnown, countDiags := evaluateResourceCountExpressionKnown(n.Config.Count, ctx) | |
31 | diags = diags.Append(countDiags) | |
32 | if countDiags.HasErrors() { | |
33 | return nil, diags.Err() | |
34 | } | |
35 | if !countKnown { | |
36 | // If the count isn't known yet, we'll skip refreshing and try expansion | |
37 | // again during the plan walk. | |
38 | return nil, nil | |
39 | } | |
40 | ||
41 | forEachMap, forEachKnown, forEachDiags := evaluateResourceForEachExpressionKnown(n.Config.ForEach, ctx) | |
42 | if forEachDiags.HasErrors() { | |
43 | return nil, diags.Err() | |
44 | } | |
45 | if !forEachKnown { | |
46 | // If the for_each isn't known yet, we'll skip refreshing and try expansion | |
47 | // again during the plan walk. | |
48 | return nil, nil | |
49 | } | |
50 | ||
51 | // Next we need to potentially rename an instance address in the state | |
52 | // if we're transitioning whether "count" is set at all. | |
53 | fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1) | |
54 | ||
55 | // Our graph transformers require access to the full state, so we'll | |
56 | // temporarily lock it while we work on this. | |
57 | state := ctx.State().Lock() | |
58 | defer ctx.State().Unlock() | |
59 | ||
60 | // The concrete resource factory we'll use | |
61 | concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex { | |
62 | // Add the config and state since we don't do that via transforms | |
63 | a.Config = n.Config | |
64 | a.ResolvedProvider = n.ResolvedProvider | |
65 | ||
66 | return &NodeRefreshableDataResourceInstance{ | |
67 | NodeAbstractResourceInstance: a, | |
68 | } | |
69 | } | |
70 | ||
71 | // We also need a destroyable resource for orphans that are a result of a | |
72 | // scaled-in count. | |
73 | concreteResourceDestroyable := func(a *NodeAbstractResourceInstance) dag.Vertex { | |
74 | // Add the config and provider since we don't do that via transforms | |
75 | a.Config = n.Config | |
76 | a.ResolvedProvider = n.ResolvedProvider | |
77 | ||
78 | return &NodeDestroyableDataResourceInstance{ | |
79 | NodeAbstractResourceInstance: a, | |
80 | } | |
81 | } | |
82 | ||
83 | // Start creating the steps | |
84 | steps := []GraphTransformer{ | |
85 | // Expand the count. | |
86 | &ResourceCountTransformer{ | |
87 | Concrete: concreteResource, | |
88 | Schema: n.Schema, | |
89 | Count: count, | |
90 | ForEach: forEachMap, | |
91 | Addr: n.ResourceAddr(), | |
92 | }, | |
93 | ||
94 | // Add the count orphans. As these are orphaned refresh nodes, we add them | |
95 | // directly as NodeDestroyableDataResource. | |
96 | &OrphanResourceCountTransformer{ | |
97 | Concrete: concreteResourceDestroyable, | |
98 | Count: count, | |
99 | ForEach: forEachMap, | |
100 | Addr: n.ResourceAddr(), | |
101 | State: state, | |
102 | }, | |
103 | ||
104 | // Attach the state | |
105 | &AttachStateTransformer{State: state}, | |
106 | ||
107 | // Targeting | |
108 | &TargetsTransformer{Targets: n.Targets}, | |
109 | ||
110 | // Connect references so ordering is correct | |
111 | &ReferenceTransformer{}, | |
112 | ||
113 | // Make sure there is a single root | |
114 | &RootTransformer{}, | |
115 | } | |
116 | ||
117 | // Build the graph | |
118 | b := &BasicGraphBuilder{ | |
119 | Steps: steps, | |
120 | Validate: true, | |
121 | Name: "NodeRefreshableDataResource", | |
122 | } | |
123 | ||
124 | graph, diags := b.Build(ctx.Path()) | |
125 | return graph, diags.ErrWithWarnings() | |
126 | } | |
127 | ||
128 | // NodeRefreshableDataResourceInstance represents a single resource instance | |
129 | // that is refreshable. | |
130 | type NodeRefreshableDataResourceInstance struct { | |
131 | *NodeAbstractResourceInstance | |
132 | } | |
133 | ||
134 | // GraphNodeEvalable | |
135 | func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode { | |
136 | addr := n.ResourceInstanceAddr() | |
137 | ||
138 | // These variables are the state for the eval sequence below, and are | |
139 | // updated through pointers. | |
140 | var provider providers.Interface | |
141 | var providerSchema *ProviderSchema | |
142 | var change *plans.ResourceInstanceChange | |
143 | var state *states.ResourceInstanceObject | |
144 | var configVal cty.Value | |
145 | ||
146 | return &EvalSequence{ | |
147 | Nodes: []EvalNode{ | |
148 | &EvalGetProvider{ | |
149 | Addr: n.ResolvedProvider, | |
150 | Output: &provider, | |
151 | Schema: &providerSchema, | |
152 | }, | |
153 | ||
154 | // Always destroy the existing state first, since we must | |
155 | // make sure that values from a previous read will not | |
156 | // get interpolated if we end up needing to defer our | |
157 | // loading until apply time. | |
158 | &EvalWriteState{ | |
159 | Addr: addr.Resource, | |
160 | ProviderAddr: n.ResolvedProvider, | |
161 | State: &state, // a pointer to nil, here | |
162 | ProviderSchema: &providerSchema, | |
163 | }, | |
164 | ||
165 | &EvalIf{ | |
166 | If: func(ctx EvalContext) (bool, error) { | |
167 | // If the config explicitly has a depends_on for this | |
168 | // data source, assume the intention is to prevent | |
169 | // refreshing ahead of that dependency, and therefore | |
170 | // we need to deal with this resource during the apply | |
171 | // phase.. | |
172 | if len(n.Config.DependsOn) > 0 { | |
173 | return true, EvalEarlyExitError{} | |
174 | } | |
175 | ||
176 | return true, nil | |
177 | }, | |
178 | Then: EvalNoop{}, | |
179 | }, | |
180 | ||
181 | // EvalReadData will _attempt_ to read the data source, but may | |
182 | // generate an incomplete planned object if the configuration | |
183 | // includes values that won't be known until apply. | |
184 | &EvalReadData{ | |
185 | Addr: addr.Resource, | |
186 | Config: n.Config, | |
187 | Dependencies: n.StateReferences(), | |
188 | Provider: &provider, | |
189 | ProviderAddr: n.ResolvedProvider, | |
190 | ProviderSchema: &providerSchema, | |
191 | OutputChange: &change, | |
192 | OutputConfigValue: &configVal, | |
193 | OutputState: &state, | |
194 | }, | |
195 | ||
196 | &EvalIf{ | |
197 | If: func(ctx EvalContext) (bool, error) { | |
198 | return (*state).Status != states.ObjectPlanned, nil | |
199 | }, | |
200 | Then: &EvalSequence{ | |
201 | Nodes: []EvalNode{ | |
202 | &EvalWriteState{ | |
203 | Addr: addr.Resource, | |
204 | ProviderAddr: n.ResolvedProvider, | |
205 | State: &state, | |
206 | ProviderSchema: &providerSchema, | |
207 | }, | |
208 | &EvalUpdateStateHook{}, | |
209 | }, | |
210 | }, | |
211 | Else: &EvalSequence{ | |
212 | // We can't deal with this yet, so we'll repeat this step | |
213 | // during the plan walk to produce a planned change to read | |
214 | // this during the apply walk. However, we do still need to | |
215 | // save the generated change and partial state so that | |
216 | // results from it can be included in other data resources | |
217 | // or provider configurations during the refresh walk. | |
218 | // (The planned object we save in the state here will be | |
219 | // pruned out at the end of the refresh walk, returning | |
220 | // it back to being unset again for subsequent walks.) | |
221 | Nodes: []EvalNode{ | |
222 | &EvalWriteDiff{ | |
223 | Addr: addr.Resource, | |
224 | Change: &change, | |
225 | ProviderSchema: &providerSchema, | |
226 | }, | |
227 | &EvalWriteState{ | |
228 | Addr: addr.Resource, | |
229 | ProviderAddr: n.ResolvedProvider, | |
230 | State: &state, | |
231 | ProviderSchema: &providerSchema, | |
232 | }, | |
233 | }, | |
234 | }, | |
235 | }, | |
236 | }, | |
237 | } | |
238 | } |