+ log.Printf("[TRACE] Re-validating config for %q", n.Addr.Absolute(ctx.Path()))
+ // Allow the provider to validate the final set of values.
+ // The config was statically validated early on, but there may have been
+ // unknown values which the provider could not validate at the time.
+ validateResp := provider.ValidateResourceTypeConfig(
+ providers.ValidateResourceTypeConfigRequest{
+ TypeName: n.Addr.Resource.Type,
+ Config: configVal,
+ },
+ )
+ if validateResp.Diagnostics.HasErrors() {
+ return nil, validateResp.Diagnostics.InConfigBody(config.Config).Err()
+ }
+
+ // The provider gets an opportunity to customize the proposed new value,
+ // which in turn produces the _planned_ new value.
+ resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
+ TypeName: n.Addr.Resource.Type,
+ Config: configVal,
+ PriorState: priorVal,
+ ProposedNewState: proposedNewVal,
+ PriorPrivate: priorPrivate,
+ })
+ diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config))
+ if diags.HasErrors() {
+ return nil, diags.Err()
+ }
+
+ plannedNewVal := resp.PlannedState
+ plannedPrivate := resp.PlannedPrivate
+
+ if plannedNewVal == cty.NilVal {
+ // Should never happen. Since real-world providers return via RPC a nil
+ // is always a bug in the client-side stub. This is more likely caused
+ // by an incompletely-configured mock provider in tests, though.
+ panic(fmt.Sprintf("PlanResourceChange of %s produced nil value", absAddr.String()))
+ }
+
+ // We allow the planned new value to disagree with configuration _values_
+ // here, since that allows the provider to do special logic like a
+ // DiffSuppressFunc, but we still require that the provider produces
+ // a value whose type conforms to the schema.
+ for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) {
+ diags = diags.Append(tfdiags.Sourceless(
+ tfdiags.Error,
+ "Provider produced invalid plan",
+ fmt.Sprintf(
+ "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
+ n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
+ ),
+ ))
+ }
+ if diags.HasErrors() {
+ return nil, diags.Err()
+ }
+
+ if errs := objchange.AssertPlanValid(schema, priorVal, configVal, plannedNewVal); len(errs) > 0 {
+ if resp.LegacyTypeSystem {
+ // The shimming of the old type system in the legacy SDK is not precise
+ // enough to pass this consistency check, so we'll give it a pass here,
+ // but we will generate a warning about it so that we are more likely
+ // to notice in the logs if an inconsistency beyond the type system
+ // leads to a downstream provider failure.
+ var buf strings.Builder
+ fmt.Fprintf(&buf, "[WARN] Provider %q produced an invalid plan 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)
+ for _, err := range errs {
+ fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err))
+ }
+ log.Print(buf.String())
+ } else {
+ for _, err := range errs {
+ diags = diags.Append(tfdiags.Sourceless(
+ tfdiags.Error,
+ "Provider produced invalid plan",
+ fmt.Sprintf(
+ "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
+ n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
+ ),
+ ))
+ }
+ return nil, diags.Err()
+ }