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

Issue:
Compatibility with Terraform 1.10
Hi,

## Describe the enhancement

Terraform 1.10 was released two hours ago and Terragrunt is not yet compatible with this version !! 

Two hours !! What are you doing, guys ?! 🤣 

Thanks, ;)

David 


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4afc601616..9eeee9eb56 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -9,7 +9,7 @@ env: &env
     GRUNTWORK_INSTALLER_VERSION: v0.0.39
     MODULE_CI_VERSION: v0.57.0
     OPENTOFU_VERSION: "1.9.0"
-    TERRAFORM_VERSION: "1.9.7"
+    TERRAFORM_VERSION: "1.10.5"
     TFLINT_VERSION: "0.47.0"
     TOFU_ENGINE_VERSION: "v0.0.11"
 
diff --git a/docs/_docs/04_reference/07-supported-versions.md b/docs/_docs/04_reference/07-supported-versions.md
index 1984401b85..c4426ad24a 100644
--- a/docs/_docs/04_reference/07-supported-versions.md
+++ b/docs/_docs/04_reference/07-supported-versions.md
@@ -28,6 +28,7 @@ The officially supported versions are:
 
 | Terraform Version | Terragrunt Version                                                                                                                                    |
 |-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 1.10.x            | >= [0.74.0](https://github.com/gruntwork-io/terragrunt/releases/tag/v0.74.0)                                                                          |
 | 1.9.x             | >= [0.60.0](https://github.com/gruntwork-io/terragrunt/releases/tag/v0.60.0)                                                                          |
 | 1.8.x             | >= [0.57.0](https://github.com/gruntwork-io/terragrunt/releases/tag/v0.57.0)                                                                          |
 | 1.7.x             | >= [0.56.0](https://github.com/gruntwork-io/terragrunt/releases/tag/v0.56.0)                                                                          |


Test patch:
diff --git a/test/fixtures/ephemeral-inputs/main.tf b/test/fixtures/ephemeral-inputs/main.tf
new file mode 100644
index 0000000000..a19c74927d
--- /dev/null
+++ b/test/fixtures/ephemeral-inputs/main.tf
@@ -0,0 +1,14 @@
+variable "test" {
+  type      = string
+  default   = "test_value"
+  ephemeral = true
+}
+
+output "output" {
+  value = ephemeralasnull(var.test)
+}
+
+resource "local_file" "file" {
+  content  = "test"
+  filename = "${path.module}/test.txt"
+}
diff --git a/test/fixtures/ephemeral-inputs/terragrunt.hcl b/test/fixtures/ephemeral-inputs/terragrunt.hcl
new file mode 100644
index 0000000000..4e8ec1a0db
--- /dev/null
+++ b/test/fixtures/ephemeral-inputs/terragrunt.hcl
@@ -0,0 +1,4 @@
+
+inputs = {
+  test = "test input 46521694"
+}
diff --git a/test/helpers/package.go b/test/helpers/package.go
index baf08c70f2..d1778ed481 100644
--- a/test/helpers/package.go
+++ b/test/helpers/package.go
@@ -658,10 +658,35 @@ func ExpectedWrongCommandErr(command string) error {
 	return run.WrongTerraformCommand(command)
 }
 
+// IsTerraform checks if the wrapped binary currently in use is the Terraform binary.
 func IsTerraform() bool {
 	return WrappedBinary() == TerraformBinary
 }
 
+// IsTerraform110OrHigher checks if the installed Terraform binary is version 1.10.0 or higher.
+func IsTerraform110OrHigher() bool {
+	const (
+		requiredMajor = 1
+		requiredMinor = 10
+	)
+
+	if !IsTerraform() {
+		return false
+	}
+
+	output, err := exec.Command(WrappedBinary(), "-version").Output()
+	if err != nil {
+		return false
+	}
+
+	matches := regexp.MustCompile(`Terraform v(\d+)\.(\d+)\.`).FindStringSubmatch(string(output))
+
+	major, _ := strconv.Atoi(matches[1])
+	minor, _ := strconv.Atoi(matches[2])
+
+	return major > requiredMajor || (major == requiredMajor && minor >= requiredMinor)
+}
+
 func FindFilesWithExtension(dir string, ext string) ([]string, error) {
 	var files []string
 
diff --git a/test/integration_destroy_test.go b/test/integration_destroy_test.go
index e2d2d75ead..28d8795910 100644
--- a/test/integration_destroy_test.go
+++ b/test/integration_destroy_test.go
@@ -313,6 +313,9 @@ func TestStorePlanFilesRunAllDestroy(t *testing.T) {
 	helpers.CleanupTerraformFolder(t, tmpEnvPath)
 	testPath := util.JoinPath(tmpEnvPath, testFixtureOutDir)
 
+	dependencyPath := util.JoinPath(tmpEnvPath, testFixtureOutDir, "dependency")
+	helpers.RunTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", dependencyPath, tmpDir))
+
 	// plan and apply
 	_, _, err := helpers.RunTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
 	require.NoError(t, err)
diff --git a/test/integration_test.go b/test/integration_test.go
index b5b551cecf..532c29f4c1 100644
--- a/test/integration_test.go
+++ b/test/integration_test.go
@@ -110,6 +110,7 @@ const (
 	textFixtureDisjointSymlinks               = "fixtures/stack/disjoint-symlinks"
 	testFixtureLogStreaming                   = "fixtures/streaming"
 	testFixtureCLIFlagHints                   = "fixtures/cli-flag-hints"
+	testFixtureEphemeralInputs                = "fixtures/ephemeral-inputs"
 
 	terraformFolder = ".terraform"
 
@@ -528,12 +529,12 @@ func TestLogStdoutLevel(t *testing.T) {
 	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureLogStdoutLevel)
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureLogStdoutLevel)
 
-	stdout, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt apply -auto-approve --terragrunt-log-level trace --terragrunt-non-interactive -no-color --terragrunt-no-color --terragrunt-log-format=pretty  --terragrunt-working-dir "+rootPath)
+	stdout, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt apply -auto-approve --terragrunt-non-interactive -no-color --terragrunt-no-color --terragrunt-log-format=pretty  --terragrunt-working-dir "+rootPath)
 	require.NoError(t, err)
 
 	assert.Contains(t, stdout, "STDOUT "+wrappedBinary()+": Changes to Outputs")
 
-	stdout, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt destroy -auto-approve --terragrunt-log-level trace --terragrunt-non-interactive -no-color --terragrunt-no-color --terragrunt-log-format=pretty  --terragrunt-working-dir "+rootPath)
+	stdout, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt destroy -auto-approve --terragrunt-non-interactive -no-color --terragrunt-no-color --terragrunt-log-format=pretty  --terragrunt-working-dir "+rootPath)
 	require.NoError(t, err)
 
 	assert.Contains(t, stdout, "STDOUT "+wrappedBinary()+": Changes to Outputs")
@@ -3763,6 +3764,9 @@ func TestStorePlanFilesRunAllPlanApply(t *testing.T) {
 	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureOutDir)
 	helpers.CleanupTerraformFolder(t, tmpEnvPath)
 	testPath := util.JoinPath(tmpEnvPath, testFixtureOutDir)
+	dependencyPath := util.JoinPath(tmpEnvPath, testFixtureOutDir, "dependency")
+
+	helpers.RunTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", dependencyPath, tmpDir))
 
 	// run plan with output directory
 	_, output, err := helpers.RunTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
@@ -3789,6 +3793,9 @@ func TestStorePlanFilesRunAllPlanApplyRelativePath(t *testing.T) {
 	helpers.CleanupTerraformFolder(t, tmpEnvPath)
 	testPath := util.JoinPath(tmpEnvPath, testFixtureOutDir)
 
+	dependencyPath := util.JoinPath(tmpEnvPath, testFixtureOutDir, "dependency")
+	helpers.RunTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", dependencyPath, testPath))
+
 	// run plan with output directory
 	_, _, err := helpers.RunTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, "test"))
 	require.NoError(t, err)
@@ -3892,6 +3899,9 @@ func TestPlanJsonPlanBinaryRunAll(t *testing.T) {
 	helpers.CleanupTerraformFolder(t, tmpEnvPath)
 	testPath := util.JoinPath(tmpEnvPath, testFixtureOutDir)
 
+	dependencyPath := util.JoinPath(tmpEnvPath, testFixtureOutDir, "dependency")
+	helpers.RunTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", dependencyPath, tmpDir))
+
 	// run plan with output directory
 	_, _, err := helpers.RunTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-json-out-dir %s --terragrunt-out-dir %s", testPath, tmpDir, tmpDir))
 	require.NoError(t, err)
@@ -3927,6 +3937,9 @@ func TestTerragruntRunAllPlanAndShow(t *testing.T) {
 	helpers.CleanupTerraformFolder(t, tmpEnvPath)
 	testPath := util.JoinPath(tmpEnvPath, testFixtureOutDir)
 
+	dependencyPath := util.JoinPath(tmpEnvPath, testFixtureOutDir, "dependency")
+	helpers.RunTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", dependencyPath, tmpDir))
+
 	// run plan and apply
 	_, _, err := helpers.RunTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir %s --terragrunt-out-dir %s", testPath, tmpDir))
 	require.NoError(t, err)
@@ -4152,3 +4165,24 @@ func TestLogFormatBare(t *testing.T) {
 	assert.Contains(t, stdout, "Initializing the backend...")
 	assert.NotContains(t, stdout, "STDO[0000] Initializing the backend...")
 }
+
+func TestTF110EphemeralVars(t *testing.T) {
+	t.Parallel()
+	if !helpers.IsTerraform110OrHigher() {
+		t.Skip("This test requires Terraform 1.10 or higher")
+
+		return
+	}
+
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureEphemeralInputs)
+	helpers.CleanupTerraformFolder(t, tmpEnvPath)
+	testPath := util.JoinPath(tmpEnvPath, testFixtureEphemeralInputs)
+
+	stdout, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt plan --terragrunt-non-interactive --terragrunt-working-dir "+testPath)
+	require.NoError(t, err)
+	assert.Contains(t, stdout, "Plan: 1 to add, 0 to change, 0 to destroy")
+
+	stdout, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt apply --auto-approve --terragrunt-non-interactive --terragrunt-working-dir "+testPath)
+	require.NoError(t, err)
+	assert.Contains(t, stdout, "Apply complete! Resources: 1 added, 0 changed, 0 destroyed")
+}