8 "github.com/hashicorp/go-multierror"
9 "github.com/hashicorp/hcl2/hcl"
10 "github.com/zclconf/go-cty/cty"
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"
22 // EvalApply is an EvalNode implementation that writes the diff to
24 type EvalApply struct {
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
39 func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
40 var diags tfdiags.Diagnostics
43 provider := *n.Provider
45 absAddr := n.Addr.Absolute(ctx.Path())
48 state = &states.ResourceInstanceObject{}
51 schema, _ := (*n.ProviderSchema).SchemaForResourceType(n.Addr.Resource.Mode, n.Addr.Resource.Type)
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)
57 if n.CreateNew != nil {
58 *n.CreateNew = (change.Action == plans.Create || change.Action.IsReplace())
61 configVal := cty.NullVal(cty.DynamicPseudoType)
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()
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,
77 PlannedState: change.After,
78 PlannedPrivate: change.Private,
80 applyDiags := resp.Diagnostics
82 applyDiags = applyDiags.InConfigBody(n.Config.Config)
84 diags = diags.Append(applyDiags)
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
91 newVal := resp.NewState
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
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())
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.
117 var conformDiags tfdiags.Diagnostics
118 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
119 conformDiags = conformDiags.Append(tfdiags.Sourceless(
121 "Provider produced invalid object",
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()),
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()
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
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
152 cty.Walk(newVal, func(path cty.Path, val cty.Value) (bool, error) {
154 pathStr := tfdiags.FormatCtyPath(path)
155 diags = diags.Append(tfdiags.Sourceless(
157 "Provider returned invalid result object after apply",
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,
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)
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.)
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))
198 log.Print(buf.String())
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.
208 for _, err := range errs {
209 diags = diags.Append(tfdiags.Sourceless(
211 "Provider produced inconsistent result after apply",
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),
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(
230 "Provider returned invalid result object after apply",
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()),
237 if change.Action != plans.Delete && newVal.IsNull() {
238 diags = diags.Append(tfdiags.Sourceless(
240 "Provider returned invalid result object after apply",
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()),
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
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,
260 newVal = change.Before
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,
268 Private: resp.Private,
269 Dependencies: n.Dependencies, // Should be populated by the caller from the StateDependencies method on the resource instance node
273 // Write the final state
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
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)
290 return nil, diags.ErrWithWarnings()
293 // EvalApplyPre is an EvalNode implementation that does the pre-Apply work
294 type EvalApplyPre struct {
295 Addr addrs.ResourceInstance
296 Gen states.Generation
297 State **states.ResourceInstanceObject
298 Change **plans.ResourceInstanceChange
302 func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) {
304 absAddr := n.Addr.Absolute(ctx.Path())
307 panic(fmt.Sprintf("EvalApplyPre for %s called with nil Change", absAddr))
310 if resourceHasUserVisibleApply(n.Addr) {
311 priorState := change.Before
312 plannedNewState := change.After
314 err := ctx.Hook(func(h Hook) (HookAction, error) {
315 return h.PreApply(absAddr, n.Gen, change.Action, priorState, plannedNewState)
325 // EvalApplyPost is an EvalNode implementation that does the post-Apply work
326 type EvalApplyPost struct {
327 Addr addrs.ResourceInstance
328 Gen states.Generation
329 State **states.ResourceInstanceObject
334 func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
337 if resourceHasUserVisibleApply(n.Addr) {
338 absAddr := n.Addr.Absolute(ctx.Path())
339 var newState cty.Value
341 newState = state.Value
343 newState = cty.NullVal(cty.DynamicPseudoType)
350 hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
351 return h.PostApply(absAddr, n.Gen, newState, err)
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.
365 // This EvalNode never returns an error, to ensure that a subsequent EvalNode
366 // can still record the possibly-tainted object in the state.
367 type EvalMaybeTainted struct {
368 Addr addrs.ResourceInstance
369 Gen states.Generation
370 Change **plans.ResourceInstanceChange
371 State **states.ResourceInstanceObject
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
381 func (n *EvalMaybeTainted) Eval(ctx EvalContext) (interface{}, error) {
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()))
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.
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()
405 *n.StateOutput = state
412 // resourceHasUserVisibleApply returns true if the given resource is one where
413 // apply actions should be exposed to the user.
415 // Certain resources do apply actions only as an implementation detail, so
416 // these should not be advertised to code outside of this package.
417 func resourceHasUserVisibleApply(addr addrs.ResourceInstance) bool {
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.)
423 return addr.ContainingResource().Mode == addrs.ManagedResourceMode
426 // EvalApplyProvisioners is an EvalNode implementation that executes
427 // the provisioners for a resource.
429 // TODO(mitchellh): This should probably be split up into a more fine-grained
430 // ApplyProvisioner (single) that is looped over.
431 type EvalApplyProvisioners struct {
432 Addr addrs.ResourceInstance
433 State **states.ResourceInstanceObject
434 ResourceConfig *configs.Resource
438 // When is the type of provisioner to run at this point
439 When configs.ProvisionerWhen
443 func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
444 absAddr := n.Addr.Absolute(ctx.Path())
447 log.Printf("[TRACE] EvalApplyProvisioners: %s has no state, so skipping provisioners", n.Addr)
450 if n.When == configs.ProvisionerWhenCreate && n.CreateNew != nil && !*n.CreateNew {
451 // If we're not creating a new resource, then don't run provisioners
452 log.Printf("[TRACE] EvalApplyProvisioners: %s is not freshly-created, so no provisioning is required", n.Addr)
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)
462 provs := n.filterProvisioners()
464 // We have no provisioners, so don't do anything
468 if n.Error != nil && *n.Error != nil {
469 // We're already tainted, so just return out
475 err := ctx.Hook(func(h Hook) (HookAction, error) {
476 return h.PreProvisionInstance(absAddr, state.Value)
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)
487 *n.Error = multierror.Append(*n.Error, err)
491 log.Printf("[TRACE] EvalApplyProvisioners: %s provisioning failed, but we will continue anyway at the caller's request", absAddr)
498 err := ctx.Hook(func(h Hook) (HookAction, error) {
499 return h.PostProvisionInstance(absAddr, state.Value)
509 // filterProvisioners filters the provisioners on the resource to only
510 // the provisioners specified by the "when" option.
511 func (n *EvalApplyProvisioners) filterProvisioners() []*configs.Provisioner {
512 // Fast path the zero case
513 if n.ResourceConfig == nil || n.ResourceConfig.Managed == nil {
517 if len(n.ResourceConfig.Managed.Provisioners) == 0 {
521 result := make([]*configs.Provisioner, 0, len(n.ResourceConfig.Managed.Provisioners))
522 for _, p := range n.ResourceConfig.Managed.Provisioners {
523 if p.When == n.When {
524 result = append(result, p)
531 func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisioner) error {
532 var diags tfdiags.Diagnostics
533 instanceAddr := n.Addr
534 absAddr := instanceAddr.Absolute(ctx.Path())
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
539 var baseConn hcl.Body
540 if n.ResourceConfig.Managed != nil && n.ResourceConfig.Managed.Connection != nil {
541 baseConn = n.ResourceConfig.Managed.Connection.Config
544 for _, prov := range provs {
545 log.Printf("[TRACE] EvalApplyProvisioners: provisioning %s with %q", absAddr, prov.Type)
547 // Get the provisioner
548 provisioner := ctx.Provisioner(prov.Type)
549 schema := ctx.ProvisionerSchema(prov.Type)
551 keyData := EvalDataForInstanceKey(instanceAddr.Key)
553 // Evaluate the main provisioner configuration.
554 config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, keyData)
555 diags = diags.Append(configDiags)
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
564 var connBody hcl.Body
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:
574 case localConn != nil:
578 // start with an empty connInfo
579 connInfo := cty.NullVal(connectionBlockSupersetSchema.ImpliedType())
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.
594 err := ctx.Hook(func(h Hook) (HookAction, error) {
595 return h.PreProvisionInstanceStep(absAddr, prov.Type)
602 // The output function
603 outputFn := func(msg string) {
604 ctx.Hook(func(h Hook) (HookAction, error) {
605 h.ProvisionOutput(absAddr, prov.Type, msg)
606 return HookActionContinue, nil
610 output := CallbackUIOutput{OutputFn: outputFn}
611 resp := provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{
613 Connection: connInfo,
616 applyDiags := resp.Diagnostics.InConfigBody(prov.Config)
619 hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
620 return h.PostProvisionInstanceStep(absAddr, prov.Type, applyDiags.Err())
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)
628 // Maybe there are warnings that we still want to see
629 diags = diags.Append(applyDiags)
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)
639 // Deal with the hook
645 return diags.ErrWithWarnings()