]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - terraform/node_resource_refresh.go
Merge branch 'fix_read_test' of github.com:alexandreFre/terraform-provider-statuscake
[github/fretlink/terraform-provider-statuscake.git] / terraform / node_resource_refresh.go
1 package terraform
2
3 import (
4 "fmt"
5
6 "github.com/hashicorp/terraform/config"
7 "github.com/hashicorp/terraform/dag"
8 )
9
10 // NodeRefreshableManagedResource represents a resource that is expanabled into
11 // NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
12 type NodeRefreshableManagedResource struct {
13 *NodeAbstractCountResource
14 }
15
16 // GraphNodeDynamicExpandable
17 func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
18 // Grab the state which we read
19 state, lock := ctx.State()
20 lock.RLock()
21 defer lock.RUnlock()
22
23 // Expand the resource count which must be available by now from EvalTree
24 count, err := n.Config.Count()
25 if err != nil {
26 return nil, err
27 }
28
29 // The concrete resource factory we'll use
30 concreteResource := func(a *NodeAbstractResource) dag.Vertex {
31 // Add the config and state since we don't do that via transforms
32 a.Config = n.Config
33 a.ResolvedProvider = n.ResolvedProvider
34
35 return &NodeRefreshableManagedResourceInstance{
36 NodeAbstractResource: a,
37 }
38 }
39
40 // Start creating the steps
41 steps := []GraphTransformer{
42 // Expand the count.
43 &ResourceCountTransformer{
44 Concrete: concreteResource,
45 Count: count,
46 Addr: n.ResourceAddr(),
47 },
48
49 // Add the count orphans to make sure these resources are accounted for
50 // during a scale in.
51 &OrphanResourceCountTransformer{
52 Concrete: concreteResource,
53 Count: count,
54 Addr: n.ResourceAddr(),
55 State: state,
56 },
57
58 // Attach the state
59 &AttachStateTransformer{State: state},
60
61 // Targeting
62 &TargetsTransformer{ParsedTargets: n.Targets},
63
64 // Connect references so ordering is correct
65 &ReferenceTransformer{},
66
67 // Make sure there is a single root
68 &RootTransformer{},
69 }
70
71 // Build the graph
72 b := &BasicGraphBuilder{
73 Steps: steps,
74 Validate: true,
75 Name: "NodeRefreshableManagedResource",
76 }
77
78 return b.Build(ctx.Path())
79 }
80
81 // NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
82 // it is ready to be applied and is represented by a diff.
83 type NodeRefreshableManagedResourceInstance struct {
84 *NodeAbstractResource
85 }
86
87 // GraphNodeDestroyer
88 func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *ResourceAddress {
89 return n.Addr
90 }
91
92 // GraphNodeEvalable
93 func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode {
94 // Eval info is different depending on what kind of resource this is
95 switch mode := n.Addr.Mode; mode {
96 case config.ManagedResourceMode:
97 if n.ResourceState == nil {
98 return n.evalTreeManagedResourceNoState()
99 }
100 return n.evalTreeManagedResource()
101
102 case config.DataResourceMode:
103 // Get the data source node. If we don't have a configuration
104 // then it is an orphan so we destroy it (remove it from the state).
105 var dn GraphNodeEvalable
106 if n.Config != nil {
107 dn = &NodeRefreshableDataResourceInstance{
108 NodeAbstractResource: n.NodeAbstractResource,
109 }
110 } else {
111 dn = &NodeDestroyableDataResource{
112 NodeAbstractResource: n.NodeAbstractResource,
113 }
114 }
115
116 return dn.EvalTree()
117 default:
118 panic(fmt.Errorf("unsupported resource mode %s", mode))
119 }
120 }
121
122 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
123 addr := n.NodeAbstractResource.Addr
124
125 // stateId is the ID to put into the state
126 stateId := addr.stateId()
127
128 // Build the instance info. More of this will be populated during eval
129 info := &InstanceInfo{
130 Id: stateId,
131 Type: addr.Type,
132 }
133
134 // Declare a bunch of variables that are used for state during
135 // evaluation. Most of this are written to by-address below.
136 var provider ResourceProvider
137 var state *InstanceState
138
139 // This happened during initial development. All known cases were
140 // fixed and tested but as a sanity check let's assert here.
141 if n.ResourceState == nil {
142 err := fmt.Errorf(
143 "No resource state attached for addr: %s\n\n"+
144 "This is a bug. Please report this to Terraform with your configuration\n"+
145 "and state attached. Please be careful to scrub any sensitive information.",
146 addr)
147 return &EvalReturnError{Error: &err}
148 }
149
150 return &EvalSequence{
151 Nodes: []EvalNode{
152 &EvalGetProvider{
153 Name: n.ResolvedProvider,
154 Output: &provider,
155 },
156 &EvalReadState{
157 Name: stateId,
158 Output: &state,
159 },
160 &EvalRefresh{
161 Info: info,
162 Provider: &provider,
163 State: &state,
164 Output: &state,
165 },
166 &EvalWriteState{
167 Name: stateId,
168 ResourceType: n.ResourceState.Type,
169 Provider: n.ResolvedProvider,
170 Dependencies: n.ResourceState.Dependencies,
171 State: &state,
172 },
173 },
174 }
175 }
176
177 // evalTreeManagedResourceNoState produces an EvalSequence for refresh resource
178 // nodes that don't have state attached. An example of where this functionality
179 // is useful is when a resource that already exists in state is being scaled
180 // out, ie: has its resource count increased. In this case, the scaled out node
181 // needs to be available to other nodes (namely data sources) that may depend
182 // on it for proper interpolation, or confusing "index out of range" errors can
183 // occur.
184 //
185 // The steps in this sequence are very similar to the steps carried out in
186 // plan, but nothing is done with the diff after it is created - it is dropped,
187 // and its changes are not counted in the UI.
188 func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode {
189 // Declare a bunch of variables that are used for state during
190 // evaluation. Most of this are written to by-address below.
191 var provider ResourceProvider
192 var state *InstanceState
193 var resourceConfig *ResourceConfig
194
195 addr := n.NodeAbstractResource.Addr
196 stateID := addr.stateId()
197 info := &InstanceInfo{
198 Id: stateID,
199 Type: addr.Type,
200 ModulePath: normalizeModulePath(addr.Path),
201 }
202
203 // Build the resource for eval
204 resource := &Resource{
205 Name: addr.Name,
206 Type: addr.Type,
207 CountIndex: addr.Index,
208 }
209 if resource.CountIndex < 0 {
210 resource.CountIndex = 0
211 }
212
213 // Determine the dependencies for the state.
214 stateDeps := n.StateReferences()
215
216 // n.Config can be nil if the config and state don't match
217 var raw *config.RawConfig
218 if n.Config != nil {
219 raw = n.Config.RawConfig.Copy()
220 }
221
222 return &EvalSequence{
223 Nodes: []EvalNode{
224 &EvalInterpolate{
225 Config: raw,
226 Resource: resource,
227 Output: &resourceConfig,
228 },
229 &EvalGetProvider{
230 Name: n.ResolvedProvider,
231 Output: &provider,
232 },
233 // Re-run validation to catch any errors we missed, e.g. type
234 // mismatches on computed values.
235 &EvalValidateResource{
236 Provider: &provider,
237 Config: &resourceConfig,
238 ResourceName: n.Config.Name,
239 ResourceType: n.Config.Type,
240 ResourceMode: n.Config.Mode,
241 IgnoreWarnings: true,
242 },
243 &EvalReadState{
244 Name: stateID,
245 Output: &state,
246 },
247 &EvalDiff{
248 Name: stateID,
249 Info: info,
250 Config: &resourceConfig,
251 Resource: n.Config,
252 Provider: &provider,
253 State: &state,
254 OutputState: &state,
255 Stub: true,
256 },
257 &EvalWriteState{
258 Name: stateID,
259 ResourceType: n.Config.Type,
260 Provider: n.ResolvedProvider,
261 Dependencies: stateDeps,
262 State: &state,
263 },
264 },
265 }
266 }