blob: 9714ed3465f7283dbf2d797d71f2768e2c734e89 [file] [log] [blame]
package initwd
import (
"context"
"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 := t.TempDir()
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)
}
t.Cleanup(func() {
os.Chdir(oldDir)
})
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)
}