218 lines
7.3 KiB
Go
218 lines
7.3 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, plugins *contextPlugins) (*Schemas, error) {
|
|
schemas := &Schemas{
|
|
Providers: map[addrs.Provider]*ProviderSchema{},
|
|
Provisioners: map[string]*configschema.Block{},
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
newDiags := loadProviderSchemas(schemas.Providers, config, state, plugins)
|
|
diags = diags.Append(newDiags)
|
|
newDiags = loadProvisionerSchemas(schemas.Provisioners, config, plugins)
|
|
diags = diags.Append(newDiags)
|
|
|
|
return schemas, diags.Err()
|
|
}
|
|
|
|
func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *configs.Config, state *states.State, plugins *contextPlugins) 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)
|
|
schema, err := plugins.ProviderSchema(fqn)
|
|
if err != nil {
|
|
// 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] = &ProviderSchema{}
|
|
diags = diags.Append(
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Failed to obtain provider schema",
|
|
fmt.Sprintf("Could not load the schema for provider %s: %s.", fqn, err),
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
schemas[fqn] = schema
|
|
}
|
|
|
|
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, plugins *contextPlugins) 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)
|
|
schema, err := plugins.ProvisionerSchema(name)
|
|
if err != nil {
|
|
// 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[name] = &configschema.Block{}
|
|
diags = diags.Append(
|
|
tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Failed to obtain provisioner schema",
|
|
fmt.Sprintf("Could not load the schema for provisioner %q: %s.", name, err),
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
schemas[name] = schema
|
|
}
|
|
|
|
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, plugins)
|
|
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)
|
|
}
|