Skip to content

Commit 698cb0d

Browse files
authored
init: detect and report invalid submodules (#35839)
1 parent c986269 commit 698cb0d

File tree

4 files changed

+109
-11
lines changed

4 files changed

+109
-11
lines changed

internal/initwd/module_install.go

+39-10
Original file line numberDiff line numberDiff line change
@@ -659,16 +659,45 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *config
659659
// Finally we are ready to try actually loading the module.
660660
mod, mDiags := i.loader.Parser().LoadConfigDir(modDir)
661661
if mod == nil {
662-
// nil indicates missing or unreadable directory, so we'll
663-
// discard the returned diags and return a more specific
664-
// error message here. For registry modules this actually
665-
// indicates a bug in the code above, since it's not the
666-
// user's responsibility to create the directory in this case.
667-
diags = diags.Append(&hcl.Diagnostic{
668-
Severity: hcl.DiagError,
669-
Summary: "Unreadable module directory",
670-
Detail: fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
671-
})
662+
// a nil module indicates a missing or unreadable directory, typically
663+
// this would indicate that Terraform has done something wrong.
664+
// However, if the subDir is not empty then it is possible that the
665+
// module was properly downloaded but the user is trying to read a
666+
// subdirectory that doesn't exist. In this case, it's not a problem
667+
// with Terraform.
668+
if len(subDir) > 0 {
669+
// Let's make this error message as precise as possible.
670+
_, instErr := os.Stat(instPath)
671+
_, subErr := os.Stat(modDir)
672+
if instErr == nil && os.IsNotExist(subErr) {
673+
// Then the root directory the module was downloaded to could
674+
// be loaded fine, but the subdirectory does not exist. This
675+
// definitely means the user is trying to read a subdirectory
676+
// that doesn't exist.
677+
diags = diags.Append(&hcl.Diagnostic{
678+
Severity: hcl.DiagError,
679+
Summary: "Unreadable module subdirectory",
680+
Detail: fmt.Sprintf("The directory %s does not exist. The target submodule %s does not exist within the target module.", modDir, subDir),
681+
})
682+
} else {
683+
// There's something else gone wrong here, so we'll report it
684+
// as a bug in Terraform.
685+
diags = diags.Append(&hcl.Diagnostic{
686+
Severity: hcl.DiagError,
687+
Summary: "Unreadable module directory",
688+
Detail: fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
689+
})
690+
}
691+
} else {
692+
// If there is no subDir, then somehow the module was downloaded but
693+
// could not be read even at the root directory it was downloaded into.
694+
// This is definitely something that Terraform is doing wrong.
695+
diags = diags.Append(&hcl.Diagnostic{
696+
Severity: hcl.DiagError,
697+
Summary: "Unreadable module directory",
698+
Detail: fmt.Sprintf("The directory %s could not be read. This is a bug in Terraform and should be reported.", modDir),
699+
})
700+
}
672701
} else if vDiags := mod.CheckCoreVersionRequirements(req.Path, req.SourceAddr); vDiags.HasErrors() {
673702
// If the core version requirements are not met, we drop any other
674703
// diagnostics, as they may reflect language changes from future

internal/initwd/module_install_test.go

+41-1
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,45 @@ func TestModuleInstaller_symlink(t *testing.T) {
431431
assertResultDeepEqual(t, gotTraces, wantTraces)
432432
}
433433

434+
func TestLoaderInstallModules_invalidRegistry(t *testing.T) {
435+
if os.Getenv("TF_ACC") == "" {
436+
t.Skip("this test accesses registry.terraform.io and github.com; set TF_ACC=1 to run it")
437+
}
438+
439+
fixtureDir := filepath.Clean("testdata/invalid-registry-modules")
440+
tmpDir, done := tempChdir(t, fixtureDir)
441+
// the module installer runs filepath.EvalSymlinks() on the destination
442+
// directory before copying files, and the resultant directory is what is
443+
// returned by the install hooks. Without this, tests could fail on machines
444+
// where the default temp dir was a symlink.
445+
dir, err := filepath.EvalSymlinks(tmpDir)
446+
if err != nil {
447+
t.Error(err)
448+
}
449+
450+
defer done()
451+
452+
hooks := &testInstallHooks{}
453+
modulesDir := filepath.Join(dir, ".terraform/modules")
454+
455+
loader, close := configload.NewLoaderForTests(t)
456+
defer close()
457+
inst := NewModuleInstaller(modulesDir, loader, registry.NewClient(nil, nil))
458+
_, diags := inst.InstallModules(context.Background(), dir, "tests", false, false, hooks)
459+
460+
if !diags.HasErrors() {
461+
t.Fatal("expected error")
462+
} else {
463+
assertDiagnosticCount(t, diags, 1)
464+
assertDiagnosticSummary(t, diags, "Unreadable module subdirectory")
465+
466+
// the diagnostic should specifically call out the submodule that failed
467+
if !strings.Contains(diags[0].Description().Detail, "target submodule modules/child_c") {
468+
t.Errorf("unmatched error detail")
469+
}
470+
}
471+
}
472+
434473
func TestLoaderInstallModules_registry(t *testing.T) {
435474
if os.Getenv("TF_ACC") == "" {
436475
t.Skip("this test accesses registry.terraform.io and github.com; set TF_ACC=1 to run it")
@@ -980,13 +1019,14 @@ func assertDiagnosticSummary(t *testing.T, diags tfdiags.Diagnostics, want strin
9801019

9811020
for _, diag := range diags {
9821021
if diag.Description().Summary == want {
1022+
t.Logf("matching diagnostic detail %q", diag.Description().Detail)
9831023
return false
9841024
}
9851025
}
9861026

9871027
t.Errorf("missing diagnostic summary %q", want)
9881028
for _, diag := range diags {
989-
t.Logf("- %#v", diag)
1029+
t.Logf("- %#v", diag.Description().Summary)
9901030
}
9911031
return true
9921032
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.terraform/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This fixture indirectly depends on a github repo at:
2+
# https://github.com/hashicorp/terraform-aws-module-installer-acctest
3+
# ...and expects its v0.0.1 tag to be pointing at the following commit:
4+
# d676ab2559d4e0621d59e3c3c4cbb33958ac4608
5+
#
6+
# This repository is accessed indirectly via:
7+
# https://registry.terraform.io/modules/hashicorp/module-installer-acctest/aws/0.0.1
8+
#
9+
# Since the tag's id is included in a downloaded archive, it is expected to
10+
# have the following id:
11+
# 853d03855b3290a3ca491d4c3a7684572dd42237
12+
# (this particular assumption is encoded in the tests that use this fixture)
13+
14+
15+
variable "v" {
16+
description = "in local caller for registry-modules"
17+
default = ""
18+
}
19+
20+
// see ../registry-modules/root.tf for more info, and for valid usages to the
21+
// acceptance test module
22+
23+
// this sub module is not available in the registry, we should see an error
24+
// message in the output
25+
module "acctest_child_c" {
26+
source = "hashicorp/module-installer-acctest/aws//modules/child_c"
27+
version = "0.0.1"
28+
}

0 commit comments

Comments
 (0)