diff --git a/internal/initwd/module_install.go b/internal/initwd/module_install.go index adc5dec5ec1e..2a1f7b685f3d 100644 --- a/internal/initwd/module_install.go +++ b/internal/initwd/module_install.go @@ -10,7 +10,9 @@ import ( "path/filepath" "strings" + "github.com/apparentlymart/go-versions/versions" version "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-config-inspect/tfconfig" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/earlyconfig" @@ -387,9 +389,45 @@ func (i *ModuleInstaller) installRegistryModule(ctx context.Context, req *earlyc // If we've found a pre-release version then we'll ignore it unless // it was exactly requested. - if v.Prerelease() != "" && req.VersionConstraints.String() != v.String() { - log.Printf("[TRACE] ModuleInstaller: %s ignoring %s because it is a pre-release and was not requested exactly", key, v) - continue + // + // The prerelease checking will be handled by a different library for + // 2 reasons. First, this other library automatically includes the + // "prerelease versions must be exactly requested" behaviour that we are + // looking for. Second, this other library is used to handle all version + // constraints for the provider logic and this is the first step to + // making the module and provider version logic match. + if v.Prerelease() != "" { + // At this point all versions published by the module with + // 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()) + 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()) + continue + } + + // Validate the version is also readable by the other versions + // library. + version, err := versions.ParseVersion(v.String()) + if err != nil { + log.Printf("[WARN] ModuleInstaller: %s ignoring %s because the version (%s) reported by the module could not be parsed: %s", key, v, v.String(), err.Error()) + continue + } + + // Finally, check if the prerelease is acceptable to version. As + // highlighted previously, we go through all of this because the + // apparentlymart/go-versions library handles prerelease constraints + // in the apporach we want to. + if !acceptableVersions.Has(version) { + log.Printf("[TRACE] ModuleInstaller: %s ignoring %s because it is a pre-release and was not requested exactly", key, v) + continue + } + + // If we reach here, it means this prerelease version was exactly + // requested according to the extra constraints of this library. + // We fall through and allow the other library to also validate it + // for consistency. } if latestVersion == nil || v.GreaterThan(latestVersion) { diff --git a/internal/initwd/module_install_test.go b/internal/initwd/module_install_test.go index b05c5619f665..6f77c60f60ea 100644 --- a/internal/initwd/module_install_test.go +++ b/internal/initwd/module_install_test.go @@ -178,6 +178,54 @@ func TestModuleInstaller_explicitPackageBoundary(t *testing.T) { } } +func TestModuleInstaller_ExactMatchPrerelease(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/prerelease-version-constraint-match") + dir, done := tempChdir(t, fixtureDir) + defer done() + + hooks := &testInstallHooks{} + + modulesDir := filepath.Join(dir, ".terraform/modules") + inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil)) + cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks) + + if diags.HasErrors() { + t.Fatalf("found unexpected errors: %s", diags.Err()) + } + + if !cfg.Children["acctest_exact"].Version.Equal(version.Must(version.NewVersion("v0.0.3-alpha.1"))) { + t.Fatalf("expected version %s but found version %s", "v0.0.3-alpha.1", cfg.Version.String()) + } +} + +func TestModuleInstaller_PartialMatchPrerelease(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/prerelease-version-constraint") + dir, done := tempChdir(t, fixtureDir) + defer done() + + hooks := &testInstallHooks{} + + modulesDir := filepath.Join(dir, ".terraform/modules") + inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil)) + cfg, diags := inst.InstallModules(context.Background(), ".", false, hooks) + + if diags.HasErrors() { + t.Fatalf("found unexpected errors: %s", diags.Err()) + } + + if !cfg.Children["acctest_partial"].Version.Equal(version.Must(version.NewVersion("v0.0.2"))) { + t.Fatalf("expected version %s but found version %s", "v0.0.2", cfg.Version.String()) + } +} + func TestModuleInstaller_invalid_version_constraint_error(t *testing.T) { fixtureDir := filepath.Clean("testdata/invalid-version-constraint") dir, done := tempChdir(t, fixtureDir) diff --git a/internal/initwd/testdata/prerelease-version-constraint-match/root.tf b/internal/initwd/testdata/prerelease-version-constraint-match/root.tf new file mode 100644 index 000000000000..b68baf770eb0 --- /dev/null +++ b/internal/initwd/testdata/prerelease-version-constraint-match/root.tf @@ -0,0 +1,7 @@ +# We expect this test to download the requested version because it is an exact +# match for a prerelease version. + +module "acctest_exact" { + source = "hashicorp/module-installer-acctest/aws" + version = "=0.0.3-alpha.1" +} diff --git a/internal/initwd/testdata/prerelease-version-constraint/root.tf b/internal/initwd/testdata/prerelease-version-constraint/root.tf new file mode 100644 index 000000000000..8ff3dd68da55 --- /dev/null +++ b/internal/initwd/testdata/prerelease-version-constraint/root.tf @@ -0,0 +1,8 @@ +# We expect this test to download the version 0.0.2, the one before the +# specified version even with the equality because the specified version is a +# prerelease. + +module "acctest_partial" { + source = "hashicorp/module-installer-acctest/aws" + version = "<=0.0.3-alpha.1" +}