]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "github.com/hashicorp/terraform/dag" | |
5 | ) | |
6 | ||
7 | // NodeRefreshableDataResource represents a resource that is "plannable": | |
8 | // it is ready to be planned in order to create a diff. | |
9 | type NodeRefreshableDataResource struct { | |
10 | *NodeAbstractCountResource | |
11 | } | |
12 | ||
13 | // GraphNodeDynamicExpandable | |
14 | func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) { | |
15 | // Grab the state which we read | |
16 | state, lock := ctx.State() | |
17 | lock.RLock() | |
18 | defer lock.RUnlock() | |
19 | ||
20 | // Expand the resource count which must be available by now from EvalTree | |
21 | count, err := n.Config.Count() | |
22 | if err != nil { | |
23 | return nil, err | |
24 | } | |
25 | ||
26 | // The concrete resource factory we'll use | |
27 | concreteResource := func(a *NodeAbstractResource) dag.Vertex { | |
28 | // Add the config and state since we don't do that via transforms | |
29 | a.Config = n.Config | |
30 | ||
31 | return &NodeRefreshableDataResourceInstance{ | |
32 | NodeAbstractResource: a, | |
33 | } | |
34 | } | |
35 | ||
9b12e4fe JC |
36 | // We also need a destroyable resource for orphans that are a result of a |
37 | // scaled-in count. | |
38 | concreteResourceDestroyable := func(a *NodeAbstractResource) dag.Vertex { | |
39 | // Add the config since we don't do that via transforms | |
40 | a.Config = n.Config | |
41 | ||
42 | return &NodeDestroyableDataResource{ | |
43 | NodeAbstractResource: a, | |
44 | } | |
45 | } | |
46 | ||
bae9f6d2 JC |
47 | // Start creating the steps |
48 | steps := []GraphTransformer{ | |
49 | // Expand the count. | |
50 | &ResourceCountTransformer{ | |
51 | Concrete: concreteResource, | |
52 | Count: count, | |
53 | Addr: n.ResourceAddr(), | |
54 | }, | |
55 | ||
9b12e4fe JC |
56 | // Add the count orphans. As these are orphaned refresh nodes, we add them |
57 | // directly as NodeDestroyableDataResource. | |
58 | &OrphanResourceCountTransformer{ | |
59 | Concrete: concreteResourceDestroyable, | |
60 | Count: count, | |
61 | Addr: n.ResourceAddr(), | |
62 | State: state, | |
63 | }, | |
64 | ||
bae9f6d2 JC |
65 | // Attach the state |
66 | &AttachStateTransformer{State: state}, | |
67 | ||
68 | // Targeting | |
69 | &TargetsTransformer{ParsedTargets: n.Targets}, | |
70 | ||
71 | // Connect references so ordering is correct | |
72 | &ReferenceTransformer{}, | |
73 | ||
74 | // Make sure there is a single root | |
75 | &RootTransformer{}, | |
76 | } | |
77 | ||
78 | // Build the graph | |
79 | b := &BasicGraphBuilder{ | |
80 | Steps: steps, | |
81 | Validate: true, | |
82 | Name: "NodeRefreshableDataResource", | |
83 | } | |
84 | ||
85 | return b.Build(ctx.Path()) | |
86 | } | |
87 | ||
88 | // NodeRefreshableDataResourceInstance represents a _single_ resource instance | |
89 | // that is refreshable. | |
90 | type NodeRefreshableDataResourceInstance struct { | |
91 | *NodeAbstractResource | |
92 | } | |
93 | ||
94 | // GraphNodeEvalable | |
95 | func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode { | |
96 | addr := n.NodeAbstractResource.Addr | |
97 | ||
98 | // stateId is the ID to put into the state | |
99 | stateId := addr.stateId() | |
100 | ||
101 | // Build the instance info. More of this will be populated during eval | |
102 | info := &InstanceInfo{ | |
103 | Id: stateId, | |
104 | Type: addr.Type, | |
105 | } | |
106 | ||
107 | // Get the state if we have it, if not we build it | |
108 | rs := n.ResourceState | |
109 | if rs == nil { | |
110 | rs = &ResourceState{} | |
111 | } | |
112 | ||
113 | // If the config isn't empty we update the state | |
114 | if n.Config != nil { | |
115 | rs = &ResourceState{ | |
116 | Type: n.Config.Type, | |
117 | Provider: n.Config.Provider, | |
118 | Dependencies: n.StateReferences(), | |
119 | } | |
120 | } | |
121 | ||
122 | // Build the resource for eval | |
123 | resource := &Resource{ | |
124 | Name: addr.Name, | |
125 | Type: addr.Type, | |
126 | CountIndex: addr.Index, | |
127 | } | |
128 | if resource.CountIndex < 0 { | |
129 | resource.CountIndex = 0 | |
130 | } | |
131 | ||
132 | // Declare a bunch of variables that are used for state during | |
133 | // evaluation. Most of this are written to by-address below. | |
134 | var config *ResourceConfig | |
135 | var diff *InstanceDiff | |
136 | var provider ResourceProvider | |
137 | var state *InstanceState | |
138 | ||
139 | return &EvalSequence{ | |
140 | Nodes: []EvalNode{ | |
141 | // Always destroy the existing state first, since we must | |
142 | // make sure that values from a previous read will not | |
143 | // get interpolated if we end up needing to defer our | |
144 | // loading until apply time. | |
145 | &EvalWriteState{ | |
146 | Name: stateId, | |
147 | ResourceType: rs.Type, | |
148 | Provider: rs.Provider, | |
149 | Dependencies: rs.Dependencies, | |
150 | State: &state, // state is nil here | |
151 | }, | |
152 | ||
153 | &EvalInterpolate{ | |
154 | Config: n.Config.RawConfig.Copy(), | |
155 | Resource: resource, | |
156 | Output: &config, | |
157 | }, | |
158 | ||
159 | // The rest of this pass can proceed only if there are no | |
160 | // computed values in our config. | |
161 | // (If there are, we'll deal with this during the plan and | |
162 | // apply phases.) | |
163 | &EvalIf{ | |
164 | If: func(ctx EvalContext) (bool, error) { | |
165 | if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 { | |
166 | return true, EvalEarlyExitError{} | |
167 | } | |
168 | ||
169 | // If the config explicitly has a depends_on for this | |
170 | // data source, assume the intention is to prevent | |
171 | // refreshing ahead of that dependency. | |
172 | if len(n.Config.DependsOn) > 0 { | |
173 | return true, EvalEarlyExitError{} | |
174 | } | |
175 | ||
176 | return true, nil | |
177 | }, | |
178 | ||
179 | Then: EvalNoop{}, | |
180 | }, | |
181 | ||
182 | // The remainder of this pass is the same as running | |
183 | // a "plan" pass immediately followed by an "apply" pass, | |
184 | // populating the state early so it'll be available to | |
185 | // provider configurations that need this data during | |
186 | // refresh/plan. | |
187 | &EvalGetProvider{ | |
188 | Name: n.ProvidedBy()[0], | |
189 | Output: &provider, | |
190 | }, | |
191 | ||
192 | &EvalReadDataDiff{ | |
193 | Info: info, | |
194 | Config: &config, | |
195 | Provider: &provider, | |
196 | Output: &diff, | |
197 | OutputState: &state, | |
198 | }, | |
199 | ||
200 | &EvalReadDataApply{ | |
201 | Info: info, | |
202 | Diff: &diff, | |
203 | Provider: &provider, | |
204 | Output: &state, | |
205 | }, | |
206 | ||
207 | &EvalWriteState{ | |
208 | Name: stateId, | |
209 | ResourceType: rs.Type, | |
210 | Provider: rs.Provider, | |
211 | Dependencies: rs.Dependencies, | |
212 | State: &state, | |
213 | }, | |
214 | ||
215 | &EvalUpdateStateHook{}, | |
216 | }, | |
217 | } | |
218 | } |