From ec3a38e5ed99a7b235c58c39b3dd5b24598b8cf9 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 5 Jul 2023 17:31:39 -0400 Subject: [PATCH] update providers.Schemas type Use the global providers.SchemaCache and update all schema access to the providers.Schemas, except where the provider.GetProviderSchemaResponse type name would be expected. Some tests that reuse provider factories needed a little more careful handling. Change the fixed func to only reset the provider on the first call. --- internal/terraform/context.go | 2 +- internal/terraform/context_plan2_test.go | 78 +++++++-- internal/terraform/context_plugins.go | 73 ++------ internal/terraform/context_plugins_test.go | 1 - internal/terraform/eval_context.go | 2 +- internal/terraform/eval_context_builtin.go | 27 +-- internal/terraform/eval_context_mock.go | 4 +- internal/terraform/eval_provider.go | 6 +- internal/terraform/evaluate_test.go | 159 +++++++++--------- internal/terraform/evaluate_valid_test.go | 27 +-- .../terraform/graph_builder_apply_test.go | 8 +- internal/terraform/graph_walk_context.go | 4 +- .../node_resource_abstract_instance.go | 31 +--- .../node_resource_abstract_instance_test.go | 2 +- .../terraform/node_resource_abstract_test.go | 4 +- .../terraform/node_resource_apply_instance.go | 3 +- .../node_resource_destroy_deposed.go | 4 - .../node_resource_destroy_deposed_test.go | 34 ++-- .../terraform/node_resource_plan_instance.go | 2 +- .../node_resource_plan_orphan_test.go | 25 +-- internal/terraform/node_resource_validate.go | 4 - .../terraform/node_resource_validate_test.go | 16 +- internal/terraform/provider_mock.go | 26 --- .../terraform/resource_provider_mock_test.go | 83 ++++++--- internal/terraform/schemas.go | 27 +-- internal/terraform/schemas_test.go | 28 +-- internal/terraform/terraform_test.go | 34 ++-- .../terraform/transform_import_state_test.go | 28 +-- .../transform_transitive_reduction_test.go | 23 +-- internal/terraform/validate_selfref.go | 8 +- internal/terraform/validate_selfref_test.go | 17 +- 31 files changed, 390 insertions(+), 400 deletions(-) diff --git a/internal/terraform/context.go b/internal/terraform/context.go index 35e3a69c50..5d3f84e46d 100644 --- a/internal/terraform/context.go +++ b/internal/terraform/context.go @@ -146,7 +146,7 @@ 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 + // FIXME: 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 diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index d7afec1d43..367b367e92 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 24e5371c5c..da6d80991d 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() - - if schema, ok := cp.providerSchemas[addr]; ok { - return schema, nil - } - +func (cp *contextPlugins) ProviderSchema(addr addrs.Provider) (providers.Schemas, error) { log.Printf("[TRACE] terraform.contextPlugins: Initializing provider %q to read its schema", addr) + // check the global schema cache first + schemas, ok := providers.SchemaCache.Get(addr) + if ok { + return schemas, nil + } + 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 20b89451fb..cb3bf3a219 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 0835a3547d..c3cb0779e0 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.Schemas, 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 0070cec527..d95c79e965 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.Schemas, 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 30af84df32..d99e667013 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.Schemas 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.Schemas, 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 fd727dd836..d21a30a293 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.Schemas, 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.Schemas{}, 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.Schemas{}, 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 a3d2ae669b..ed8987c702 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.Schemas{ 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, - }, - }, - 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}, - }, + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, }, - Nesting: configschema.NestingList, - }, - "nesting_map": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "foo": {Type: cty.String, Optional: true, Sensitive: true}, - }, + "value": { + Type: cty.String, + Computed: true, + Sensitive: true, }, - Nesting: configschema.NestingMap, }, - "nesting_set": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "baz": {Type: cty.String, Optional: true, Sensitive: true}, - }, - }, - Nesting: configschema.NestingSet, - }, - "nesting_single": { - Block: configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "boop": {Type: cty.String, Optional: true, Sensitive: true}, - }, - }, - 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: configschema.NestingList, + 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_map": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": {Type: cty.String, Optional: true, Sensitive: true}, + }, + }, + Nesting: configschema.NestingMap, + }, + "nesting_set": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "baz": {Type: cty.String, Optional: true, Sensitive: true}, + }, + }, + Nesting: configschema.NestingSet, + }, + "nesting_single": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "boop": {Type: cty.String, Optional: true, Sensitive: true}, + }, + }, + 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: 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.Schemas{ 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 04028cf3c4..73dc79bd75 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.Schemas{ 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 c217be2b70..0dc2fb3ca7 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 3b4409d6b4..ffdb3c2389 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.Schemas 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.Schemas) 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 790efb5db3..383269f541 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.Schemas) (*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 bc8e70a8ba..3888be369f 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 85f3f209f3..d65a8685e0 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 98d77d8aa1..390770118f 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.Schemas) 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 0e68ae9942..70f4256827 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 d1be7da792..9c01f3e869 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.Schemas{ + 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.Schemas{ + 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 4d211e8c7d..58f48d57e3 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.Schemas) (*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 caf0d1d310..a9a1c171bc 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.Schemas{ + 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.Schemas{ + 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.Schemas{ + 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 9afd88eb7c..f5b5cb57e2 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 12262afc9e..cd93f22cc2 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 41a14646de..10c8902184 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() diff --git a/internal/terraform/resource_provider_mock_test.go b/internal/terraform/resource_provider_mock_test.go index 0853c67d84..4a71a5301c 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 db3577c005..1d114fcdf0 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.Schemas 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.Schemas { 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.Schemas{}, 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.Schemas, 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.Schemas{} diags = diags.Append( tfdiags.Sourceless( tfdiags.Error, diff --git a/internal/terraform/schemas_test.go b/internal/terraform/schemas_test.go index 46792c70a6..4df542d4bb 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.Schemas{ + 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.Schemas) *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 31e567cde4..b80cb3411c 100644 --- a/internal/terraform/terraform_test.go +++ b/internal/terraform/terraform_test.go @@ -170,24 +170,24 @@ 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 } } diff --git a/internal/terraform/transform_import_state_test.go b/internal/terraform/transform_import_state_test.go index 3229940543..64ad2787cd 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.Schemas{ + 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.Schemas{ + 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 894cf1ff38..758a50627e 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.Schemas{ 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 77cded4524..3d12af440d 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.Schemas) 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 41de79a44c..18c33103e7 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.Schemas{ + ResourceTypes: map[string]providers.Schema{ + "aws_instance": { + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": { + Type: cty.String, + Required: true, + }, }, }, },