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 {