291 lines
9.6 KiB
Go
291 lines
9.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"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]*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) *ProviderSchema {
|
|
if ss.Providers == nil {
|
|
return nil
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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 == nil || 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, components contextComponentFactory) (*Schemas, error) {
|
|
schemas := &Schemas{
|
|
Providers: map[addrs.Provider]*ProviderSchema{},
|
|
Provisioners: map[string]*configschema.Block{},
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
newDiags := loadProviderSchemas(schemas.Providers, config, state, components)
|
|
diags = diags.Append(newDiags)
|
|
newDiags = loadProvisionerSchemas(schemas.Provisioners, config, components)
|
|
diags = diags.Append(newDiags)
|
|
|
|
return schemas, diags.Err()
|
|
}
|
|
|
|
func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *configs.Config, state *states.State, components contextComponentFactory) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
ensure := func(fqn addrs.Provider) {
|
|
name := fqn.String()
|
|
|
|
if _, exists := schemas[fqn]; exists {
|
|
return
|
|
}
|
|
|
|
log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", name)
|
|
provider, err := components.ResourceProvider(fqn)
|
|
if err != nil {
|
|
// We'll put a stub in the map so we won't re-attempt this on
|
|
// future calls.
|
|
schemas[fqn] = &ProviderSchema{}
|
|
diags = diags.Append(
|
|
fmt.Errorf("failed to instantiate provider %q to obtain schema: %s", name, err),
|
|
)
|
|
return
|
|
}
|
|
defer func() {
|
|
provider.Close()
|
|
}()
|
|
|
|
resp := provider.GetProviderSchema()
|
|
if resp.Diagnostics.HasErrors() {
|
|
// We'll put a stub in the map so we won't re-attempt this on
|
|
// future calls.
|
|
schemas[fqn] = &ProviderSchema{}
|
|
diags = diags.Append(
|
|
fmt.Errorf("failed to retrieve schema from provider %q: %s", name, resp.Diagnostics.Err()),
|
|
)
|
|
return
|
|
}
|
|
|
|
s := &ProviderSchema{
|
|
Provider: resp.Provider.Block,
|
|
ResourceTypes: make(map[string]*configschema.Block),
|
|
DataSources: make(map[string]*configschema.Block),
|
|
|
|
ResourceTypeSchemaVersions: make(map[string]uint64),
|
|
}
|
|
|
|
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.
|
|
diags = diags.Append(
|
|
fmt.Errorf("invalid negative schema version provider configuration for provider %q", name),
|
|
)
|
|
}
|
|
|
|
for t, r := range resp.ResourceTypes {
|
|
if err := r.Block.InternalValidate(); err != nil {
|
|
diags = diags.Append(fmt.Errorf(errProviderSchemaInvalid, name, "resource", t, err))
|
|
}
|
|
s.ResourceTypes[t] = r.Block
|
|
s.ResourceTypeSchemaVersions[t] = uint64(r.Version)
|
|
if r.Version < 0 {
|
|
diags = diags.Append(
|
|
fmt.Errorf("invalid negative schema version for resource type %s in provider %q", t, name),
|
|
)
|
|
}
|
|
}
|
|
|
|
for t, d := range resp.DataSources {
|
|
if err := d.Block.InternalValidate(); err != nil {
|
|
diags = diags.Append(fmt.Errorf(errProviderSchemaInvalid, name, "data source", 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.
|
|
diags = diags.Append(
|
|
fmt.Errorf("invalid negative schema version for data source %s in provider %q", t, name),
|
|
)
|
|
}
|
|
}
|
|
|
|
schemas[fqn] = s
|
|
|
|
if resp.ProviderMeta.Block != nil {
|
|
s.ProviderMeta = resp.ProviderMeta.Block
|
|
}
|
|
}
|
|
|
|
if config != nil {
|
|
for _, fqn := range config.ProviderTypes() {
|
|
ensure(fqn)
|
|
}
|
|
}
|
|
|
|
if state != nil {
|
|
needed := providers.AddressedTypesAbs(state.ProviderAddrs())
|
|
for _, typeAddr := range needed {
|
|
ensure(typeAddr)
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, components contextComponentFactory) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
ensure := func(name string) {
|
|
if _, exists := schemas[name]; exists {
|
|
return
|
|
}
|
|
|
|
log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name)
|
|
provisioner, err := components.ResourceProvisioner(name)
|
|
if err != nil {
|
|
// We'll put a stub in the map so we won't re-attempt this on
|
|
// future calls.
|
|
schemas[name] = &configschema.Block{}
|
|
diags = diags.Append(
|
|
fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %s", name, err),
|
|
)
|
|
return
|
|
}
|
|
defer func() {
|
|
provisioner.Close()
|
|
}()
|
|
|
|
resp := provisioner.GetSchema()
|
|
if resp.Diagnostics.HasErrors() {
|
|
// We'll put a stub in the map so we won't re-attempt this on
|
|
// future calls.
|
|
schemas[name] = &configschema.Block{}
|
|
diags = diags.Append(
|
|
fmt.Errorf("failed to retrieve schema from provisioner %q: %s", name, resp.Diagnostics.Err()),
|
|
)
|
|
return
|
|
}
|
|
|
|
schemas[name] = resp.Provisioner
|
|
}
|
|
|
|
if config != nil {
|
|
for _, rc := range config.Module.ManagedResources {
|
|
for _, pc := range rc.Managed.Provisioners {
|
|
ensure(pc.Type)
|
|
}
|
|
}
|
|
|
|
// Must also visit our child modules, recursively.
|
|
for _, cc := range config.Children {
|
|
childDiags := loadProvisionerSchemas(schemas, cc, components)
|
|
diags = diags.Append(childDiags)
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
// ProviderSchema represents the schema for a provider's own configuration
|
|
// and the configuration for some or all of its resources and data sources.
|
|
//
|
|
// The completeness of this structure depends on how it was constructed.
|
|
// When constructed for a configuration, it will generally include only
|
|
// resource types and data sources used by that configuration.
|
|
type ProviderSchema struct {
|
|
Provider *configschema.Block
|
|
ProviderMeta *configschema.Block
|
|
ResourceTypes map[string]*configschema.Block
|
|
DataSources map[string]*configschema.Block
|
|
|
|
ResourceTypeSchemaVersions map[string]uint64
|
|
}
|
|
|
|
// SchemaForResourceType attempts to find a schema for the given mode and type.
|
|
// Returns nil if no such schema is available.
|
|
func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) {
|
|
switch mode {
|
|
case addrs.ManagedResourceMode:
|
|
return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName]
|
|
case addrs.DataResourceMode:
|
|
// Data resources don't have schema versions right now, since state is discarded for each refresh
|
|
return ps.DataSources[typeName], 0
|
|
default:
|
|
// Shouldn't happen, because the above cases are comprehensive.
|
|
return nil, 0
|
|
}
|
|
}
|
|
|
|
// SchemaForResourceAddr attempts to find a schema for the mode and type from
|
|
// the given resource address. Returns nil if no such schema is available.
|
|
func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
|
|
return ps.SchemaForResourceType(addr.Mode, addr.Type)
|
|
}
|
|
|
|
const errProviderSchemaInvalid = `
|
|
Internal validation of the provider failed! This is always a bug with the
|
|
provider itself, and not a user issue. Please report this bug to the
|
|
maintainers of the %q provider:
|
|
|
|
%s %s: %s
|
|
`
|