terraform/internal/earlyconfig/config.go

211 lines
7.1 KiB
Go

package earlyconfig
import (
"fmt"
"sort"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-config-inspect/tfconfig"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/moduledeps"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/tfdiags"
)
// A Config is a node in the tree of modules within a configuration.
//
// The module tree is constructed by following ModuleCall instances recursively
// through the root module transitively into descendent modules.
type Config struct {
// RootModule points to the Config for the root module within the same
// module tree as this module. If this module _is_ the root module then
// this is self-referential.
Root *Config
// ParentModule points to the Config for the module that directly calls
// this module. If this is the root module then this field is nil.
Parent *Config
// Path is a sequence of module logical names that traverse from the root
// module to this config. Path is empty for the root module.
//
// This should only be used to display paths to the end-user in rare cases
// where we are talking about the static module tree, before module calls
// have been resolved. In most cases, an addrs.ModuleInstance describing
// a node in the dynamic module tree is better, since it will then include
// any keys resulting from evaluating "count" and "for_each" arguments.
Path addrs.Module
// ChildModules points to the Config for each of the direct child modules
// called from this module. The keys in this map match the keys in
// Module.ModuleCalls.
Children map[string]*Config
// Module points to the object describing the configuration for the
// various elements (variables, resources, etc) defined by this module.
Module *tfconfig.Module
// CallPos is the source position for the header of the module block that
// requested this module.
//
// This field is meaningless for the root module, where its contents are undefined.
CallPos tfconfig.SourcePos
// SourceAddr is the source address that the referenced module was requested
// from, as specified in configuration.
//
// This field is meaningless for the root module, where its contents are undefined.
SourceAddr string
// Version is the specific version that was selected for this module,
// based on version constraints given in configuration.
//
// This field is nil if the module was loaded from a non-registry source,
// since versions are not supported for other sources.
//
// This field is meaningless for the root module, where it will always
// be nil.
Version *version.Version
}
// ProviderRequirements searches the full tree of modules under the receiver
// for both explicit and implicit dependencies on providers.
//
// The result is a full manifest of all of the providers that must be available
// in order to work with the receiving configuration.
//
// If the returned diagnostics includes errors then the resulting Requirements
// may be incomplete.
func (c *Config) ProviderRequirements() (getproviders.Requirements, tfdiags.Diagnostics) {
reqs := make(getproviders.Requirements)
diags := c.addProviderRequirements(reqs)
return reqs, diags
}
// addProviderRequirements is the main part of the ProviderRequirements
// implementation, gradually mutating a shared requirements object to
// eventually return.
func (c *Config) addProviderRequirements(reqs getproviders.Requirements) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
// First we'll deal with the requirements directly in _our_ module...
for localName, providerReqs := range c.Module.RequiredProviders {
var fqn addrs.Provider
if source := providerReqs.Source; source != "" {
addr, moreDiags := addrs.ParseProviderSourceString(source)
if moreDiags.HasErrors() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider source address",
fmt.Sprintf("Invalid source %q for provider %q in %s", source, localName, c.Path),
))
continue
}
fqn = addr
}
if fqn.IsZero() {
fqn = addrs.NewDefaultProvider(localName)
}
if _, ok := reqs[fqn]; !ok {
// We'll at least have an unconstrained dependency then, but might
// add to this in the loop below.
reqs[fqn] = nil
}
for _, constraintsStr := range providerReqs.VersionConstraints {
if constraintsStr != "" {
constraints, err := getproviders.ParseVersionConstraints(constraintsStr)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider version constraint",
fmt.Sprintf("Provider %q in %s has invalid version constraint %q: %s.", localName, c.Path, constraintsStr, err),
))
continue
}
reqs[fqn] = append(reqs[fqn], constraints...)
}
}
}
// ...and now we'll recursively visit all of the child modules to merge
// in their requirements too.
for _, childConfig := range c.Children {
moreDiags := childConfig.addProviderRequirements(reqs)
diags = diags.Append(moreDiags)
}
return diags
}
// ProviderDependencies is a deprecated variant of ProviderRequirements which
// uses the moduledeps models for representation. This is preserved to allow
// a gradual transition over to ProviderRequirements, but note that its
// support for fully-qualified provider addresses has some idiosyncracies.
func (c *Config) ProviderDependencies() (*moduledeps.Module, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
var name string
if len(c.Path) > 0 {
name = c.Path[len(c.Path)-1]
}
ret := &moduledeps.Module{
Name: name,
}
providers := make(moduledeps.Providers)
for name, reqs := range c.Module.RequiredProviders {
var fqn addrs.Provider
if source := reqs.Source; source != "" {
addr, diags := addrs.ParseProviderSourceString(source)
if diags.HasErrors() {
diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
Severity: tfconfig.DiagError,
Summary: "Invalid provider source",
Detail: fmt.Sprintf("Invalid source %q for provider", name),
}))
continue
}
fqn = addr
}
if fqn.IsZero() {
fqn = addrs.NewLegacyProvider(name)
}
var constraints version.Constraints
for _, reqStr := range reqs.VersionConstraints {
if reqStr != "" {
constraint, err := version.NewConstraint(reqStr)
if err != nil {
diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
Severity: tfconfig.DiagError,
Summary: "Invalid provider version constraint",
Detail: fmt.Sprintf("Invalid version constraint %q for provider %s.", reqStr, fqn.LegacyString()),
}))
continue
}
constraints = append(constraints, constraint...)
}
}
providers[fqn] = moduledeps.ProviderDependency{
Constraints: discovery.NewConstraints(constraints),
Reason: moduledeps.ProviderDependencyExplicit,
}
}
ret.Providers = providers
childNames := make([]string, 0, len(c.Children))
for name := range c.Children {
childNames = append(childNames, name)
}
sort.Strings(childNames)
for _, name := range childNames {
child, childDiags := c.Children[name].ProviderDependencies()
ret.Children = append(ret.Children, child)
diags = diags.Append(childDiags)
}
return ret, diags
}