diff --git a/backend/local/backend_refresh_test.go b/backend/local/backend_refresh_test.go index cb6cb9b4f..73b223df3 100644 --- a/backend/local/backend_refresh_test.go +++ b/backend/local/backend_refresh_test.go @@ -135,6 +135,52 @@ func TestLocal_refreshValidate(t *testing.T) { } <-run.Done() + checkState(t, b.StateOutPath, ` +test_instance.foo: + ID = yes + provider = provider["registry.terraform.io/hashicorp/test"] + `) +} + +func TestLocal_refreshValidateProviderConfigured(t *testing.T) { + b, cleanup := TestLocal(t) + defer cleanup() + + schema := &terraform.ProviderSchema{ + Provider: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": {Type: cty.String, Optional: true}, + }, + }, + ResourceTypes: map[string]*configschema.Block{ + "test_instance": { + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + }, + }, + }, + } + + p := TestLocalProvider(t, b, "test", schema) + testStateFile(t, b.StatePath, testRefreshState()) + p.ReadResourceFn = nil + p.ReadResourceResponse = providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("yes"), + })} + + // Enable validation + b.OpValidation = true + + op, configCleanup := testOperationRefresh(t, "./testdata/refresh-provider-config") + defer configCleanup() + + run, err := b.Operation(context.Background(), op) + if err != nil { + t.Fatalf("bad: %s", err) + } + <-run.Done() + if !p.PrepareProviderConfigCalled { t.Fatal("Prepare provider config should be called") } diff --git a/backend/local/testdata/refresh-provider-config/main.tf b/backend/local/testdata/refresh-provider-config/main.tf new file mode 100644 index 000000000..f3a3ebb85 --- /dev/null +++ b/backend/local/testdata/refresh-provider-config/main.tf @@ -0,0 +1,7 @@ +resource "test_instance" "foo" { + ami = "bar" +} + +provider "test" { + value = "foo" +} diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index e2b737c36..d08c61422 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -595,8 +595,8 @@ func TestContext2Validate_providerConfig_bad(t *testing.T) { } } -func TestContext2Validate_providerConfig_badEmpty(t *testing.T) { - m := testModule(t, "validate-bad-pc-empty") +func TestContext2Validate_providerConfig_skippedEmpty(t *testing.T) { + m := testModule(t, "validate-skipped-pc-empty") p := testProvider("aws") p.GetSchemaReturn = &ProviderSchema{ Provider: &configschema.Block{ @@ -619,12 +619,12 @@ func TestContext2Validate_providerConfig_badEmpty(t *testing.T) { }) p.PrepareProviderConfigResponse = providers.PrepareProviderConfigResponse{ - Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("bad")), + Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("should not be called")), } diags := c.Validate() - if !diags.HasErrors() { - t.Fatalf("succeeded; want error") + if diags.HasErrors() { + t.Fatalf("unexpected error: %s", diags.Err()) } } diff --git a/terraform/node_provider.go b/terraform/node_provider.go index 585417340..0ea431493 100644 --- a/terraform/node_provider.go +++ b/terraform/node_provider.go @@ -48,6 +48,14 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider provi configBody := buildProviderConfig(ctx, n.Addr, n.ProviderConfig()) + // if a provider config is empty (only an alias), return early and don't continue + // validation. validate doesn't need to fully configure the provider itself, so + // skipping a provider with an implied configuration won't prevent other validation from completing. + _, noConfigDiags := configBody.Content(&hcl.BodySchema{}) + if !noConfigDiags.HasErrors() { + return nil + } + resp := provider.GetSchema() diags = diags.Append(resp.Diagnostics) if diags.HasErrors() { @@ -64,19 +72,7 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx EvalContext, provider provi configVal, _, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey) if evalDiags.HasErrors() { - if n.Config == nil { - // If there isn't an explicit "provider" block in the configuration, - // this error message won't be very clear. Add some detail to the - // error message in this case. - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid provider configuration", - fmt.Sprintf(providerConfigErr, evalDiags.Err(), n.Addr.Provider), - )) - return diags - } else { - return diags.Append(evalDiags) - } + return diags.Append(evalDiags) } diags = diags.Append(evalDiags) diff --git a/terraform/node_provider_test.go b/terraform/node_provider_test.go index 04d566f5c..6a20049cd 100644 --- a/terraform/node_provider_test.go +++ b/terraform/node_provider_test.go @@ -196,6 +196,40 @@ func TestNodeApplyableProviderExecute_sensitiveValidate(t *testing.T) { } } +func TestNodeApplyableProviderExecute_emptyValidate(t *testing.T) { + config := &configs.Provider{ + Name: "foo", + Config: configs.SynthBody("", map[string]cty.Value{}), + } + provider := mockProviderWithConfigSchema(&configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "test_string": { + Type: cty.String, + Required: true, + }, + }, + }) + providerAddr := addrs.AbsProviderConfig{ + Module: addrs.RootModule, + Provider: addrs.NewDefaultProvider("foo"), + } + + n := &NodeApplyableProvider{&NodeAbstractProvider{ + Addr: providerAddr, + Config: config, + }} + + ctx := &MockEvalContext{ProviderProvider: provider} + ctx.installSimpleEval() + if err := n.Execute(ctx, walkValidate); err != nil { + t.Fatalf("err: %s", err) + } + + if ctx.ConfigureProviderCalled { + t.Fatal("should not be called") + } +} + func TestNodeApplyableProvider_Validate(t *testing.T) { provider := &MockProvider{ GetSchemaReturn: &ProviderSchema{ @@ -233,7 +267,28 @@ func TestNodeApplyableProvider_Validate(t *testing.T) { } }) - t.Run("missing required config", func(t *testing.T) { + t.Run("invalid", func(t *testing.T) { + config := &configs.Provider{ + Name: "test", + Config: configs.SynthBody("", map[string]cty.Value{ + "region": cty.MapValEmpty(cty.String), + }), + } + + node := NodeApplyableProvider{ + NodeAbstractProvider: &NodeAbstractProvider{ + Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), + Config: config, + }, + } + + diags := node.ValidateProvider(ctx, provider) + if !diags.HasErrors() { + t.Error("missing expected error with invalid config") + } + }) + + t.Run("empty config", func(t *testing.T) { node := NodeApplyableProvider{ NodeAbstractProvider: &NodeAbstractProvider{ Addr: mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`), @@ -241,8 +296,8 @@ func TestNodeApplyableProvider_Validate(t *testing.T) { } diags := node.ValidateProvider(ctx, provider) - if !diags.HasErrors() { - t.Error("missing expected error with invalid config") + if diags.HasErrors() { + t.Errorf("unexpected error with empty config: %s", diags.Err()) } }) } diff --git a/terraform/testdata/validate-bad-pc-empty/main.tf b/terraform/testdata/validate-skipped-pc-empty/main.tf similarity index 100% rename from terraform/testdata/validate-bad-pc-empty/main.tf rename to terraform/testdata/validate-skipped-pc-empty/main.tf