diff --git a/internal/initwd/module_install.go b/internal/initwd/module_install.go index 543405b5b..c07591bf7 100644 --- a/internal/initwd/module_install.go +++ b/internal/initwd/module_install.go @@ -2,7 +2,6 @@ package initwd import ( "fmt" - "github.com/hashicorp/terraform/registry" "log" "os" "path/filepath" @@ -13,6 +12,7 @@ import ( "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/internal/earlyconfig" "github.com/hashicorp/terraform/internal/modsdir" + "github.com/hashicorp/terraform/registry" "github.com/hashicorp/terraform/registry/regsrc" "github.com/hashicorp/terraform/tfdiags" ) @@ -245,7 +245,18 @@ func (i *ModuleInstaller) installLocalModule(req *earlyconfig.ModuleRequest, key // filesystem at all because the parent already wrote // the files we need, and so we just load up what's already here. newDir := filepath.Join(parentRecord.Dir, req.SourceAddr) + log.Printf("[TRACE] ModuleInstaller: %s uses directory from parent: %s", key, newDir) + // 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()), + )) + } + mod, mDiags := earlyconfig.LoadModule(newDir) if mod == nil { // nil indicates missing or unreadable directory, so we'll diff --git a/internal/initwd/module_install_test.go b/internal/initwd/module_install_test.go index eb2e877c7..bd44ac344 100644 --- a/internal/initwd/module_install_test.go +++ b/internal/initwd/module_install_test.go @@ -111,6 +111,67 @@ func TestModuleInstaller_error(t *testing.T) { } } +func TestModuleInstaller_symlink(t *testing.T) { + fixtureDir := filepath.Clean("test-fixtures/local-module-symlink") + dir, done := tempChdir(t, fixtureDir) + defer done() + + hooks := &testInstallHooks{} + + modulesDir := filepath.Join(dir, ".terraform/modules") + inst := NewModuleInstaller(modulesDir, nil) + _, diags := inst.InstallModules(".", false, hooks) + assertNoDiagnostics(t, diags) + + wantCalls := []testInstallHookCall{ + { + Name: "Install", + ModuleAddr: "child_a", + PackageAddr: "", + LocalPath: "child_a", + }, + { + Name: "Install", + ModuleAddr: "child_a.child_b", + PackageAddr: "", + LocalPath: "child_a/child_b", + }, + } + + if assertResultDeepEqual(t, hooks.Calls, wantCalls) { + return + } + + loader, err := configload.NewLoader(&configload.Config{ + ModulesDir: modulesDir, + }) + 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(".") + assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags)) + + 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] = "" + return + } + varDesc := c.Module.Variables["v"].Description + gotTraces[path] = varDesc + }) + assertResultDeepEqual(t, gotTraces, wantTraces) +} + func TestLoaderInstallModules_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") diff --git a/internal/initwd/test-fixtures/local-module-symlink/child_a/child_a.tf b/internal/initwd/test-fixtures/local-module-symlink/child_a/child_a.tf new file mode 100644 index 000000000..68ebb8e40 --- /dev/null +++ b/internal/initwd/test-fixtures/local-module-symlink/child_a/child_a.tf @@ -0,0 +1,9 @@ + +variable "v" { + description = "in child_a module" + default = "" +} + +module "child_b" { + source = "./child_b" +} diff --git a/internal/initwd/test-fixtures/local-module-symlink/child_a/child_b/child_b.tf b/internal/initwd/test-fixtures/local-module-symlink/child_a/child_b/child_b.tf new file mode 100644 index 000000000..e2e220916 --- /dev/null +++ b/internal/initwd/test-fixtures/local-module-symlink/child_a/child_b/child_b.tf @@ -0,0 +1,9 @@ + +variable "v" { + description = "in child_b module" + default = "" +} + +output "hello" { + value = "Hello from child_b!" +} diff --git a/internal/initwd/test-fixtures/local-module-symlink/modules/child_a b/internal/initwd/test-fixtures/local-module-symlink/modules/child_a new file mode 120000 index 000000000..0d568b143 --- /dev/null +++ b/internal/initwd/test-fixtures/local-module-symlink/modules/child_a @@ -0,0 +1 @@ +../child_a/ \ No newline at end of file diff --git a/internal/initwd/test-fixtures/local-module-symlink/root.tf b/internal/initwd/test-fixtures/local-module-symlink/root.tf new file mode 100644 index 000000000..1ca7ca32c --- /dev/null +++ b/internal/initwd/test-fixtures/local-module-symlink/root.tf @@ -0,0 +1,9 @@ + +variable "v" { + description = "in root module" + default = "" +} + +module "child_a" { + source = "./modules/child_a" +}