diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 237d376e34..d1a45d62ec 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -2,6 +2,7 @@ package terraform import ( "bytes" + "errors" "fmt" "os" "reflect" @@ -5724,6 +5725,49 @@ resource "aws_instance" "foo" { } } +func TestContext2Plan_variableValidation(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +variable "x" { + default = "bar" +} + +resource "aws_instance" "foo" { + foo = var.x +}`, + }) + + p := testProvider("aws") + p.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) { + foo := req.Config.GetAttr("foo").AsString() + if foo == "bar" { + resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo cannot be bar")) + } + return + } + + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { + resp.PlannedState = req.ProposedNewState + return + } + + ctx := testContext2(t, &ContextOpts{ + Config: m, + ProviderResolver: providers.ResolverFixed( + map[string]providers.Factory{ + "aws": testProviderFuncFixed(p), + }, + ), + }) + + _, diags := ctx.Plan() + if !diags.HasErrors() { + // Should get this error: + // Unsupported attribute: This object does not have an attribute named "missing" + t.Fatal("succeeded; want errors") + } +} + func checkVals(t *testing.T, expected, got cty.Value) { t.Helper() if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) { diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index b7acfb06dc..ecf4892587 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -174,6 +174,20 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { } } + 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{