]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - terraform/eval_apply.go
Merge branch 'fix_read_test' of github.com:alexandreFre/terraform-provider-statuscake
[github/fretlink/terraform-provider-statuscake.git] / terraform / eval_apply.go
1 package terraform
2
3 import (
4 "fmt"
5 "log"
6 "strconv"
7
8 "github.com/hashicorp/go-multierror"
9 "github.com/hashicorp/terraform/config"
10 )
11
12 // EvalApply is an EvalNode implementation that writes the diff to
13 // the full diff.
14 type EvalApply struct {
15 Info *InstanceInfo
16 State **InstanceState
17 Diff **InstanceDiff
18 Provider *ResourceProvider
19 Output **InstanceState
20 CreateNew *bool
21 Error *error
22 }
23
24 // TODO: test
25 func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
26 diff := *n.Diff
27 provider := *n.Provider
28 state := *n.State
29
30 // If we have no diff, we have nothing to do!
31 if diff.Empty() {
32 log.Printf(
33 "[DEBUG] apply: %s: diff is empty, doing nothing.", n.Info.Id)
34 return nil, nil
35 }
36
37 // Remove any output values from the diff
38 for k, ad := range diff.CopyAttributes() {
39 if ad.Type == DiffAttrOutput {
40 diff.DelAttribute(k)
41 }
42 }
43
44 // If the state is nil, make it non-nil
45 if state == nil {
46 state = new(InstanceState)
47 }
48 state.init()
49
50 // Flag if we're creating a new instance
51 if n.CreateNew != nil {
52 *n.CreateNew = state.ID == "" && !diff.GetDestroy() || diff.RequiresNew()
53 }
54
55 // With the completed diff, apply!
56 log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
57 state, err := provider.Apply(n.Info, state, diff)
58 if state == nil {
59 state = new(InstanceState)
60 }
61 state.init()
62
63 // Force the "id" attribute to be our ID
64 if state.ID != "" {
65 state.Attributes["id"] = state.ID
66 }
67
68 // If the value is the unknown variable value, then it is an error.
69 // In this case we record the error and remove it from the state
70 for ak, av := range state.Attributes {
71 if av == config.UnknownVariableValue {
72 err = multierror.Append(err, fmt.Errorf(
73 "Attribute with unknown value: %s", ak))
74 delete(state.Attributes, ak)
75 }
76 }
77
78 // Write the final state
79 if n.Output != nil {
80 *n.Output = state
81 }
82
83 // If there are no errors, then we append it to our output error
84 // if we have one, otherwise we just output it.
85 if err != nil {
86 if n.Error != nil {
87 helpfulErr := fmt.Errorf("%s: %s", n.Info.Id, err.Error())
88 *n.Error = multierror.Append(*n.Error, helpfulErr)
89 } else {
90 return nil, err
91 }
92 }
93
94 return nil, nil
95 }
96
97 // EvalApplyPre is an EvalNode implementation that does the pre-Apply work
98 type EvalApplyPre struct {
99 Info *InstanceInfo
100 State **InstanceState
101 Diff **InstanceDiff
102 }
103
104 // TODO: test
105 func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) {
106 state := *n.State
107 diff := *n.Diff
108
109 // If the state is nil, make it non-nil
110 if state == nil {
111 state = new(InstanceState)
112 }
113 state.init()
114
115 if resourceHasUserVisibleApply(n.Info) {
116 // Call post-apply hook
117 err := ctx.Hook(func(h Hook) (HookAction, error) {
118 return h.PreApply(n.Info, state, diff)
119 })
120 if err != nil {
121 return nil, err
122 }
123 }
124
125 return nil, nil
126 }
127
128 // EvalApplyPost is an EvalNode implementation that does the post-Apply work
129 type EvalApplyPost struct {
130 Info *InstanceInfo
131 State **InstanceState
132 Error *error
133 }
134
135 // TODO: test
136 func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
137 state := *n.State
138
139 if resourceHasUserVisibleApply(n.Info) {
140 // Call post-apply hook
141 err := ctx.Hook(func(h Hook) (HookAction, error) {
142 return h.PostApply(n.Info, state, *n.Error)
143 })
144 if err != nil {
145 return nil, err
146 }
147 }
148
149 return nil, *n.Error
150 }
151
152 // resourceHasUserVisibleApply returns true if the given resource is one where
153 // apply actions should be exposed to the user.
154 //
155 // Certain resources do apply actions only as an implementation detail, so
156 // these should not be advertised to code outside of this package.
157 func resourceHasUserVisibleApply(info *InstanceInfo) bool {
158 addr := info.ResourceAddress()
159
160 // Only managed resources have user-visible apply actions.
161 // In particular, this excludes data resources since we "apply" these
162 // only as an implementation detail of removing them from state when
163 // they are destroyed. (When reading, they don't get here at all because
164 // we present them as "Refresh" actions.)
165 return addr.Mode == config.ManagedResourceMode
166 }
167
168 // EvalApplyProvisioners is an EvalNode implementation that executes
169 // the provisioners for a resource.
170 //
171 // TODO(mitchellh): This should probably be split up into a more fine-grained
172 // ApplyProvisioner (single) that is looped over.
173 type EvalApplyProvisioners struct {
174 Info *InstanceInfo
175 State **InstanceState
176 Resource *config.Resource
177 InterpResource *Resource
178 CreateNew *bool
179 Error *error
180
181 // When is the type of provisioner to run at this point
182 When config.ProvisionerWhen
183 }
184
185 // TODO: test
186 func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
187 state := *n.State
188
189 if n.CreateNew != nil && !*n.CreateNew {
190 // If we're not creating a new resource, then don't run provisioners
191 return nil, nil
192 }
193
194 provs := n.filterProvisioners()
195 if len(provs) == 0 {
196 // We have no provisioners, so don't do anything
197 return nil, nil
198 }
199
200 // taint tells us whether to enable tainting.
201 taint := n.When == config.ProvisionerWhenCreate
202
203 if n.Error != nil && *n.Error != nil {
204 if taint {
205 state.Tainted = true
206 }
207
208 // We're already tainted, so just return out
209 return nil, nil
210 }
211
212 {
213 // Call pre hook
214 err := ctx.Hook(func(h Hook) (HookAction, error) {
215 return h.PreProvisionResource(n.Info, state)
216 })
217 if err != nil {
218 return nil, err
219 }
220 }
221
222 // If there are no errors, then we append it to our output error
223 // if we have one, otherwise we just output it.
224 err := n.apply(ctx, provs)
225 if err != nil {
226 if taint {
227 state.Tainted = true
228 }
229
230 *n.Error = multierror.Append(*n.Error, err)
231 return nil, err
232 }
233
234 {
235 // Call post hook
236 err := ctx.Hook(func(h Hook) (HookAction, error) {
237 return h.PostProvisionResource(n.Info, state)
238 })
239 if err != nil {
240 return nil, err
241 }
242 }
243
244 return nil, nil
245 }
246
247 // filterProvisioners filters the provisioners on the resource to only
248 // the provisioners specified by the "when" option.
249 func (n *EvalApplyProvisioners) filterProvisioners() []*config.Provisioner {
250 // Fast path the zero case
251 if n.Resource == nil {
252 return nil
253 }
254
255 if len(n.Resource.Provisioners) == 0 {
256 return nil
257 }
258
259 result := make([]*config.Provisioner, 0, len(n.Resource.Provisioners))
260 for _, p := range n.Resource.Provisioners {
261 if p.When == n.When {
262 result = append(result, p)
263 }
264 }
265
266 return result
267 }
268
269 func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*config.Provisioner) error {
270 state := *n.State
271
272 // Store the original connection info, restore later
273 origConnInfo := state.Ephemeral.ConnInfo
274 defer func() {
275 state.Ephemeral.ConnInfo = origConnInfo
276 }()
277
278 for _, prov := range provs {
279 // Get the provisioner
280 provisioner := ctx.Provisioner(prov.Type)
281
282 // Interpolate the provisioner config
283 provConfig, err := ctx.Interpolate(prov.RawConfig.Copy(), n.InterpResource)
284 if err != nil {
285 return err
286 }
287
288 // Interpolate the conn info, since it may contain variables
289 connInfo, err := ctx.Interpolate(prov.ConnInfo.Copy(), n.InterpResource)
290 if err != nil {
291 return err
292 }
293
294 // Merge the connection information
295 overlay := make(map[string]string)
296 if origConnInfo != nil {
297 for k, v := range origConnInfo {
298 overlay[k] = v
299 }
300 }
301 for k, v := range connInfo.Config {
302 switch vt := v.(type) {
303 case string:
304 overlay[k] = vt
305 case int64:
306 overlay[k] = strconv.FormatInt(vt, 10)
307 case int32:
308 overlay[k] = strconv.FormatInt(int64(vt), 10)
309 case int:
310 overlay[k] = strconv.FormatInt(int64(vt), 10)
311 case float32:
312 overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32)
313 case float64:
314 overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64)
315 case bool:
316 overlay[k] = strconv.FormatBool(vt)
317 default:
318 overlay[k] = fmt.Sprintf("%v", vt)
319 }
320 }
321 state.Ephemeral.ConnInfo = overlay
322
323 {
324 // Call pre hook
325 err := ctx.Hook(func(h Hook) (HookAction, error) {
326 return h.PreProvision(n.Info, prov.Type)
327 })
328 if err != nil {
329 return err
330 }
331 }
332
333 // The output function
334 outputFn := func(msg string) {
335 ctx.Hook(func(h Hook) (HookAction, error) {
336 h.ProvisionOutput(n.Info, prov.Type, msg)
337 return HookActionContinue, nil
338 })
339 }
340
341 // Invoke the Provisioner
342 output := CallbackUIOutput{OutputFn: outputFn}
343 applyErr := provisioner.Apply(&output, state, provConfig)
344
345 // Call post hook
346 hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
347 return h.PostProvision(n.Info, prov.Type, applyErr)
348 })
349
350 // Handle the error before we deal with the hook
351 if applyErr != nil {
352 // Determine failure behavior
353 switch prov.OnFailure {
354 case config.ProvisionerOnFailureContinue:
355 log.Printf(
356 "[INFO] apply: %s [%s]: error during provision, continue requested",
357 n.Info.Id, prov.Type)
358
359 case config.ProvisionerOnFailureFail:
360 return applyErr
361 }
362 }
363
364 // Deal with the hook
365 if hookErr != nil {
366 return hookErr
367 }
368 }
369
370 return nil
371
372 }