diff --git a/internal/backend/local/backend_apply.go b/internal/backend/local/backend_apply.go index 2aabfab8904d..c1572fc00242 100644 --- a/internal/backend/local/backend_apply.go +++ b/internal/backend/local/backend_apply.go @@ -99,13 +99,7 @@ func (b *Local) opApply( // plan.Errored will be true in this case, which our plan // renderer can rely on to tailor its messaging. if plan != nil && (len(plan.Changes.Resources) != 0 || len(plan.Changes.Outputs) != 0) { - schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState) - // If schema loading returns errors then we'll just give up and - // ignore them to avoid distracting from the plan-time errors we're - // mainly trying to report here. - if !moreDiags.HasErrors() { - op.View.Plan(plan, schemas) - } + op.View.Plan(plan, schemas) } op.ReportResult(runningOp, diags) return diff --git a/internal/backend/local/backend_apply_test.go b/internal/backend/local/backend_apply_test.go index 5ae4fd4d4fb6..594002e60667 100644 --- a/internal/backend/local/backend_apply_test.go +++ b/internal/backend/local/backend_apply_test.go @@ -27,7 +27,6 @@ import ( "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/terraform/internal/terminal" - "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -123,7 +122,7 @@ func TestLocal_applyCheck(t *testing.T) { func TestLocal_applyEmptyDir(t *testing.T) { b := TestLocal(t) - p := TestLocalProvider(t, b, "test", &terraform.ProviderSchema{}) + p := TestLocalProvider(t, b, "test", providers.ProviderSchema{}) p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{NewState: cty.ObjectVal(map[string]cty.Value{"id": cty.StringVal("yes")})} op, configCleanup, done := testOperationApply(t, "./testdata/empty") @@ -157,7 +156,7 @@ func TestLocal_applyEmptyDir(t *testing.T) { func TestLocal_applyEmptyDirDestroy(t *testing.T) { b := TestLocal(t) - p := TestLocalProvider(t, b, "test", &terraform.ProviderSchema{}) + p := TestLocalProvider(t, b, "test", providers.ProviderSchema{}) p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{} op, configCleanup, done := testOperationApply(t, "./testdata/empty") @@ -187,12 +186,14 @@ func TestLocal_applyEmptyDirDestroy(t *testing.T) { func TestLocal_applyError(t *testing.T) { b := TestLocal(t) - schema := &terraform.ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + schema := providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "ami": {Type: cty.String, Optional: true}, - "id": {Type: cty.String, Computed: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "ami": {Type: cty.String, Optional: true}, + "id": {Type: cty.String, Computed: true}, + }, }, }, }, @@ -386,13 +387,15 @@ func testOperationApply(t *testing.T, configDir string) (*backend.Operation, fun // applyFixtureSchema returns a schema suitable for processing the // configuration in testdata/apply . This schema should be // assigned to a mock provider named "test". -func applyFixtureSchema() *terraform.ProviderSchema { - return &terraform.ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ +func applyFixtureSchema() providers.ProviderSchema { + return providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "ami": {Type: cty.String, Optional: true}, - "id": {Type: cty.String, Computed: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "ami": {Type: cty.String, Optional: true}, + "id": {Type: cty.String, Computed: true}, + }, }, }, }, diff --git a/internal/backend/local/backend_plan_test.go b/internal/backend/local/backend_plan_test.go index ee213df6d4f7..de2f3cc51615 100644 --- a/internal/backend/local/backend_plan_test.go +++ b/internal/backend/local/backend_plan_test.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/plans/planfile" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/internal/terraform" @@ -88,7 +89,7 @@ func TestLocal_planInAutomation(t *testing.T) { func TestLocal_planNoConfig(t *testing.T) { b := TestLocal(t) - TestLocalProvider(t, b, "test", &terraform.ProviderSchema{}) + TestLocalProvider(t, b, "test", providers.ProviderSchema{}) op, configCleanup, done := testOperationPlan(t, "./testdata/empty") defer configCleanup() @@ -854,30 +855,34 @@ func testReadPlan(t *testing.T, path string) *plans.Plan { // planFixtureSchema returns a schema suitable for processing the // configuration in testdata/plan . This schema should be // assigned to a mock provider named "test". -func planFixtureSchema() *terraform.ProviderSchema { - return &terraform.ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ +func planFixtureSchema() providers.ProviderSchema { + return providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "ami": {Type: cty.String, Optional: true}, - }, - BlockTypes: map[string]*configschema.NestedBlock{ - "network_interface": { - Nesting: configschema.NestingList, - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "device_index": {Type: cty.Number, Optional: true}, - "description": {Type: cty.String, Optional: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "ami": {Type: cty.String, Optional: true}, + }, + BlockTypes: map[string]*configschema.NestedBlock{ + "network_interface": { + Nesting: configschema.NestingList, + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "device_index": {Type: cty.Number, Optional: true}, + "description": {Type: cty.String, Optional: true}, + }, }, }, }, }, }, }, - DataSources: map[string]*configschema.Block{ + DataSources: map[string]providers.Schema{ "test_ds": { - Attributes: map[string]*configschema.Attribute{ - "filter": {Type: cty.String, Required: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "filter": {Type: cty.String, Required: true}, + }, }, }, }, diff --git a/internal/backend/local/backend_refresh_test.go b/internal/backend/local/backend_refresh_test.go index eaf7e7d760cc..1572d1481ed8 100644 --- a/internal/backend/local/backend_refresh_test.go +++ b/internal/backend/local/backend_refresh_test.go @@ -63,18 +63,22 @@ test_instance.foo: func TestLocal_refreshInput(t *testing.T) { b := TestLocal(t) - schema := &terraform.ProviderSchema{ - Provider: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "value": {Type: cty.String, Optional: true}, + schema := providers.ProviderSchema{ + Provider: providers.Schema{ + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": {Type: cty.String, Optional: true}, + }, }, }, - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Computed: true}, - "foo": {Type: cty.String, Optional: true}, - "ami": {Type: cty.String, Optional: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Computed: true}, + "foo": {Type: cty.String, Optional: true}, + "ami": {Type: cty.String, Optional: true}, + }, }, }, }, @@ -154,17 +158,21 @@ test_instance.foo: func TestLocal_refreshValidateProviderConfigured(t *testing.T) { b := TestLocal(t) - schema := &terraform.ProviderSchema{ - Provider: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "value": {Type: cty.String, Optional: true}, + schema := providers.ProviderSchema{ + Provider: providers.Schema{ + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": {Type: cty.String, Optional: true}, + }, }, }, - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Computed: true}, - "ami": {Type: cty.String, Optional: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + }, }, }, }, @@ -297,13 +305,15 @@ func testRefreshState() *states.State { // refreshFixtureSchema returns a schema suitable for processing the // configuration in testdata/refresh . This schema should be // assigned to a mock provider named "test". -func refreshFixtureSchema() *terraform.ProviderSchema { - return &terraform.ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ +func refreshFixtureSchema() providers.ProviderSchema { + return providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "ami": {Type: cty.String, Optional: true}, - "id": {Type: cty.String, Computed: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "ami": {Type: cty.String, Optional: true}, + "id": {Type: cty.String, Computed: true}, + }, }, }, }, diff --git a/internal/backend/local/testing.go b/internal/backend/local/testing.go index 1900a8f8f8a9..1c4e426a5d07 100644 --- a/internal/backend/local/testing.go +++ b/internal/backend/local/testing.go @@ -42,28 +42,11 @@ func TestLocal(t *testing.T) *Local { // TestLocalProvider modifies the ContextOpts of the *Local parameter to // have a provider with the given name. -func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.ProviderSchema) *terraform.MockProvider { +func TestLocalProvider(t *testing.T, b *Local, name string, schema providers.ProviderSchema) *terraform.MockProvider { // Build a mock resource provider for in-memory operations p := new(terraform.MockProvider) - if schema == nil { - schema = &terraform.ProviderSchema{} // default schema is empty - } - p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{Block: schema.Provider}, - ProviderMeta: providers.Schema{Block: schema.ProviderMeta}, - ResourceTypes: map[string]providers.Schema{}, - DataSources: map[string]providers.Schema{}, - } - for name, res := range schema.ResourceTypes { - p.GetProviderSchemaResponse.ResourceTypes[name] = providers.Schema{ - Block: res, - Version: int64(schema.ResourceTypeSchemaVersions[name]), - } - } - for name, dat := range schema.DataSources { - p.GetProviderSchemaResponse.DataSources[name] = providers.Schema{Block: dat} - } + p.GetProviderSchemaResponse = &schema p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { // this is a destroy plan, diff --git a/internal/backend/remote/testing.go b/internal/backend/remote/testing.go index 0721087099fc..5f7dba18b85a 100644 --- a/internal/backend/remote/testing.go +++ b/internal/backend/remote/testing.go @@ -182,11 +182,13 @@ func testLocalBackend(t *testing.T, remote *Remote) backend.Enhanced { b := backendLocal.NewWithBackend(remote) // Add a test provider to the local backend. - p := backendLocal.TestLocalProvider(t, b, "null", &terraform.ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + p := backendLocal.TestLocalProvider(t, b, "null", providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "null_resource": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Computed: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Computed: true}, + }, }, }, }, diff --git a/internal/cloud/testing.go b/internal/cloud/testing.go index 119f57d50916..81def5c22394 100644 --- a/internal/cloud/testing.go +++ b/internal/cloud/testing.go @@ -345,11 +345,13 @@ func testLocalBackend(t *testing.T, cloud *Cloud) backend.Enhanced { b := backendLocal.NewWithBackend(cloud) // Add a test provider to the local backend. - p := backendLocal.TestLocalProvider(t, b, "null", &terraform.ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + p := backendLocal.TestLocalProvider(t, b, "null", providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "null_resource": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Computed: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Computed: true}, + }, }, }, }, diff --git a/internal/command/jsonformat/plan_test.go b/internal/command/jsonformat/plan_test.go index 061fad67ab0f..81bdf1fc6b9f 100644 --- a/internal/command/jsonformat/plan_test.go +++ b/internal/command/jsonformat/plan_test.go @@ -6987,13 +6987,17 @@ func runTestCases(t *testing.T, testCases map[string]testCase) { } tfschemas := &terraform.Schemas{ - Providers: map[addrs.Provider]*providers.Schemas{ + Providers: map[addrs.Provider]providers.ProviderSchema{ src.ProviderAddr.Provider: { - ResourceTypes: map[string]*configschema.Block{ - src.Addr.Resource.Resource.Type: tc.Schema, + ResourceTypes: map[string]providers.Schema{ + src.Addr.Resource.Resource.Type: { + Block: tc.Schema, + }, }, - DataSources: map[string]*configschema.Block{ - src.Addr.Resource.Resource.Type: tc.Schema, + DataSources: map[string]providers.Schema{ + src.Addr.Resource.Resource.Type: { + Block: tc.Schema, + }, }, }, }, diff --git a/internal/command/jsonformat/state_test.go b/internal/command/jsonformat/state_test.go index 716650141daf..64f9973baa0d 100644 --- a/internal/command/jsonformat/state_test.go +++ b/internal/command/jsonformat/state_test.go @@ -156,8 +156,8 @@ func testProviderSchema() *providers.GetProviderSchemaResponse { func testSchemas() *terraform.Schemas { provider := testProvider() return &terraform.Schemas{ - Providers: map[addrs.Provider]*terraform.ProviderSchema{ - addrs.NewDefaultProvider("test"): provider.ProviderSchema(), + Providers: map[addrs.Provider]providers.ProviderSchema{ + addrs.NewDefaultProvider("test"): provider.GetProviderSchema(), }, } } diff --git a/internal/command/jsonplan/values_test.go b/internal/command/jsonplan/values_test.go index 2ac7ec5a5f1c..bb84b5cbc4ba 100644 --- a/internal/command/jsonplan/values_test.go +++ b/internal/command/jsonplan/values_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/plans" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/terraform" "github.com/zclconf/go-cty/cty" ) @@ -344,19 +345,19 @@ func TestMarshalPlanValuesNoopDeposed(t *testing.T) { func testSchemas() *terraform.Schemas { return &terraform.Schemas{ - Providers: map[addrs.Provider]*terraform.ProviderSchema{ - addrs.NewDefaultProvider("test"): &terraform.ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + Providers: map[addrs.Provider]providers.ProviderSchema{ + addrs.NewDefaultProvider("test"): providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "test_thing": { - Attributes: map[string]*configschema.Attribute{ - "woozles": {Type: cty.String, Optional: true, Computed: true}, - "foozles": {Type: cty.String, Optional: true}, + Version: 1, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "woozles": {Type: cty.String, Optional: true, Computed: true}, + "foozles": {Type: cty.String, Optional: true}, + }, }, }, }, - ResourceTypeSchemaVersions: map[string]uint64{ - "test_thing": 1, - }, }, }, } diff --git a/internal/command/jsonprovider/provider.go b/internal/command/jsonprovider/provider.go index 17cd841d949c..5ff01cc9658f 100644 --- a/internal/command/jsonprovider/provider.go +++ b/internal/command/jsonprovider/provider.go @@ -6,6 +6,7 @@ package jsonprovider import ( "encoding/json" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/terraform" ) @@ -14,8 +15,8 @@ import ( // consuming parser. const FormatVersion = "1.0" -// providers is the top-level object returned when exporting provider schemas -type providers struct { +// Providers is the top-level object returned when exporting provider schemas +type Providers struct { FormatVersion string `json:"format_version"` Schemas map[string]*Provider `json:"provider_schemas,omitempty"` } @@ -26,9 +27,9 @@ type Provider struct { DataSourceSchemas map[string]*Schema `json:"data_source_schemas,omitempty"` } -func newProviders() *providers { +func newProviders() *Providers { schemas := make(map[string]*Provider) - return &providers{ + return &Providers{ FormatVersion: FormatVersion, Schemas: schemas, } @@ -53,29 +54,10 @@ func Marshal(s *terraform.Schemas) ([]byte, error) { return ret, err } -func marshalProvider(tps *terraform.ProviderSchema) *Provider { - if tps == nil { - return &Provider{} - } - - var ps *Schema - var rs, ds map[string]*Schema - - if tps.Provider != nil { - ps = marshalSchema(tps.Provider) - } - - if tps.ResourceTypes != nil { - rs = marshalSchemas(tps.ResourceTypes, tps.ResourceTypeSchemaVersions) - } - - if tps.DataSources != nil { - ds = marshalSchemas(tps.DataSources, tps.ResourceTypeSchemaVersions) - } - +func marshalProvider(tps providers.ProviderSchema) *Provider { return &Provider{ - Provider: ps, - ResourceSchemas: rs, - DataSourceSchemas: ds, + Provider: marshalSchema(tps.Provider), + ResourceSchemas: marshalSchemas(tps.ResourceTypes), + DataSourceSchemas: marshalSchemas(tps.DataSources), } } diff --git a/internal/command/jsonprovider/provider_test.go b/internal/command/jsonprovider/provider_test.go index 9fa8a07cfb25..bab2c3921bef 100644 --- a/internal/command/jsonprovider/provider_test.go +++ b/internal/command/jsonprovider/provider_test.go @@ -5,23 +5,28 @@ package jsonprovider import ( "encoding/json" + "fmt" "testing" "github.com/google/go-cmp/cmp" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/configs/configschema" - "github.com/hashicorp/terraform/internal/terraform" + "github.com/hashicorp/terraform/internal/providers" ) func TestMarshalProvider(t *testing.T) { tests := []struct { - Input *terraform.ProviderSchema + Input providers.ProviderSchema Want *Provider }{ { - nil, - &Provider{}, + providers.ProviderSchema{}, + &Provider{ + Provider: &Schema{}, + ResourceSchemas: map[string]*Schema{}, + DataSourceSchemas: map[string]*Schema{}, + }, }, { testProvider(), @@ -143,73 +148,78 @@ func TestMarshalProvider(t *testing.T) { }, } - for _, test := range tests { - got := marshalProvider(test.Input) - if !cmp.Equal(got, test.Want) { - t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, test.Want)) - } + for i, test := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + got := marshalProvider(test.Input) + if !cmp.Equal(got, test.Want) { + t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, test.Want)) + } + }) } } -func testProvider() *terraform.ProviderSchema { - return &terraform.ProviderSchema{ - Provider: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Required: true}, +func testProvider() providers.ProviderSchema { + return providers.ProviderSchema{ + Provider: providers.Schema{ + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Required: true}, + }, }, }, - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Optional: true, Computed: true}, - "ami": {Type: cty.String, Optional: true}, - "volumes": { - Optional: true, - NestedType: &configschema.Object{ - Nesting: configschema.NestingList, - Attributes: map[string]*configschema.Attribute{ - "size": {Type: cty.String, Required: true}, - "mount_point": {Type: cty.String, Required: true}, + Version: 42, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + "volumes": { + Optional: true, + NestedType: &configschema.Object{ + Nesting: configschema.NestingList, + Attributes: map[string]*configschema.Attribute{ + "size": {Type: cty.String, Required: true}, + "mount_point": {Type: cty.String, Required: true}, + }, }, }, }, - }, - BlockTypes: map[string]*configschema.NestedBlock{ - "network_interface": { - Nesting: configschema.NestingList, - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "device_index": {Type: cty.String, Optional: true}, - "description": {Type: cty.String, Optional: true}, + BlockTypes: map[string]*configschema.NestedBlock{ + "network_interface": { + Nesting: configschema.NestingList, + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "device_index": {Type: cty.String, Optional: true}, + "description": {Type: cty.String, Optional: true}, + }, }, }, }, }, }, }, - DataSources: map[string]*configschema.Block{ + DataSources: map[string]providers.Schema{ "test_data_source": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Optional: true, Computed: true}, - "ami": {Type: cty.String, Optional: true}, - }, - BlockTypes: map[string]*configschema.NestedBlock{ - "network_interface": { - Nesting: configschema.NestingList, - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "device_index": {Type: cty.String, Optional: true}, - "description": {Type: cty.String, Optional: true}, + Version: 3, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + }, + BlockTypes: map[string]*configschema.NestedBlock{ + "network_interface": { + Nesting: configschema.NestingList, + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "device_index": {Type: cty.String, Optional: true}, + "description": {Type: cty.String, Optional: true}, + }, }, }, }, }, }, }, - - ResourceTypeSchemaVersions: map[string]uint64{ - "test_instance": 42, - "test_data_source": 3, - }, } } diff --git a/internal/command/jsonprovider/schema.go b/internal/command/jsonprovider/schema.go index b5c5d53c81ca..af3e07bcdef8 100644 --- a/internal/command/jsonprovider/schema.go +++ b/internal/command/jsonprovider/schema.go @@ -4,7 +4,7 @@ package jsonprovider import ( - "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/providers" ) type Schema struct { @@ -14,28 +14,25 @@ type Schema struct { // marshalSchema is a convenience wrapper around mashalBlock. Schema version // should be set by the caller. -func marshalSchema(block *configschema.Block) *Schema { - if block == nil { +func marshalSchema(schema providers.Schema) *Schema { + if schema.Block == nil { return &Schema{} } var ret Schema - ret.Block = marshalBlock(block) + ret.Block = marshalBlock(schema.Block) + ret.Version = uint64(schema.Version) return &ret } -func marshalSchemas(blocks map[string]*configschema.Block, rVersions map[string]uint64) map[string]*Schema { - if blocks == nil { +func marshalSchemas(schemas map[string]providers.Schema) map[string]*Schema { + if schemas == nil { return map[string]*Schema{} } - ret := make(map[string]*Schema, len(blocks)) - for k, v := range blocks { + ret := make(map[string]*Schema, len(schemas)) + for k, v := range schemas { ret[k] = marshalSchema(v) - version, ok := rVersions[k] - if ok { - ret[k].Version = version - } } return ret } diff --git a/internal/command/jsonprovider/schema_test.go b/internal/command/jsonprovider/schema_test.go index 3797b612b551..de577f5e92c4 100644 --- a/internal/command/jsonprovider/schema_test.go +++ b/internal/command/jsonprovider/schema_test.go @@ -8,24 +8,22 @@ import ( "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/providers" ) func TestMarshalSchemas(t *testing.T) { tests := []struct { - Input map[string]*configschema.Block - Versions map[string]uint64 - Want map[string]*Schema + Input map[string]providers.Schema + Want map[string]*Schema }{ { nil, - map[string]uint64{}, map[string]*Schema{}, }, } for _, test := range tests { - got := marshalSchemas(test.Input, test.Versions) + got := marshalSchemas(test.Input) if !cmp.Equal(got, test.Want) { t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, test.Want)) } @@ -34,11 +32,11 @@ func TestMarshalSchemas(t *testing.T) { func TestMarshalSchema(t *testing.T) { tests := map[string]struct { - Input *configschema.Block + Input providers.Schema Want *Schema }{ "nil_block": { - nil, + providers.Schema{}, &Schema{}, }, } diff --git a/internal/command/jsonstate/state_test.go b/internal/command/jsonstate/state_test.go index 27544de6789e..72bb17232add 100644 --- a/internal/command/jsonstate/state_test.go +++ b/internal/command/jsonstate/state_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang/marks" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/terraform" ) @@ -807,25 +808,31 @@ func TestMarshalModules_parent_no_resources(t *testing.T) { func testSchemas() *terraform.Schemas { return &terraform.Schemas{ - Providers: map[addrs.Provider]*terraform.ProviderSchema{ + Providers: map[addrs.Provider]providers.ProviderSchema{ addrs.NewDefaultProvider("test"): { - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "test_thing": { - Attributes: map[string]*configschema.Attribute{ - "woozles": {Type: cty.String, Optional: true, Computed: true}, - "foozles": {Type: cty.String, Optional: true, Sensitive: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "woozles": {Type: cty.String, Optional: true, Computed: true}, + "foozles": {Type: cty.String, Optional: true, Sensitive: true}, + }, }, }, "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Optional: true, Computed: true}, - "foo": {Type: cty.String, Optional: true}, - "bar": {Type: cty.String, Optional: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "foo": {Type: cty.String, Optional: true}, + "bar": {Type: cty.String, Optional: true}, + }, }, }, "test_map_attr": { - Attributes: map[string]*configschema.Attribute{ - "data": {Type: cty.Map(cty.String), Optional: true, Computed: true, Sensitive: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "data": {Type: cty.Map(cty.String), Optional: true, Computed: true, Sensitive: true}, + }, }, }, }, diff --git a/internal/command/meta_providers.go b/internal/command/meta_providers.go index ea8ecc66d234..ba1aca8ec257 100644 --- a/internal/command/meta_providers.go +++ b/internal/command/meta_providers.go @@ -382,10 +382,12 @@ func providerFactory(meta *providercache.CachedProvider) providers.Factory { case 5: p := raw.(*tfplugin.GRPCProvider) p.PluginClient = client + p.Addr = meta.Provider return p, nil case 6: p := raw.(*tfplugin6.GRPCProvider) p.PluginClient = client + p.Addr = meta.Provider return p, nil default: panic("unsupported protocol version") diff --git a/internal/command/views/plan_test.go b/internal/command/views/plan_test.go index 0011f6b54f45..3cb64344104f 100644 --- a/internal/command/views/plan_test.go +++ b/internal/command/views/plan_test.go @@ -133,8 +133,8 @@ func testPlanWithDatasource(t *testing.T) *plans.Plan { func testSchemas() *terraform.Schemas { provider := testProvider() return &terraform.Schemas{ - Providers: map[addrs.Provider]*terraform.ProviderSchema{ - addrs.NewDefaultProvider("test"): provider.ProviderSchema(), + Providers: map[addrs.Provider]providers.ProviderSchema{ + addrs.NewDefaultProvider("test"): provider.GetProviderSchema(), }, } } diff --git a/internal/command/views/show_test.go b/internal/command/views/show_test.go index 2101b8252c5d..cb29c5e78d68 100644 --- a/internal/command/views/show_test.go +++ b/internal/command/views/show_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/plans" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/terminal" @@ -130,13 +131,15 @@ func TestShowJSON(t *testing.T) { v := NewShow(arguments.ViewJSON, view) schemas := &terraform.Schemas{ - Providers: map[addrs.Provider]*terraform.ProviderSchema{ + Providers: map[addrs.Provider]providers.ProviderSchema{ addrs.NewDefaultProvider("test"): { - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "test_resource": { - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Optional: true, Computed: true}, - "foo": {Type: cty.String, Optional: true}, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "foo": {Type: cty.String, Optional: true}, + }, }, }, }, diff --git a/internal/lang/globalref/analyzer.go b/internal/lang/globalref/analyzer.go index fa78de8074fd..eb4b58624936 100644 --- a/internal/lang/globalref/analyzer.go +++ b/internal/lang/globalref/analyzer.go @@ -37,7 +37,7 @@ import ( // the Analyzer contains caches derived from data in the configuration tree. type Analyzer struct { cfg *configs.Config - providerSchemas map[addrs.Provider]*providers.Schemas + providerSchemas map[addrs.Provider]providers.ProviderSchema } // NewAnalyzer constructs a new analyzer bound to the given configuration and @@ -48,7 +48,7 @@ type Analyzer struct { // The given provider schemas must cover at least all of the providers used // in the given configuration. If not then analysis results will be silently // incomplete for any decision that requires checking schema. -func NewAnalyzer(cfg *configs.Config, providerSchemas map[addrs.Provider]*providers.Schemas) *Analyzer { +func NewAnalyzer(cfg *configs.Config, providerSchemas map[addrs.Provider]providers.ProviderSchema) *Analyzer { if !cfg.Path.IsRoot() { panic(fmt.Sprintf("constructing an Analyzer with non-root module %s", cfg.Path)) } diff --git a/internal/lang/globalref/analyzer_meta_references.go b/internal/lang/globalref/analyzer_meta_references.go index 4d450eb00911..e69df8067e07 100644 --- a/internal/lang/globalref/analyzer_meta_references.go +++ b/internal/lang/globalref/analyzer_meta_references.go @@ -200,8 +200,8 @@ func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstanc // available. In invalid cases we might be dealing with partial information, // and so the schema might be nil so we won't be able to return reference // information for this particular situation. - providerSchema := a.providerSchemas[rc.Provider] - if providerSchema == nil { + providerSchema, ok := a.providerSchemas[rc.Provider] + if !ok { return nil } diff --git a/internal/lang/globalref/analyzer_test.go b/internal/lang/globalref/analyzer_test.go index 46235d818b06..7dfe6537601c 100644 --- a/internal/lang/globalref/analyzer_test.go +++ b/internal/lang/globalref/analyzer_test.go @@ -86,13 +86,17 @@ func testAnalyzer(t *testing.T, fixtureName string) *Analyzer { }, }, } - schemas := map[addrs.Provider]*providers.Schemas{ + schemas := map[addrs.Provider]providers.ProviderSchema{ addrs.MustParseProviderSourceString("hashicorp/test"): { - ResourceTypes: map[string]*configschema.Block{ - "test_thing": resourceTypeSchema, + ResourceTypes: map[string]providers.Schema{ + "test_thing": { + Block: resourceTypeSchema, + }, }, - DataSources: map[string]*configschema.Block{ - "test_thing": resourceTypeSchema, + DataSources: map[string]providers.Schema{ + "test_thing": { + Block: resourceTypeSchema, + }, }, }, } diff --git a/internal/plugin/grpc_provider.go b/internal/plugin/grpc_provider.go index 11098e2b6496..c1b9a1af0752 100644 --- a/internal/plugin/grpc_provider.go +++ b/internal/plugin/grpc_provider.go @@ -12,6 +12,7 @@ import ( "github.com/zclconf/go-cty/cty" plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/logging" "github.com/hashicorp/terraform/internal/plugin/convert" "github.com/hashicorp/terraform/internal/providers" @@ -54,6 +55,11 @@ type GRPCProvider struct { // used in an end to end test of a provider. TestServer *grpc.Server + // Addr uniquely identifies the type of provider. + // Normally executed providers will have this set during initialization, + // but it may not always be available for alternative execute modes. + Addr addrs.Provider + // Proto client use to make the grpc service calls. client proto.ProviderClient @@ -67,24 +73,18 @@ type GRPCProvider struct { schemas providers.GetProviderSchemaResponse } -// getSchema is used internally to get the cached provider schema -func (p *GRPCProvider) getSchema() providers.GetProviderSchemaResponse { - p.mu.Lock() - // unlock inline in case GetSchema needs to be called - if p.schemas.Provider.Block != nil { - p.mu.Unlock() - return p.schemas - } - p.mu.Unlock() - - return p.GetProviderSchema() -} - func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) { logger.Trace("GRPCProvider: GetProviderSchema") p.mu.Lock() defer p.mu.Unlock() + // check the global cache if we can + if !p.Addr.IsZero() { + if resp, ok := providers.SchemaCache.Get(p.Addr); ok { + return resp + } + } + if p.schemas.Provider.Block != nil { return p.schemas } @@ -137,6 +137,15 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp resp.ServerCapabilities.PlanDestroy = protoResp.ServerCapabilities.PlanDestroy } + // FIXME: Waiting for a provider capability to prevent caching + // providers which always need GetProviderSchema called. + // set the global cache if we can + //if !p.Addr.IsZero() { + // providers.SchemaCache.Set(p.Addr, resp) + //} else { + // // otherwise store it in the local cache + // p.schemas = resp + //} p.schemas = resp return resp @@ -145,7 +154,7 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { logger.Trace("GRPCProvider: ValidateProviderConfig") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -183,7 +192,7 @@ func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfig func (p *GRPCProvider) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { logger.Trace("GRPCProvider: ValidateResourceConfig") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -219,7 +228,7 @@ func (p *GRPCProvider) ValidateResourceConfig(r providers.ValidateResourceConfig func (p *GRPCProvider) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) (resp providers.ValidateDataResourceConfigResponse) { logger.Trace("GRPCProvider: ValidateDataResourceConfig") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -254,7 +263,7 @@ func (p *GRPCProvider) ValidateDataResourceConfig(r providers.ValidateDataResour func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) { logger.Trace("GRPCProvider: UpgradeResourceState") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -301,7 +310,7 @@ func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequ func (p *GRPCProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { logger.Trace("GRPCProvider: ConfigureProvider") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -349,7 +358,7 @@ func (p *GRPCProvider) Stop() error { func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { logger.Trace("GRPCProvider: ReadResource") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -405,7 +414,7 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { logger.Trace("GRPCProvider: PlanResourceChange") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -491,7 +500,7 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { logger.Trace("GRPCProvider: ApplyResourceChange") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -562,7 +571,7 @@ func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) { logger.Trace("GRPCProvider: ImportResourceState") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -607,7 +616,7 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques func (p *GRPCProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { logger.Trace("GRPCProvider: ReadDataSource") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp diff --git a/internal/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index a20d6390c853..c4888d709cc3 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -12,6 +12,7 @@ import ( "github.com/zclconf/go-cty/cty" plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/logging" "github.com/hashicorp/terraform/internal/plugin6/convert" "github.com/hashicorp/terraform/internal/providers" @@ -54,6 +55,11 @@ type GRPCProvider struct { // used in an end to end test of a provider. TestServer *grpc.Server + // Addr uniquely identifies the type of provider. + // Normally executed providers will have this set during initialization, + // but it may not always be available for alternative execute modes. + Addr addrs.Provider + // Proto client use to make the grpc service calls. client proto6.ProviderClient @@ -67,31 +73,18 @@ type GRPCProvider struct { schemas providers.GetProviderSchemaResponse } -func New(client proto6.ProviderClient, ctx context.Context) GRPCProvider { - return GRPCProvider{ - client: client, - ctx: ctx, - } -} - -// getSchema is used internally to get the cached provider schema. -func (p *GRPCProvider) getSchema() providers.GetProviderSchemaResponse { - p.mu.Lock() - // unlock inline in case GetProviderSchema needs to be called - if p.schemas.Provider.Block != nil { - p.mu.Unlock() - return p.schemas - } - p.mu.Unlock() - - return p.GetProviderSchema() -} - func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) { logger.Trace("GRPCProvider.v6: GetProviderSchema") p.mu.Lock() defer p.mu.Unlock() + // check the global cache if we can + if !p.Addr.IsZero() { + if resp, ok := providers.SchemaCache.Get(p.Addr); ok { + return resp + } + } + if p.schemas.Provider.Block != nil { return p.schemas } @@ -144,6 +137,15 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp resp.ServerCapabilities.PlanDestroy = protoResp.ServerCapabilities.PlanDestroy } + // FIXME: Waiting for a provider capability to prevent caching + // providers which always need GetProviderSchema called. + // set the global cache if we can + //if !p.Addr.IsZero() { + // providers.SchemaCache.Set(p.Addr, resp) + //} else { + // // otherwise store it in the local cache + // p.schemas = resp + //} p.schemas = resp return resp @@ -152,7 +154,7 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { logger.Trace("GRPCProvider.v6: ValidateProviderConfig") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -183,7 +185,7 @@ func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfig func (p *GRPCProvider) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) { logger.Trace("GRPCProvider.v6: ValidateResourceConfig") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -219,7 +221,7 @@ func (p *GRPCProvider) ValidateResourceConfig(r providers.ValidateResourceConfig func (p *GRPCProvider) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) (resp providers.ValidateDataResourceConfigResponse) { logger.Trace("GRPCProvider.v6: ValidateDataResourceConfig") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -254,7 +256,7 @@ func (p *GRPCProvider) ValidateDataResourceConfig(r providers.ValidateDataResour func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) { logger.Trace("GRPCProvider.v6: UpgradeResourceState") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -301,7 +303,7 @@ func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequ func (p *GRPCProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) { logger.Trace("GRPCProvider.v6: ConfigureProvider") - schema := p.getSchema() + schema := p.GetProviderSchema() var mp []byte @@ -345,7 +347,7 @@ func (p *GRPCProvider) Stop() error { func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp providers.ReadResourceResponse) { logger.Trace("GRPCProvider.v6: ReadResource") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -401,7 +403,7 @@ func (p *GRPCProvider) ReadResource(r providers.ReadResourceRequest) (resp provi func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { logger.Trace("GRPCProvider.v6: PlanResourceChange") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -487,7 +489,7 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { logger.Trace("GRPCProvider.v6: ApplyResourceChange") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -558,7 +560,7 @@ func (p *GRPCProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) { logger.Trace("GRPCProvider.v6: ImportResourceState") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp @@ -603,7 +605,7 @@ func (p *GRPCProvider) ImportResourceState(r providers.ImportResourceStateReques func (p *GRPCProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) { logger.Trace("GRPCProvider.v6: ReadDataSource") - schema := p.getSchema() + schema := p.GetProviderSchema() if schema.Diagnostics.HasErrors() { resp.Diagnostics = schema.Diagnostics return resp diff --git a/internal/providers/factory.go b/internal/providers/factory.go index 8e28935f215c..fc436aff01f8 100644 --- a/internal/providers/factory.go +++ b/internal/providers/factory.go @@ -20,47 +20,3 @@ func FactoryFixed(p Interface) Factory { return p, nil } } - -// ProviderHasResource is a helper that requests schema from the given provider -// and checks if it has a resource type of the given name. -// -// This function is more expensive than it may first appear since it must -// retrieve the entire schema from the underlying provider, and so it should -// be used sparingly and especially not in tight loops. -// -// Since retrieving the provider may fail (e.g. if the provider is accessed -// over an RPC channel that has operational problems), this function will -// return false if the schema cannot be retrieved, under the assumption that -// a subsequent call to do anything with the resource type would fail -// anyway. -func ProviderHasResource(provider Interface, typeName string) bool { - resp := provider.GetProviderSchema() - if resp.Diagnostics.HasErrors() { - return false - } - - _, exists := resp.ResourceTypes[typeName] - return exists -} - -// ProviderHasDataSource is a helper that requests schema from the given -// provider and checks if it has a data source of the given name. -// -// This function is more expensive than it may first appear since it must -// retrieve the entire schema from the underlying provider, and so it should -// be used sparingly and especially not in tight loops. -// -// Since retrieving the provider may fail (e.g. if the provider is accessed -// over an RPC channel that has operational problems), this function will -// return false if the schema cannot be retrieved, under the assumption that -// a subsequent call to do anything with the data source would fail -// anyway. -func ProviderHasDataSource(provider Interface, dataSourceName string) bool { - resp := provider.GetProviderSchema() - if resp.Diagnostics.HasErrors() { - return false - } - - _, exists := resp.DataSources[dataSourceName] - return exists -} diff --git a/internal/providers/provider.go b/internal/providers/provider.go index bfa400ff5bdc..5e2cc067597e 100644 --- a/internal/providers/provider.go +++ b/internal/providers/provider.go @@ -6,6 +6,7 @@ package providers import ( "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -73,6 +74,11 @@ type Interface interface { Close() error } +// GetProviderSchemaResponse is the return type for GetProviderSchema, and +// should only be used when handling a value for that method. The handling of +// of schemas in any other context should always use ProviderSchema, so that +// the in-memory representation can be more easily changed separately from the +// RCP protocol. type GetProviderSchemaResponse struct { // Provider is the schema for the provider itself. Provider Schema @@ -93,6 +99,17 @@ type GetProviderSchemaResponse struct { ServerCapabilities ServerCapabilities } +// Schema pairs a provider or resource schema with that schema's version. +// This is used to be able to upgrade the schema in UpgradeResourceState. +// +// This describes the schema for a single object within a provider. Type +// "Schemas" (plural) instead represents the overall collection of schemas +// for everything within a particular provider. +type Schema struct { + Version int64 + Block *configschema.Block +} + // ServerCapabilities allows providers to communicate extra information // regarding supported protocol features. This is used to indicate availability // of certain forward-compatible changes which may be optional in a major diff --git a/internal/providers/schema_cache.go b/internal/providers/schema_cache.go new file mode 100644 index 000000000000..caac0504d8c6 --- /dev/null +++ b/internal/providers/schema_cache.go @@ -0,0 +1,41 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package providers + +import ( + "sync" + + "github.com/hashicorp/terraform/internal/addrs" +) + +// SchemaCache is a global cache of Schemas. +// This will be accessed by both core and the provider clients to ensure that +// large schemas are stored in a single location. +var SchemaCache = &schemaCache{ + m: make(map[addrs.Provider]ProviderSchema), +} + +// Global cache for provider schemas +// Cache the entire response to ensure we capture any new fields, like +// ServerCapabilities. This also serves to capture errors so that multiple +// concurrent calls resulting in an error can be handled in the same manner. +type schemaCache struct { + mu sync.Mutex + m map[addrs.Provider]ProviderSchema +} + +func (c *schemaCache) Set(p addrs.Provider, s ProviderSchema) { + c.mu.Lock() + defer c.mu.Unlock() + + c.m[p] = s +} + +func (c *schemaCache) Get(p addrs.Provider) (ProviderSchema, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + s, ok := c.m[p] + return s, ok +} diff --git a/internal/providers/schemas.go b/internal/providers/schemas.go index 0fc40b4ab2b8..5e7ffcb2d207 100644 --- a/internal/providers/schemas.go +++ b/internal/providers/schemas.go @@ -8,39 +8,21 @@ import ( "github.com/hashicorp/terraform/internal/configs/configschema" ) -// Schemas is an overall container for all of the schemas for all configurable -// objects defined within a particular provider. -// -// The schema for each individual configurable object is represented by nested -// instances of type Schema (singular) within this data structure. -// -// This type used to be known as terraform.ProviderSchema, but moved out here -// as part of our ongoing efforts to shrink down the "terraform" package. -// There's still a type alias at the old name, but we should prefer using -// providers.Schema in new code. However, a consequence of this transitional -// situation is that the "terraform" package still has the responsibility for -// constructing a providers.Schemas object based on responses from the provider -// API; hopefully we'll continue this refactor later so that functions in this -// package totally encapsulate the unmarshalling and include this as part of -// providers.GetProviderSchemaResponse. -type Schemas struct { - Provider *configschema.Block - ProviderMeta *configschema.Block - ResourceTypes map[string]*configschema.Block - DataSources map[string]*configschema.Block - - ResourceTypeSchemaVersions map[string]uint64 -} +// ProviderSchema is an overall container for all of the schemas for all +// configurable objects defined within a particular provider. All storage of +// provider schemas should use this type. +type ProviderSchema = GetProviderSchemaResponse // SchemaForResourceType attempts to find a schema for the given mode and type. // Returns nil if no such schema is available. -func (ss *Schemas) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) { +func (ss ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) { switch mode { case addrs.ManagedResourceMode: - return ss.ResourceTypes[typeName], ss.ResourceTypeSchemaVersions[typeName] + res := ss.ResourceTypes[typeName] + return res.Block, uint64(res.Version) case addrs.DataResourceMode: // Data resources don't have schema versions right now, since state is discarded for each refresh - return ss.DataSources[typeName], 0 + return ss.DataSources[typeName].Block, 0 default: // Shouldn't happen, because the above cases are comprehensive. return nil, 0 @@ -49,17 +31,6 @@ func (ss *Schemas) SchemaForResourceType(mode addrs.ResourceMode, typeName strin // SchemaForResourceAddr attempts to find a schema for the mode and type from // the given resource address. Returns nil if no such schema is available. -func (ss *Schemas) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) { +func (ss ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) { return ss.SchemaForResourceType(addr.Mode, addr.Type) } - -// Schema pairs a provider or resource schema with that schema's version. -// This is used to be able to upgrade the schema in UpgradeResourceState. -// -// This describes the schema for a single object within a provider. Type -// "Schemas" (plural) instead represents the overall collection of schemas -// for everything within a particular provider. -type Schema struct { - Version int64 - Block *configschema.Block -} diff --git a/internal/terraform/context.go b/internal/terraform/context.go index 35e3a69c50a8..c5f0b837b616 100644 --- a/internal/terraform/context.go +++ b/internal/terraform/context.go @@ -146,13 +146,6 @@ func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) { } func (c *Context) Schemas(config *configs.Config, state *states.State) (*Schemas, tfdiags.Diagnostics) { - // TODO: This method gets called multiple times on the same context with - // the same inputs by different parts of Terraform that all need the - // schemas, and it's typically quite expensive because it has to spin up - // plugins to gather their schemas, so it'd be good to have some caching - // here to remember plugin schemas we already loaded since the plugin - // selections can't change during the life of a *Context object. - var diags tfdiags.Diagnostics ret, err := loadSchemas(config, state, c.plugins) diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index d7afec1d4302..367b367e9244 100644 --- a/internal/terraform/context_plan2_test.go +++ b/internal/terraform/context_plan2_test.go @@ -2782,13 +2782,13 @@ resource "test_resource" "a" { }, }) - ctx := testContext2(t, &ContextOpts{ - Providers: map[addrs.Provider]providers.Factory{ - addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), - }, - }) - t.Run("conditions pass", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { m := req.ProposedNewState.AsValueMap() m["output"] = cty.StringVal("bar") @@ -2820,6 +2820,12 @@ resource "test_resource" "a" { }) t.Run("precondition fail", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + _, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ Mode: plans.NormalMode, SetVariables: InputValues{ @@ -2841,6 +2847,12 @@ resource "test_resource" "a" { }) t.Run("precondition fail refresh-only", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`), @@ -2869,6 +2881,12 @@ resource "test_resource" "a" { }) t.Run("postcondition fail", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { m := req.ProposedNewState.AsValueMap() m["output"] = cty.StringVal("") @@ -2898,6 +2916,12 @@ resource "test_resource" "a" { }) t.Run("postcondition fail refresh-only", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`), @@ -2945,6 +2969,12 @@ resource "test_resource" "a" { }) t.Run("precondition and postcondition fail refresh-only", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`), @@ -3054,13 +3084,12 @@ resource "test_resource" "a" { }, }) - ctx := testContext2(t, &ContextOpts{ - Providers: map[addrs.Provider]providers.Factory{ - addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), - }, - }) - t.Run("conditions pass", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ State: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("boop"), @@ -3106,6 +3135,11 @@ resource "test_resource" "a" { }) t.Run("precondition fail", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) _, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ Mode: plans.NormalMode, SetVariables: InputValues{ @@ -3127,6 +3161,11 @@ resource "test_resource" "a" { }) t.Run("precondition fail refresh-only", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ Mode: plans.RefreshOnlyMode, SetVariables: InputValues{ @@ -3160,6 +3199,11 @@ resource "test_resource" "a" { }) t.Run("postcondition fail", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ State: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("boop"), @@ -3187,6 +3231,11 @@ resource "test_resource" "a" { }) t.Run("postcondition fail refresh-only", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ State: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("boop"), @@ -3223,6 +3272,11 @@ resource "test_resource" "a" { }) t.Run("precondition and postcondition fail refresh-only", func(t *testing.T) { + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{ State: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("nope"), diff --git a/internal/terraform/context_plugins.go b/internal/terraform/context_plugins.go index 24e5371c5c4e..1297afef81c6 100644 --- a/internal/terraform/context_plugins.go +++ b/internal/terraform/context_plugins.go @@ -6,7 +6,6 @@ package terraform import ( "fmt" "log" - "sync" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" @@ -21,15 +20,6 @@ import ( type contextPlugins struct { providerFactories map[addrs.Provider]providers.Factory provisionerFactories map[string]provisioners.Factory - - // We memoize the schemas we've previously loaded in here, to avoid - // repeatedly paying the cost of activating the same plugins to access - // their schemas in various different spots. We use schemas for many - // purposes in Terraform, so there isn't a single choke point where - // it makes sense to preload all of them. - providerSchemas map[addrs.Provider]*ProviderSchema - provisionerSchemas map[string]*configschema.Block - schemasLock sync.Mutex } func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) *contextPlugins { @@ -37,15 +27,9 @@ func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, p providerFactories: providerFactories, provisionerFactories: provisionerFactories, } - ret.init() return ret } -func (cp *contextPlugins) init() { - cp.providerSchemas = make(map[addrs.Provider]*ProviderSchema, len(cp.providerFactories)) - cp.provisionerSchemas = make(map[string]*configschema.Block, len(cp.provisionerFactories)) -} - func (cp *contextPlugins) HasProvider(addr addrs.Provider) bool { _, ok := cp.providerFactories[addr] return ok @@ -81,70 +65,53 @@ func (cp *contextPlugins) NewProvisionerInstance(typ string) (provisioners.Inter // ProviderSchema memoizes results by unique provider address, so it's fine // to repeatedly call this method with the same address if various different // parts of Terraform all need the same schema information. -func (cp *contextPlugins) ProviderSchema(addr addrs.Provider) (*ProviderSchema, error) { - cp.schemasLock.Lock() - defer cp.schemasLock.Unlock() +func (cp *contextPlugins) ProviderSchema(addr addrs.Provider) (providers.ProviderSchema, error) { + log.Printf("[TRACE] terraform.contextPlugins: Initializing provider %q to read its schema", addr) - if schema, ok := cp.providerSchemas[addr]; ok { - return schema, nil + // check the global schema cache first + schemas, ok := providers.SchemaCache.Get(addr) + if ok { + return schemas, nil } - log.Printf("[TRACE] terraform.contextPlugins: Initializing provider %q to read its schema", addr) - provider, err := cp.NewProviderInstance(addr) if err != nil { - return nil, fmt.Errorf("failed to instantiate provider %q to obtain schema: %s", addr, err) + return schemas, fmt.Errorf("failed to instantiate provider %q to obtain schema: %s", addr, err) } defer provider.Close() resp := provider.GetProviderSchema() if resp.Diagnostics.HasErrors() { - return nil, fmt.Errorf("failed to retrieve schema from provider %q: %s", addr, resp.Diagnostics.Err()) - } - - s := &ProviderSchema{ - Provider: resp.Provider.Block, - ResourceTypes: make(map[string]*configschema.Block), - DataSources: make(map[string]*configschema.Block), - - ResourceTypeSchemaVersions: make(map[string]uint64), + return resp, fmt.Errorf("failed to retrieve schema from provider %q: %s", addr, resp.Diagnostics.Err()) } if resp.Provider.Version < 0 { // We're not using the version numbers here yet, but we'll check // for validity anyway in case we start using them in future. - return nil, fmt.Errorf("provider %s has invalid negative schema version for its configuration blocks,which is a bug in the provider ", addr) + return resp, fmt.Errorf("provider %s has invalid negative schema version for its configuration blocks,which is a bug in the provider ", addr) } for t, r := range resp.ResourceTypes { if err := r.Block.InternalValidate(); err != nil { - return nil, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %q", addr, t, err) + return resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %q", addr, t, err) } - s.ResourceTypes[t] = r.Block - s.ResourceTypeSchemaVersions[t] = uint64(r.Version) if r.Version < 0 { - return nil, fmt.Errorf("provider %s has invalid negative schema version for managed resource type %q, which is a bug in the provider", addr, t) + return resp, fmt.Errorf("provider %s has invalid negative schema version for managed resource type %q, which is a bug in the provider", addr, t) } } for t, d := range resp.DataSources { if err := d.Block.InternalValidate(); err != nil { - return nil, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %q", addr, t, err) + return resp, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %q", addr, t, err) } - s.DataSources[t] = d.Block if d.Version < 0 { // We're not using the version numbers here yet, but we'll check // for validity anyway in case we start using them in future. - return nil, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t) + return resp, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t) } } - if resp.ProviderMeta.Block != nil { - s.ProviderMeta = resp.ProviderMeta.Block - } - - cp.providerSchemas[addr] = s - return s, nil + return resp, nil } // ProviderConfigSchema is a helper wrapper around ProviderSchema which first @@ -157,7 +124,7 @@ func (cp *contextPlugins) ProviderConfigSchema(providerAddr addrs.Provider) (*co return nil, err } - return providerSchema.Provider, nil + return providerSchema.Provider.Block, nil } // ResourceTypeSchema is a helper wrapper around ProviderSchema which first @@ -188,13 +155,6 @@ func (cp *contextPlugins) ResourceTypeSchema(providerAddr addrs.Provider, resour // to repeatedly call this method with the same name if various different // parts of Terraform all need the same schema information. func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, error) { - cp.schemasLock.Lock() - defer cp.schemasLock.Unlock() - - if schema, ok := cp.provisionerSchemas[typ]; ok { - return schema, nil - } - log.Printf("[TRACE] terraform.contextPlugins: Initializing provisioner %q to read its schema", typ) provisioner, err := cp.NewProvisionerInstance(typ) if err != nil { @@ -207,6 +167,5 @@ func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, er return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %s", typ, resp.Diagnostics.Err()) } - cp.provisionerSchemas[typ] = resp.Provisioner return resp.Provisioner, nil } diff --git a/internal/terraform/context_plugins_test.go b/internal/terraform/context_plugins_test.go index 20b89451fbf1..cb3bf3a2195f 100644 --- a/internal/terraform/context_plugins_test.go +++ b/internal/terraform/context_plugins_test.go @@ -41,7 +41,6 @@ func simpleMockPluginLibrary() *contextPlugins { }, }, } - ret.init() // prepare the internal cache data structures return ret } diff --git a/internal/terraform/eval_context.go b/internal/terraform/eval_context.go index 0835a3547d4a..5965a083e38c 100644 --- a/internal/terraform/eval_context.go +++ b/internal/terraform/eval_context.go @@ -57,7 +57,7 @@ type EvalContext interface { // // This method expects an _absolute_ provider configuration address, since // resources in one module are able to use providers from other modules. - ProviderSchema(addrs.AbsProviderConfig) (*ProviderSchema, error) + ProviderSchema(addrs.AbsProviderConfig) (providers.ProviderSchema, error) // CloseProvider closes provider connections that aren't needed anymore. // diff --git a/internal/terraform/eval_context_builtin.go b/internal/terraform/eval_context_builtin.go index 0070cec5272a..00de8eb7e8d9 100644 --- a/internal/terraform/eval_context_builtin.go +++ b/internal/terraform/eval_context_builtin.go @@ -149,7 +149,22 @@ func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) providers. return ctx.ProviderCache[addr.String()] } -func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (*ProviderSchema, error) { +func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) { + // first see if we have already have an initialized provider to avoid + // re-loading it only for the schema + p := ctx.Provider(addr) + if p != nil { + resp := p.GetProviderSchema() + // convert any diagnostics here in case this is the first call + // FIXME: better control provider instantiation so we can be sure this + // won't be the first call to ProviderSchema + var err error + if resp.Diagnostics.HasErrors() { + err = resp.Diagnostics.ErrWithWarnings() + } + return resp, err + } + return ctx.Plugins.ProviderSchema(addr.Provider) } @@ -181,16 +196,6 @@ func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, c return diags } - providerSchema, err := ctx.ProviderSchema(addr) - if err != nil { - diags = diags.Append(fmt.Errorf("failed to read schema for %s: %s", addr, err)) - return diags - } - if providerSchema == nil { - diags = diags.Append(fmt.Errorf("schema for %s is not available", addr)) - return diags - } - req := providers.ConfigureProviderRequest{ TerraformVersion: version.String(), Config: cfg, diff --git a/internal/terraform/eval_context_mock.go b/internal/terraform/eval_context_mock.go index 30af84df3213..276730c44935 100644 --- a/internal/terraform/eval_context_mock.go +++ b/internal/terraform/eval_context_mock.go @@ -46,7 +46,7 @@ type MockEvalContext struct { ProviderSchemaCalled bool ProviderSchemaAddr addrs.AbsProviderConfig - ProviderSchemaSchema *ProviderSchema + ProviderSchemaSchema providers.ProviderSchema ProviderSchemaError error CloseProviderCalled bool @@ -190,7 +190,7 @@ func (c *MockEvalContext) Provider(addr addrs.AbsProviderConfig) providers.Inter return c.ProviderProvider } -func (c *MockEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (*ProviderSchema, error) { +func (c *MockEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) { c.ProviderSchemaCalled = true c.ProviderSchemaAddr = addr return c.ProviderSchemaSchema, c.ProviderSchemaError diff --git a/internal/terraform/eval_provider.go b/internal/terraform/eval_provider.go index fd727dd836c4..90b9cbcc3d07 100644 --- a/internal/terraform/eval_provider.go +++ b/internal/terraform/eval_provider.go @@ -43,20 +43,20 @@ func buildProviderConfig(ctx EvalContext, addr addrs.AbsProviderConfig, config * } // getProvider returns the providers.Interface and schema for a given provider. -func getProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Interface, *ProviderSchema, error) { +func getProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Interface, providers.ProviderSchema, error) { if addr.Provider.Type == "" { // Should never happen panic("GetProvider used with uninitialized provider configuration address") } provider := ctx.Provider(addr) if provider == nil { - return nil, &ProviderSchema{}, fmt.Errorf("provider %s not initialized", addr) + return nil, providers.ProviderSchema{}, fmt.Errorf("provider %s not initialized", addr) } // Not all callers require a schema, so we will leave checking for a nil // schema to the callers. schema, err := ctx.ProviderSchema(addr) if err != nil { - return nil, &ProviderSchema{}, fmt.Errorf("failed to read schema for provider %s: %w", addr, err) + return nil, providers.ProviderSchema{}, fmt.Errorf("failed to read schema for provider %s: %w", addr, err) } return provider, schema, nil } diff --git a/internal/terraform/evaluate_test.go b/internal/terraform/evaluate_test.go index a3d2ae669b16..56ac09c005dc 100644 --- a/internal/terraform/evaluate_test.go +++ b/internal/terraform/evaluate_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/plans" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -264,71 +265,72 @@ func TestEvaluatorGetResource(t *testing.T) { }, }, State: stateSync, - Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]*ProviderSchema{ + Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]providers.ProviderSchema{ addrs.NewDefaultProvider("test"): { - Provider: &configschema.Block{}, - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "test_resource": { - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, - }, - "value": { - Type: cty.String, - Computed: true, - Sensitive: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "value": { + Type: cty.String, + Computed: true, + Sensitive: true, + }, }, - }, - BlockTypes: map[string]*configschema.NestedBlock{ - "nesting_list": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "value": {Type: cty.String, Optional: true}, - "sensitive_value": {Type: cty.String, Optional: true, Sensitive: true}, + BlockTypes: map[string]*configschema.NestedBlock{ + "nesting_list": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": {Type: cty.String, Optional: true}, + "sensitive_value": {Type: cty.String, Optional: true, Sensitive: true}, + }, }, + Nesting: configschema.NestingList, }, - Nesting: configschema.NestingList, - }, - "nesting_map": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "foo": {Type: cty.String, Optional: true, Sensitive: true}, + "nesting_map": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": {Type: cty.String, Optional: true, Sensitive: true}, + }, }, + Nesting: configschema.NestingMap, }, - Nesting: configschema.NestingMap, - }, - "nesting_set": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "baz": {Type: cty.String, Optional: true, Sensitive: true}, + "nesting_set": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "baz": {Type: cty.String, Optional: true, Sensitive: true}, + }, }, + Nesting: configschema.NestingSet, }, - Nesting: configschema.NestingSet, - }, - "nesting_single": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "boop": {Type: cty.String, Optional: true, Sensitive: true}, + "nesting_single": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "boop": {Type: cty.String, Optional: true, Sensitive: true}, + }, }, + Nesting: configschema.NestingSingle, }, - Nesting: configschema.NestingSingle, - }, - "nesting_nesting": { - Block: configschema.Block{ - BlockTypes: map[string]*configschema.NestedBlock{ - "nesting_list": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "value": {Type: cty.String, Optional: true}, - "sensitive_value": {Type: cty.String, Optional: true, Sensitive: true}, + "nesting_nesting": { + Block: configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "nesting_list": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "value": {Type: cty.String, Optional: true}, + "sensitive_value": {Type: cty.String, Optional: true, Sensitive: true}, + }, }, + Nesting: configschema.NestingList, }, - Nesting: configschema.NestingList, }, }, + Nesting: configschema.NestingSingle, }, - Nesting: configschema.NestingSingle, }, }, }, @@ -435,29 +437,30 @@ func TestEvaluatorGetResource_changes(t *testing.T) { // Set up our schemas schemas := &Schemas{ - Providers: map[addrs.Provider]*ProviderSchema{ + Providers: map[addrs.Provider]providers.ProviderSchema{ addrs.NewDefaultProvider("test"): { - Provider: &configschema.Block{}, - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "test_resource": { - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, - }, - "to_mark_val": { - Type: cty.String, - Computed: true, - }, - "sensitive_value": { - Type: cty.String, - Computed: true, - Sensitive: true, - }, - "sensitive_collection": { - Type: cty.Map(cty.String), - Computed: true, - Sensitive: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + "to_mark_val": { + Type: cty.String, + Computed: true, + }, + "sensitive_value": { + Type: cty.String, + Computed: true, + Sensitive: true, + }, + "sensitive_collection": { + Type: cty.Map(cty.String), + Computed: true, + Sensitive: true, + }, }, }, }, diff --git a/internal/terraform/evaluate_valid_test.go b/internal/terraform/evaluate_valid_test.go index 04028cf3c42b..adf056ab362f 100644 --- a/internal/terraform/evaluate_valid_test.go +++ b/internal/terraform/evaluate_valid_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang" + "github.com/hashicorp/terraform/internal/providers" ) func TestStaticValidateReferences(t *testing.T) { @@ -83,23 +84,29 @@ For example, to correlate with indices of a referring resource, use: cfg := testModule(t, "static-validate-refs") evaluator := &Evaluator{ Config: cfg, - Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]*ProviderSchema{ + Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]providers.ProviderSchema{ addrs.NewDefaultProvider("aws"): { - ResourceTypes: map[string]*configschema.Block{ - "aws_instance": {}, + ResourceTypes: map[string]providers.Schema{ + "aws_instance": { + Block: &configschema.Block{}, + }, }, }, addrs.MustParseProviderSourceString("foobar/beep"): { - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ // intentional mismatch between resource type prefix and provider type - "boop_instance": {}, + "boop_instance": { + Block: &configschema.Block{}, + }, }, - DataSources: map[string]*configschema.Block{ + DataSources: map[string]providers.Schema{ "boop_data": { - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Optional: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Optional: true, + }, }, }, }, diff --git a/internal/terraform/graph_builder_apply_test.go b/internal/terraform/graph_builder_apply_test.go index c217be2b7099..0dc2fb3ca720 100644 --- a/internal/terraform/graph_builder_apply_test.go +++ b/internal/terraform/graph_builder_apply_test.go @@ -467,12 +467,12 @@ func TestApplyGraphBuilder_updateFromOrphan(t *testing.T) { cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("b_id"), "test_string": cty.StringVal("a_id"), - }), instanceSchema.ImpliedType()) + }), instanceSchema.Block.ImpliedType()) bAfter, _ := plans.NewDynamicValue( cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("b_id"), "test_string": cty.StringVal("changed"), - }), instanceSchema.ImpliedType()) + }), instanceSchema.Block.ImpliedType()) changes := &plans.Changes{ Resources: []*plans.ResourceInstanceChangeSrc{ @@ -572,12 +572,12 @@ func TestApplyGraphBuilder_updateFromCBDOrphan(t *testing.T) { cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("b_id"), "test_string": cty.StringVal("a_id"), - }), instanceSchema.ImpliedType()) + }), instanceSchema.Block.ImpliedType()) bAfter, _ := plans.NewDynamicValue( cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("b_id"), "test_string": cty.StringVal("changed"), - }), instanceSchema.ImpliedType()) + }), instanceSchema.Block.ImpliedType()) changes := &plans.Changes{ Resources: []*plans.ResourceInstanceChangeSrc{ diff --git a/internal/terraform/graph_walk_context.go b/internal/terraform/graph_walk_context.go index 3b4409d6b42d..dc0188846a3a 100644 --- a/internal/terraform/graph_walk_context.go +++ b/internal/terraform/graph_walk_context.go @@ -54,7 +54,7 @@ type ContextGraphWalker struct { variableValues map[string]map[string]cty.Value variableValuesLock sync.Mutex providerCache map[string]providers.Interface - providerSchemas map[string]*ProviderSchema + providerSchemas map[string]providers.ProviderSchema providerLock sync.Mutex provisionerCache map[string]provisioners.Interface provisionerSchemas map[string]*configschema.Block @@ -122,7 +122,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext { func (w *ContextGraphWalker) init() { w.contexts = make(map[string]*BuiltinEvalContext) w.providerCache = make(map[string]providers.Interface) - w.providerSchemas = make(map[string]*ProviderSchema) + w.providerSchemas = make(map[string]providers.ProviderSchema) w.provisionerCache = make(map[string]provisioners.Interface) w.provisionerSchemas = make(map[string]*configschema.Block) w.variableValues = make(map[string]map[string]cty.Value) diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index 790efb5db3af..e0db1183d77e 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -137,7 +137,7 @@ func (n *NodeAbstractResourceInstance) AttachResourceState(s *states.Resource) { // readDiff returns the planned change for a particular resource instance // object. -func (n *NodeAbstractResourceInstance) readDiff(ctx EvalContext, providerSchema *ProviderSchema) (*plans.ResourceInstanceChange, error) { +func (n *NodeAbstractResourceInstance) readDiff(ctx EvalContext, providerSchema providers.ProviderSchema) (*plans.ResourceInstanceChange, error) { changes := ctx.Changes() addr := n.ResourceInstanceAddr() @@ -323,18 +323,13 @@ func (n *NodeAbstractResourceInstance) writeResourceInstanceStateImpl(ctx EvalCo return nil } - if providerSchema == nil { - // Should never happen, unless our state object is nil - panic("writeResourceInstanceStateImpl used with nil ProviderSchema") - } - if obj != nil { log.Printf("[TRACE] %s: writing state object for %s", logFuncName, absAddr) } else { log.Printf("[TRACE] %s: removing state object for %s", logFuncName, absAddr) } - schema, currentVersion := (*providerSchema).SchemaForResourceAddr(absAddr.ContainingResource().Resource) + schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource) if schema == nil { // It shouldn't be possible to get this far in any real scenario // without a schema, but we might end up here in contrived tests that @@ -663,10 +658,6 @@ func (n *NodeAbstractResourceInstance) plan( return nil, nil, keyData, diags.Append(err) } - if providerSchema == nil { - diags = diags.Append(fmt.Errorf("provider schema is unavailable for %s", n.Addr)) - return nil, nil, keyData, diags - } schema, _ := providerSchema.SchemaForResourceAddr(resource) if schema == nil { // Should be caught during validation, so we don't bother with a pretty error here @@ -1416,10 +1407,6 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal if diags.HasErrors() { return newVal, diags } - if providerSchema == nil { - diags = diags.Append(fmt.Errorf("provider schema not available for %s", n.Addr)) - return newVal, diags - } schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) if schema == nil { // Should be caught during validation, so we don't bother with a pretty error here @@ -1539,13 +1526,10 @@ func (n *NodeAbstractResourceInstance) providerMetas(ctx EvalContext) (cty.Value if err != nil { return metaConfigVal, diags.Append(err) } - if providerSchema == nil { - return metaConfigVal, diags.Append(fmt.Errorf("provider schema not available for %s", n.Addr)) - } if n.ProviderMetas != nil { if m, ok := n.ProviderMetas[n.ResolvedProvider.Provider]; ok && m != nil { // if the provider doesn't support this feature, throw an error - if providerSchema.ProviderMeta == nil { + if providerSchema.ProviderMeta.Block == nil { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: fmt.Sprintf("Provider %s doesn't support provider_meta", n.ResolvedProvider.Provider.String()), @@ -1554,7 +1538,7 @@ func (n *NodeAbstractResourceInstance) providerMetas(ctx EvalContext) (cty.Value }) } else { var configDiags tfdiags.Diagnostics - metaConfigVal, _, configDiags = ctx.EvaluateBlock(m.Config, providerSchema.ProviderMeta, nil, EvalDataForNoInstanceKey) + metaConfigVal, _, configDiags = ctx.EvaluateBlock(m.Config, providerSchema.ProviderMeta.Block, nil, EvalDataForNoInstanceKey) diags = diags.Append(configDiags) } } @@ -1580,9 +1564,6 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule if err != nil { return nil, nil, keyData, diags.Append(err) } - if providerSchema == nil { - return nil, nil, keyData, diags.Append(fmt.Errorf("provider schema not available for %s", n.Addr)) - } config := *n.Config schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) @@ -1851,10 +1832,6 @@ func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned if err != nil { return nil, keyData, diags.Append(err) } - if providerSchema == nil { - return nil, keyData, diags.Append(fmt.Errorf("provider schema not available for %s", n.Addr)) - } - if planned != nil && planned.Action != plans.Read && planned.Action != plans.NoOp { // If any other action gets in here then that's always a bug; this // EvalNode only deals with reading. diff --git a/internal/terraform/node_resource_abstract_instance_test.go b/internal/terraform/node_resource_abstract_instance_test.go index bc8e70a8ba16..3888be369fbc 100644 --- a/internal/terraform/node_resource_abstract_instance_test.go +++ b/internal/terraform/node_resource_abstract_instance_test.go @@ -171,7 +171,7 @@ func TestNodeAbstractResourceInstance_WriteResourceInstanceState(t *testing.T) { }, } ctx.ProviderProvider = mockProvider - ctx.ProviderSchemaSchema = mockProvider.ProviderSchema() + ctx.ProviderSchemaSchema = mockProvider.GetProviderSchema() err := node.writeResourceInstanceState(ctx, obj, workingState) if err != nil { diff --git a/internal/terraform/node_resource_abstract_test.go b/internal/terraform/node_resource_abstract_test.go index 85f3f209f34d..d65a8685e075 100644 --- a/internal/terraform/node_resource_abstract_test.go +++ b/internal/terraform/node_resource_abstract_test.go @@ -230,7 +230,7 @@ func TestNodeAbstractResource_ReadResourceInstanceState(t *testing.T) { ctx := new(MockEvalContext) ctx.StateState = test.State.SyncWrapper() ctx.PathPath = addrs.RootModuleInstance - ctx.ProviderSchemaSchema = mockProvider.ProviderSchema() + ctx.ProviderSchemaSchema = mockProvider.GetProviderSchema() ctx.ProviderProvider = providers.Interface(mockProvider) @@ -295,7 +295,7 @@ func TestNodeAbstractResource_ReadResourceInstanceStateDeposed(t *testing.T) { ctx := new(MockEvalContext) ctx.StateState = test.State.SyncWrapper() ctx.PathPath = addrs.RootModuleInstance - ctx.ProviderSchemaSchema = mockProvider.ProviderSchema() + ctx.ProviderSchemaSchema = mockProvider.GetProviderSchema() ctx.ProviderProvider = providers.Interface(mockProvider) key := states.DeposedKey("00000001") // shim from legacy state assigns 0th deposed index this key diff --git a/internal/terraform/node_resource_apply_instance.go b/internal/terraform/node_resource_apply_instance.go index 98d77d8aa100..035d4f5b0576 100644 --- a/internal/terraform/node_resource_apply_instance.go +++ b/internal/terraform/node_resource_apply_instance.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/plans/objchange" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -400,7 +401,7 @@ func (n *NodeApplyableResourceInstance) managedResourcePostconditions(ctx EvalCo // Errors here are most often indicative of a bug in the provider, so our error // messages will report with that in mind. It's also possible that there's a bug // in Terraform's Core's own "proposed new value" code in EvalDiff. -func (n *NodeApplyableResourceInstance) checkPlannedChange(ctx EvalContext, plannedChange, actualChange *plans.ResourceInstanceChange, providerSchema *ProviderSchema) tfdiags.Diagnostics { +func (n *NodeApplyableResourceInstance) checkPlannedChange(ctx EvalContext, plannedChange, actualChange *plans.ResourceInstanceChange, providerSchema providers.ProviderSchema) tfdiags.Diagnostics { var diags tfdiags.Diagnostics addr := n.ResourceInstanceAddr().Resource diff --git a/internal/terraform/node_resource_destroy_deposed.go b/internal/terraform/node_resource_destroy_deposed.go index 0e68ae9942aa..70f425682706 100644 --- a/internal/terraform/node_resource_destroy_deposed.go +++ b/internal/terraform/node_resource_destroy_deposed.go @@ -314,10 +314,6 @@ func (n *NodeDestroyDeposedResourceInstanceObject) writeResourceInstanceState(ct if err != nil { return err } - if providerSchema == nil { - // Should never happen, unless our state object is nil - panic("writeResourceInstanceStateDeposed used with no ProviderSchema object") - } schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource) if schema == nil { diff --git a/internal/terraform/node_resource_destroy_deposed_test.go b/internal/terraform/node_resource_destroy_deposed_test.go index d1be7da79225..e110649e6694 100644 --- a/internal/terraform/node_resource_destroy_deposed_test.go +++ b/internal/terraform/node_resource_destroy_deposed_test.go @@ -40,13 +40,15 @@ func TestNodePlanDeposedResourceInstanceObject_Execute(t *testing.T) { PrevRunStateState: state.DeepCopy().SyncWrapper(), RefreshStateState: state.DeepCopy().SyncWrapper(), ProviderProvider: p, - ProviderSchemaSchema: &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + ProviderSchemaSchema: providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, }, }, }, @@ -96,13 +98,15 @@ func TestNodeDestroyDeposedResourceInstanceObject_Execute(t *testing.T) { mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), ) - schema := &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + schema := providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "test_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, }, }, }, @@ -111,7 +115,7 @@ func TestNodeDestroyDeposedResourceInstanceObject_Execute(t *testing.T) { p := testProvider("test") p.ConfigureProvider(providers.ConfigureProviderRequest{}) - p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(schema) + p.GetProviderSchemaResponse = &schema p.UpgradeResourceStateResponse = &providers.UpgradeResourceStateResponse{ UpgradedState: cty.ObjectVal(map[string]cty.Value{ @@ -159,7 +163,7 @@ func TestNodeDestroyDeposedResourceInstanceObject_WriteResourceInstanceState(t * }, }) ctx.ProviderProvider = mockProvider - ctx.ProviderSchemaSchema = mockProvider.ProviderSchema() + ctx.ProviderSchemaSchema = mockProvider.GetProviderSchema() obj := &states.ResourceInstanceObject{ Value: cty.ObjectVal(map[string]cty.Value{ @@ -194,7 +198,7 @@ func TestNodeDestroyDeposedResourceInstanceObject_ExecuteMissingState(t *testing ctx := &MockEvalContext{ StateState: states.NewState().SyncWrapper(), ProviderProvider: simpleMockProvider(), - ProviderSchemaSchema: p.ProviderSchema(), + ProviderSchemaSchema: p.GetProviderSchema(), ChangesChanges: plans.NewChanges().SyncWrapper(), } diff --git a/internal/terraform/node_resource_plan_instance.go b/internal/terraform/node_resource_plan_instance.go index 4d211e8c7db1..565054f160f7 100644 --- a/internal/terraform/node_resource_plan_instance.go +++ b/internal/terraform/node_resource_plan_instance.go @@ -439,7 +439,7 @@ func (n *NodePlannableResourceInstance) replaceTriggered(ctx EvalContext, repDat return diags } -func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.AbsResourceInstance, provider providers.Interface, providerSchema *ProviderSchema) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { +func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.AbsResourceInstance, provider providers.Interface, providerSchema providers.ProviderSchema) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics absAddr := addr.Resource.Absolute(ctx.Path()) diff --git a/internal/terraform/node_resource_plan_orphan_test.go b/internal/terraform/node_resource_plan_orphan_test.go index caf0d1d310fd..fe23c4f5b9b3 100644 --- a/internal/terraform/node_resource_plan_orphan_test.go +++ b/internal/terraform/node_resource_plan_orphan_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/instances" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/providers" @@ -43,9 +42,11 @@ func TestNodeResourcePlanOrphanExecute(t *testing.T) { PrevRunStateState: state.DeepCopy().SyncWrapper(), InstanceExpanderExpander: instances.NewExpander(), ProviderProvider: p, - ProviderSchemaSchema: &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ - "test_object": simpleTestSchema(), + ProviderSchemaSchema: providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ + "test_object": { + Block: simpleTestSchema(), + }, }, }, ChangesChanges: plans.NewChanges().SyncWrapper(), @@ -107,9 +108,11 @@ func TestNodeResourcePlanOrphanExecute_alreadyDeleted(t *testing.T) { PrevRunStateState: prevRunState.SyncWrapper(), InstanceExpanderExpander: instances.NewExpander(), ProviderProvider: p, - ProviderSchemaSchema: &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ - "test_object": simpleTestSchema(), + ProviderSchemaSchema: providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ + "test_object": { + Block: simpleTestSchema(), + }, }, }, ChangesChanges: changes.SyncWrapper(), @@ -187,9 +190,11 @@ func TestNodeResourcePlanOrphanExecute_deposed(t *testing.T) { PrevRunStateState: prevRunState.SyncWrapper(), InstanceExpanderExpander: instances.NewExpander(), ProviderProvider: p, - ProviderSchemaSchema: &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ - "test_object": simpleTestSchema(), + ProviderSchemaSchema: providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ + "test_object": { + Block: simpleTestSchema(), + }, }, }, ChangesChanges: changes.SyncWrapper(), diff --git a/internal/terraform/node_resource_validate.go b/internal/terraform/node_resource_validate.go index 9afd88eb7cef..f5b5cb57e240 100644 --- a/internal/terraform/node_resource_validate.go +++ b/internal/terraform/node_resource_validate.go @@ -276,10 +276,6 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag if diags.HasErrors() { return diags } - if providerSchema == nil { - diags = diags.Append(fmt.Errorf("validateResource has nil schema for %s", n.Addr)) - return diags - } keyData := EvalDataForNoInstanceKey diff --git a/internal/terraform/node_resource_validate_test.go b/internal/terraform/node_resource_validate_test.go index 12262afc9e4b..cd93f22cc273 100644 --- a/internal/terraform/node_resource_validate_test.go +++ b/internal/terraform/node_resource_validate_test.go @@ -193,7 +193,7 @@ func TestNodeValidatableResource_ValidateResource_managedResource(t *testing.T) ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p err := node.validateResource(ctx) @@ -223,7 +223,7 @@ func TestNodeValidatableResource_ValidateResource_managedResourceCount(t *testin ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p tests := []struct { @@ -307,7 +307,7 @@ func TestNodeValidatableResource_ValidateResource_dataSource(t *testing.T) { ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p diags := node.validateResource(ctx) @@ -343,7 +343,7 @@ func TestNodeValidatableResource_ValidateResource_valid(t *testing.T) { ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p diags := node.validateResource(ctx) @@ -380,7 +380,7 @@ func TestNodeValidatableResource_ValidateResource_warningsAndErrorsPassedThrough ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p diags := node.validateResource(ctx) @@ -443,7 +443,7 @@ func TestNodeValidatableResource_ValidateResource_invalidDependsOn(t *testing.T) ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p diags := node.validateResource(ctx) @@ -527,7 +527,7 @@ func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesNonexisten ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p diags := node.validateResource(ctx) @@ -610,7 +610,7 @@ func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesComputed(t ctx := &MockEvalContext{} ctx.installSimpleEval() - ctx.ProviderSchemaSchema = mp.ProviderSchema() + ctx.ProviderSchemaSchema = mp.GetProviderSchema() ctx.ProviderProvider = p diags := node.validateResource(ctx) diff --git a/internal/terraform/provider_mock.go b/internal/terraform/provider_mock.go index 41a14646de92..05f94699d98b 100644 --- a/internal/terraform/provider_mock.go +++ b/internal/terraform/provider_mock.go @@ -11,7 +11,6 @@ import ( ctyjson "github.com/zclconf/go-cty/cty/json" "github.com/zclconf/go-cty/cty/msgpack" - "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/configs/hcl2shim" "github.com/hashicorp/terraform/internal/providers" ) @@ -112,31 +111,6 @@ func (p *MockProvider) getProviderSchema() providers.GetProviderSchemaResponse { } } -// ProviderSchema is a helper to convert from the internal GetProviderSchemaResponse to -// a ProviderSchema. -func (p *MockProvider) ProviderSchema() *ProviderSchema { - resp := p.getProviderSchema() - - schema := &ProviderSchema{ - Provider: resp.Provider.Block, - ProviderMeta: resp.ProviderMeta.Block, - ResourceTypes: map[string]*configschema.Block{}, - DataSources: map[string]*configschema.Block{}, - ResourceTypeSchemaVersions: map[string]uint64{}, - } - - for resType, s := range resp.ResourceTypes { - schema.ResourceTypes[resType] = s.Block - schema.ResourceTypeSchemaVersions[resType] = uint64(s.Version) - } - - for dataSource, s := range resp.DataSources { - schema.DataSources[dataSource] = s.Block - } - - return schema -} - func (p *MockProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) { p.Lock() defer p.Unlock() @@ -537,6 +511,9 @@ func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp p } func (p *MockProvider) Close() error { + p.Lock() + defer p.Unlock() + p.CloseCalled = true return p.CloseError } diff --git a/internal/terraform/resource_provider_mock_test.go b/internal/terraform/resource_provider_mock_test.go index 0853c67d84f0..4a71a5301cd2 100644 --- a/internal/terraform/resource_provider_mock_test.go +++ b/internal/terraform/resource_provider_mock_test.go @@ -49,30 +49,6 @@ func mockProviderWithResourceTypeSchema(name string, schema *configschema.Block) } } -// getProviderSchemaResponseFromProviderSchema is a test helper to convert a -// ProviderSchema to a GetProviderSchemaResponse for use when building a mock provider. -func getProviderSchemaResponseFromProviderSchema(providerSchema *ProviderSchema) *providers.GetProviderSchemaResponse { - resp := &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{Block: providerSchema.Provider}, - ProviderMeta: providers.Schema{Block: providerSchema.ProviderMeta}, - ResourceTypes: map[string]providers.Schema{}, - DataSources: map[string]providers.Schema{}, - } - - for name, schema := range providerSchema.ResourceTypes { - resp.ResourceTypes[name] = providers.Schema{ - Block: schema, - Version: int64(providerSchema.ResourceTypeSchemaVersions[name]), - } - } - - for name, schema := range providerSchema.DataSources { - resp.DataSources[name] = providers.Schema{Block: schema} - } - - return resp -} - // simpleMockProvider returns a MockProvider that is pre-configured // with schema for its own config, for a resource type called "test_object" and // for a data source also called "test_object". @@ -103,3 +79,62 @@ func simpleMockProvider() *MockProvider { }, } } + +// ProviderSchema is a helper to convert from the internal GetProviderSchemaResponse to +// a ProviderSchema. +func (p *MockProvider) ProviderSchema() *ProviderSchema { + resp := p.getProviderSchema() + + schema := &ProviderSchema{ + Provider: resp.Provider.Block, + ProviderMeta: resp.ProviderMeta.Block, + ResourceTypes: map[string]*configschema.Block{}, + DataSources: map[string]*configschema.Block{}, + ResourceTypeSchemaVersions: map[string]uint64{}, + } + + for resType, s := range resp.ResourceTypes { + schema.ResourceTypes[resType] = s.Block + schema.ResourceTypeSchemaVersions[resType] = uint64(s.Version) + } + + for dataSource, s := range resp.DataSources { + schema.DataSources[dataSource] = s.Block + } + + return schema +} + +// the type was refactored out with all the functionality handled within the +// provider package, but we keep this here for a shim in existing tests. +type ProviderSchema struct { + Provider *configschema.Block + ProviderMeta *configschema.Block + ResourceTypes map[string]*configschema.Block + ResourceTypeSchemaVersions map[string]uint64 + DataSources map[string]*configschema.Block +} + +// getProviderSchemaResponseFromProviderSchema is a test helper to convert a +// ProviderSchema to a GetProviderSchemaResponse for use when building a mock provider. +func getProviderSchemaResponseFromProviderSchema(providerSchema *ProviderSchema) *providers.GetProviderSchemaResponse { + resp := &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{Block: providerSchema.Provider}, + ProviderMeta: providers.Schema{Block: providerSchema.ProviderMeta}, + ResourceTypes: map[string]providers.Schema{}, + DataSources: map[string]providers.Schema{}, + } + + for name, schema := range providerSchema.ResourceTypes { + resp.ResourceTypes[name] = providers.Schema{ + Block: schema, + Version: int64(providerSchema.ResourceTypeSchemaVersions[name]), + } + } + + for name, schema := range providerSchema.DataSources { + resp.DataSources[name] = providers.Schema{Block: schema} + } + + return resp +} diff --git a/internal/terraform/schemas.go b/internal/terraform/schemas.go index db3577c0057a..ab494df4beb1 100644 --- a/internal/terraform/schemas.go +++ b/internal/terraform/schemas.go @@ -15,16 +15,10 @@ import ( "github.com/hashicorp/terraform/internal/tfdiags" ) -// ProviderSchema is an alias for providers.Schemas, which is the new location -// for what we originally called terraform.ProviderSchema but which has -// moved out as part of ongoing refactoring to shrink down the main "terraform" -// package. -type ProviderSchema = providers.Schemas - // Schemas is a container for various kinds of schema that Terraform needs // during processing. type Schemas struct { - Providers map[addrs.Provider]*providers.Schemas + Providers map[addrs.Provider]providers.ProviderSchema Provisioners map[string]*configschema.Block } @@ -33,21 +27,14 @@ type Schemas struct { // // It's usually better to go use the more precise methods offered by type // Schemas to handle this detail automatically. -func (ss *Schemas) ProviderSchema(provider addrs.Provider) *providers.Schemas { - if ss.Providers == nil { - return nil - } +func (ss *Schemas) ProviderSchema(provider addrs.Provider) providers.ProviderSchema { return ss.Providers[provider] } // ProviderConfig returns the schema for the provider configuration of the // given provider type, or nil if no such schema is available. func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block { - ps := ss.ProviderSchema(provider) - if ps == nil { - return nil - } - return ps.Provider + return ss.ProviderSchema(provider).Provider.Block } // ResourceTypeConfig returns the schema for the configuration of a given @@ -61,7 +48,7 @@ func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block { // redundant. func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { ps := ss.ProviderSchema(provider) - if ps == nil || ps.ResourceTypes == nil { + if ps.ResourceTypes == nil { return nil, 0 } return ps.SchemaForResourceType(resourceMode, resourceType) @@ -85,7 +72,7 @@ func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block { // still valid but may be incomplete. func loadSchemas(config *configs.Config, state *states.State, plugins *contextPlugins) (*Schemas, error) { schemas := &Schemas{ - Providers: map[addrs.Provider]*providers.Schemas{}, + Providers: map[addrs.Provider]providers.ProviderSchema{}, Provisioners: map[string]*configschema.Block{}, } var diags tfdiags.Diagnostics @@ -98,7 +85,7 @@ func loadSchemas(config *configs.Config, state *states.State, plugins *contextPl return schemas, diags.Err() } -func loadProviderSchemas(schemas map[addrs.Provider]*providers.Schemas, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics { +func loadProviderSchemas(schemas map[addrs.Provider]providers.ProviderSchema, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics { var diags tfdiags.Diagnostics ensure := func(fqn addrs.Provider) { @@ -114,7 +101,7 @@ func loadProviderSchemas(schemas map[addrs.Provider]*providers.Schemas, config * // We'll put a stub in the map so we won't re-attempt this on // future calls, which would then repeat the same error message // multiple times. - schemas[fqn] = &providers.Schemas{} + schemas[fqn] = providers.ProviderSchema{} diags = diags.Append( tfdiags.Sourceless( tfdiags.Error, diff --git a/internal/terraform/schemas_test.go b/internal/terraform/schemas_test.go index 46792c70a643..d36550e60d12 100644 --- a/internal/terraform/schemas_test.go +++ b/internal/terraform/schemas_test.go @@ -14,8 +14,8 @@ func simpleTestSchemas() *Schemas { provisioner := simpleMockProvisioner() return &Schemas{ - Providers: map[addrs.Provider]*ProviderSchema{ - addrs.NewDefaultProvider("test"): provider.ProviderSchema(), + Providers: map[addrs.Provider]providers.ProviderSchema{ + addrs.NewDefaultProvider("test"): provider.GetProviderSchema(), }, Provisioners: map[string]*configschema.Block{ "test": provisioner.GetSchemaResponse.Provisioner, @@ -31,32 +31,14 @@ func simpleTestSchemas() *Schemas { // The intended use for this is in testing components that use schemas to // drive other behavior, such as reference analysis during graph construction, // but that don't actually need to interact with providers otherwise. -func schemaOnlyProvidersForTesting(schemas map[addrs.Provider]*ProviderSchema) *contextPlugins { +func schemaOnlyProvidersForTesting(schemas map[addrs.Provider]providers.ProviderSchema) *contextPlugins { factories := make(map[addrs.Provider]providers.Factory, len(schemas)) for providerAddr, schema := range schemas { - - resp := &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Block: schema.Provider, - }, - ResourceTypes: make(map[string]providers.Schema), - DataSources: make(map[string]providers.Schema), - } - for t, tSchema := range schema.ResourceTypes { - resp.ResourceTypes[t] = providers.Schema{ - Block: tSchema, - Version: int64(schema.ResourceTypeSchemaVersions[t]), - } - } - for t, tSchema := range schema.DataSources { - resp.DataSources[t] = providers.Schema{ - Block: tSchema, - } - } + schema := schema provider := &MockProvider{ - GetProviderSchemaResponse: resp, + GetProviderSchemaResponse: &schema, } factories[providerAddr] = func() (providers.Interface, error) { diff --git a/internal/terraform/terraform_test.go b/internal/terraform/terraform_test.go index 31e567cde404..2ba8829a4acb 100644 --- a/internal/terraform/terraform_test.go +++ b/internal/terraform/terraform_test.go @@ -170,32 +170,33 @@ func testSetResourceInstanceTainted(module *states.Module, resource, attrsJson, } func testProviderFuncFixed(rp providers.Interface) providers.Factory { - return func() (providers.Interface, error) { - if p, ok := rp.(*MockProvider); ok { - // make sure none of the methods were "called" on this new instance - p.GetProviderSchemaCalled = false - p.ValidateProviderConfigCalled = false - p.ValidateResourceConfigCalled = false - p.ValidateDataResourceConfigCalled = false - p.UpgradeResourceStateCalled = false - p.ConfigureProviderCalled = false - p.StopCalled = false - p.ReadResourceCalled = false - p.PlanResourceChangeCalled = false - p.ApplyResourceChangeCalled = false - p.ImportResourceStateCalled = false - p.ReadDataSourceCalled = false - p.CloseCalled = false - } + if p, ok := rp.(*MockProvider); ok { + // make sure none of the methods were "called" on this new instance + p.GetProviderSchemaCalled = false + p.ValidateProviderConfigCalled = false + p.ValidateResourceConfigCalled = false + p.ValidateDataResourceConfigCalled = false + p.UpgradeResourceStateCalled = false + p.ConfigureProviderCalled = false + p.StopCalled = false + p.ReadResourceCalled = false + p.PlanResourceChangeCalled = false + p.ApplyResourceChangeCalled = false + p.ImportResourceStateCalled = false + p.ReadDataSourceCalled = false + p.CloseCalled = false + } + return func() (providers.Interface, error) { return rp, nil } } func testProvisionerFuncFixed(rp *MockProvisioner) provisioners.Factory { + // make sure this provisioner has has not been closed + rp.CloseCalled = false + return func() (provisioners.Interface, error) { - // make sure this provisioner has has not been closed - rp.CloseCalled = false return rp, nil } } diff --git a/internal/terraform/transform_import_state_test.go b/internal/terraform/transform_import_state_test.go index 32299405432c..34a21426a1e4 100644 --- a/internal/terraform/transform_import_state_test.go +++ b/internal/terraform/transform_import_state_test.go @@ -72,13 +72,15 @@ func TestGraphNodeImportStateSubExecute(t *testing.T) { ctx := &MockEvalContext{ StateState: state.SyncWrapper(), ProviderProvider: provider, - ProviderSchemaSchema: &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + ProviderSchemaSchema: providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "aws_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, }, }, }, @@ -132,13 +134,15 @@ func TestGraphNodeImportStateSubExecuteNull(t *testing.T) { ctx := &MockEvalContext{ StateState: state.SyncWrapper(), ProviderProvider: provider, - ProviderSchemaSchema: &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ + ProviderSchemaSchema: providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ "aws_instance": { - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, }, }, }, diff --git a/internal/terraform/transform_transitive_reduction_test.go b/internal/terraform/transform_transitive_reduction_test.go index 894cf1ff3883..28f675bfeca2 100644 --- a/internal/terraform/transform_transitive_reduction_test.go +++ b/internal/terraform/transform_transitive_reduction_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/providers" "github.com/zclconf/go-cty/cty" ) @@ -33,18 +34,20 @@ func TestTransitiveReductionTransformer(t *testing.T) { { transform := &AttachSchemaTransformer{ - Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]*ProviderSchema{ + Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]providers.ProviderSchema{ addrs.NewDefaultProvider("aws"): { - ResourceTypes: map[string]*configschema.Block{ + ResourceTypes: map[string]providers.Schema{ "aws_instance": { - Attributes: map[string]*configschema.Attribute{ - "A": { - Type: cty.String, - Optional: true, - }, - "B": { - Type: cty.String, - Optional: true, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "A": { + Type: cty.String, + Optional: true, + }, + "B": { + Type: cty.String, + Optional: true, + }, }, }, }, diff --git a/internal/terraform/validate_selfref.go b/internal/terraform/validate_selfref.go index 77cded45242e..f6975152130f 100644 --- a/internal/terraform/validate_selfref.go +++ b/internal/terraform/validate_selfref.go @@ -11,12 +11,13 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/lang" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/tfdiags" ) // validateSelfRef checks to ensure that expressions within a particular // referencable block do not reference that same block. -func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema *ProviderSchema) tfdiags.Diagnostics { +func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema providers.ProviderSchema) tfdiags.Diagnostics { var diags tfdiags.Diagnostics addrStrs := make([]string, 0, 1) @@ -27,11 +28,6 @@ func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema * addrStrs = append(addrStrs, tAddr.ContainingResource().String()) } - if providerSchema == nil { - diags = diags.Append(fmt.Errorf("provider schema unavailable while validating %s for self-references; this is a bug in Terraform and should be reported", addr)) - return diags - } - var schema *configschema.Block switch tAddr := addr.(type) { case addrs.Resource: diff --git a/internal/terraform/validate_selfref_test.go b/internal/terraform/validate_selfref_test.go index 41de79a44cd4..ca9bfe6fa6d9 100644 --- a/internal/terraform/validate_selfref_test.go +++ b/internal/terraform/validate_selfref_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hcltest" @@ -82,13 +83,15 @@ func TestValidateSelfRef(t *testing.T) { }, }) - ps := &ProviderSchema{ - ResourceTypes: map[string]*configschema.Block{ - "aws_instance": &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "foo": { - Type: cty.String, - Required: true, + ps := providers.ProviderSchema{ + ResourceTypes: map[string]providers.Schema{ + "aws_instance": { + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": { + Type: cty.String, + Required: true, + }, }, }, },