mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-17 20:22:58 -06:00
40ec62c139
Earlier work to make "terraform init" interruptible made the getproviders package context-aware in order to allow provider installation to be cancelled. Here we make a similar change for module installation, which is now also cancellable with SIGINT. This involves plumbing context through initwd and getmodules. Functions which can make network requests now include a context parameter whose cancellation cancels those requests. Since the module installation code is shared, "terraform get" is now also interruptible during module installation.
302 lines
9.0 KiB
Go
302 lines
9.0 KiB
Go
package initwd
|
|
|
|
import (
|
|
"context"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
version "github.com/hashicorp/go-version"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/configs/configload"
|
|
"github.com/hashicorp/terraform/internal/copy"
|
|
"github.com/hashicorp/terraform/internal/registry"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
func TestDirFromModule_registry(t *testing.T) {
|
|
if os.Getenv("TF_ACC") == "" {
|
|
t.Skip("this test accesses registry.terraform.io and github.com; set TF_ACC=1 to run it")
|
|
}
|
|
|
|
fixtureDir := filepath.Clean("testdata/empty")
|
|
tmpDir, done := tempChdir(t, fixtureDir)
|
|
defer done()
|
|
|
|
// the module installer runs filepath.EvalSymlinks() on the destination
|
|
// directory before copying files, and the resultant directory is what is
|
|
// returned by the install hooks. Without this, tests could fail on machines
|
|
// where the default temp dir was a symlink.
|
|
dir, err := filepath.EvalSymlinks(tmpDir)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
modsDir := filepath.Join(dir, ".terraform/modules")
|
|
|
|
hooks := &testInstallHooks{}
|
|
|
|
reg := registry.NewClient(nil, nil)
|
|
diags := DirFromModule(context.Background(), dir, modsDir, "hashicorp/module-installer-acctest/aws//examples/main", reg, hooks)
|
|
assertNoDiagnostics(t, diags)
|
|
|
|
v := version.Must(version.NewVersion("0.0.2"))
|
|
|
|
wantCalls := []testInstallHookCall{
|
|
// The module specified to populate the root directory is not mentioned
|
|
// here, because the hook mechanism is defined to talk about descendent
|
|
// modules only and so a caller to InitDirFromModule is expected to
|
|
// produce its own user-facing announcement about the root module being
|
|
// installed.
|
|
|
|
// Note that "root" in the following examples is, confusingly, the
|
|
// label on the module block in the example we've installed here:
|
|
// module "root" {
|
|
|
|
{
|
|
Name: "Download",
|
|
ModuleAddr: "root",
|
|
PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws",
|
|
Version: v,
|
|
},
|
|
{
|
|
Name: "Install",
|
|
ModuleAddr: "root",
|
|
Version: v,
|
|
// NOTE: This local path and the other paths derived from it below
|
|
// can vary depending on how the registry is implemented. At the
|
|
// time of writing this test, registry.terraform.io returns
|
|
// git repository source addresses and so this path refers to the
|
|
// root of the git clone, but historically the registry referred
|
|
// to GitHub-provided tar archives which meant that there was an
|
|
// extra level of subdirectory here for the typical directory
|
|
// nesting in tar archives, which would've been reflected as
|
|
// an extra segment on this path. If this test fails due to an
|
|
// additional path segment in future, then a change to the upstream
|
|
// registry might be the root cause.
|
|
LocalPath: filepath.Join(dir, ".terraform/modules/root"),
|
|
},
|
|
{
|
|
Name: "Install",
|
|
ModuleAddr: "root.child_a",
|
|
LocalPath: filepath.Join(dir, ".terraform/modules/root/modules/child_a"),
|
|
},
|
|
{
|
|
Name: "Install",
|
|
ModuleAddr: "root.child_a.child_b",
|
|
LocalPath: filepath.Join(dir, ".terraform/modules/root/modules/child_b"),
|
|
},
|
|
}
|
|
|
|
if diff := cmp.Diff(wantCalls, hooks.Calls); diff != "" {
|
|
t.Fatalf("wrong installer calls\n%s", diff)
|
|
}
|
|
|
|
loader, err := configload.NewLoader(&configload.Config{
|
|
ModulesDir: modsDir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Make sure the configuration is loadable now.
|
|
// (This ensures that correct information is recorded in the manifest.)
|
|
config, loadDiags := loader.LoadConfig(".")
|
|
if assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags)) {
|
|
return
|
|
}
|
|
|
|
wantTraces := map[string]string{
|
|
"": "in example",
|
|
"root": "in root module",
|
|
"root.child_a": "in child_a module",
|
|
"root.child_a.child_b": "in child_b module",
|
|
}
|
|
gotTraces := map[string]string{}
|
|
config.DeepEach(func(c *configs.Config) {
|
|
path := strings.Join(c.Path, ".")
|
|
if c.Module.Variables["v"] == nil {
|
|
gotTraces[path] = "<missing>"
|
|
return
|
|
}
|
|
varDesc := c.Module.Variables["v"].Description
|
|
gotTraces[path] = varDesc
|
|
})
|
|
assertResultDeepEqual(t, gotTraces, wantTraces)
|
|
}
|
|
|
|
func TestDirFromModule_submodules(t *testing.T) {
|
|
fixtureDir := filepath.Clean("testdata/empty")
|
|
fromModuleDir, err := filepath.Abs("./testdata/local-modules")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// DirFromModule will expand ("canonicalize") the pathnames, so we must do
|
|
// the same for our "wantCalls" comparison values. Otherwise this test
|
|
// will fail when building in a source tree with symlinks in $PWD.
|
|
//
|
|
// See also: https://github.com/hashicorp/terraform/issues/26014
|
|
//
|
|
fromModuleDirRealpath, err := filepath.EvalSymlinks(fromModuleDir)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
tmpDir, done := tempChdir(t, fixtureDir)
|
|
defer done()
|
|
|
|
hooks := &testInstallHooks{}
|
|
dir, err := filepath.EvalSymlinks(tmpDir)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
modInstallDir := filepath.Join(dir, ".terraform/modules")
|
|
|
|
diags := DirFromModule(context.Background(), dir, modInstallDir, fromModuleDir, nil, hooks)
|
|
assertNoDiagnostics(t, diags)
|
|
wantCalls := []testInstallHookCall{
|
|
{
|
|
Name: "Install",
|
|
ModuleAddr: "child_a",
|
|
LocalPath: filepath.Join(fromModuleDirRealpath, "child_a"),
|
|
},
|
|
{
|
|
Name: "Install",
|
|
ModuleAddr: "child_a.child_b",
|
|
LocalPath: filepath.Join(fromModuleDirRealpath, "child_a/child_b"),
|
|
},
|
|
}
|
|
|
|
if assertResultDeepEqual(t, hooks.Calls, wantCalls) {
|
|
return
|
|
}
|
|
|
|
loader, err := configload.NewLoader(&configload.Config{
|
|
ModulesDir: modInstallDir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Make sure the configuration is loadable now.
|
|
// (This ensures that correct information is recorded in the manifest.)
|
|
config, loadDiags := loader.LoadConfig(".")
|
|
if assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags)) {
|
|
return
|
|
}
|
|
wantTraces := map[string]string{
|
|
"": "in root module",
|
|
"child_a": "in child_a module",
|
|
"child_a.child_b": "in child_b module",
|
|
}
|
|
gotTraces := map[string]string{}
|
|
|
|
config.DeepEach(func(c *configs.Config) {
|
|
path := strings.Join(c.Path, ".")
|
|
if c.Module.Variables["v"] == nil {
|
|
gotTraces[path] = "<missing>"
|
|
return
|
|
}
|
|
varDesc := c.Module.Variables["v"].Description
|
|
gotTraces[path] = varDesc
|
|
})
|
|
assertResultDeepEqual(t, gotTraces, wantTraces)
|
|
}
|
|
|
|
// TestDirFromModule_rel_submodules is similar to the test above, but the
|
|
// from-module is relative to the install dir ("../"):
|
|
// https://github.com/hashicorp/terraform/issues/23010
|
|
func TestDirFromModule_rel_submodules(t *testing.T) {
|
|
// This test creates a tmpdir with the following directory structure:
|
|
// - tmpdir/local-modules (with contents of testdata/local-modules)
|
|
// - tmpdir/empty: the workDir we CD into for the test
|
|
// - tmpdir/empty/target (target, the destination for init -from-module)
|
|
tmpDir, err := ioutil.TempDir("", "terraform-configload")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fromModuleDir := filepath.Join(tmpDir, "local-modules")
|
|
workDir := filepath.Join(tmpDir, "empty")
|
|
if err := os.Mkdir(fromModuleDir, os.ModePerm); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := copy.CopyDir(fromModuleDir, "testdata/local-modules"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Mkdir(workDir, os.ModePerm); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
targetDir := filepath.Join(tmpDir, "target")
|
|
if err := os.Mkdir(targetDir, os.ModePerm); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
oldDir, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = os.Chdir(targetDir)
|
|
if err != nil {
|
|
t.Fatalf("failed to switch to temp dir %s: %s", tmpDir, err)
|
|
}
|
|
defer os.Chdir(oldDir)
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
hooks := &testInstallHooks{}
|
|
|
|
modInstallDir := ".terraform/modules"
|
|
sourceDir := "../local-modules"
|
|
diags := DirFromModule(context.Background(), ".", modInstallDir, sourceDir, nil, hooks)
|
|
assertNoDiagnostics(t, diags)
|
|
wantCalls := []testInstallHookCall{
|
|
{
|
|
Name: "Install",
|
|
ModuleAddr: "child_a",
|
|
LocalPath: filepath.Join(sourceDir, "child_a"),
|
|
},
|
|
{
|
|
Name: "Install",
|
|
ModuleAddr: "child_a.child_b",
|
|
LocalPath: filepath.Join(sourceDir, "child_a/child_b"),
|
|
},
|
|
}
|
|
|
|
if assertResultDeepEqual(t, hooks.Calls, wantCalls) {
|
|
return
|
|
}
|
|
|
|
loader, err := configload.NewLoader(&configload.Config{
|
|
ModulesDir: modInstallDir,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Make sure the configuration is loadable now.
|
|
// (This ensures that correct information is recorded in the manifest.)
|
|
config, loadDiags := loader.LoadConfig(".")
|
|
if assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags)) {
|
|
return
|
|
}
|
|
wantTraces := map[string]string{
|
|
"": "in root module",
|
|
"child_a": "in child_a module",
|
|
"child_a.child_b": "in child_b module",
|
|
}
|
|
gotTraces := map[string]string{}
|
|
|
|
config.DeepEach(func(c *configs.Config) {
|
|
path := strings.Join(c.Path, ".")
|
|
if c.Module.Variables["v"] == nil {
|
|
gotTraces[path] = "<missing>"
|
|
return
|
|
}
|
|
varDesc := c.Module.Variables["v"].Description
|
|
gotTraces[path] = varDesc
|
|
})
|
|
assertResultDeepEqual(t, gotTraces, wantTraces)
|
|
}
|