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