From c41eb0e6e4acb590c7b79d119c322b50b1a7d032 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 31 May 2019 20:09:20 -0500 Subject: [PATCH] re-validate config during Plan The config is statically validated early on for structural issues, but the provider can't validate any inputs that were unknown at the time. Run ValidateResourceTypeConfig during Plan, so that the provider can validate the final config values, including those interpolated from other resources. --- terraform/context_plan_test.go | 44 ++++++++++++++++++++++++++++++++++ terraform/eval_diff.go | 14 +++++++++++ 2 files changed, 58 insertions(+) diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 237d376e3..d1a45d62e 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 b7acfb06d..ecf489258 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{