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

Issue:
Authentication Provider Command Does Not Work With Dependencies
## Describe the bug

When parsing `terragrunt.hcl` files as part of interacting with a dependency, the [terragrunt-auth-provider-cmd](https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-auth-provider-cmd) flat does not get executed.

This can be problematic if dependencies use calls like `get_aws_account_id()`, and need access to AWS credentials via the auth provider command.

## Steps To Reproduce

Steps to reproduce the behavior, code snippets and examples which can be used to reproduce the issue.

Add this:

```hcl
locals {
  aws_account_id = get_aws_account_id()
}
```
To:

`test/fixture-auth-provider-cmd/multiple-apps/app3/terragrunt.hcl`

You can see it fail [here in CircleCI](https://app.circleci.com/pipelines/github/gruntwork-io/terragrunt/4226/workflows/7d615079-a018-4cca-94a2-4c1b8724463f/jobs/33243).

## Expected behavior

The authentication provider command is re-run before parsing any `terragrunt.hcl` files in order to ensure that the credentials are available while trying to interact with resources that could require authentication.

## Nice to haves

- [x] Terminal output
- [ ] Screenshots

## Versions

- Terragrunt version: 0.63.6
- OpenTofu version: 1.7.3
- Environment details (Ubuntu 20.04, Windows 10, etc.): GitHub Actions Ubuntu Latest



Discussion / hints:

Fix patch:
diff --git a/configstack/stack.go b/configstack/stack.go
index 1b4b2c30c1..8a50e486e4 100644
--- a/configstack/stack.go
+++ b/configstack/stack.go
@@ -13,6 +13,8 @@ import (
 
 	"github.com/gruntwork-io/go-commons/collections"
 
+	"github.com/gruntwork-io/terragrunt/cli/commands/terraform/creds"
+	"github.com/gruntwork-io/terragrunt/cli/commands/terraform/creds/providers/externalcmd"
 	"github.com/gruntwork-io/terragrunt/config/hclparse"
 	"github.com/gruntwork-io/terragrunt/telemetry"
 	"github.com/gruntwork-io/terragrunt/terraform"
@@ -495,6 +497,13 @@ func (stack *Stack) resolveTerraformModule(ctx context.Context, terragruntConfig
 			config.DependencyBlock,
 		)
 
+	// Credentials have to be acquired before the config is parsed, as the config may contain interpolation functions
+	// that require credentials to be available.
+	credsGetter := creds.NewGetter()
+	if err := credsGetter.ObtainAndUpdateEnvIfNecessary(ctx, opts, externalcmd.NewProvider(opts)); err != nil {
+		return nil, err
+	}
+
 	// We only partially parse the config, only using the pieces that we need in this section. This config will be fully
 	// parsed at a later stage right before the action is run. This is to delay interpolation of functions until right
 	// before we call out to terraform.


Test patch:
diff --git a/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/creds.config b/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/creds.config
new file mode 100644
index 0000000000..6c9f7dc886
--- /dev/null
+++ b/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/creds.config
@@ -0,0 +1,4 @@
+access_key_id=__FILL_AWS_ACCESS_KEY_ID__
+secret_access_key=__FILL_AWS_SECRET_ACCESS_KEY__
+session_token=
+tf_var_foo=
diff --git a/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/main.tf b/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/main.tf
new file mode 100644
index 0000000000..17d5d1f214
--- /dev/null
+++ b/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/main.tf
@@ -0,0 +1,3 @@
+output "foo" {
+  value = "foo"
+}
diff --git a/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/terragrunt.hcl b/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/terragrunt.hcl
new file mode 100644
index 0000000000..a18d72e4d4
--- /dev/null
+++ b/test/fixture-auth-provider-cmd/creds-for-dependency/dependency/terragrunt.hcl
@@ -0,0 +1,3 @@
+locals {
+    aws_account_id = get_aws_account_id()
+}
diff --git a/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/creds.config b/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/creds.config
new file mode 100644
index 0000000000..6c9f7dc886
--- /dev/null
+++ b/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/creds.config
@@ -0,0 +1,4 @@
+access_key_id=__FILL_AWS_ACCESS_KEY_ID__
+secret_access_key=__FILL_AWS_SECRET_ACCESS_KEY__
+session_token=
+tf_var_foo=
diff --git a/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/main.tf b/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/main.tf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/terragrunt.hcl b/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/terragrunt.hcl
new file mode 100644
index 0000000000..2f88117281
--- /dev/null
+++ b/test/fixture-auth-provider-cmd/creds-for-dependency/dependent/terragrunt.hcl
@@ -0,0 +1,3 @@
+dependency "dependency" {
+  config_path  = "../dependency"
+}
diff --git a/test/fixture-auth-provider-cmd/multiple-apps/creds.config b/test/fixture-auth-provider-cmd/multiple-apps/creds.config
new file mode 100644
index 0000000000..01133189b5
--- /dev/null
+++ b/test/fixture-auth-provider-cmd/multiple-apps/creds.config
@@ -0,0 +1,4 @@
+access_key_id=app1_access_key_id
+secret_access_key=app1_secret_access_key
+session_token=app1_session_token
+tf_var_foo=top-level
diff --git a/test/integration_serial_test.go b/test/integration_serial_test.go
index d788e0db3f..ba5e0ac26f 100644
--- a/test/integration_serial_test.go
+++ b/test/integration_serial_test.go
@@ -912,12 +912,42 @@ func TestReadTerragruntAuthProviderCmdRemoteState(t *testing.T) {
 		os.Setenv("AWS_SECRET_ACCESS_KEY", secretAccessKey)
 	}()
 
-	creadsConfig := util.JoinPath(rootPath, "creds.config")
+	credsConfig := util.JoinPath(rootPath, "creds.config")
 
-	copyAndFillMapPlaceholders(t, creadsConfig, creadsConfig, map[string]string{
+	copyAndFillMapPlaceholders(t, credsConfig, credsConfig, map[string]string{
 		"__FILL_AWS_ACCESS_KEY_ID__":     accessKeyID,
 		"__FILL_AWS_SECRET_ACCESS_KEY__": secretAccessKey,
 	})
 
 	runTerragrunt(t, fmt.Sprintf("terragrunt plan --terragrunt-non-interactive --terragrunt-working-dir %s --terragrunt-auth-provider-cmd %s", rootPath, mockAuthCmd))
 }
+
+func TestReadTerragruntAuthProviderCmdCredsForDependency(t *testing.T) {
+	cleanupTerraformFolder(t, TEST_FIXTURE_AUTH_PROVIDER_CMD)
+	tmpEnvPath := copyEnvironment(t, TEST_FIXTURE_AUTH_PROVIDER_CMD)
+	rootPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_AUTH_PROVIDER_CMD, "creds-for-dependency")
+	mockAuthCmd := filepath.Join(tmpEnvPath, TEST_FIXTURE_AUTH_PROVIDER_CMD, "mock-auth-cmd.sh")
+
+	accessKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
+	secretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
+	os.Setenv("AWS_ACCESS_KEY_ID", "")
+	os.Setenv("AWS_SECRET_ACCESS_KEY", "")
+
+	defer func() {
+		os.Setenv("AWS_ACCESS_KEY_ID", accessKeyID)
+		os.Setenv("AWS_SECRET_ACCESS_KEY", secretAccessKey)
+	}()
+
+	dependencyCredsConfig := util.JoinPath(rootPath, "dependency", "creds.config")
+	copyAndFillMapPlaceholders(t, dependencyCredsConfig, dependencyCredsConfig, map[string]string{
+		"__FILL_AWS_ACCESS_KEY_ID__":     accessKeyID,
+		"__FILL_AWS_SECRET_ACCESS_KEY__": secretAccessKey,
+	})
+
+	dependentCredsConfig := util.JoinPath(rootPath, "dependent", "creds.config")
+	copyAndFillMapPlaceholders(t, dependentCredsConfig, dependentCredsConfig, map[string]string{
+		"__FILL_AWS_ACCESS_KEY_ID__":     accessKeyID,
+		"__FILL_AWS_SECRET_ACCESS_KEY__": secretAccessKey,
+	})
+	runTerragrunt(t, fmt.Sprintf("terragrunt run-all apply --terragrunt-non-interactive --terragrunt-working-dir %s --terragrunt-auth-provider-cmd %s", rootPath, mockAuthCmd))
+}