From 034bf4487faa889503b313aa4b719d9cd4d507df Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 11 Mar 2024 17:47:27 -0700 Subject: [PATCH] schemarepo: Schema repositories get their own package It's a cross-cutting concern to need the dynamic value schemas of the various plugins used with a Terraform configuration, but previously we had the type for that in "package terraform", forcing lots of packages to depend on the modules runtime even though they have no direct interest in evaluating Terraform modules. We'll now split it out into its own package "schemarepo". There are currently forwarding aliases left behind in "package terraform" so that we don't need to update all of the callers at once. We'll update the callers whose dependency changes will have the most significant benefits in future commits. The new package is further split into a separate "laodschemas" part that deals with the plugin interactions, because most packages that use a schema repository expect that it was already constructed by someone else, and so don't need or want to depend indirectly on all of the plugin execution machinery. --- internal/schemarepo/doc.go | 7 + internal/schemarepo/loadschemas/doc.go | 6 + .../loadschemas/load.go} | 68 +---- internal/schemarepo/loadschemas/plugins.go | 258 ++++++++++++++++++ internal/schemarepo/schemas.go | 55 ++++ internal/terraform/context_plan.go | 2 +- internal/terraform/context_plugins.go | 238 +--------------- internal/terraform/context_plugins_test.go | 13 +- internal/terraform/schemas_test.go | 6 +- 9 files changed, 359 insertions(+), 294 deletions(-) create mode 100644 internal/schemarepo/doc.go create mode 100644 internal/schemarepo/loadschemas/doc.go rename internal/{terraform/schemas.go => schemarepo/loadschemas/load.go} (51%) create mode 100644 internal/schemarepo/loadschemas/plugins.go create mode 100644 internal/schemarepo/schemas.go diff --git a/internal/schemarepo/doc.go b/internal/schemarepo/doc.go new file mode 100644 index 0000000000..4d66764f5e --- /dev/null +++ b/internal/schemarepo/doc.go @@ -0,0 +1,7 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Package schemarepo deals with the cross-cutting concern of gathering together +// all of the schemas needed for manipulating data produced by the various +// kinds of plugins. +package schemarepo diff --git a/internal/schemarepo/loadschemas/doc.go b/internal/schemarepo/loadschemas/doc.go new file mode 100644 index 0000000000..5c7b25c2ef --- /dev/null +++ b/internal/schemarepo/loadschemas/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Package loadschemas knows how to load schemas from plugins to produce a +// schema repository. +package loadschemas diff --git a/internal/terraform/schemas.go b/internal/schemarepo/loadschemas/load.go similarity index 51% rename from internal/terraform/schemas.go rename to internal/schemarepo/loadschemas/load.go index 7a5f68ea2d..509c142a12 100644 --- a/internal/terraform/schemas.go +++ b/internal/schemarepo/loadschemas/load.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 -package terraform +package loadschemas import ( "fmt" @@ -11,67 +11,15 @@ import ( "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/schemarepo" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/tfdiags" ) -// Schemas is a container for various kinds of schema that Terraform needs -// during processing. -type Schemas struct { - Providers map[addrs.Provider]providers.ProviderSchema - Provisioners map[string]*configschema.Block -} - -// ProviderSchema returns the entire ProviderSchema object that was produced -// by the plugin for the given provider, or nil if no such schema is available. -// -// 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.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 { - return ss.ProviderSchema(provider).Provider.Block -} - -// ResourceTypeConfig returns the schema for the configuration of a given -// resource type belonging to a given provider type, or nil of no such -// schema is available. -// -// In many cases the provider type is inferrable from the resource type name, -// but this is not always true because users can override the provider for -// a resource using the "provider" meta-argument. Therefore it's important to -// always pass the correct provider name, even though it many cases it feels -// redundant. -func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { - ps := ss.ProviderSchema(provider) - if ps.ResourceTypes == nil { - return nil, 0 - } - return ps.SchemaForResourceType(resourceMode, resourceType) -} - -// ProvisionerConfig returns the schema for the configuration of a given -// provisioner, or nil of no such schema is available. -func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block { - return ss.Provisioners[name] -} - -// loadSchemas searches the given configuration, state and plan (any of which -// may be nil) for constructs that have an associated schema, requests the -// necessary schemas from the given component factory (which must _not_ be nil), -// and returns a single object representing all of the necessary schemas. -// -// If an error is returned, it may be a wrapped tfdiags.Diagnostics describing -// errors across multiple separate objects. Errors here will usually indicate -// either misbehavior on the part of one of the providers or of the provider -// protocol itself. When returned with errors, the returned schemas object is -// still valid but may be incomplete. -func loadSchemas(config *configs.Config, state *states.State, plugins *contextPlugins) (*Schemas, error) { - schemas := &Schemas{ +// LoadSchemas loads all of the schemas that might be needed to work with the +// given configuration and state, using the given plugins. +func LoadSchemas(config *configs.Config, state *states.State, plugins *Plugins) (*schemarepo.Schemas, error) { + schemas := &schemarepo.Schemas{ Providers: map[addrs.Provider]providers.ProviderSchema{}, Provisioners: map[string]*configschema.Block{}, } @@ -85,7 +33,7 @@ func loadSchemas(config *configs.Config, state *states.State, plugins *contextPl return schemas, diags.Err() } -func loadProviderSchemas(schemas map[addrs.Provider]providers.ProviderSchema, 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 *Plugins) tfdiags.Diagnostics { var diags tfdiags.Diagnostics ensure := func(fqn addrs.Provider) { @@ -131,7 +79,7 @@ func loadProviderSchemas(schemas map[addrs.Provider]providers.ProviderSchema, co return diags } -func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, plugins *contextPlugins) tfdiags.Diagnostics { +func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, plugins *Plugins) tfdiags.Diagnostics { var diags tfdiags.Diagnostics ensure := func(name string) { diff --git a/internal/schemarepo/loadschemas/plugins.go b/internal/schemarepo/loadschemas/plugins.go new file mode 100644 index 0000000000..1bff1dbf4b --- /dev/null +++ b/internal/schemarepo/loadschemas/plugins.go @@ -0,0 +1,258 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package loadschemas + +import ( + "fmt" + "log" + + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/provisioners" +) + +// Plugins represents a library of available plugins for which it's safe +// to cache certain information for performance reasons. +type Plugins struct { + providerFactories map[addrs.Provider]providers.Factory + provisionerFactories map[string]provisioners.Factory + + preloadedProviderSchemas map[addrs.Provider]providers.ProviderSchema +} + +func NewPlugins( + providerFactories map[addrs.Provider]providers.Factory, + provisionerFactories map[string]provisioners.Factory, + preloadedProviderSchemas map[addrs.Provider]providers.ProviderSchema, +) *Plugins { + ret := &Plugins{ + providerFactories: providerFactories, + provisionerFactories: provisionerFactories, + preloadedProviderSchemas: preloadedProviderSchemas, + } + return ret +} + +// ProviderFactories returns a map of all of the registered provider factories. +// +// Callers must not modify the returned map and must not access it concurrently +// with any other method of this type. +func (cp *Plugins) ProviderFactories() map[addrs.Provider]providers.Factory { + return cp.providerFactories +} + +func (cp *Plugins) HasProvider(addr addrs.Provider) bool { + _, ok := cp.providerFactories[addr] + return ok +} + +func (cp *Plugins) HasPreloadedSchemaForProvider(addr addrs.Provider) bool { + _, ok := cp.preloadedProviderSchemas[addr] + return ok +} + +func (cp *Plugins) NewProviderInstance(addr addrs.Provider) (providers.Interface, error) { + f, ok := cp.providerFactories[addr] + if !ok { + return nil, fmt.Errorf("unavailable provider %q", addr.String()) + } + + return f() +} + +// ProvisionerFactories returns a map of all of the registered provisioner +// factories. +// +// Callers must not modify the returned map and must not access it concurrently +// with any other method of this type. +func (cp *Plugins) ProvisionerFactories() map[string]provisioners.Factory { + return cp.provisionerFactories +} + +func (cp *Plugins) HasProvisioner(typ string) bool { + _, ok := cp.provisionerFactories[typ] + return ok +} + +func (cp *Plugins) NewProvisionerInstance(typ string) (provisioners.Interface, error) { + f, ok := cp.provisionerFactories[typ] + if !ok { + return nil, fmt.Errorf("unavailable provisioner %q", typ) + } + + return f() +} + +// ProviderSchema uses a temporary instance of the provider with the given +// address to obtain the full schema for all aspects of that provider. +// +// 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 *Plugins) ProviderSchema(addr addrs.Provider) (providers.ProviderSchema, error) { + // Check the global schema cache first. + // This cache is only written by the provider client, and transparently + // used by GetProviderSchema, but we check it here because at this point we + // may be able to avoid spinning up the provider instance at all. + // We skip this if we have preloaded schemas because that suggests that + // our caller is not Terraform CLI and therefore it's probably inappropriate + // to assume that provider schemas are unique process-wide. + // + // FIXME: A global cache is inappropriate when Terraform Core is being + // used in a non-Terraform-CLI mode where we shouldn't assume that all + // calls share the same provider implementations. + schemas, ok := providers.SchemaCache.Get(addr) + if ok { + log.Printf("[TRACE] terraform.contextPlugins: Schema for provider %q is in the global cache", addr) + return schemas, nil + } + + // We might have a non-global preloaded copy of this provider's schema. + if schema, ok := cp.preloadedProviderSchemas[addr]; ok { + log.Printf("[TRACE] terraform.contextPlugins: Provider %q has a preloaded schema", addr) + return schema, nil + } + + log.Printf("[TRACE] terraform.contextPlugins: Initializing provider %q to read its schema", addr) + provider, err := cp.NewProviderInstance(addr) + if err != nil { + 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 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 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 resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %q", addr, t, err) + } + if r.Version < 0 { + 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 resp, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %q", addr, t, err) + } + 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 resp, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t) + } + } + + for n, f := range resp.Functions { + if !hclsyntax.ValidIdentifier(n) { + return resp, fmt.Errorf("provider %s declares function with invalid name %q", addr, n) + } + // We'll also do some enforcement of parameter names, even though they + // are only for docs/UI for now, to leave room for us to potentially + // use them for other purposes later. + seenParams := make(map[string]int, len(f.Parameters)) + for i, p := range f.Parameters { + if !hclsyntax.ValidIdentifier(p.Name) { + return resp, fmt.Errorf("provider %s function %q declares invalid name %q for parameter %d", addr, n, p.Name, i) + } + if prevIdx, exists := seenParams[p.Name]; exists { + return resp, fmt.Errorf("provider %s function %q reuses name %q for both parameters %d and %d", addr, n, p.Name, prevIdx, i) + } + seenParams[p.Name] = i + } + if p := f.VariadicParameter; p != nil { + if !hclsyntax.ValidIdentifier(p.Name) { + return resp, fmt.Errorf("provider %s function %q declares invalid name %q for its variadic parameter", addr, n, p.Name) + } + if prevIdx, exists := seenParams[p.Name]; exists { + return resp, fmt.Errorf("provider %s function %q reuses name %q for both parameter %d and its variadic parameter", addr, n, p.Name, prevIdx) + } + } + } + + return resp, nil +} + +// ProviderConfigSchema is a helper wrapper around ProviderSchema which first +// reads the full schema of the given provider and then extracts just the +// provider's configuration schema, which defines what's expected in a +// "provider" block in the configuration when configuring this provider. +func (cp *Plugins) ProviderConfigSchema(providerAddr addrs.Provider) (*configschema.Block, error) { + providerSchema, err := cp.ProviderSchema(providerAddr) + if err != nil { + return nil, err + } + + return providerSchema.Provider.Block, nil +} + +// ResourceTypeSchema is a helper wrapper around ProviderSchema which first +// reads the schema of the given provider and then tries to find the schema +// for the resource type of the given resource mode in that provider. +// +// ResourceTypeSchema will return an error if the provider schema lookup +// fails, but will return nil if the provider schema lookup succeeds but then +// the provider doesn't have a resource of the requested type. +// +// Managed resource types have versioned schemas, so the second return value +// is the current schema version number for the requested resource. The version +// is irrelevant for other resource modes. +func (cp *Plugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) { + providerSchema, err := cp.ProviderSchema(providerAddr) + if err != nil { + return nil, 0, err + } + + schema, version := providerSchema.SchemaForResourceType(resourceMode, resourceType) + return schema, version, nil +} + +// ProvisionerSchema uses a temporary instance of the provisioner with the +// given type name to obtain the schema for that provisioner's configuration. +// +// ProvisionerSchema memoizes results by provisioner type name, so it's fine +// to repeatedly call this method with the same name if various different +// parts of Terraform all need the same schema information. +func (cp *Plugins) ProvisionerSchema(typ string) (*configschema.Block, error) { + log.Printf("[TRACE] terraform.contextPlugins: Initializing provisioner %q to read its schema", typ) + provisioner, err := cp.NewProvisionerInstance(typ) + if err != nil { + return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %s", typ, err) + } + defer provisioner.Close() + + resp := provisioner.GetSchema() + if resp.Diagnostics.HasErrors() { + return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %s", typ, resp.Diagnostics.Err()) + } + + return resp.Provisioner, nil +} + +// ProviderFunctionDecls is a helper wrapper around ProviderSchema which first +// reads the schema of the given provider and then returns all of the +// functions it declares, if any. +// +// ProviderFunctionDecl will return an error if the provider schema lookup +// fails, but will return an empty set of functions if a successful response +// returns no functions, or if the provider is using an older protocol version +// which has no support for provider-contributed functions. +func (cp *Plugins) ProviderFunctionDecls(providerAddr addrs.Provider) (map[string]providers.FunctionDecl, error) { + providerSchema, err := cp.ProviderSchema(providerAddr) + if err != nil { + return nil, err + } + + return providerSchema.Functions, nil +} diff --git a/internal/schemarepo/schemas.go b/internal/schemarepo/schemas.go new file mode 100644 index 0000000000..5fad3fd9cc --- /dev/null +++ b/internal/schemarepo/schemas.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package schemarepo + +import ( + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/providers" +) + +// Schemas is a container for various kinds of schema that Terraform needs +// during processing. +type Schemas struct { + Providers map[addrs.Provider]providers.ProviderSchema + Provisioners map[string]*configschema.Block +} + +// ProviderSchema returns the entire ProviderSchema object that was produced +// by the plugin for the given provider, or nil if no such schema is available. +// +// 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.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 { + return ss.ProviderSchema(provider).Provider.Block +} + +// ResourceTypeConfig returns the schema for the configuration of a given +// resource type belonging to a given provider type, or nil of no such +// schema is available. +// +// In many cases the provider type is inferrable from the resource type name, +// but this is not always true because users can override the provider for +// a resource using the "provider" meta-argument. Therefore it's important to +// always pass the correct provider name, even though it many cases it feels +// redundant. +func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { + ps := ss.ProviderSchema(provider) + if ps.ResourceTypes == nil { + return nil, 0 + } + return ps.SchemaForResourceType(resourceMode, resourceType) +} + +// ProvisionerConfig returns the schema for the configuration of a given +// provisioner, or nil of no such schema is available. +func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block { + return ss.Provisioners[name] +} diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index e43eacfb13..7facb219c4 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -513,7 +513,7 @@ func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState moveStmts = append(moveStmts, explicitMoveStmts...) moveStmts = append(moveStmts, implicitMoveStmts...) } - moveResults, diags := refactoring.ApplyMoves(moveStmts, prevRunState, c.plugins.providerFactories) + moveResults, diags := refactoring.ApplyMoves(moveStmts, prevRunState, c.plugins.ProviderFactories()) return moveStmts, moveResults, diags } diff --git a/internal/terraform/context_plugins.go b/internal/terraform/context_plugins.go index 33df850204..883c64507f 100644 --- a/internal/terraform/context_plugins.go +++ b/internal/terraform/context_plugins.go @@ -4,241 +4,29 @@ package terraform import ( - "fmt" - "log" - - "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/provisioners" + "github.com/hashicorp/terraform/internal/schemarepo" + "github.com/hashicorp/terraform/internal/schemarepo/loadschemas" + "github.com/hashicorp/terraform/internal/states" ) -// contextPlugins represents a library of available plugins (providers and -// provisioners) which we assume will all be used with the same -// terraform.Context, and thus it'll be safe to cache certain information -// about the providers for performance reasons. -type contextPlugins struct { - providerFactories map[addrs.Provider]providers.Factory - provisionerFactories map[string]provisioners.Factory - - preloadedProviderSchemas map[addrs.Provider]providers.ProviderSchema -} +// contextPlugins is a deprecated old name for loadschemas.Plugins +type contextPlugins = loadschemas.Plugins func newContextPlugins( providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory, preloadedProviderSchemas map[addrs.Provider]providers.ProviderSchema, -) *contextPlugins { - ret := &contextPlugins{ - providerFactories: providerFactories, - provisionerFactories: provisionerFactories, - preloadedProviderSchemas: preloadedProviderSchemas, - } - return ret +) *loadschemas.Plugins { + return loadschemas.NewPlugins(providerFactories, provisionerFactories, preloadedProviderSchemas) } -func (cp *contextPlugins) HasProvider(addr addrs.Provider) bool { - _, ok := cp.providerFactories[addr] - return ok -} - -func (cp *contextPlugins) HasPreloadedSchemaForProvider(addr addrs.Provider) bool { - _, ok := cp.preloadedProviderSchemas[addr] - return ok -} - -func (cp *contextPlugins) NewProviderInstance(addr addrs.Provider) (providers.Interface, error) { - f, ok := cp.providerFactories[addr] - if !ok { - return nil, fmt.Errorf("unavailable provider %q", addr.String()) - } - - return f() - -} - -func (cp *contextPlugins) HasProvisioner(typ string) bool { - _, ok := cp.provisionerFactories[typ] - return ok -} - -func (cp *contextPlugins) NewProvisionerInstance(typ string) (provisioners.Interface, error) { - f, ok := cp.provisionerFactories[typ] - if !ok { - return nil, fmt.Errorf("unavailable provisioner %q", typ) - } - - return f() -} - -// ProviderSchema uses a temporary instance of the provider with the given -// address to obtain the full schema for all aspects of that provider. -// -// 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) (providers.ProviderSchema, error) { - // Check the global schema cache first. - // This cache is only written by the provider client, and transparently - // used by GetProviderSchema, but we check it here because at this point we - // may be able to avoid spinning up the provider instance at all. - // We skip this if we have preloaded schemas because that suggests that - // our caller is not Terraform CLI and therefore it's probably inappropriate - // to assume that provider schemas are unique process-wide. - // - // FIXME: A global cache is inappropriate when Terraform Core is being - // used in a non-Terraform-CLI mode where we shouldn't assume that all - // calls share the same provider implementations. - schemas, ok := providers.SchemaCache.Get(addr) - if ok { - log.Printf("[TRACE] terraform.contextPlugins: Schema for provider %q is in the global cache", addr) - return schemas, nil - } - - // We might have a non-global preloaded copy of this provider's schema. - if schema, ok := cp.preloadedProviderSchemas[addr]; ok { - log.Printf("[TRACE] terraform.contextPlugins: Provider %q has a preloaded schema", addr) - return schema, nil - } - - log.Printf("[TRACE] terraform.contextPlugins: Initializing provider %q to read its schema", addr) - provider, err := cp.NewProviderInstance(addr) - if err != nil { - 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 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 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 resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %q", addr, t, err) - } - if r.Version < 0 { - 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 resp, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %q", addr, t, err) - } - 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 resp, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t) - } - } - - for n, f := range resp.Functions { - if !hclsyntax.ValidIdentifier(n) { - return resp, fmt.Errorf("provider %s declares function with invalid name %q", addr, n) - } - // We'll also do some enforcement of parameter names, even though they - // are only for docs/UI for now, to leave room for us to potentially - // use them for other purposes later. - seenParams := make(map[string]int, len(f.Parameters)) - for i, p := range f.Parameters { - if !hclsyntax.ValidIdentifier(p.Name) { - return resp, fmt.Errorf("provider %s function %q declares invalid name %q for parameter %d", addr, n, p.Name, i) - } - if prevIdx, exists := seenParams[p.Name]; exists { - return resp, fmt.Errorf("provider %s function %q reuses name %q for both parameters %d and %d", addr, n, p.Name, prevIdx, i) - } - seenParams[p.Name] = i - } - if p := f.VariadicParameter; p != nil { - if !hclsyntax.ValidIdentifier(p.Name) { - return resp, fmt.Errorf("provider %s function %q declares invalid name %q for its variadic parameter", addr, n, p.Name) - } - if prevIdx, exists := seenParams[p.Name]; exists { - return resp, fmt.Errorf("provider %s function %q reuses name %q for both parameter %d and its variadic parameter", addr, n, p.Name, prevIdx) - } - } - } - - return resp, nil -} - -// ProviderConfigSchema is a helper wrapper around ProviderSchema which first -// reads the full schema of the given provider and then extracts just the -// provider's configuration schema, which defines what's expected in a -// "provider" block in the configuration when configuring this provider. -func (cp *contextPlugins) ProviderConfigSchema(providerAddr addrs.Provider) (*configschema.Block, error) { - providerSchema, err := cp.ProviderSchema(providerAddr) - if err != nil { - return nil, err - } - - return providerSchema.Provider.Block, nil -} - -// ResourceTypeSchema is a helper wrapper around ProviderSchema which first -// reads the schema of the given provider and then tries to find the schema -// for the resource type of the given resource mode in that provider. -// -// ResourceTypeSchema will return an error if the provider schema lookup -// fails, but will return nil if the provider schema lookup succeeds but then -// the provider doesn't have a resource of the requested type. -// -// Managed resource types have versioned schemas, so the second return value -// is the current schema version number for the requested resource. The version -// is irrelevant for other resource modes. -func (cp *contextPlugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) { - providerSchema, err := cp.ProviderSchema(providerAddr) - if err != nil { - return nil, 0, err - } - - schema, version := providerSchema.SchemaForResourceType(resourceMode, resourceType) - return schema, version, nil -} - -// ProvisionerSchema uses a temporary instance of the provisioner with the -// given type name to obtain the schema for that provisioner's configuration. -// -// ProvisionerSchema memoizes results by provisioner type name, so it's fine -// 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) { - log.Printf("[TRACE] terraform.contextPlugins: Initializing provisioner %q to read its schema", typ) - provisioner, err := cp.NewProvisionerInstance(typ) - if err != nil { - return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %s", typ, err) - } - defer provisioner.Close() - - resp := provisioner.GetSchema() - if resp.Diagnostics.HasErrors() { - return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %s", typ, resp.Diagnostics.Err()) - } - - return resp.Provisioner, nil -} - -// ProviderFunctionDecls is a helper wrapper around ProviderSchema which first -// reads the schema of the given provider and then returns all of the -// functions it declares, if any. -// -// ProviderFunctionDecl will return an error if the provider schema lookup -// fails, but will return an empty set of functions if a successful response -// returns no functions, or if the provider is using an older protocol version -// which has no support for provider-contributed functions. -func (cp *contextPlugins) ProviderFunctionDecls(providerAddr addrs.Provider) (map[string]providers.FunctionDecl, error) { - providerSchema, err := cp.ProviderSchema(providerAddr) - if err != nil { - return nil, err - } - - return providerSchema.Functions, nil +// Schemas is a deprecated old name for schemarepo.Schemas +type Schemas = schemarepo.Schemas + +func loadSchemas(config *configs.Config, state *states.State, plugins *loadschemas.Plugins) (*schemarepo.Schemas, error) { + return loadschemas.LoadSchemas(config, state, plugins) } diff --git a/internal/terraform/context_plugins_test.go b/internal/terraform/context_plugins_test.go index c8df540275..a6764a82e5 100644 --- a/internal/terraform/context_plugins_test.go +++ b/internal/terraform/context_plugins_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/provisioners" + "github.com/hashicorp/terraform/internal/schemarepo/loadschemas" ) // simpleMockPluginLibrary returns a plugin library pre-configured with @@ -22,26 +23,26 @@ import ( // Each call to this function produces an entirely-separate set of objects, // so the caller can feel free to modify the returned value to further // customize the mocks contained within. -func simpleMockPluginLibrary() *contextPlugins { +func simpleMockPluginLibrary() *loadschemas.Plugins { // We create these out here, rather than in the factory functions below, // because we want each call to the factory to return the _same_ instance, // so that test code can customize it before passing this component // factory into real code under test. provider := simpleMockProvider() provisioner := simpleMockProvisioner() - ret := &contextPlugins{ - providerFactories: map[addrs.Provider]providers.Factory{ + return loadschemas.NewPlugins( + map[addrs.Provider]providers.Factory{ addrs.NewDefaultProvider("test"): func() (providers.Interface, error) { return provider, nil }, }, - provisionerFactories: map[string]provisioners.Factory{ + map[string]provisioners.Factory{ "test": func() (provisioners.Interface, error) { return provisioner, nil }, }, - } - return ret + nil, + ) } // simpleTestSchema returns a block schema that contains a few optional diff --git a/internal/terraform/schemas_test.go b/internal/terraform/schemas_test.go index af76fb7081..71e447ce0f 100644 --- a/internal/terraform/schemas_test.go +++ b/internal/terraform/schemas_test.go @@ -8,9 +8,11 @@ import ( "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/providers/testing" + "github.com/hashicorp/terraform/internal/schemarepo" + "github.com/hashicorp/terraform/internal/schemarepo/loadschemas" ) -func simpleTestSchemas() *Schemas { +func simpleTestSchemas() *schemarepo.Schemas { provider := simpleMockProvider() provisioner := simpleMockProvisioner() @@ -32,7 +34,7 @@ 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]providers.ProviderSchema) *contextPlugins { +func schemaOnlyProvidersForTesting(schemas map[addrs.Provider]providers.ProviderSchema) *loadschemas.Plugins { factories := make(map[addrs.Provider]providers.Factory, len(schemas)) for providerAddr, schema := range schemas {