]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/eval_apply.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_apply.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
4 "fmt"
5 "log"
107c1cdb 6 "strings"
bae9f6d2
JC
7
8 "github.com/hashicorp/go-multierror"
107c1cdb
ND
9 "github.com/hashicorp/hcl2/hcl"
10 "github.com/zclconf/go-cty/cty"
11
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/configs"
14 "github.com/hashicorp/terraform/plans"
15 "github.com/hashicorp/terraform/plans/objchange"
16 "github.com/hashicorp/terraform/providers"
17 "github.com/hashicorp/terraform/provisioners"
18 "github.com/hashicorp/terraform/states"
19 "github.com/hashicorp/terraform/tfdiags"
bae9f6d2
JC
20)
21
22// EvalApply is an EvalNode implementation that writes the diff to
23// the full diff.
24type EvalApply struct {
107c1cdb
ND
25 Addr addrs.ResourceInstance
26 Config *configs.Resource
27 Dependencies []addrs.Referenceable
28 State **states.ResourceInstanceObject
29 Change **plans.ResourceInstanceChange
30 ProviderAddr addrs.AbsProviderConfig
31 Provider *providers.Interface
32 ProviderSchema **ProviderSchema
33 Output **states.ResourceInstanceObject
34 CreateNew *bool
35 Error *error
bae9f6d2
JC
36}
37
38// TODO: test
39func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
107c1cdb
ND
40 var diags tfdiags.Diagnostics
41
42 change := *n.Change
bae9f6d2
JC
43 provider := *n.Provider
44 state := *n.State
107c1cdb 45 absAddr := n.Addr.Absolute(ctx.Path())
bae9f6d2 46
107c1cdb
ND
47 if state == nil {
48 state = &states.ResourceInstanceObject{}
49 }
50
51 schema, _ := (*n.ProviderSchema).SchemaForResourceType(n.Addr.Resource.Mode, n.Addr.Resource.Type)
52 if schema == nil {
53 // Should be caught during validation, so we don't bother with a pretty error here
54 return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
55 }
56
57 if n.CreateNew != nil {
58 *n.CreateNew = (change.Action == plans.Create || change.Action.IsReplace())
bae9f6d2
JC
59 }
60
107c1cdb
ND
61 configVal := cty.NullVal(cty.DynamicPseudoType)
62 if n.Config != nil {
63 var configDiags tfdiags.Diagnostics
64 keyData := EvalDataForInstanceKey(n.Addr.Key)
65 configVal, _, configDiags = ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
66 diags = diags.Append(configDiags)
67 if configDiags.HasErrors() {
68 return nil, diags.Err()
bae9f6d2
JC
69 }
70 }
71
107c1cdb
ND
72 log.Printf("[DEBUG] %s: applying the planned %s change", n.Addr.Absolute(ctx.Path()), change.Action)
73 resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
74 TypeName: n.Addr.Resource.Type,
75 PriorState: change.Before,
76 Config: configVal,
77 PlannedState: change.After,
78 PlannedPrivate: change.Private,
79 })
80 applyDiags := resp.Diagnostics
81 if n.Config != nil {
82 applyDiags = applyDiags.InConfigBody(n.Config.Config)
bae9f6d2 83 }
107c1cdb
ND
84 diags = diags.Append(applyDiags)
85
86 // Even if there are errors in the returned diagnostics, the provider may
87 // have returned a _partial_ state for an object that already exists but
88 // failed to fully configure, and so the remaining code must always run
89 // to completion but must be defensive against the new value being
90 // incomplete.
91 newVal := resp.NewState
92
93 if newVal == cty.NilVal {
94 // Providers are supposed to return a partial new value even when errors
95 // occur, but sometimes they don't and so in that case we'll patch that up
96 // by just using the prior state, so we'll at least keep track of the
97 // object for the user to retry.
98 newVal = change.Before
99
100 // As a special case, we'll set the new value to null if it looks like
101 // we were trying to execute a delete, because the provider in this case
102 // probably left the newVal unset intending it to be interpreted as "null".
103 if change.After.IsNull() {
104 newVal = cty.NullVal(schema.ImpliedType())
105 }
bae9f6d2 106
107c1cdb
ND
107 // Ideally we'd produce an error or warning here if newVal is nil and
108 // there are no errors in diags, because that indicates a buggy
109 // provider not properly reporting its result, but unfortunately many
110 // of our historical test mocks behave in this way and so producing
111 // a diagnostic here fails hundreds of tests. Instead, we must just
112 // silently retain the old value for now. Returning a nil value with
113 // no errors is still always considered a bug in the provider though,
114 // and should be fixed for any "real" providers that do it.
bae9f6d2
JC
115 }
116
107c1cdb
ND
117 var conformDiags tfdiags.Diagnostics
118 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
119 conformDiags = conformDiags.Append(tfdiags.Sourceless(
120 tfdiags.Error,
121 "Provider produced invalid object",
122 fmt.Sprintf(
123 "Provider %q produced an invalid value after apply for %s. The result cannot not be saved in the Terraform state.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
124 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
125 ),
126 ))
127 }
128 diags = diags.Append(conformDiags)
129 if conformDiags.HasErrors() {
130 // Bail early in this particular case, because an object that doesn't
131 // conform to the schema can't be saved in the state anyway -- the
132 // serializer will reject it.
133 return nil, diags.Err()
134 }
135
136 // After this point we have a type-conforming result object and so we
137 // must always run to completion to ensure it can be saved. If n.Error
138 // is set then we must not return a non-nil error, in order to allow
139 // evaluation to continue to a later point where our state object will
140 // be saved.
141
142 // By this point there must not be any unknown values remaining in our
143 // object, because we've applied the change and we can't save unknowns
144 // in our persistent state. If any are present then we will indicate an
145 // error (which is always a bug in the provider) but we will also replace
146 // them with nulls so that we can successfully save the portions of the
147 // returned value that are known.
148 if !newVal.IsWhollyKnown() {
149 // To generate better error messages, we'll go for a walk through the
150 // value and make a separate diagnostic for each unknown value we
151 // find.
152 cty.Walk(newVal, func(path cty.Path, val cty.Value) (bool, error) {
153 if !val.IsKnown() {
154 pathStr := tfdiags.FormatCtyPath(path)
155 diags = diags.Append(tfdiags.Sourceless(
156 tfdiags.Error,
157 "Provider returned invalid result object after apply",
158 fmt.Sprintf(
159 "After the apply operation, the provider still indicated an unknown value for %s%s. All values must be known after apply, so this is always a bug in the provider and should be reported in the provider's own repository. Terraform will still save the other known object values in the state.",
160 n.Addr.Absolute(ctx.Path()), pathStr,
161 ),
162 ))
163 }
164 return true, nil
165 })
166
167 // NOTE: This operation can potentially be lossy if there are multiple
168 // elements in a set that differ only by unknown values: after
169 // replacing with null these will be merged together into a single set
170 // element. Since we can only get here in the presence of a provider
171 // bug, we accept this because storing a result here is always a
172 // best-effort sort of thing.
173 newVal = cty.UnknownAsNull(newVal)
174 }
175
176 if change.Action != plans.Delete && !diags.HasErrors() {
177 // Only values that were marked as unknown in the planned value are allowed
178 // to change during the apply operation. (We do this after the unknown-ness
179 // check above so that we also catch anything that became unknown after
180 // being known during plan.)
181 //
182 // If we are returning other errors anyway then we'll give this
183 // a pass since the other errors are usually the explanation for
184 // this one and so it's more helpful to let the user focus on the
185 // root cause rather than distract with this extra problem.
186 if errs := objchange.AssertObjectCompatible(schema, change.After, newVal); len(errs) > 0 {
187 if resp.LegacyTypeSystem {
188 // The shimming of the old type system in the legacy SDK is not precise
189 // enough to pass this consistency check, so we'll give it a pass here,
190 // but we will generate a warning about it so that we are more likely
191 // to notice in the logs if an inconsistency beyond the type system
192 // leads to a downstream provider failure.
193 var buf strings.Builder
194 fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", n.ProviderAddr.ProviderConfig.Type, absAddr)
195 for _, err := range errs {
196 fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err))
197 }
198 log.Print(buf.String())
199
200 // The sort of inconsistency we won't catch here is if a known value
201 // in the plan is changed during apply. That can cause downstream
202 // problems because a dependent resource would make its own plan based
203 // on the planned value, and thus get a different result during the
204 // apply phase. This will usually lead to a "Provider produced invalid plan"
205 // error that incorrectly blames the downstream resource for the change.
206
207 } else {
208 for _, err := range errs {
209 diags = diags.Append(tfdiags.Sourceless(
210 tfdiags.Error,
211 "Provider produced inconsistent result after apply",
212 fmt.Sprintf(
213 "When applying changes to %s, provider %q produced an unexpected new value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
214 absAddr, n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatError(err),
215 ),
216 ))
217 }
218 }
219 }
220 }
221
222 // If a provider returns a null or non-null object at the wrong time then
223 // we still want to save that but it often causes some confusing behaviors
224 // where it seems like Terraform is failing to take any action at all,
225 // so we'll generate some errors to draw attention to it.
226 if !diags.HasErrors() {
227 if change.Action == plans.Delete && !newVal.IsNull() {
228 diags = diags.Append(tfdiags.Sourceless(
229 tfdiags.Error,
230 "Provider returned invalid result object after apply",
231 fmt.Sprintf(
232 "After applying a %s plan, the provider returned a non-null object for %s. Destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository. Terraform will still save this errant object in the state for debugging and recovery.",
233 change.Action, n.Addr.Absolute(ctx.Path()),
234 ),
235 ))
236 }
237 if change.Action != plans.Delete && newVal.IsNull() {
238 diags = diags.Append(tfdiags.Sourceless(
239 tfdiags.Error,
240 "Provider returned invalid result object after apply",
241 fmt.Sprintf(
242 "After applying a %s plan, the provider returned a null object for %s. Only destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository.",
243 change.Action, n.Addr.Absolute(ctx.Path()),
244 ),
245 ))
246 }
bae9f6d2 247 }
bae9f6d2 248
107c1cdb
ND
249 // Sometimes providers return a null value when an operation fails for some
250 // reason, but we'd rather keep the prior state so that the error can be
251 // corrected on a subsequent run. We must only do this for null new value
252 // though, or else we may discard partial updates the provider was able to
253 // complete.
254 if diags.HasErrors() && newVal.IsNull() {
255 // Otherwise, we'll continue but using the prior state as the new value,
256 // making this effectively a no-op. If the item really _has_ been
257 // deleted then our next refresh will detect that and fix it up.
258 // If change.Action is Create then change.Before will also be null,
259 // which is fine.
260 newVal = change.Before
bae9f6d2
JC
261 }
262
107c1cdb
ND
263 var newState *states.ResourceInstanceObject
264 if !newVal.IsNull() { // null value indicates that the object is deleted, so we won't set a new state in that case
265 newState = &states.ResourceInstanceObject{
266 Status: states.ObjectReady,
267 Value: newVal,
268 Private: resp.Private,
269 Dependencies: n.Dependencies, // Should be populated by the caller from the StateDependencies method on the resource instance node
bae9f6d2
JC
270 }
271 }
272
273 // Write the final state
274 if n.Output != nil {
107c1cdb 275 *n.Output = newState
bae9f6d2
JC
276 }
277
107c1cdb
ND
278 if diags.HasErrors() {
279 // If the caller provided an error pointer then they are expected to
280 // handle the error some other way and we treat our own result as
281 // success.
bae9f6d2 282 if n.Error != nil {
107c1cdb
ND
283 err := diags.Err()
284 *n.Error = err
285 log.Printf("[DEBUG] %s: apply errored, but we're indicating that via the Error pointer rather than returning it: %s", n.Addr.Absolute(ctx.Path()), err)
286 return nil, nil
bae9f6d2
JC
287 }
288 }
289
107c1cdb 290 return nil, diags.ErrWithWarnings()
bae9f6d2
JC
291}
292
293// EvalApplyPre is an EvalNode implementation that does the pre-Apply work
294type EvalApplyPre struct {
107c1cdb
ND
295 Addr addrs.ResourceInstance
296 Gen states.Generation
297 State **states.ResourceInstanceObject
298 Change **plans.ResourceInstanceChange
bae9f6d2
JC
299}
300
301// TODO: test
302func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) {
107c1cdb
ND
303 change := *n.Change
304 absAddr := n.Addr.Absolute(ctx.Path())
bae9f6d2 305
107c1cdb
ND
306 if change == nil {
307 panic(fmt.Sprintf("EvalApplyPre for %s called with nil Change", absAddr))
bae9f6d2 308 }
bae9f6d2 309
107c1cdb
ND
310 if resourceHasUserVisibleApply(n.Addr) {
311 priorState := change.Before
312 plannedNewState := change.After
313
bae9f6d2 314 err := ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 315 return h.PreApply(absAddr, n.Gen, change.Action, priorState, plannedNewState)
bae9f6d2
JC
316 })
317 if err != nil {
318 return nil, err
319 }
320 }
321
322 return nil, nil
323}
324
325// EvalApplyPost is an EvalNode implementation that does the post-Apply work
326type EvalApplyPost struct {
107c1cdb
ND
327 Addr addrs.ResourceInstance
328 Gen states.Generation
329 State **states.ResourceInstanceObject
bae9f6d2
JC
330 Error *error
331}
332
333// TODO: test
334func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
335 state := *n.State
336
107c1cdb
ND
337 if resourceHasUserVisibleApply(n.Addr) {
338 absAddr := n.Addr.Absolute(ctx.Path())
339 var newState cty.Value
340 if state != nil {
341 newState = state.Value
342 } else {
343 newState = cty.NullVal(cty.DynamicPseudoType)
344 }
345 var err error
346 if n.Error != nil {
347 err = *n.Error
348 }
349
350 hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
351 return h.PostApply(absAddr, n.Gen, newState, err)
bae9f6d2 352 })
107c1cdb
ND
353 if hookErr != nil {
354 return nil, hookErr
bae9f6d2
JC
355 }
356 }
357
358 return nil, *n.Error
359}
360
107c1cdb
ND
361// EvalMaybeTainted is an EvalNode that takes the planned change, new value,
362// and possible error from an apply operation and produces a new instance
363// object marked as tainted if it appears that a create operation has failed.
364//
365// This EvalNode never returns an error, to ensure that a subsequent EvalNode
366// can still record the possibly-tainted object in the state.
367type EvalMaybeTainted struct {
368 Addr addrs.ResourceInstance
369 Gen states.Generation
370 Change **plans.ResourceInstanceChange
371 State **states.ResourceInstanceObject
372 Error *error
373
374 // If StateOutput is not nil, its referent will be assigned either the same
375 // pointer as State or a new object with its status set as Tainted,
376 // depending on whether an error is given and if this was a create action.
377 StateOutput **states.ResourceInstanceObject
378}
379
380// TODO: test
381func (n *EvalMaybeTainted) Eval(ctx EvalContext) (interface{}, error) {
382 state := *n.State
383 change := *n.Change
384 err := *n.Error
385
386 if state != nil && state.Status == states.ObjectTainted {
387 log.Printf("[TRACE] EvalMaybeTainted: %s was already tainted, so nothing to do", n.Addr.Absolute(ctx.Path()))
388 return nil, nil
389 }
390
391 if n.StateOutput != nil {
392 if err != nil && change.Action == plans.Create {
393 // If there are errors during a _create_ then the object is
394 // in an undefined state, and so we'll mark it as tainted so
395 // we can try again on the next run.
396 //
397 // We don't do this for other change actions because errors
398 // during updates will often not change the remote object at all.
399 // If there _were_ changes prior to the error, it's the provider's
400 // responsibility to record the effect of those changes in the
401 // object value it returned.
402 log.Printf("[TRACE] EvalMaybeTainted: %s encountered an error during creation, so it is now marked as tainted", n.Addr.Absolute(ctx.Path()))
403 *n.StateOutput = state.AsTainted()
404 } else {
405 *n.StateOutput = state
406 }
407 }
408
409 return nil, nil
410}
411
15c0b25d
AP
412// resourceHasUserVisibleApply returns true if the given resource is one where
413// apply actions should be exposed to the user.
414//
415// Certain resources do apply actions only as an implementation detail, so
416// these should not be advertised to code outside of this package.
107c1cdb 417func resourceHasUserVisibleApply(addr addrs.ResourceInstance) bool {
15c0b25d
AP
418 // Only managed resources have user-visible apply actions.
419 // In particular, this excludes data resources since we "apply" these
420 // only as an implementation detail of removing them from state when
421 // they are destroyed. (When reading, they don't get here at all because
422 // we present them as "Refresh" actions.)
107c1cdb 423 return addr.ContainingResource().Mode == addrs.ManagedResourceMode
15c0b25d
AP
424}
425
bae9f6d2
JC
426// EvalApplyProvisioners is an EvalNode implementation that executes
427// the provisioners for a resource.
428//
429// TODO(mitchellh): This should probably be split up into a more fine-grained
430// ApplyProvisioner (single) that is looped over.
431type EvalApplyProvisioners struct {
107c1cdb
ND
432 Addr addrs.ResourceInstance
433 State **states.ResourceInstanceObject
434 ResourceConfig *configs.Resource
bae9f6d2
JC
435 CreateNew *bool
436 Error *error
437
438 // When is the type of provisioner to run at this point
107c1cdb 439 When configs.ProvisionerWhen
bae9f6d2
JC
440}
441
442// TODO: test
443func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
107c1cdb 444 absAddr := n.Addr.Absolute(ctx.Path())
bae9f6d2 445 state := *n.State
107c1cdb
ND
446 if state == nil {
447 log.Printf("[TRACE] EvalApplyProvisioners: %s has no state, so skipping provisioners", n.Addr)
448 return nil, nil
449 }
450 if n.When == configs.ProvisionerWhenCreate && n.CreateNew != nil && !*n.CreateNew {
bae9f6d2 451 // If we're not creating a new resource, then don't run provisioners
107c1cdb
ND
452 log.Printf("[TRACE] EvalApplyProvisioners: %s is not freshly-created, so no provisioning is required", n.Addr)
453 return nil, nil
454 }
455 if state.Status == states.ObjectTainted {
456 // No point in provisioning an object that is already tainted, since
457 // it's going to get recreated on the next apply anyway.
458 log.Printf("[TRACE] EvalApplyProvisioners: %s is tainted, so skipping provisioning", n.Addr)
bae9f6d2
JC
459 return nil, nil
460 }
461
462 provs := n.filterProvisioners()
463 if len(provs) == 0 {
464 // We have no provisioners, so don't do anything
465 return nil, nil
466 }
467
bae9f6d2 468 if n.Error != nil && *n.Error != nil {
bae9f6d2
JC
469 // We're already tainted, so just return out
470 return nil, nil
471 }
472
473 {
474 // Call pre hook
475 err := ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 476 return h.PreProvisionInstance(absAddr, state.Value)
bae9f6d2
JC
477 })
478 if err != nil {
479 return nil, err
480 }
481 }
482
483 // If there are no errors, then we append it to our output error
484 // if we have one, otherwise we just output it.
485 err := n.apply(ctx, provs)
486 if err != nil {
15c0b25d 487 *n.Error = multierror.Append(*n.Error, err)
107c1cdb
ND
488 if n.Error == nil {
489 return nil, err
490 } else {
491 log.Printf("[TRACE] EvalApplyProvisioners: %s provisioning failed, but we will continue anyway at the caller's request", absAddr)
492 return nil, nil
493 }
bae9f6d2
JC
494 }
495
496 {
497 // Call post hook
498 err := ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 499 return h.PostProvisionInstance(absAddr, state.Value)
bae9f6d2
JC
500 })
501 if err != nil {
502 return nil, err
503 }
504 }
505
506 return nil, nil
507}
508
509// filterProvisioners filters the provisioners on the resource to only
510// the provisioners specified by the "when" option.
107c1cdb 511func (n *EvalApplyProvisioners) filterProvisioners() []*configs.Provisioner {
bae9f6d2 512 // Fast path the zero case
107c1cdb 513 if n.ResourceConfig == nil || n.ResourceConfig.Managed == nil {
bae9f6d2
JC
514 return nil
515 }
516
107c1cdb 517 if len(n.ResourceConfig.Managed.Provisioners) == 0 {
bae9f6d2
JC
518 return nil
519 }
520
107c1cdb
ND
521 result := make([]*configs.Provisioner, 0, len(n.ResourceConfig.Managed.Provisioners))
522 for _, p := range n.ResourceConfig.Managed.Provisioners {
bae9f6d2
JC
523 if p.When == n.When {
524 result = append(result, p)
525 }
526 }
527
528 return result
529}
530
107c1cdb
ND
531func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisioner) error {
532 var diags tfdiags.Diagnostics
533 instanceAddr := n.Addr
534 absAddr := instanceAddr.Absolute(ctx.Path())
535
536 // If there's a connection block defined directly inside the resource block
537 // then it'll serve as a base connection configuration for all of the
538 // provisioners.
539 var baseConn hcl.Body
540 if n.ResourceConfig.Managed != nil && n.ResourceConfig.Managed.Connection != nil {
541 baseConn = n.ResourceConfig.Managed.Connection.Config
542 }
bae9f6d2
JC
543
544 for _, prov := range provs {
107c1cdb
ND
545 log.Printf("[TRACE] EvalApplyProvisioners: provisioning %s with %q", absAddr, prov.Type)
546
bae9f6d2
JC
547 // Get the provisioner
548 provisioner := ctx.Provisioner(prov.Type)
107c1cdb 549 schema := ctx.ProvisionerSchema(prov.Type)
bae9f6d2 550
107c1cdb 551 keyData := EvalDataForInstanceKey(instanceAddr.Key)
bae9f6d2 552
107c1cdb
ND
553 // Evaluate the main provisioner configuration.
554 config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, keyData)
555 diags = diags.Append(configDiags)
556
557 // If the provisioner block contains a connection block of its own then
558 // it can override the base connection configuration, if any.
559 var localConn hcl.Body
560 if prov.Connection != nil {
561 localConn = prov.Connection.Config
bae9f6d2
JC
562 }
563
107c1cdb
ND
564 var connBody hcl.Body
565 switch {
566 case baseConn != nil && localConn != nil:
567 // Our standard merging logic applies here, similar to what we do
568 // with _override.tf configuration files: arguments from the
569 // base connection block will be masked by any arguments of the
570 // same name in the local connection block.
571 connBody = configs.MergeBodies(baseConn, localConn)
572 case baseConn != nil:
573 connBody = baseConn
574 case localConn != nil:
575 connBody = localConn
bae9f6d2 576 }
107c1cdb
ND
577
578 // start with an empty connInfo
579 connInfo := cty.NullVal(connectionBlockSupersetSchema.ImpliedType())
580
581 if connBody != nil {
582 var connInfoDiags tfdiags.Diagnostics
583 connInfo, _, connInfoDiags = ctx.EvaluateBlock(connBody, connectionBlockSupersetSchema, instanceAddr, keyData)
584 diags = diags.Append(connInfoDiags)
585 if diags.HasErrors() {
586 // "on failure continue" setting only applies to failures of the
587 // provisioner itself, not to invalid configuration.
588 return diags.Err()
bae9f6d2
JC
589 }
590 }
bae9f6d2
JC
591
592 {
593 // Call pre hook
594 err := ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 595 return h.PreProvisionInstanceStep(absAddr, prov.Type)
bae9f6d2
JC
596 })
597 if err != nil {
598 return err
599 }
600 }
601
602 // The output function
603 outputFn := func(msg string) {
604 ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 605 h.ProvisionOutput(absAddr, prov.Type, msg)
bae9f6d2
JC
606 return HookActionContinue, nil
607 })
608 }
609
bae9f6d2 610 output := CallbackUIOutput{OutputFn: outputFn}
107c1cdb
ND
611 resp := provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{
612 Config: config,
613 Connection: connInfo,
614 UIOutput: &output,
615 })
616 applyDiags := resp.Diagnostics.InConfigBody(prov.Config)
bae9f6d2
JC
617
618 // Call post hook
619 hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
107c1cdb 620 return h.PostProvisionInstanceStep(absAddr, prov.Type, applyDiags.Err())
bae9f6d2
JC
621 })
622
107c1cdb
ND
623 switch prov.OnFailure {
624 case configs.ProvisionerOnFailureContinue:
625 if applyDiags.HasErrors() {
626 log.Printf("[WARN] Errors while provisioning %s with %q, but continuing as requested in configuration", n.Addr, prov.Type)
627 } else {
628 // Maybe there are warnings that we still want to see
629 diags = diags.Append(applyDiags)
630 }
631 default:
632 diags = diags.Append(applyDiags)
633 if applyDiags.HasErrors() {
634 log.Printf("[WARN] Errors while provisioning %s with %q, so aborting", n.Addr, prov.Type)
635 return diags.Err()
bae9f6d2
JC
636 }
637 }
638
639 // Deal with the hook
640 if hookErr != nil {
641 return hookErr
642 }
643 }
644
107c1cdb 645 return diags.ErrWithWarnings()
bae9f6d2 646}