You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/internal/stacks/stackruntime/internal/stackeval/component_config.go

713 lines
27 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package stackeval
import (
"context"
"fmt"
"github.com/apparentlymart/go-versions/versions"
"github.com/hashicorp/go-slug/sourceaddrs"
"github.com/hashicorp/go-slug/sourcebundle"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/typeexpr"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/promising"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackconfig"
"github.com/hashicorp/terraform/internal/stacks/stackconfig/stackconfigtypes"
"github.com/hashicorp/terraform/internal/stacks/stackplan"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
)
type ComponentConfig struct {
addr stackaddrs.ConfigComponent
config *stackconfig.Component
main *Main
validate promising.Once[tfdiags.Diagnostics]
moduleTree promising.Once[withDiagnostics[*configs.Config]]
}
func newComponentConfig(main *Main, addr stackaddrs.ConfigComponent, config *stackconfig.Component) *ComponentConfig {
return &ComponentConfig{
addr: addr,
config: config,
main: main,
}
}
func (c *ComponentConfig) Addr() stackaddrs.ConfigComponent {
return c.addr
}
func (c *ComponentConfig) Declaration(ctx context.Context) *stackconfig.Component {
return c.config
}
func (c *ComponentConfig) StackConfig(ctx context.Context) *StackConfig {
return c.main.mustStackConfig(ctx, c.addr.Stack)
}
// ModuleTree returns the static representation of the tree of modules starting
// at the component's configured source address, or nil if any of the
// modules have errors that prevent even static decoding.
func (c *ComponentConfig) ModuleTree(ctx context.Context) *configs.Config {
ret, _ := c.CheckModuleTree(ctx)
return ret
}
// CheckModuleTree loads the tree of Terraform modules starting at the
// component block's configured source address, returning the resulting
// configuration object if successful.
//
// If the module has any problems that prevent even static decoding then
// this instead returns diagnostics and a nil configuration object.
func (c *ComponentConfig) CheckModuleTree(ctx context.Context) (*configs.Config, tfdiags.Diagnostics) {
return doOnceWithDiags(
ctx, &c.moduleTree, c.main,
func(ctx context.Context) (*configs.Config, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
decl := c.Declaration(ctx)
sources := c.main.SourceBundle(ctx)
rootModuleSource := decl.FinalSourceAddr
if rootModuleSource == nil {
// If we get here then the configuration was loaded incorrectly,
// either by the stackconfig package or by the caller of the
// stackconfig package using the wrong loading function.
panic("component configuration lacks final source address")
}
parser := configs.NewSourceBundleParser(sources)
parser.AllowLanguageExperiments(c.main.LanguageExperimentsAllowed())
if !parser.IsConfigDir(rootModuleSource) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Can't load module for component",
Detail: fmt.Sprintf("The source location %s does not contain a Terraform module.", rootModuleSource),
Subject: decl.SourceAddrRange.ToHCL().Ptr(),
})
return nil, diags
}
rootMod, hclDiags := parser.LoadConfigDir(rootModuleSource)
diags = diags.Append(hclDiags)
if hclDiags.HasErrors() {
return nil, diags
}
walker := newSourceBundleModuleWalker(rootModuleSource, sources, parser)
configRoot, hclDiags := configs.BuildConfig(rootMod, walker, nil)
diags = diags.Append(hclDiags)
if hclDiags.HasErrors() {
return nil, diags
}
// We also have a small selection of additional static validation
// rules that apply only to modules used within stack components.
diags = diags.Append(c.validateModuleTreeForStacks(configRoot))
return configRoot, diags
},
)
}
// validateModuleTreeForStacks imposes some additional validation constraints
// on a module tree after it's been loaded by the main configuration packages.
//
// These rules deal with a small number of exceptions where the modules language
// as used by stacks is a subset of the modules language from traditional
// Terraform. Not all such exceptions are handled in this way because
// some of them cannot be handled statically, but this is a reasonable place
// to handle the simpler concerns and allows us to return error messages that
// talk specifically about stacks, which would be harder to achieve if these
// exceptions were made at a different layer.
func (c *ComponentConfig) validateModuleTreeForStacks(startNode *configs.Config) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
diags = diags.Append(c.validateModuleForStacks(startNode.Path, startNode.Module))
for _, childNode := range startNode.Children {
diags = diags.Append(c.validateModuleTreeForStacks(childNode))
}
return diags
}
func (c *ComponentConfig) validateModuleForStacks(moduleAddr addrs.Module, module *configs.Module) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
// Inline provider configurations are not allowed when running under stacks,
// because provider configurations live in the stack configuration and
// then get passed in to the modules as special arguments.
for _, pc := range module.ProviderConfigs {
// We use some slightly different language for the topmost module
// that's being directly called from the stack configuration, because
// we can give some direct advice for how to correct the problem there,
// whereas for a nested module we assume that it's a third-party module
// written for much older versions of Terraform before we deprecated
// inline provider configurations and thus the solution is most likely
// to be selecting a different module that is Stacks-compatible, because
// removing a legacy inline provider configuration from a shared module
// would be a breaking change to that module.
if moduleAddr.IsRoot() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Inline provider configuration not allowed",
Detail: "A module used as a stack component must have all of its provider configurations passed from the stack configuration, using the \"providers\" argument within the component configuration block.",
Subject: pc.DeclRange.Ptr(),
})
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Inline provider configuration not allowed",
Detail: "This module is not compatible with Terraform Stacks, because it declares an inline provider configuration.\n\nTo be used with stacks, this module must instead accept provider configurations from its caller.",
Subject: pc.DeclRange.Ptr(),
})
}
}
return diags
}
// InputsType returns an object type that the object representing the caller's
// values for this component's input variables must conform to.
func (c *ComponentConfig) InputsType(ctx context.Context) (cty.Type, *typeexpr.Defaults) {
moduleTree := c.ModuleTree(ctx)
if moduleTree == nil {
// If the module tree is invalid itself then we can't determine which
// input variables are declared.
return cty.NilType, nil
}
vars := moduleTree.Module.Variables
atys := make(map[string]cty.Type, len(vars))
defs := &typeexpr.Defaults{
DefaultValues: make(map[string]cty.Value),
Children: map[string]*typeexpr.Defaults{},
}
var opts []string
for name, v := range vars {
atys[name] = v.ConstraintType
if def := v.Default; def != cty.NilVal {
defs.DefaultValues[name] = def
opts = append(opts, name)
}
if childDefs := v.TypeDefaults; childDefs != nil {
defs.Children[name] = childDefs
}
}
retTy := cty.ObjectWithOptionalAttrs(atys, opts)
defs.Type = retTy
return retTy, defs
}
func (c *ComponentConfig) CheckInputVariableValues(ctx context.Context, phase EvalPhase) tfdiags.Diagnostics {
wantTy, defs := c.InputsType(ctx)
if wantTy == cty.NilType {
// Suggests that the module tree is invalid. We validate the full module
// tree elsewhere, which will hopefully detect the problems here.
return nil
}
decl := c.Declaration(ctx)
// We don't care about the returned value, only that it has no errors.
_, diags := EvalComponentInputVariables(ctx, wantTy, defs, decl, phase, c)
return diags
}
// RequiredProviderInstances returns a description of all of the provider
// instance slots ("provider configurations" in main Terraform language
// terminology) that are either explicitly declared or implied by the
// root module of the component's module tree.
//
// The component configuration must include a "providers" argument that
// binds each of these slots to a real provider instance in the stack
// configuration, by referring to dynamic values of the appropriate
// provider instance reference type.
//
// In the returned map the keys describe provider configurations from
// the perspective of an object inside the root module, and so the LocalName
// field values are an implementation detail that must not be exposed into
// the calling stack and are included here only so that we can potentially
// return error messages referring to declarations inside the module.
//
// If any modules in the component's root module tree are invalid then this
// result could under-promise or over-promise depending on the kind of
// invalidity.
func (c *ComponentConfig) RequiredProviderInstances(ctx context.Context) addrs.Map[addrs.RootProviderConfig, addrs.LocalProviderConfig] {
moduleTree := c.ModuleTree(ctx)
if moduleTree == nil || moduleTree.Root == nil {
// If we get here then we presumably failed to load the module, and
// so we'll just unwind quickly so a different return path can return
// the error diagnostics.
return addrs.MakeMap[addrs.RootProviderConfig, addrs.LocalProviderConfig]()
}
return moduleTree.Root.EffectiveRequiredProviderConfigs()
}
func (c *ComponentConfig) CheckProviders(ctx context.Context, phase EvalPhase) (addrs.Set[addrs.RootProviderConfig], tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
stackConfig := c.StackConfig(ctx)
declConfigs := c.Declaration(ctx).ProviderConfigs
neededProviders := c.RequiredProviderInstances(ctx)
ret := addrs.MakeSet[addrs.RootProviderConfig]()
for _, elem := range neededProviders.Elems {
// sourceAddr is the addrs.RootProviderConfig that should be used to
// set this provider in the component later.
sourceAddr := elem.Key
// componentAddr is the addrs.LocalProviderConfig that specifies the
// local name and (optional) alias of the provider in the component.
componentAddr := elem.Value
// typeAddr is the absolute address of the provider type itself.
typeAddr := sourceAddr.Provider
// This type should be in the stack's required_providers list.
if _, ok := stackConfig.ProviderLocalName(ctx, typeAddr); !ok {
// This means we haven't got this provider in the stacks
// required_provider list.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Component requires undeclared provider",
Detail: fmt.Sprintf(
"The root module for %s requires a configuration for provider %q, which isn't declared as a dependency of this stack configuration.\n\nDeclare this provider in the stack's required_providers block, and then assign a configuration for that provider in this component's \"providers\" argument.",
c.Addr(), typeAddr.ForDisplay(),
),
Subject: c.Declaration(ctx).DeclRange.ToHCL().Ptr(),
})
continue
}
expr, exists := declConfigs[componentAddr]
if !exists {
// Then this provider isn't listed in the `providers` block of this
// component. Which is bad!
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing required provider configuration",
Detail: fmt.Sprintf(
"The root module for %s requires a provider configuration named %q for provider %q, which is not assigned in the component's \"providers\" argument.",
c.Addr(), componentAddr.StringCompact(), typeAddr.ForDisplay(),
),
Subject: c.Declaration(ctx).DeclRange.ToHCL().Ptr(),
})
continue
}
// At the validation stage, it's really likely the result here is
// unknown. But, we can still check the returned type to make sure it
// matches everything expected.
result, hclDiags := EvalExprAndEvalContext(ctx, expr, phase, c)
diags = diags.Append(hclDiags)
if hclDiags.HasErrors() {
continue
}
const errSummary = "Invalid provider configuration"
if actualTy := result.Value.Type(); stackconfigtypes.IsProviderConfigType(actualTy) {
// Then we at least got a provider reference of some kind.
actualTypeAddr := stackconfigtypes.ProviderForProviderConfigType(actualTy)
if actualTypeAddr != typeAddr {
// But, unfortunately, the underlying types of the providers
// do not match up.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errSummary,
Detail: fmt.Sprintf(
"The provider configuration slot %s requires a configuration for provider %q, not for provider %q.",
componentAddr.StringCompact(), typeAddr, actualTypeAddr,
),
Subject: result.Expression.Range().Ptr(),
})
continue
}
} else if result.Value == cty.DynamicVal {
// Then we don't know the concrete type of this reference at this
// time, so we'll just have to accept it. This is somewhat expected
// during the validation phase, and even during the planning phase
// if we have deferred attributes. We'll get an error later (ie.
// during the plan phase) if the type doesn't match up then.
} else {
// We got something that isn't a provider reference at all.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: errSummary,
Detail: fmt.Sprintf(
"The provider configuration slot %s requires a configuration for provider %q.",
componentAddr.StringCompact(), typeAddr,
),
Subject: result.Expression.Range().Ptr(),
})
continue
}
// If we made it here, the types all matched up so we've done everything
// we can. component_instance.go will do additional checks to make sure
// the result is known and not null when it comes time to actually
// check the plan.
ret.Add(sourceAddr)
}
return ret, diags
}
func (c *ComponentConfig) neededProviderClients(ctx context.Context, phase EvalPhase) (map[addrs.RootProviderConfig]providers.Interface, bool) {
insts := make(map[addrs.RootProviderConfig]providers.Interface)
valid := true
providers, _ := c.CheckProviders(ctx, phase)
for _, provider := range providers {
pTy := c.main.ProviderType(ctx, provider.Provider)
if pTy == nil {
valid = false
continue // not our job to report a missing provider
}
// We don't need to configure the client for validate functionality.
inst, err := pTy.UnconfiguredClient(ctx)
if err != nil {
valid = false
continue
}
insts[provider] = inst
}
return insts, valid
}
func (c *ComponentConfig) neededProviderSchemas(ctx context.Context, phase EvalPhase) (map[addrs.Provider]providers.ProviderSchema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
config := c.ModuleTree(ctx)
decl := c.Declaration(ctx)
providerSchemas := make(map[addrs.Provider]providers.ProviderSchema)
for _, sourceAddr := range config.ProviderTypes() {
pTy := c.main.ProviderType(ctx, sourceAddr)
if pTy == nil {
continue // not our job to report a missing provider
}
schema, err := pTy.Schema(ctx)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Provider initialization error",
Detail: fmt.Sprintf("Failed to fetch the provider schema for %s: %s.", sourceAddr, err),
Subject: decl.DeclRange.ToHCL().Ptr(),
})
continue
}
providerSchemas[sourceAddr] = schema
}
return providerSchemas, diags
}
// ExprReferenceValue implements Referenceable.
func (c *ComponentConfig) ExprReferenceValue(ctx context.Context, phase EvalPhase) cty.Value {
// Currently we don't say anything at all about component results during
// validation, since the main Terraform language's validate call doesn't
// return any information about hypothetical root module output values.
// We don't expose ComponentConfig in any scope outside of the validation
// phase, so this is sufficient for all phases. (See [Component] for how
// component results get calculated during the plan and apply phases.)
return cty.DynamicVal
}
func (c *ComponentConfig) ResolveExpressionReference(ctx context.Context, ref stackaddrs.Reference) (Referenceable, tfdiags.Diagnostics) {
repetition := instances.RepetitionData{}
if c.Declaration(ctx).ForEach != nil {
// For validation, we'll return unknown for the instance data.
repetition.EachKey = cty.UnknownVal(cty.String).RefineNotNull()
repetition.EachValue = cty.DynamicVal
}
return c.StackConfig(ctx).resolveExpressionReference(ctx, ref, nil, repetition)
}
func (c *ComponentConfig) checkValid(ctx context.Context, phase EvalPhase) tfdiags.Diagnostics {
diags, err := c.validate.Do(ctx, func(ctx context.Context) (tfdiags.Diagnostics, error) {
var diags tfdiags.Diagnostics
moduleTree, moreDiags := c.CheckModuleTree(ctx)
diags = diags.Append(moreDiags)
if moduleTree == nil {
return diags, nil
}
decl := c.Declaration(ctx)
variableDiags := c.CheckInputVariableValues(ctx, phase)
diags = diags.Append(variableDiags)
// We don't actually exit if we found errors with the input variables,
// we can still validate the actual module tree without them.
_, providerDiags := c.CheckProviders(ctx, phase)
diags = diags.Append(providerDiags)
if providerDiags.HasErrors() {
// If there's invalid provider configuration, we can't actually go
// on and validate the module tree. We need the providers and if
// they're invalid we'll just get crazy and confusing errors
// later if we try and carry on.
return diags, nil
}
providerSchemas, moreDiags := c.neededProviderSchemas(ctx, phase)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return diags, nil
}
tfCtx, err := terraform.NewContext(&terraform.ContextOpts{
PreloadedProviderSchemas: providerSchemas,
Provisioners: c.main.availableProvisioners(),
})
if err != nil {
// Should not get here because we should always pass a valid
// ContextOpts above.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to instantiate Terraform modules runtime",
fmt.Sprintf("Could not load the main Terraform language runtime: %s.\n\nThis is a bug in Terraform; please report it!", err),
))
return diags, nil
}
providerClients, valid := c.neededProviderClients(ctx, phase)
if !valid {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Cannot validate component",
Detail: fmt.Sprintf("Cannot validate %s because its provider configuration assignments are invalid.", c.Addr()),
Subject: decl.DeclRange.ToHCL().Ptr(),
})
return diags, nil
}
defer func() {
// Close the unconfigured provider clients that we opened in
// neededProviderClients.
for _, client := range providerClients {
client.Close()
}
}()
diags = diags.Append(tfCtx.Validate(moduleTree, &terraform.ValidateOpts{
ExternalProviders: providerClients,
}))
return diags, nil
})
if err != nil {
// this is crazy, we never return an error from the inner function so
// this really shouldn't happen.
panic(fmt.Sprintf("unexpected error from validate.Do: %s", err))
}
return diags
}
// Validate implements Validatable.
func (c *ComponentConfig) Validate(ctx context.Context) tfdiags.Diagnostics {
return c.checkValid(ctx, ValidatePhase)
}
// PlanChanges implements Plannable.
func (c *ComponentConfig) PlanChanges(ctx context.Context) ([]stackplan.PlannedChange, tfdiags.Diagnostics) {
return nil, c.checkValid(ctx, PlanPhase)
}
func (c *ComponentConfig) tracingName() string {
return c.Addr().String()
}
// sourceBundleModuleWalker is an implementation of [configs.ModuleWalker]
// that loads all modules from a single source bundle.
type sourceBundleModuleWalker struct {
absoluteSourceAddrs map[string]sourceaddrs.FinalSource
sources *sourcebundle.Bundle
parser *configs.SourceBundleParser
}
func newSourceBundleModuleWalker(rootModuleSource sourceaddrs.FinalSource, sources *sourcebundle.Bundle, parser *configs.SourceBundleParser) *sourceBundleModuleWalker {
absoluteSourceAddrs := make(map[string]sourceaddrs.FinalSource, 1)
absoluteSourceAddrs[addrs.RootModule.String()] = rootModuleSource
return &sourceBundleModuleWalker{
absoluteSourceAddrs: absoluteSourceAddrs,
sources: sources,
parser: parser,
}
}
// LoadModule implements configs.ModuleWalker.
func (w *sourceBundleModuleWalker) LoadModule(req *configs.ModuleRequest) (*configs.Module, *version.Version, hcl.Diagnostics) {
var diags hcl.Diagnostics
// First we need to assemble the "final source address" for the module
// by asking the source bundle to match the given source address and
// version against what's in the bundle manifest. This should cause
// use to make the same decision that the source bundler made about
// which real package to use.
finalSourceAddr, err := w.finalSourceForModule(req.SourceAddr, &req.VersionConstraint.Required)
if err != nil {
// We should not typically get here because we're translating
// Terraform's own source address representations to the same
// representations the source bundle builder would've used, but
// we'll be robust about it nonetheless.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Can't load module for component",
Detail: fmt.Sprintf("Invalid source address: %s.", err),
Subject: req.SourceAddrRange.Ptr(),
})
return nil, nil, diags
}
absoluteSourceAddr, err := w.absoluteSourceAddr(finalSourceAddr, req.Parent)
if err != nil {
// Again, this should not happen, but let's ensure we can debug if it
// does.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Can't load module for component",
Detail: fmt.Sprintf("Unable to determin absolute source address: %s.", err),
Subject: req.SourceAddrRange.Ptr(),
})
return nil, nil, diags
}
// We store the absolute source address for this module so that any in-repo
// child modules can use it to construct their absolute source addresses
// too.
w.absoluteSourceAddrs[req.Path.String()] = absoluteSourceAddr
_, err = w.sources.LocalPathForSource(absoluteSourceAddr)
if err != nil {
// We should not get here if the source bundle was constructed
// correctly.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Can't load module for component",
Detail: fmt.Sprintf("Failed to load this component's module %s: %s.", req.Path.String(), tfdiags.FormatError(err)),
Subject: req.SourceAddrRange.Ptr(),
})
return nil, nil, diags
}
mod, moreDiags := w.parser.LoadConfigDir(absoluteSourceAddr)
diags = append(diags, moreDiags...)
// Annoyingly we now need to translate our version selection back into
// the legacy type again, so we can return it through the ModuleWalker API.
var legacyV *version.Version
if modSrc, ok := finalSourceAddr.(sourceaddrs.RegistrySourceFinal); ok {
legacyV, err = w.legacyVersionForVersion(modSrc.SelectedVersion())
if err != nil {
// It would be very strange to get in here because by now we've
// already round-tripped between the legacy and modern version
// constraint representations once, so we should have a version
// number that's compatible with both.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Can't load module for component",
Detail: fmt.Sprintf("Invalid version string %q: %s.", modSrc.SelectedVersion(), err),
Subject: req.SourceAddrRange.Ptr(),
})
}
}
return mod, legacyV, diags
}
func (w *sourceBundleModuleWalker) finalSourceForModule(tfSourceAddr addrs.ModuleSource, versionConstraints *version.Constraints) (sourceaddrs.FinalSource, error) {
// Unfortunately the configs package still uses our old model of version
// constraints and Terraform's own form of source addresses, so we need
// to adapt to what the sourcebundle API is expecting.
sourceAddr, err := w.bundleSourceAddrForTerraformSourceAddr(tfSourceAddr)
if err != nil {
return nil, err
}
var allowedVersions versions.Set
if versionConstraints != nil {
allowedVersions, err = w.versionSetForLegacyVersionConstraints(versionConstraints)
if err != nil {
return nil, fmt.Errorf("invalid version constraints: %w", err)
}
} else {
allowedVersions = versions.Released
}
switch sourceAddr := sourceAddr.(type) {
case sourceaddrs.FinalSource:
// Most source address types are already final source addresses.
return sourceAddr, nil
case sourceaddrs.RegistrySource:
// Registry sources are trickier because we need to figure out which
// exact version we're using.
vs := w.sources.RegistryPackageVersions(sourceAddr.Package())
v := vs.NewestInSet(allowedVersions)
return sourceAddr.Versioned(v), nil
default:
// Should not get here because the above should be exhaustive for all
// possible address types.
return nil, fmt.Errorf("unsupported source address type %T", tfSourceAddr)
}
}
func (w *sourceBundleModuleWalker) bundleSourceAddrForTerraformSourceAddr(tfSourceAddr addrs.ModuleSource) (sourceaddrs.Source, error) {
// In practice this should always succeed because the source bundle builder
// would've parsed the same source addresses using these same parsers
// and so source bundle building would've failed if the given address were
// outside the subset supported for source bundles.
switch tfSourceAddr := tfSourceAddr.(type) {
case addrs.ModuleSourceLocal:
return sourceaddrs.ParseLocalSource(tfSourceAddr.String())
case addrs.ModuleSourceRemote:
return sourceaddrs.ParseRemoteSource(tfSourceAddr.String())
case addrs.ModuleSourceRegistry:
return sourceaddrs.ParseRegistrySource(tfSourceAddr.String())
default:
// Should not get here because the above should be exhaustive for all
// possible address types.
return nil, fmt.Errorf("unsupported source address type %T", tfSourceAddr)
}
}
func (w *sourceBundleModuleWalker) absoluteSourceAddr(sourceAddr sourceaddrs.FinalSource, parent *configs.Config) (sourceaddrs.FinalSource, error) {
switch source := sourceAddr.(type) {
case sourceaddrs.LocalSource:
parentPath := addrs.RootModule
if parent != nil {
parentPath = parent.Path
}
absoluteParentSourceAddr, ok := w.absoluteSourceAddrs[parentPath.String()]
if !ok {
return nil, fmt.Errorf("unexpected missing source address for module parent %q", parentPath)
}
return sourceaddrs.ResolveRelativeFinalSource(absoluteParentSourceAddr, source)
default:
return sourceAddr, nil
}
}
func (w *sourceBundleModuleWalker) versionSetForLegacyVersionConstraints(versionConstraints *version.Constraints) (versions.Set, error) {
// In practice this should always succeed because the source bundle builder
// would've parsed the same version constraints using this same parser
// and so source bundle building would've failed if the given address were
// outside the subset supported for source bundles.
return versions.MeetingConstraintsStringRuby(versionConstraints.String())
}
func (w *sourceBundleModuleWalker) legacyVersionForVersion(v versions.Version) (*version.Version, error) {
return version.NewVersion(v.String())
}