Repository: gruntwork-io/terragrunt (language: go)

Issue:
Pre-releases break version constraints
I should probably also mention that the beta build broke the TG version check we had:
```
% terragrunt apply
18:24:40.515 ERROR  The currently installed version of Terragrunt (0.67.5-beta2024091201) is not compatible with the version constraint requiring (>= 0.55.2).
18:24:40.515 ERROR  Unable to determine underlying exit code, so Terragrunt will exit with error code 1
```
It was easy to fix by commenting out the version constraint, but if you start to do more beta builds you'll probably want to figure out why the check is failing.

_Originally posted by @jyoder-deloitte in https://github.com/gruntwork-io/terragrunt/pull/3400#issuecomment-2347832016_


Discussion / hints:

Fix patch:
diff --git a/cli/commands/terraform/version_check.go b/cli/commands/terraform/version_check.go
index 6ad43912cf..6e0776badc 100644
--- a/cli/commands/terraform/version_check.go
+++ b/cli/commands/terraform/version_check.go
@@ -141,7 +141,18 @@ func CheckTerragruntVersionMeetsConstraint(currentVersion *version.Version, cons
 		return err
 	}
 
-	if !versionConstraint.Check(currentVersion) {
+	checkedVersion := currentVersion
+
+	if currentVersion.Prerelease() != "" {
+		// The logic in hashicorp/go-version is such that it will not consider a prerelease version to be
+		// compatible with a constraint that does not have a prerelease version. This is not the behavior we want
+		// for Terragrunt, so we strip the prerelease version before checking the constraint.
+		//
+		// https://github.com/hashicorp/go-version/issues/130
+		checkedVersion = currentVersion.Core()
+	}
+
+	if !versionConstraint.Check(checkedVersion) {
 		return errors.WithStackTrace(InvalidTerragruntVersion{CurrentVersion: currentVersion, VersionConstraints: versionConstraint})
 	}
 


Test patch:
diff --git a/cli/commands/terraform/version_check_test.go b/cli/commands/terraform/version_check_test.go
index 7ac2de9ce1..424814123f 100644
--- a/cli/commands/terraform/version_check_test.go
+++ b/cli/commands/terraform/version_check_test.go
@@ -120,6 +120,8 @@ func testParseTerraformVersion(t *testing.T, versionString string, expectedVersi
 	}
 }
 
+// TODO: Refactor these into a test table.
+
 // Terragrunt Version Checking
 func TestCheckTerragruntVersionMeetsConstraintEqual(t *testing.T) {
 	t.Parallel()
@@ -146,6 +148,11 @@ func TestCheckTerragruntVersionMeetsConstraintLessMajor(t *testing.T) {
 	testCheckTerragruntVersionMeetsConstraint(t, "v0.22.15", ">= v0.23.18", false)
 }
 
+func TestCheckTerragruntVersionMeetsConstraintPrerelease(t *testing.T) {
+	t.Parallel()
+	testCheckTerragruntVersionMeetsConstraint(t, "v0.23.18-alpha202409013", ">= v0.23.18", true)
+}
+
 func testCheckTerragruntVersionMeetsConstraint(t *testing.T, currentVersion string, versionConstraint string, versionMeetsConstraint bool) {
 	t.Helper()
 
diff --git a/test/integration_test.go b/test/integration_test.go
index 99e27bf586..e099fe8b7c 100644
--- a/test/integration_test.go
+++ b/test/integration_test.go
@@ -2393,7 +2393,10 @@ func TestTerragruntIncludeParentHclFile(t *testing.T) {
 	assert.Equal(t, 1, strings.Count(out, "common_hcl"))
 }
 
-func TestTerragruntVersionConstraints(t *testing.T) {
+// The over all test here can run in parallel, but the subtests cannot.
+// This is due to a race condition brought about by overriding `version.Version` in
+// runTerragruntVersionCommand
+func TestTerragruntVersionConstraints(t *testing.T) { //nolint:tparallel
 	t.Parallel()
 
 	tc := []struct {
@@ -2409,37 +2412,45 @@ func TestTerragruntVersionConstraints(t *testing.T) {
 			true,
 		},
 		{
-			"version meets constriant greater patch",
+			"version meets constraint greater patch",
 			"v0.23.19",
 			"terragrunt_version_constraint = \">= v0.23.18\"",
 			true,
 		},
 		{
-			"version meets constriant greater major",
+			"version meets constraint greater major",
 			"v1.0.0",
 			"terragrunt_version_constraint = \">= v0.23.18\"",
 			true,
 		},
 		{
-			"version meets constriant less patch",
+			"version fails constraint less patch",
 			"v0.23.17",
 			"terragrunt_version_constraint = \">= v0.23.18\"",
 			false,
 		},
 		{
-			"version meets constriant less major",
+			"version fails constraint less major",
 			"v0.22.18",
 			"terragrunt_version_constraint = \">= v0.23.18\"",
 			false,
 		},
+		{
+			"version meets constraint pre-release",
+			"v0.23.18-alpha2024091301",
+			"terragrunt_version_constraint = \">= v0.23.18\"",
+			true,
+		},
+		{
+			"version fails constraint pre-release",
+			"v0.23.18-alpha2024091301",
+			"terragrunt_version_constraint = \"< v0.23.18\"",
+			false,
+		},
 	}
 
-	for _, tt := range tc {
-		tt := tt
-
+	for _, tt := range tc { //nolint:paralleltest
 		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-
 			tmpEnvPath := copyEnvironment(t, testFixtureReadConfig)
 			rootPath := filepath.Join(tmpEnvPath, testFixtureReadConfig, "with_constraints")
 
@@ -2448,7 +2459,18 @@ func TestTerragruntVersionConstraints(t *testing.T) {
 			stdout := bytes.Buffer{}
 			stderr := bytes.Buffer{}
 
-			err := runTerragruntVersionCommand(t, tt.terragruntVersion, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-config %s --terragrunt-working-dir %s", tmpTerragruntConfigPath, rootPath), &stdout, &stderr)
+			err := runTerragruntVersionCommand(
+				t,
+				tt.terragruntVersion,
+				fmt.Sprintf(
+					"terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-config %s --terragrunt-working-dir %s",
+					tmpTerragruntConfigPath,
+					rootPath,
+				),
+				&stdout,
+				&stderr,
+			)
+
 			logBufferContentsLineByLine(t, stdout, "stdout")
 			logBufferContentsLineByLine(t, stderr, "stderr")