github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/eval_apply.go (about)

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