mirror of
https://github.com/opentofu/opentofu.git
synced 2026-02-18 18:17:54 -05:00
initwd: Switch from earlyconfig to configs
This is a mostly mechanical refactor with a handful of changes which are necessary due to the semantic difference between earlyconfig and configs. When parsing root and descendant modules in the module installer, we now check the core version requirements inline. If the Terraform version is incompatible, we drop any other module loader diagnostics. This ensures that future language additions don't clutter the output and confuse the user. We also add two new checks during the module load process: * Don't try to load a module with a `nil` source address. This is a necessary change due to the move away from earlyconfig. * Don't try to load a module with a blank name (i.e. `module ""`). Because our module loading manifest uses the stringified module path as its map key, this causes a collision with the root module, and a later panic. This is the bug which triggered this refactor in the first place.
This commit is contained in:
parent
3d51147862
commit
8df065a2fe
21 changed files with 494 additions and 358 deletions
|
|
@ -14,7 +14,7 @@ func TestChecksHappyPath(t *testing.T) {
|
|||
const fixtureDir = "testdata/happypath"
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), nil)
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, nil)
|
||||
_, instDiags := inst.InstallModules(context.Background(), fixtureDir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *config
|
|||
// Test modules usually do not refer to remote sources, and for local
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
|
|
|||
|
|
@ -152,7 +152,13 @@ func (m *Meta) installModules(rootDir string, upgrade bool, hooks initwd.ModuleI
|
|||
return true, diags
|
||||
}
|
||||
|
||||
inst := m.moduleInstaller()
|
||||
loader, err := m.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return true, diags
|
||||
}
|
||||
|
||||
inst := initwd.NewModuleInstaller(m.modulesDir(), loader, m.registryClient())
|
||||
|
||||
// Installation can be aborted by interruption signals
|
||||
ctx, done := m.InterruptibleContext()
|
||||
|
|
@ -184,8 +190,14 @@ func (m *Meta) initDirFromModule(targetDir string, addr string, hooks initwd.Mod
|
|||
ctx, done := m.InterruptibleContext()
|
||||
defer done()
|
||||
|
||||
loader, err := m.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return true, diags
|
||||
}
|
||||
|
||||
targetDir = m.normalizePath(targetDir)
|
||||
moreDiags := initwd.DirFromModule(ctx, targetDir, m.modulesDir(), addr, m.registryClient(), hooks)
|
||||
moreDiags := initwd.DirFromModule(ctx, loader, targetDir, m.modulesDir(), addr, m.registryClient(), hooks)
|
||||
diags = diags.Append(moreDiags)
|
||||
if ctx.Err() == context.Canceled {
|
||||
m.showDiagnostics(diags)
|
||||
|
|
@ -316,13 +328,6 @@ func (m *Meta) initConfigLoader() (*configload.Loader, error) {
|
|||
return m.configLoader, nil
|
||||
}
|
||||
|
||||
// moduleInstaller instantiates and returns a module installer for use by
|
||||
// "terraform init" (directly or indirectly).
|
||||
func (m *Meta) moduleInstaller() *initwd.ModuleInstaller {
|
||||
reg := m.registryClient()
|
||||
return initwd.NewModuleInstaller(m.modulesDir(), reg)
|
||||
}
|
||||
|
||||
// registryClient instantiates and returns a new Terraform Registry client.
|
||||
func (m *Meta) registryClient() *registry.Client {
|
||||
return registry.NewClient(m.Services, nil)
|
||||
|
|
|
|||
|
|
@ -248,7 +248,12 @@ func (c *TestCommand) prepareSuiteDir(ctx context.Context, suiteName string) (te
|
|||
suiteDirs.ModulesDir = filepath.Join(configDir, ".terraform", "modules")
|
||||
os.MkdirAll(suiteDirs.ModulesDir, 0755) // if this fails then we'll ignore it and let InstallModules below fail instead
|
||||
reg := c.registryClient()
|
||||
moduleInst := initwd.NewModuleInstaller(suiteDirs.ModulesDir, reg)
|
||||
loader, err := c.initConfigLoader()
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
return suiteDirs, diags
|
||||
}
|
||||
moduleInst := initwd.NewModuleInstaller(suiteDirs.ModulesDir, loader, reg)
|
||||
_, moreDiags := moduleInst.InstallModules(ctx, configDir, true, nil)
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
|
|
@ -260,7 +265,7 @@ func (c *TestCommand) prepareSuiteDir(ctx context.Context, suiteName string) (te
|
|||
// with a separate config loader because the Meta.configLoader instance
|
||||
// is intended for interacting with the current working directory, not
|
||||
// with the test suite subdirectories.
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: suiteDirs.ModulesDir,
|
||||
Services: c.Services,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -555,3 +555,16 @@ func (c *Config) ProviderForConfigAddr(addr addrs.LocalProviderConfig) addrs.Pro
|
|||
}
|
||||
return c.ResolveAbsProviderAddr(addr, addrs.RootModule).Provider
|
||||
}
|
||||
|
||||
func (c *Config) CheckCoreVersionRequirements() hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
diags = diags.Extend(c.Module.CheckCoreVersionRequirements(c.Path, c.SourceAddr))
|
||||
|
||||
for _, c := range c.Children {
|
||||
childDiags := c.CheckCoreVersionRequirements()
|
||||
diags = diags.Extend(childDiags)
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/experiments"
|
||||
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
// Module is a container for a set of configuration constructs that are
|
||||
|
|
@ -589,3 +591,59 @@ func (m *Module) ImpliedProviderForUnqualifiedType(pType string) addrs.Provider
|
|||
}
|
||||
return addrs.ImpliedProviderForUnqualifiedType(pType)
|
||||
}
|
||||
|
||||
func (m *Module) CheckCoreVersionRequirements(path addrs.Module, sourceAddr addrs.ModuleSource) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for _, constraint := range m.CoreVersionConstraints {
|
||||
// Before checking if the constraints are met, check that we are not using any prerelease fields as these
|
||||
// are not currently supported.
|
||||
var prereleaseDiags hcl.Diagnostics
|
||||
for _, required := range constraint.Required {
|
||||
if required.Prerelease() {
|
||||
prereleaseDiags = prereleaseDiags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid required_version constraint",
|
||||
Detail: fmt.Sprintf(
|
||||
"Prerelease version constraints are not supported: %s. Remove the prerelease information from the constraint. Prerelease versions of terraform will match constraints using their version core only.",
|
||||
required.String()),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(prereleaseDiags) > 0 {
|
||||
// There were some prerelease fields in the constraints. Don't check the constraints as they will
|
||||
// fail, and populate the diagnostics for these constraints with the prerelease diagnostics.
|
||||
diags = diags.Extend(prereleaseDiags)
|
||||
continue
|
||||
}
|
||||
|
||||
if !constraint.Required.Check(tfversion.SemVer) {
|
||||
switch {
|
||||
case len(path) == 0:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"Module %s (from %s) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
path, sourceAddr, tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configload"
|
||||
"github.com/hashicorp/terraform/internal/copy"
|
||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||
"github.com/hashicorp/terraform/internal/getmodules"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/modsdir"
|
||||
"github.com/hashicorp/terraform/internal/registry"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
|
|
@ -42,7 +43,7 @@ const initFromModuleRootKeyPrefix = initFromModuleRootCallName + "."
|
|||
// references using ../ from that module to be unresolvable. Error diagnostics
|
||||
// are produced in that case, to prompt the user to rewrite the source strings
|
||||
// to be absolute references to the original remote module.
|
||||
func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string, reg *registry.Client, hooks ModuleInstallHooks) tfdiags.Diagnostics {
|
||||
func DirFromModule(ctx context.Context, loader *configload.Loader, rootDir, modulesDir, sourceAddrStr string, reg *registry.Client, hooks ModuleInstallHooks) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// The way this function works is pretty ugly, but we accept it because
|
||||
|
|
@ -87,8 +88,8 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
|||
}
|
||||
|
||||
instDir := filepath.Join(rootDir, ".terraform/init-from-module")
|
||||
inst := NewModuleInstaller(instDir, reg)
|
||||
log.Printf("[DEBUG] installing modules in %s to initialize working directory from %q", instDir, sourceAddr)
|
||||
inst := NewModuleInstaller(instDir, loader, reg)
|
||||
log.Printf("[DEBUG] installing modules in %s to initialize working directory from %q", instDir, sourceAddrStr)
|
||||
os.RemoveAll(instDir) // if this fails then we'll fail on MkdirAll below too
|
||||
err := os.MkdirAll(instDir, os.ModePerm)
|
||||
if err != nil {
|
||||
|
|
@ -103,12 +104,6 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
|||
instManifest := make(modsdir.Manifest)
|
||||
retManifest := make(modsdir.Manifest)
|
||||
|
||||
fakeFilename := fmt.Sprintf("-from-module=%q", sourceAddr)
|
||||
fakePos := tfconfig.SourcePos{
|
||||
Filename: fakeFilename,
|
||||
Line: 1,
|
||||
}
|
||||
|
||||
// -from-module allows relative paths but it's different than a normal
|
||||
// module address where it'd be resolved relative to the module call
|
||||
// (which is synthetic, here.) To address this, we'll just patch up any
|
||||
|
|
@ -117,25 +112,33 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
|||
// that the result will be "downloaded" with go-getter (copied from the
|
||||
// source location), rather than just recorded as a relative path.
|
||||
{
|
||||
maybePath := filepath.ToSlash(sourceAddr)
|
||||
maybePath := filepath.ToSlash(sourceAddrStr)
|
||||
if maybePath == "." || strings.HasPrefix(maybePath, "./") || strings.HasPrefix(maybePath, "../") {
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
sourceAddr = filepath.Join(wd, sourceAddr)
|
||||
log.Printf("[TRACE] -from-module relative path rewritten to absolute path %s", sourceAddr)
|
||||
sourceAddrStr = filepath.Join(wd, sourceAddrStr)
|
||||
log.Printf("[TRACE] -from-module relative path rewritten to absolute path %s", sourceAddrStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we need to create an artificial root module that will seed our
|
||||
// installation process.
|
||||
fakeRootModule := &tfconfig.Module{
|
||||
ModuleCalls: map[string]*tfconfig.ModuleCall{
|
||||
sourceAddr, err := addrs.ParseModuleSource(sourceAddrStr)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid module source address",
|
||||
fmt.Sprintf("Failed to parse module source address: %s", err),
|
||||
))
|
||||
}
|
||||
fakeRootModule := &configs.Module{
|
||||
ModuleCalls: map[string]*configs.ModuleCall{
|
||||
initFromModuleRootCallName: {
|
||||
Name: initFromModuleRootCallName,
|
||||
Source: sourceAddr,
|
||||
Pos: fakePos,
|
||||
Name: initFromModuleRootCallName,
|
||||
SourceAddr: sourceAddr,
|
||||
},
|
||||
},
|
||||
ProviderRequirements: &configs.RequiredProviders{},
|
||||
}
|
||||
|
||||
// wrapHooks filters hook notifications to only include Download calls
|
||||
|
|
@ -181,7 +184,7 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
|||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to copy root module",
|
||||
fmt.Sprintf("Error copying root module %q from %s to %s: %s.", sourceAddr, record.Dir, rootDir, err),
|
||||
fmt.Sprintf("Error copying root module %q from %s to %s: %s.", sourceAddrStr, record.Dir, rootDir, err),
|
||||
))
|
||||
continue
|
||||
}
|
||||
|
|
@ -191,12 +194,12 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
|||
// and must thus be rewritten to be absolute addresses again.
|
||||
// For now we can't do this rewriting automatically, but we'll
|
||||
// generate an error to help the user do it manually.
|
||||
mod, _ := earlyconfig.LoadModule(rootDir) // ignore diagnostics since we're just doing value-add here anyway
|
||||
mod, _ := loader.Parser().LoadConfigDir(rootDir) // ignore diagnostics since we're just doing value-add here anyway
|
||||
if mod != nil {
|
||||
for _, mc := range mod.ModuleCalls {
|
||||
if pathTraversesUp(mc.Source) {
|
||||
packageAddr, givenSubdir := getmodules.SplitPackageSubdir(sourceAddr)
|
||||
newSubdir := filepath.Join(givenSubdir, mc.Source)
|
||||
if pathTraversesUp(mc.SourceAddrRaw) {
|
||||
packageAddr, givenSubdir := getmodules.SplitPackageSubdir(sourceAddrStr)
|
||||
newSubdir := filepath.Join(givenSubdir, mc.SourceAddrRaw)
|
||||
if pathTraversesUp(newSubdir) {
|
||||
// This should never happen in any reasonable
|
||||
// configuration since this suggests a path that
|
||||
|
|
@ -214,7 +217,7 @@ func DirFromModule(ctx context.Context, rootDir, modulesDir, sourceAddr string,
|
|||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Root module references parent directory",
|
||||
fmt.Sprintf("The requested module %q refers to a module via its parent directory. To use this as a new root module this source string must be rewritten as a remote source address, such as %q.", sourceAddr, newAddr),
|
||||
fmt.Sprintf("The requested module %q refers to a module via its parent directory. To use this as a new root module this source string must be rewritten as a remote source address, such as %q.", sourceAddrStr, newAddr),
|
||||
))
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ func TestDirFromModule_registry(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
reg := registry.NewClient(nil, nil)
|
||||
diags := DirFromModule(context.Background(), dir, modsDir, "hashicorp/module-installer-acctest/aws//examples/main", reg, hooks)
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
diags := DirFromModule(context.Background(), loader, dir, modsDir, "hashicorp/module-installer-acctest/aws//examples/main", reg, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
v := version.Must(version.NewVersion("0.0.2"))
|
||||
|
|
@ -93,7 +95,7 @@ func TestDirFromModule_registry(t *testing.T) {
|
|||
t.Fatalf("wrong installer calls\n%s", diff)
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modsDir,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -154,7 +156,9 @@ func TestDirFromModule_submodules(t *testing.T) {
|
|||
}
|
||||
modInstallDir := filepath.Join(dir, ".terraform/modules")
|
||||
|
||||
diags := DirFromModule(context.Background(), dir, modInstallDir, fromModuleDir, nil, hooks)
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
diags := DirFromModule(context.Background(), loader, dir, modInstallDir, fromModuleDir, nil, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
wantCalls := []testInstallHookCall{
|
||||
{
|
||||
|
|
@ -173,7 +177,7 @@ func TestDirFromModule_submodules(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modInstallDir,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -246,7 +250,9 @@ func TestDirFromModule_rel_submodules(t *testing.T) {
|
|||
|
||||
modInstallDir := ".terraform/modules"
|
||||
sourceDir := "../local-modules"
|
||||
diags := DirFromModule(context.Background(), ".", modInstallDir, sourceDir, nil, hooks)
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
diags := DirFromModule(context.Background(), loader, ".", modInstallDir, sourceDir, nil, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
wantCalls := []testInstallHookCall{
|
||||
{
|
||||
|
|
@ -265,7 +271,7 @@ func TestDirFromModule_rel_submodules(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modInstallDir,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
package initwd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||
"github.com/hashicorp/terraform/internal/modsdir"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// LoadConfig loads a full configuration tree that has previously had all of
|
||||
// its dependent modules installed to the given modulesDir using a
|
||||
// ModuleInstaller.
|
||||
//
|
||||
// This uses the early configuration loader and thus only reads top-level
|
||||
// metadata from the modules in the configuration. Most callers should use
|
||||
// the configs/configload package to fully load a configuration.
|
||||
func LoadConfig(rootDir, modulesDir string) (*earlyconfig.Config, tfdiags.Diagnostics) {
|
||||
rootMod, diags := earlyconfig.LoadModule(rootDir)
|
||||
if rootMod == nil {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
manifest, err := modsdir.ReadManifestSnapshotForDir(modulesDir)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to read module manifest",
|
||||
fmt.Sprintf("Terraform failed to read its manifest of locally-cached modules: %s.", err),
|
||||
))
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return earlyconfig.BuildConfig(rootMod, earlyconfig.ModuleWalkerFunc(
|
||||
func(req *earlyconfig.ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
key := manifest.ModuleKey(req.Path)
|
||||
record, exists := manifest[key]
|
||||
if !exists {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module not installed",
|
||||
fmt.Sprintf("Module %s is not yet installed. Run \"terraform init\" to install all modules required by this configuration.", req.Path.String()),
|
||||
))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
mod, mDiags := earlyconfig.LoadModule(record.Dir)
|
||||
diags = diags.Append(mDiags)
|
||||
return mod, record.Version, diags
|
||||
},
|
||||
))
|
||||
}
|
||||
|
|
@ -12,10 +12,11 @@ import (
|
|||
|
||||
"github.com/apparentlymart/go-versions/versions"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configload"
|
||||
"github.com/hashicorp/terraform/internal/getmodules"
|
||||
"github.com/hashicorp/terraform/internal/modsdir"
|
||||
"github.com/hashicorp/terraform/internal/registry"
|
||||
|
|
@ -26,6 +27,7 @@ import (
|
|||
|
||||
type ModuleInstaller struct {
|
||||
modsDir string
|
||||
loader *configload.Loader
|
||||
reg *registry.Client
|
||||
|
||||
// The keys in moduleVersions are resolved and trimmed registry source
|
||||
|
|
@ -42,9 +44,10 @@ type moduleVersion struct {
|
|||
version string
|
||||
}
|
||||
|
||||
func NewModuleInstaller(modsDir string, reg *registry.Client) *ModuleInstaller {
|
||||
func NewModuleInstaller(modsDir string, loader *configload.Loader, reg *registry.Client) *ModuleInstaller {
|
||||
return &ModuleInstaller{
|
||||
modsDir: modsDir,
|
||||
loader: loader,
|
||||
reg: reg,
|
||||
registryPackageVersions: make(map[addrs.ModuleRegistryPackage]*response.ModuleVersions),
|
||||
registryPackageSources: make(map[moduleVersion]addrs.ModuleSourceRemote),
|
||||
|
|
@ -79,12 +82,23 @@ func NewModuleInstaller(modsDir string, reg *registry.Client) *ModuleInstaller {
|
|||
// If successful (the returned diagnostics contains no errors) then the
|
||||
// first return value is the early configuration tree that was constructed by
|
||||
// the installation process.
|
||||
func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir string, upgrade bool, hooks ModuleInstallHooks) (*earlyconfig.Config, tfdiags.Diagnostics) {
|
||||
func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir string, upgrade bool, hooks ModuleInstallHooks) (*configs.Config, tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] ModuleInstaller: installing child modules for %s into %s", rootDir, i.modsDir)
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
rootMod, diags := earlyconfig.LoadModule(rootDir)
|
||||
rootMod, mDiags := i.loader.Parser().LoadConfigDir(rootDir)
|
||||
if rootMod == nil {
|
||||
// We drop the diagnostics here because we only want to report module
|
||||
// loading errors after checking the core version constraints, which we
|
||||
// can only do if the module can be at least partially loaded.
|
||||
return nil, diags
|
||||
} else if vDiags := rootMod.CheckCoreVersionRequirements(nil, nil); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Append(vDiags)
|
||||
} else {
|
||||
diags = diags.Append(mDiags)
|
||||
}
|
||||
|
||||
manifest, err := modsdir.ReadManifestSnapshotForDir(i.modsDir)
|
||||
|
|
@ -104,7 +118,7 @@ func (i *ModuleInstaller) InstallModules(ctx context.Context, rootDir string, up
|
|||
return cfg, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod *tfconfig.Module, rootDir string, manifest modsdir.Manifest, upgrade bool, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*earlyconfig.Config, tfdiags.Diagnostics) {
|
||||
func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod *configs.Module, rootDir string, manifest modsdir.Manifest, upgrade bool, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*configs.Config, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if hooks == nil {
|
||||
|
|
@ -119,8 +133,25 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
|||
Dir: rootDir,
|
||||
}
|
||||
|
||||
cfg, cDiags := earlyconfig.BuildConfig(rootMod, earlyconfig.ModuleWalkerFunc(
|
||||
func(req *earlyconfig.ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
cfg, cDiags := configs.BuildConfig(rootMod, configs.ModuleWalkerFunc(
|
||||
func(req *configs.ModuleRequest) (*configs.Module, *version.Version, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
if req.SourceAddr == nil {
|
||||
// If the parent module failed to parse the module source
|
||||
// address, we can't load it here. Return nothing as the parent
|
||||
// module's diagnostics should explain this.
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
// An empty string for a module instance name breaks our
|
||||
// manifest map, which uses that to indicate the root module.
|
||||
// Because we descend into modules which have errors, we need
|
||||
// to look out for this case, but the config loader's
|
||||
// diagnostics will report the error later.
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
key := manifest.ModuleKey(req.Path)
|
||||
instPath := i.packageInstallPath(req.Path)
|
||||
|
|
@ -139,8 +170,8 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
|||
case record.SourceAddr != req.SourceAddr.String():
|
||||
log.Printf("[TRACE] ModuleInstaller: %s source address has changed from %q to %q", key, record.SourceAddr, req.SourceAddr)
|
||||
replace = true
|
||||
case record.Version != nil && !req.VersionConstraints.Check(record.Version):
|
||||
log.Printf("[TRACE] ModuleInstaller: %s version %s no longer compatible with constraints %s", key, record.Version, req.VersionConstraints)
|
||||
case record.Version != nil && !req.VersionConstraint.Required.Check(record.Version):
|
||||
log.Printf("[TRACE] ModuleInstaller: %s version %s no longer compatible with constraints %s", key, record.Version, req.VersionConstraint.Required)
|
||||
replace = true
|
||||
}
|
||||
}
|
||||
|
|
@ -174,14 +205,14 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
|||
err := os.RemoveAll(instPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Printf("[TRACE] ModuleInstaller: failed to remove %s: %s", key, err)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to remove local module cache",
|
||||
fmt.Sprintf(
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to remove local module cache",
|
||||
Detail: fmt.Sprintf(
|
||||
"Terraform tried to remove %s in order to reinstall this module, but encountered an error: %s",
|
||||
instPath, err,
|
||||
),
|
||||
))
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
} else {
|
||||
|
|
@ -190,8 +221,19 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
|||
// keep our existing record.
|
||||
info, err := os.Stat(record.Dir)
|
||||
if err == nil && info.IsDir() {
|
||||
mod, mDiags := earlyconfig.LoadModule(record.Dir)
|
||||
diags = diags.Append(mDiags)
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(record.Dir)
|
||||
if mod == nil {
|
||||
// nil indicates an unreadable module, which should never happen,
|
||||
// so we return the full loader diagnostics here.
|
||||
diags = diags.Extend(mDiags)
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] ModuleInstaller: Module installer: %s %s already installed in %s", key, record.Version, record.Dir)
|
||||
return mod, record.Version, diags
|
||||
|
|
@ -231,7 +273,7 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
|||
|
||||
},
|
||||
))
|
||||
diags = append(diags, cDiags...)
|
||||
diags = diags.Append(cDiags)
|
||||
|
||||
err := manifest.WriteSnapshotToDir(i.modsDir)
|
||||
if err != nil {
|
||||
|
|
@ -245,8 +287,8 @@ func (i *ModuleInstaller) installDescendentModules(ctx context.Context, rootMod
|
|||
return cfg, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key string, manifest modsdir.Manifest, hooks ModuleInstallHooks) (*tfconfig.Module, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
func (i *ModuleInstaller) installLocalModule(req *configs.ModuleRequest, key string, manifest modsdir.Manifest, hooks ModuleInstallHooks) (*configs.Module, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
parentKey := manifest.ModuleKey(req.Parent.Path)
|
||||
parentRecord, recorded := manifest[parentKey]
|
||||
|
|
@ -255,12 +297,13 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key
|
|||
panic(fmt.Errorf("missing manifest record for parent module %s", parentKey))
|
||||
}
|
||||
|
||||
if len(req.VersionConstraints) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid version constraint",
|
||||
fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a relative local path.", req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
if len(req.VersionConstraint.Required) != 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid version constraint",
|
||||
Detail: fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it has a relative local path.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
// For local sources we don't actually need to modify the
|
||||
|
|
@ -272,25 +315,31 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key
|
|||
// it is possible that the local directory is a symlink
|
||||
newDir, err := filepath.EvalSymlinks(newDir)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("Unable to evaluate directory symlink: %s", err.Error()),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("Unable to evaluate directory symlink: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
mod, mDiags := earlyconfig.LoadModule(newDir)
|
||||
// Finally we are ready to try actually loading the module.
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(newDir)
|
||||
if mod == nil {
|
||||
// nil indicates missing or unreadable directory, so we'll
|
||||
// discard the returned diags and return a more specific
|
||||
// error message here.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("The directory %s could not be read for module %q at %s:%d.", newDir, req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("The directory %s could not be read for module %q at %s:%d.", newDir, req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
})
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = diags.Append(mDiags)
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
// Note the local location in our manifest.
|
||||
|
|
@ -305,8 +354,8 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key
|
|||
return mod, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyconfig.ModuleRequest, key string, instPath string, addr addrs.ModuleSourceRegistry, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *configs.ModuleRequest, key string, instPath string, addr addrs.ModuleSourceRegistry, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*configs.Module, *version.Version, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
hostname := addr.Package.Host
|
||||
reg := i.reg
|
||||
|
|
@ -331,23 +380,25 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
resp, err = reg.ModuleVersions(ctx, regsrcAddr)
|
||||
if err != nil {
|
||||
if registry.IsModuleNotFound(err) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module not found",
|
||||
fmt.Sprintf("Module %q (from %s:%d) cannot be found in the module registry at %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module not found",
|
||||
Detail: fmt.Sprintf("Module %q (from %s:%d) cannot be found in the module registry at %s.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, hostname),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
} else if errors.Is(err, context.Canceled) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module installation was interrupted",
|
||||
fmt.Sprintf("Received interrupt signal while retrieving available versions for module %q.", req.Name),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module installation was interrupted",
|
||||
Detail: fmt.Sprintf("Received interrupt signal while retrieving available versions for module %q.", req.Name),
|
||||
})
|
||||
} else {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Error accessing remote module registry",
|
||||
fmt.Sprintf("Failed to retrieve available versions for module %q (%s:%d) from %s: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, hostname, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Error accessing remote module registry",
|
||||
Detail: fmt.Sprintf("Failed to retrieve available versions for module %q (%s:%d) from %s: %s.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, hostname, err),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
}
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
|
@ -361,11 +412,12 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
if len(resp.Modules) < 1 {
|
||||
// Should never happen, but since this is a remote service that may
|
||||
// be implemented by third-parties we will handle it gracefully.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid response from remote module registry",
|
||||
fmt.Sprintf("The registry at %s returned an invalid response when Terraform requested available versions for module %q (%s:%d).", hostname, req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid response from remote module registry",
|
||||
Detail: fmt.Sprintf("The registry at %s returned an invalid response when Terraform requested available versions for module %q (%s:%d).", hostname, req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
|
|
@ -379,11 +431,12 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
// Should never happen if the registry server is compliant with
|
||||
// the protocol, but we'll warn if not to assist someone who
|
||||
// might be developing a module registry server.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Invalid response from remote module registry",
|
||||
fmt.Sprintf("The registry at %s returned an invalid version string %q for module %q (%s:%d), which Terraform ignored.", hostname, mv.Version, req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Invalid response from remote module registry",
|
||||
Detail: fmt.Sprintf("The registry at %s returned an invalid version string %q for module %q (%s:%d), which Terraform ignored.", hostname, mv.Version, req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -401,9 +454,9 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
// prerelease metadata will be checked. Users may not have even
|
||||
// requested this prerelease so don't print lots of unnecessary #
|
||||
// warnings.
|
||||
acceptableVersions, err := versions.MeetingConstraintsString(req.VersionConstraints.String())
|
||||
acceptableVersions, err := versions.MeetingConstraintsString(req.VersionConstraint.Required.String())
|
||||
if err != nil {
|
||||
log.Printf("[WARN] ModuleInstaller: %s ignoring %s because the version constraints (%s) could not be parsed: %s", key, v, req.VersionConstraints.String(), err.Error())
|
||||
log.Printf("[WARN] ModuleInstaller: %s ignoring %s because the version constraints (%s) could not be parsed: %s", key, v, req.VersionConstraint.Required.String(), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -434,7 +487,7 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
latestVersion = v
|
||||
}
|
||||
|
||||
if req.VersionConstraints.Check(v) {
|
||||
if req.VersionConstraint.Required.Check(v) {
|
||||
if latestMatch == nil || v.GreaterThan(latestMatch) {
|
||||
latestMatch = v
|
||||
}
|
||||
|
|
@ -442,20 +495,22 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
}
|
||||
|
||||
if latestVersion == nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module has no versions",
|
||||
fmt.Sprintf("Module %q (%s:%d) has no versions available on %s.", addr, req.CallPos.Filename, req.CallPos.Line, hostname),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module has no versions",
|
||||
Detail: fmt.Sprintf("Module %q (%s:%d) has no versions available on %s.", addr, req.CallRange.Filename, req.CallRange.Start.Line, hostname),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if latestMatch == nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unresolvable module version constraint",
|
||||
fmt.Sprintf("There is no available version of module %q (%s:%d) which matches the given version constraint. The newest available version is %s.", addr, req.CallPos.Filename, req.CallPos.Line, latestVersion),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unresolvable module version constraint",
|
||||
Detail: fmt.Sprintf("There is no available version of module %q (%s:%d) which matches the given version constraint. The newest available version is %s.", addr, req.CallRange.Filename, req.CallRange.Start.Line, latestVersion),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
|
|
@ -472,20 +527,20 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
realAddrRaw, err := reg.ModuleLocation(ctx, regsrcAddr, latestMatch.String())
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] %s from %s %s: %s", key, addr, latestMatch, err)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Error accessing remote module registry",
|
||||
fmt.Sprintf("Failed to retrieve a download URL for %s %s from %s: %s", addr, latestMatch, hostname, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Error accessing remote module registry",
|
||||
Detail: fmt.Sprintf("Failed to retrieve a download URL for %s %s from %s: %s", addr, latestMatch, hostname, err),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
realAddr, err := addrs.ParseModuleSource(realAddrRaw)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid package location from module registry",
|
||||
fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: %s.", hostname, realAddrRaw, addr, latestMatch, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid package location from module registry",
|
||||
Detail: fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: %s.", hostname, realAddrRaw, addr, latestMatch, err),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
switch realAddr := realAddr.(type) {
|
||||
|
|
@ -496,11 +551,11 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
case addrs.ModuleSourceRemote:
|
||||
i.registryPackageSources[moduleAddr] = realAddr
|
||||
default:
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid package location from module registry",
|
||||
fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: must be a direct remote package address.", hostname, realAddrRaw, addr, latestMatch),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid package location from module registry",
|
||||
Detail: fmt.Sprintf("Module registry %s returned invalid source location %q for %s %s: must be a direct remote package address.", hostname, realAddrRaw, addr, latestMatch),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
}
|
||||
|
|
@ -511,11 +566,11 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
|
||||
err := fetcher.FetchPackage(ctx, instPath, dlAddr.Package.String())
|
||||
if errors.Is(err, context.Canceled) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module download was interrupted",
|
||||
fmt.Sprintf("Interrupt signal received when downloading module %s.", addr),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module download was interrupted",
|
||||
Detail: fmt.Sprintf("Interrupt signal received when downloading module %s.", addr),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
if err != nil {
|
||||
|
|
@ -524,11 +579,12 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
// we have no way to recognize any specific errors to improve them
|
||||
// and masking the error entirely would hide valuable diagnostic
|
||||
// information from the user.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to download module",
|
||||
fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s.", req.Name, req.CallPos.Filename, req.CallPos.Line, dlAddr, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to download module",
|
||||
Detail: fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, dlAddr, err),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
|
|
@ -544,20 +600,25 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
log.Printf("[TRACE] ModuleInstaller: %s should now be at %s", key, modDir)
|
||||
|
||||
// Finally we are ready to try actually loading the module.
|
||||
mod, mDiags := earlyconfig.LoadModule(modDir)
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(modDir)
|
||||
if mod == nil {
|
||||
// nil indicates missing or unreadable directory, so we'll
|
||||
// discard the returned diags and return a more specific
|
||||
// error message here. For registry modules this actually
|
||||
// indicates a bug in the code above, since it's not the
|
||||
// user's responsibility to create the directory in this case.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
})
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = append(diags, mDiags...)
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
// Note the local location in our manifest.
|
||||
|
|
@ -573,20 +634,21 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc
|
|||
return mod, latestMatch, diags
|
||||
}
|
||||
|
||||
func (i *ModuleInstaller) installGoGetterModule(ctx context.Context, req *earlyconfig.ModuleRequest, key string, instPath string, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*tfconfig.Module, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
func (i *ModuleInstaller) installGoGetterModule(ctx context.Context, req *configs.ModuleRequest, key string, instPath string, manifest modsdir.Manifest, hooks ModuleInstallHooks, fetcher *getmodules.PackageFetcher) (*configs.Module, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
// Report up to the caller that we're about to start downloading.
|
||||
addr := req.SourceAddr.(addrs.ModuleSourceRemote)
|
||||
packageAddr := addr.Package
|
||||
hooks.Download(key, packageAddr.String(), nil)
|
||||
|
||||
if len(req.VersionConstraints) != 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid version constraint",
|
||||
fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it doesn't come from a module registry.", req.Name, req.CallPos.Filename, req.CallPos.Line),
|
||||
))
|
||||
if len(req.VersionConstraint.Required) != 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid version constraint",
|
||||
Detail: fmt.Sprintf("Cannot apply a version constraint to module %q (at %s:%d) because it doesn't come from a module registry.", req.Name, req.CallRange.Filename, req.CallRange.Start.Line),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
|
|
@ -599,54 +661,65 @@ func (i *ModuleInstaller) installGoGetterModule(ctx context.Context, req *earlyc
|
|||
"[TRACE] ModuleInstaller: %s looks like a local path but is missing ./ or ../",
|
||||
req.SourceAddr,
|
||||
)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module not found",
|
||||
fmt.Sprintf(
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module not found",
|
||||
Detail: fmt.Sprintf(
|
||||
"The module address %q could not be resolved.\n\n"+
|
||||
"If you intended this as a path relative to the current "+
|
||||
"module, use \"./%s\" instead. The \"./\" prefix "+
|
||||
"indicates that the address is a relative filesystem path.",
|
||||
req.SourceAddr, req.SourceAddr,
|
||||
),
|
||||
))
|
||||
})
|
||||
} else {
|
||||
// Errors returned by go-getter have very inconsistent quality as
|
||||
// end-user error messages, but for now we're accepting that because
|
||||
// we have no way to recognize any specific errors to improve them
|
||||
// and masking the error entirely would hide valuable diagnostic
|
||||
// information from the user.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to download module",
|
||||
fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s", req.Name, req.CallPos.Filename, req.CallPos.Line, packageAddr, err),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to download module",
|
||||
Detail: fmt.Sprintf("Could not download module %q (%s:%d) source code from %q: %s", req.Name, req.CallRange.Filename, req.CallRange.Start.Line, packageAddr, err),
|
||||
Subject: req.CallRange.Ptr(),
|
||||
})
|
||||
}
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
modDir, err := getmodules.ExpandSubdirGlobs(instPath, addr.Subdir)
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to expand subdir globs",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] ModuleInstaller: %s %q was downloaded to %s", key, addr, modDir)
|
||||
|
||||
mod, mDiags := earlyconfig.LoadModule(modDir)
|
||||
// Finally we are ready to try actually loading the module.
|
||||
mod, mDiags := i.loader.Parser().LoadConfigDir(modDir)
|
||||
if mod == nil {
|
||||
// nil indicates missing or unreadable directory, so we'll
|
||||
// discard the returned diags and return a more specific
|
||||
// error message here. For go-getter modules this actually
|
||||
// indicates a bug in the code above, since it's not the
|
||||
// user's responsibility to create the directory in this case.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Unreadable module directory",
|
||||
fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
))
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unreadable module directory",
|
||||
Detail: fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
|
||||
})
|
||||
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
|
||||
// If the core version requirements are not met, we drop any other
|
||||
// diagnostics, as they may reflect language changes from future
|
||||
// Terraform versions.
|
||||
diags = diags.Extend(vDiags)
|
||||
} else {
|
||||
diags = append(diags, mDiags...)
|
||||
diags = diags.Extend(mDiags)
|
||||
}
|
||||
|
||||
// Note the local location in our manifest.
|
||||
|
|
@ -674,7 +747,7 @@ func (i *ModuleInstaller) packageInstallPath(modulePath addrs.Module) string {
|
|||
//
|
||||
// This function's behavior is only reasonable for errors returned from the
|
||||
// ModuleInstaller.installLocalModule function.
|
||||
func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags.Diagnostics) tfdiags.Diagnostics {
|
||||
func maybeImproveLocalInstallError(req *configs.ModuleRequest, diags hcl.Diagnostics) hcl.Diagnostics {
|
||||
if !diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
|
@ -709,7 +782,7 @@ func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags
|
|||
Path: req.Path,
|
||||
SourceAddr: req.SourceAddr,
|
||||
})
|
||||
current := req.Parent // an earlyconfig.Config where Children isn't populated yet
|
||||
current := req.Parent // a configs.Config where Children isn't populated yet
|
||||
for {
|
||||
if current == nil || current.SourceAddr == nil {
|
||||
// We've reached the root module, in which case we aren't
|
||||
|
|
@ -753,11 +826,11 @@ func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags
|
|||
if !strings.HasPrefix(nextPath, prefix) { // ESCAPED!
|
||||
escapeeAddr := step.Path.String()
|
||||
|
||||
var newDiags tfdiags.Diagnostics
|
||||
var newDiags hcl.Diagnostics
|
||||
|
||||
// First we'll copy over any non-error diagnostics from the source diags
|
||||
for _, diag := range diags {
|
||||
if diag.Severity() != tfdiags.Error {
|
||||
if diag.Severity != hcl.DiagError {
|
||||
newDiags = newDiags.Append(diag)
|
||||
}
|
||||
}
|
||||
|
|
@ -772,14 +845,14 @@ func maybeImproveLocalInstallError(req *earlyconfig.ModuleRequest, diags tfdiags
|
|||
// about it.
|
||||
suggestion = "\n\nTerraform treats absolute filesystem paths as external modules which establish a new module package. To treat this directory as part of the same package as its caller, use a local path starting with either \"./\" or \"../\"."
|
||||
}
|
||||
newDiags = newDiags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Local module path escapes module package",
|
||||
fmt.Sprintf(
|
||||
newDiags = newDiags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Local module path escapes module package",
|
||||
Detail: fmt.Sprintf(
|
||||
"The given source directory for %s would be outside of its containing package %q. Local source addresses starting with \"../\" must stay within the same package that the calling module belongs to.%s",
|
||||
escapeeAddr, packageAddr, suggestion,
|
||||
),
|
||||
))
|
||||
})
|
||||
|
||||
return newDiags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ func TestModuleInstaller(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
|
|
@ -100,7 +102,10 @@ func TestModuleInstaller_error(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
|
|
@ -110,6 +115,27 @@ func TestModuleInstaller_error(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestModuleInstaller_emptyModuleName(t *testing.T) {
|
||||
fixtureDir := filepath.Clean("testdata/empty-module-name")
|
||||
dir, done := tempChdir(t, fixtureDir)
|
||||
defer done()
|
||||
|
||||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected error")
|
||||
} else {
|
||||
assertDiagnosticSummary(t, diags, "Invalid module instance name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleInstaller_packageEscapeError(t *testing.T) {
|
||||
fixtureDir := filepath.Clean("testdata/load-module-package-escape")
|
||||
dir, done := tempChdir(t, fixtureDir)
|
||||
|
|
@ -135,7 +161,10 @@ func TestModuleInstaller_packageEscapeError(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
|
|
@ -170,7 +199,10 @@ func TestModuleInstaller_explicitPackageBoundary(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
|
|
@ -190,7 +222,10 @@ func TestModuleInstaller_ExactMatchPrerelease(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
|
|
@ -214,7 +249,10 @@ func TestModuleInstaller_PartialMatchPrerelease(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if diags.HasErrors() {
|
||||
|
|
@ -234,7 +272,10 @@ func TestModuleInstaller_invalid_version_constraint_error(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
|
|
@ -257,7 +298,10 @@ func TestModuleInstaller_invalidVersionConstraintGetter(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
|
|
@ -280,7 +324,10 @@ func TestModuleInstaller_invalidVersionConstraintLocal(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
|
||||
if !diags.HasErrors() {
|
||||
|
|
@ -303,7 +350,10 @@ func TestModuleInstaller_symlink(t *testing.T) {
|
|||
hooks := &testInstallHooks{}
|
||||
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, nil)
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, nil)
|
||||
_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
|
|
@ -376,7 +426,10 @@ func TestLoaderInstallModules_registry(t *testing.T) {
|
|||
|
||||
hooks := &testInstallHooks{}
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
|
|
@ -482,7 +535,7 @@ func TestLoaderInstallModules_registry(t *testing.T) {
|
|||
t.Errorf("module download url cache was not populated\ngot: %s", spew.Sdump(inst.registryPackageSources))
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modulesDir,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -536,7 +589,10 @@ func TestLoaderInstallModules_goGetter(t *testing.T) {
|
|||
|
||||
hooks := &testInstallHooks{}
|
||||
modulesDir := filepath.Join(dir, ".terraform/modules")
|
||||
inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
|
||||
|
||||
loader, close := configload.NewLoaderForTests(t)
|
||||
defer close()
|
||||
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
|
||||
_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
||||
|
|
@ -609,7 +665,7 @@ func TestLoaderInstallModules_goGetter(t *testing.T) {
|
|||
t.Fatalf("wrong installer calls\n%s", diff)
|
||||
}
|
||||
|
||||
loader, err := configload.NewLoader(&configload.Config{
|
||||
loader, err = configload.NewLoader(&configload.Config{
|
||||
ModulesDir: modulesDir,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
|||
3
internal/initwd/testdata/empty-module-name/child/main.tf
vendored
Normal file
3
internal/initwd/testdata/empty-module-name/child/main.tf
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
output "boop" {
|
||||
value = "beep"
|
||||
}
|
||||
3
internal/initwd/testdata/empty-module-name/main.tf
vendored
Normal file
3
internal/initwd/testdata/empty-module-name/main.tf
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module "" {
|
||||
source = "./child"
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ func LoadConfigForTests(t *testing.T, rootDir string) (*configs.Config, *configl
|
|||
var diags tfdiags.Diagnostics
|
||||
|
||||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
inst := NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
|
||||
_, moreDiags := inst.InstallModules(context.Background(), rootDir, true, ModuleInstallHooksImpl{})
|
||||
diags = diags.Append(moreDiags)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func testAnalyzer(t *testing.T, fixtureName string) *Analyzer {
|
|||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), configDir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatalf("unexpected module installation errors: %s", instDiags.Err().Error())
|
||||
|
|
|
|||
|
|
@ -534,7 +534,7 @@ func loadRefactoringFixture(t *testing.T, dir string) (*configs.Config, instance
|
|||
loader, cleanup := configload.NewLoaderForTests(t)
|
||||
defer cleanup()
|
||||
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
|
|
|||
|
|
@ -38,14 +38,12 @@ var (
|
|||
func TestNewContextRequiredVersion(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Module string
|
||||
Version string
|
||||
Value string
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"no requirement",
|
||||
"",
|
||||
"0.1.0",
|
||||
"",
|
||||
false,
|
||||
|
|
@ -53,7 +51,6 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
|||
|
||||
{
|
||||
"doesn't match",
|
||||
"",
|
||||
"0.1.0",
|
||||
"> 0.6.0",
|
||||
true,
|
||||
|
|
@ -61,7 +58,6 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
|||
|
||||
{
|
||||
"matches",
|
||||
"",
|
||||
"0.7.0",
|
||||
"> 0.6.0",
|
||||
false,
|
||||
|
|
@ -69,7 +65,6 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
|||
|
||||
{
|
||||
"prerelease doesn't match with inequality",
|
||||
"",
|
||||
"0.8.0",
|
||||
"> 0.7.0-beta",
|
||||
true,
|
||||
|
|
@ -77,27 +72,10 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
|||
|
||||
{
|
||||
"prerelease doesn't match with equality",
|
||||
"",
|
||||
"0.7.0",
|
||||
"0.7.0-beta",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"module matches",
|
||||
"context-required-version-module",
|
||||
"0.5.0",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"module doesn't match",
|
||||
"context-required-version-module",
|
||||
"0.4.0",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
|
@ -107,11 +85,7 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
|||
tfversion.SemVer = version.Must(version.NewVersion(tc.Version))
|
||||
defer func() { tfversion.SemVer = old }()
|
||||
|
||||
name := "context-required-version"
|
||||
if tc.Module != "" {
|
||||
name = tc.Module
|
||||
}
|
||||
mod := testModule(t, name)
|
||||
mod := testModule(t, "context-required-version")
|
||||
if tc.Value != "" {
|
||||
constraint, err := version.NewConstraint(tc.Value)
|
||||
if err != nil {
|
||||
|
|
@ -134,6 +108,65 @@ func TestNewContextRequiredVersion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewContextRequiredVersion_child(t *testing.T) {
|
||||
mod := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
module "child" {
|
||||
source = "./child"
|
||||
}
|
||||
`,
|
||||
"child/main.tf": `
|
||||
terraform {}
|
||||
`,
|
||||
})
|
||||
|
||||
cases := map[string]struct {
|
||||
Version string
|
||||
Constraint string
|
||||
Err bool
|
||||
}{
|
||||
"matches": {
|
||||
"0.5.0",
|
||||
">= 0.5.0",
|
||||
false,
|
||||
},
|
||||
"doesn't match": {
|
||||
"0.4.0",
|
||||
">= 0.5.0",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Reset the version for the tests
|
||||
old := tfversion.SemVer
|
||||
tfversion.SemVer = version.Must(version.NewVersion(tc.Version))
|
||||
defer func() { tfversion.SemVer = old }()
|
||||
|
||||
if tc.Constraint != "" {
|
||||
constraint, err := version.NewConstraint(tc.Constraint)
|
||||
if err != nil {
|
||||
t.Fatalf("can't parse %q as version constraint", tc.Constraint)
|
||||
}
|
||||
child := mod.Children["child"]
|
||||
child.Module.CoreVersionConstraints = append(child.Module.CoreVersionConstraints, configs.VersionConstraint{
|
||||
Required: constraint,
|
||||
})
|
||||
}
|
||||
c, diags := NewContext(&ContextOpts{})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected NewContext errors: %s", diags.Err())
|
||||
}
|
||||
|
||||
diags = c.Validate(mod)
|
||||
if diags.HasErrors() != tc.Err {
|
||||
t.Fatalf("err: %s", diags.Err())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_missingPlugins(t *testing.T) {
|
||||
ctx, diags := NewContext(&ContextOpts{})
|
||||
assertNoDiagnostics(t, diags)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *config
|
|||
// Test modules usually do not refer to remote sources, and for local
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), dir, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
|
@ -120,7 +120,7 @@ func testModuleInline(t *testing.T, sources map[string]string) *configs.Config {
|
|||
// Test modules usually do not refer to remote sources, and for local
|
||||
// sources only this ultimately just records all of the module paths
|
||||
// in a JSON file so that we can load them below.
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), registry.NewClient(nil, nil))
|
||||
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil))
|
||||
_, instDiags := inst.InstallModules(context.Background(), cfgPath, true, initwd.ModuleInstallHooksImpl{})
|
||||
if instDiags.HasErrors() {
|
||||
t.Fatal(instDiags.Err())
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.5.0"
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
module "child" {
|
||||
source = "./child"
|
||||
}
|
||||
|
|
@ -1,14 +1,9 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
// CheckCoreVersionRequirements visits each of the modules in the given
|
||||
|
|
@ -24,62 +19,7 @@ func CheckCoreVersionRequirements(config *configs.Config) tfdiags.Diagnostics {
|
|||
}
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
module := config.Module
|
||||
|
||||
for _, constraint := range module.CoreVersionConstraints {
|
||||
// Before checking if the constraints are met, check that we are not using any prerelease fields as these
|
||||
// are not currently supported.
|
||||
var prereleaseDiags tfdiags.Diagnostics
|
||||
for _, required := range constraint.Required {
|
||||
if required.Prerelease() {
|
||||
prereleaseDiags = prereleaseDiags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid required_version constraint",
|
||||
Detail: fmt.Sprintf(
|
||||
"Prerelease version constraints are not supported: %s. Remove the prerelease information from the constraint. Prerelease versions of terraform will match constraints using their version core only.",
|
||||
required.String()),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(prereleaseDiags) > 0 {
|
||||
// There were some prerelease fields in the constraints. Don't check the constraints as they will
|
||||
// fail, and populate the diagnostics for these constraints with the prerelease diagnostics.
|
||||
diags = diags.Append(prereleaseDiags)
|
||||
continue
|
||||
}
|
||||
|
||||
if !constraint.Required.Check(tfversion.SemVer) {
|
||||
switch {
|
||||
case len(config.Path) == 0:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported Terraform Core version",
|
||||
Detail: fmt.Sprintf(
|
||||
"Module %s (from %s) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.",
|
||||
config.Path, config.SourceAddr, tfversion.String(),
|
||||
),
|
||||
Subject: constraint.DeclRange.Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range config.Children {
|
||||
childDiags := CheckCoreVersionRequirements(c)
|
||||
diags = diags.Append(childDiags)
|
||||
}
|
||||
diags = diags.Append(config.CheckCoreVersionRequirements())
|
||||
|
||||
return diags
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue