diff --git a/terraform/context_components_test.go b/terraform/context_components_test.go index 28feebdc3..0cad31643 100644 --- a/terraform/context_components_test.go +++ b/terraform/context_components_test.go @@ -62,19 +62,19 @@ func simpleTestSchema() *configschema.Block { Optional: true, }, "test_number": { - Type: cty.String, + Type: cty.Number, Optional: true, }, "test_bool": { - Type: cty.String, + Type: cty.Bool, Optional: true, }, "test_list": { - Type: cty.String, + Type: cty.List(cty.String), Optional: true, }, "test_map": { - Type: cty.String, + Type: cty.Map(cty.String), Optional: true, }, }, diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index 087de1a2e..469966805 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -1131,6 +1131,71 @@ func TestContext2Validate_interpolateMap(t *testing.T) { } } +func TestContext2Validate_varSensitive(t *testing.T) { + // Smoke test through validate where a variable has sensitive applied + m := testModuleInline(t, map[string]string{ + "main.tf": ` +variable "foo" { + default = "xyz" + sensitive = true +} + +variable "bar" { + sensitive = true +} + +data "aws_data_source" "bar" { + foo = var.bar +} + +resource "aws_instance" "foo" { + foo = var.foo +} +`, + }) + + p := testProvider("aws") + p.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { + // Providers receive unmarked values + if got, want := req.Config.GetAttr("foo"), cty.UnknownVal(cty.String); !got.RawEquals(want) { + t.Fatalf("wrong value for foo\ngot: %#v\nwant: %#v", got, want) + } + return providers.ValidateResourceTypeConfigResponse{} + } + p.ValidateDataSourceConfigFn = func(req providers.ValidateDataSourceConfigRequest) (resp providers.ValidateDataSourceConfigResponse) { + if got, want := req.Config.GetAttr("foo"), cty.UnknownVal(cty.String); !got.RawEquals(want) { + t.Fatalf("wrong value for foo\ngot: %#v\nwant: %#v", got, want) + } + return providers.ValidateDataSourceConfigResponse{} + } + + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), + }, + Variables: InputValues{ + "bar": &InputValue{ + Value: cty.StringVal("boop"), + SourceType: ValueFromCaller, + }, + }, + }) + + diags := ctx.Validate() + if diags.HasErrors() { + t.Fatal(diags.Err()) + } + + if !p.ValidateResourceTypeConfigCalled { + t.Fatal("expected ValidateResourceTypeConfigFn to be called") + } + + if !p.ValidateDataSourceConfigCalled { + t.Fatal("expected ValidateDataSourceConfigFn to be called") + } +} + // Manually validate using the new PlanGraphBuilder func TestContext2Validate_PlanGraphBuilder(t *testing.T) { fixture := contextFixtureApplyVars(t) diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go index 8f1fd7e08..38ea7086f 100644 --- a/terraform/eval_validate.go +++ b/terraform/eval_validate.go @@ -374,9 +374,11 @@ func (n *EvalValidateResource) Validate(ctx EvalContext) tfdiags.Diagnostics { } } + // Use unmarked value for validate request + unmarkedConfigVal, _ := configVal.UnmarkDeep() req := providers.ValidateResourceTypeConfigRequest{ TypeName: cfg.Type, - Config: configVal, + Config: unmarkedConfigVal, } resp := provider.ValidateResourceTypeConfig(req) @@ -404,9 +406,11 @@ func (n *EvalValidateResource) Validate(ctx EvalContext) tfdiags.Diagnostics { return diags } + // Use unmarked value for validate request + unmarkedConfigVal, _ := configVal.UnmarkDeep() req := providers.ValidateDataSourceConfigRequest{ TypeName: cfg.Type, - Config: configVal, + Config: unmarkedConfigVal, } resp := provider.ValidateDataSourceConfig(req) diff --git a/terraform/eval_validate_test.go b/terraform/eval_validate_test.go index 5ff84fee1..4d23704e6 100644 --- a/terraform/eval_validate_test.go +++ b/terraform/eval_validate_test.go @@ -26,6 +26,9 @@ func TestEvalValidateResource_managedResource(t *testing.T) { if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) } + if got, want := req.Config.GetAttr("test_number"), cty.NumberIntVal(2); !got.RawEquals(want) { + t.Fatalf("wrong value for test_number\ngot: %#v\nwant: %#v", got, want) + } return providers.ValidateResourceTypeConfigResponse{} } @@ -36,6 +39,7 @@ func TestEvalValidateResource_managedResource(t *testing.T) { Name: "foo", Config: configs.SynthBody("", map[string]cty.Value{ "test_string": cty.StringVal("bar"), + "test_number": cty.NumberIntVal(2).Mark("sensitive"), }), } node := &EvalValidateResource{ @@ -117,6 +121,9 @@ func TestEvalValidateResource_dataSource(t *testing.T) { if got, want := req.Config.GetAttr("test_string"), cty.StringVal("bar"); !got.RawEquals(want) { t.Fatalf("wrong value for test_string\ngot: %#v\nwant: %#v", got, want) } + if got, want := req.Config.GetAttr("test_number"), cty.NumberIntVal(2); !got.RawEquals(want) { + t.Fatalf("wrong value for test_number\ngot: %#v\nwant: %#v", got, want) + } return providers.ValidateDataSourceConfigResponse{} } @@ -127,6 +134,7 @@ func TestEvalValidateResource_dataSource(t *testing.T) { Name: "foo", Config: configs.SynthBody("", map[string]cty.Value{ "test_string": cty.StringVal("bar"), + "test_number": cty.NumberIntVal(2).Mark("sensitive"), }), } diff --git a/terraform/provider_mock.go b/terraform/provider_mock.go index 2a6f6dbf0..53c46d994 100644 --- a/terraform/provider_mock.go +++ b/terraform/provider_mock.go @@ -6,6 +6,7 @@ import ( "github.com/zclconf/go-cty/cty" ctyjson "github.com/zclconf/go-cty/cty/json" + "github.com/zclconf/go-cty/cty/msgpack" "github.com/hashicorp/terraform/configs/hcl2shim" "github.com/hashicorp/terraform/providers" @@ -124,6 +125,24 @@ func (p *MockProvider) getSchema() providers.GetSchemaResponse { return ret } +func (p *MockProvider) getResourceSchema(name string) providers.Schema { + schema := p.getSchema() + resSchema, ok := schema.ResourceTypes[name] + if !ok { + panic("unknown resource type " + name) + } + return resSchema +} + +func (p *MockProvider) getDatasourceSchema(name string) providers.Schema { + schema := p.getSchema() + dataSchema, ok := schema.DataSources[name] + if !ok { + panic("unknown data source " + name) + } + return dataSchema +} + func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse { p.Lock() defer p.Unlock() @@ -137,13 +156,22 @@ func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRe return p.PrepareProviderConfigResponse } -func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { +func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) { p.Lock() defer p.Unlock() p.ValidateResourceTypeConfigCalled = true p.ValidateResourceTypeConfigRequest = r + // Marshall the value to replicate behavior by the GRPC protocol, + // and return any relevant errors + resourceSchema := p.getResourceSchema(r.TypeName) + _, err := msgpack.Marshal(r.Config, resourceSchema.Block.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + if p.ValidateResourceTypeConfigFn != nil { return p.ValidateResourceTypeConfigFn(r) } @@ -151,13 +179,21 @@ func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTy return p.ValidateResourceTypeConfigResponse } -func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse { +func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) (resp providers.ValidateDataSourceConfigResponse) { p.Lock() defer p.Unlock() p.ValidateDataSourceConfigCalled = true p.ValidateDataSourceConfigRequest = r + // Marshall the value to replicate behavior by the GRPC protocol + dataSchema := p.getDatasourceSchema(r.TypeName) + _, err := msgpack.Marshal(r.Config, dataSchema.Block.ImpliedType()) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + if p.ValidateDataSourceConfigFn != nil { return p.ValidateDataSourceConfigFn(r) }