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

Issue:
There is no variable named "dependency" - terragrunt 0.55.4
**Describe the bug**
With terragrunt 0.55.4 I get errors all over the place when using dependency outputs as inputs

**To Reproduce**
Have a dependency, reference an output of that dependency as an input, and probably have the dependency have dependencies also.

```hcl

dependency "eks_cluster" {
  config_path = "../../eks-cluster"
}

dependency "kms_key_2024" {
  config_path = "../../kms/external-pv-key-2024"
}

inputs = {
  provider_url           = dependency.eks_cluster.outputs.cluster_oidc_issuer_url
  cluster_id             = dependency.eks_cluster.outputs.cluster_name
  cluster_endpoint       = dependency.eks_cluster.outputs.cluster_endpoint
  cluster_ca_cert        = dependency.eks_cluster.outputs.cluster_certificate_authority_data
  oidc_provider_arn      = dependency.eks_cluster.outputs.oidc_provider_arn
  kms_keys_for_ebs_csi   = [dependency.kms_key_2024.outputs.arn]
}
```

When planning / applying I get errors from the referenced modules:

```
ERRO[0000] Error: Unknown variable
            
ERRO[0000]   on /some/path/to/infrastructure-live/test/eu-central-1/e2e2/services/eks-01/eks-cluster/terragrunt.hcl line 47: 
ERRO[0000]   47:   kms_key_for_node_ebs = dependency.external-ebs-key-2024.outputs.arn 
ERRO[0000]                                              
ERRO[0000] There is no variable named "dependency".
    
WARN[0000] Error reading partial config  for dependency eks_cluster: /some/path/to/infrastructure-live/test/eu-central-1/e2e2/services/eks-01/eks-cluster/terragrunt.hcl:47,26-36: Unknown variable; There is no variable named "dependency". 
ERRO[0000] Error: Unknown variable
            
ERRO[0000]   on /some/path/to/infrastructure-live/test/eu-central-1/e2e2/services/eks-01/kms/external-pv-key-2024/terragrunt.hcl line 44: 
ERRO[0000]   44:   ciphertrust_aws_kms_id = dependency.thales_kms.outputs.ciphertrust_aws_kms_id 
ERRO[0000]                                              
ERRO[0000] There is no variable named "dependency".
```

In those modules I have dependencies as well, as an example in that `eks-cluster/terragrunt.hcl` line 47:

```hcl
  kms_key_for_node_ebs = dependency.external-ebs-key-2024.outputs.arn
```

where the dependency is declared earlier as:
```hcl
dependency "external-ebs-key-2024" {
  config_path = "../kms/external-ebs-key-2024"
}
```

**Expected behavior**
No errors or warning should be thrown, as with terragrunt 0.55.3 and earlier

**Nice to have**
- [x] Terminal output

**Versions**
- Terragrunt version: 0.55.4
- Terraform version: 1.7.2
- Environment details (Ubuntu 20.04, Windows 10, etc.): macOS 14.3.1

**Additional context**
I am available on the Gruntwork Slack as `@Philipp`


Discussion / hints:
It looks like it is an issue with transient dependency outputs
Happens in my test project: https://github.com/denis256/terragrunt-tests/tree/master/issue-2955


Fix patch:
diff --git a/config/config.go b/config/config.go
index 2917531541..55525d41d7 100644
--- a/config/config.go
+++ b/config/config.go
@@ -51,6 +51,7 @@ const (
 	MetadataIamAssumeRoleSessionName    = "iam_assume_role_session_name"
 	MetadataInputs                      = "inputs"
 	MetadataLocals                      = "locals"
+	MetadataLocal                       = "local"
 	MetadataCatalog                     = "catalog"
 	MetadataGenerateConfigs             = "generate"
 	MetadataRetryableErrors             = "retryable_errors"
diff --git a/config/config_helpers.go b/config/config_helpers.go
index c6b6929e34..f6665f0442 100644
--- a/config/config_helpers.go
+++ b/config/config_helpers.go
@@ -188,11 +188,11 @@ func createTerragruntEvalContext(ctx *ParsingContext, configPath string) (*hcl.E
 	}
 	evalCtx.Variables = map[string]cty.Value{}
 	if ctx.Locals != nil {
-		evalCtx.Variables["local"] = *ctx.Locals
+		evalCtx.Variables[MetadataLocal] = *ctx.Locals
 	}
 
 	if ctx.DecodedDependencies != nil {
-		evalCtx.Variables["dependency"] = *ctx.DecodedDependencies
+		evalCtx.Variables[MetadataDependency] = *ctx.DecodedDependencies
 	}
 	if ctx.TrackInclude != nil && len(ctx.TrackInclude.CurrentList) > 0 {
 		// For each include block, check if we want to expose the included config, and if so, add under the include
diff --git a/config/config_partial.go b/config/config_partial.go
index 9149a75406..2a4430f823 100644
--- a/config/config_partial.go
+++ b/config/config_partial.go
@@ -293,6 +293,15 @@ func PartialParseConfig(ctx *ParsingContext, file *hclparse.File, includeFromChi
 		case TerragruntInputs:
 			decoded := terragruntInputs{}
 
+			if _, ok := evalParsingContext.Variables[MetadataDependency]; !ok {
+				// Decode just the `dependency` blocks, retrieving the outputs from the target terragrunt config in the process.
+				retrievedOutputs, err := decodeAndRetrieveOutputs(ctx, file)
+				if err != nil {
+					return nil, err
+				}
+				evalParsingContext.Variables[MetadataDependency] = *retrievedOutputs
+			}
+
 			if err := file.Decode(&decoded, evalParsingContext); err != nil {
 				diagErr, ok := errors.Unwrap(err).(hcl.Diagnostics)
 
diff --git a/config/dependency.go b/config/dependency.go
index f51218f4c8..3f9858c3a1 100644
--- a/config/dependency.go
+++ b/config/dependency.go
@@ -177,6 +177,10 @@ func decodeAndRetrieveOutputs(ctx *ParsingContext, file *hclparse.File) (*cty.Va
 		return nil, err
 	}
 
+	if err := checkForDependencyBlockCycles(ctx, file.ConfigPath, decodedDependency); err != nil {
+		return nil, err
+	}
+
 	// Mark skipped dependencies as disabled
 	updatedDependencies := terragruntDependency{}
 	for _, dep := range decodedDependency.Dependencies {
@@ -186,8 +190,7 @@ func decodeAndRetrieveOutputs(ctx *ParsingContext, file *hclparse.File) (*cty.Va
 				WithDecodeList(TerragruntFlags, TerragruntInputs).
 				WithTerragruntOptions(cloneTerragruntOptionsForDependency(ctx, depPath))
 
-			depConfig, err := PartialParseConfigFile(depCtx, depPath, nil)
-			if err == nil {
+			if depConfig, err := PartialParseConfigFile(depCtx, depPath, nil); err == nil {
 				if depConfig.Skip {
 					ctx.TerragruntOptions.Logger.Debugf("Skipping outputs reading for disabled dependency %s", dep.Name)
 					dep.Enabled = new(bool)
@@ -217,10 +220,6 @@ func decodeAndRetrieveOutputs(ctx *ParsingContext, file *hclparse.File) (*cty.Va
 		decodedDependency = *mergedDecodedDependency
 	}
 
-	if err := checkForDependencyBlockCycles(ctx, file.ConfigPath, decodedDependency); err != nil {
-		return nil, err
-	}
-
 	return dependencyBlocksToCtyValue(ctx, decodedDependency.Dependencies)
 }
 
diff --git a/config/parsing_context.go b/config/parsing_context.go
index d984698f2f..32e523225f 100644
--- a/config/parsing_context.go
+++ b/config/parsing_context.go
@@ -25,8 +25,7 @@ type ParsingContext struct {
 
 	// DecodedDependencies are references of other terragrunt config. This contains the following attributes that map to
 	// various fields related to that config:
-	// - outputs: The map of outputs from the terraform state obtained by running `terragrunt output` on that target
-	//            config.
+	// - outputs: The map of outputs from the terraform state obtained by running `terragrunt output` on that target config.
 	DecodedDependencies *cty.Value
 
 	// PartialParseDecodeList is the list of sections that are being decoded in the current config. This can be used to


Test patch:
diff --git a/test/fixture-inputs-from-dependency/app/main.tf b/test/fixture-inputs-from-dependency/app/main.tf
index 488bb9a882..7608fb29bb 100644
--- a/test/fixture-inputs-from-dependency/app/main.tf
+++ b/test/fixture-inputs-from-dependency/app/main.tf
@@ -27,3 +27,23 @@ output "baz" {
   value       = var.baz
   description = "Test baz value"
 }
+
+variable "dep-output-test" {
+  type    = string
+  default = "empty"
+}
+
+output "dep-output-test" {
+  value       = var.dep-output-test
+  description = "Test dep-output-test value"
+}
+
+variable "dep-cluster-id" {
+  type    = string
+  default = "empty"
+}
+
+output "dep-cluster-id" {
+  value       = var.dep-cluster-id
+  description = "Test dep-cluster-id value"
+}
diff --git a/test/fixture-inputs-from-dependency/app/terragrunt.hcl b/test/fixture-inputs-from-dependency/app/terragrunt.hcl
index c7aaabb812..dd63473240 100644
--- a/test/fixture-inputs-from-dependency/app/terragrunt.hcl
+++ b/test/fixture-inputs-from-dependency/app/terragrunt.hcl
@@ -2,12 +2,15 @@ dependencies {
   paths = ["../dependency"]
 }
 
-dependency "test" {
+dependency "dep" {
   config_path = "../dependency"
 }
 
 inputs = {
-  foo = dependency.test.inputs.foo
-  bar = dependency.test.inputs.bar
-  baz = dependency.test.inputs.baz
+  foo = dependency.dep.inputs.foo
+  bar = dependency.dep.inputs.bar
+  baz = dependency.dep.inputs.baz
+
+  dep-output-test = dependency.dep.outputs.test
+  dep-cluster-id  = dependency.dep.outputs.cluster-id
 }
diff --git a/test/fixture-inputs-from-dependency/cluster/main.tf b/test/fixture-inputs-from-dependency/cluster/main.tf
new file mode 100644
index 0000000000..9194624c55
--- /dev/null
+++ b/test/fixture-inputs-from-dependency/cluster/main.tf
@@ -0,0 +1,3 @@
+output "id" {
+  value = "test-id-value"
+}
diff --git a/test/fixture-inputs-from-dependency/cluster/terragrunt.hcl b/test/fixture-inputs-from-dependency/cluster/terragrunt.hcl
new file mode 100644
index 0000000000..bb7b160deb
--- /dev/null
+++ b/test/fixture-inputs-from-dependency/cluster/terragrunt.hcl
@@ -0,0 +1,1 @@
+# Intentionally empty
diff --git a/test/fixture-inputs-from-dependency/dependency/main.tf b/test/fixture-inputs-from-dependency/dependency/main.tf
index 2bbf6adbed..bf6cac755f 100644
--- a/test/fixture-inputs-from-dependency/dependency/main.tf
+++ b/test/fixture-inputs-from-dependency/dependency/main.tf
@@ -7,3 +7,13 @@ output "test" {
   value       = var.test
   description = "Test value"
 }
+
+variable "cluster-id" {
+  type    = string
+  default = "empty"
+}
+
+output "cluster-id" {
+  value       = var.cluster-id
+  description = "Test cluster-id value"
+}
diff --git a/test/fixture-inputs-from-dependency/dependency/terragrunt.hcl b/test/fixture-inputs-from-dependency/dependency/terragrunt.hcl
index 5e762c3dc8..ab9c9aa1c9 100644
--- a/test/fixture-inputs-from-dependency/dependency/terragrunt.hcl
+++ b/test/fixture-inputs-from-dependency/dependency/terragrunt.hcl
@@ -6,6 +6,11 @@ include "local" {
   path = "local.hcl"
 }
 
+dependency "cluster" {
+  config_path = "../cluster"
+}
+
 inputs = {
-  foo = "dependency-input-foo-value"
+  foo        = "dependency-input-foo-value"
+  cluster-id = dependency.cluster.outputs.id
 }
diff --git a/test/integration_test.go b/test/integration_test.go
index 078685f06f..7dfa3349c0 100644
--- a/test/integration_test.go
+++ b/test/integration_test.go
@@ -959,9 +959,11 @@ func TestTerragruntInputsFromDependency(t *testing.T) {
 	runTerragruntRedirectOutput(t, fmt.Sprintf("terragrunt output --terragrunt-non-interactive --terragrunt-working-dir %s", appTerragruntPath), &stdout, &stderr)
 
 	expectedOutpus := map[string]string{
-		"bar": "dependency-parent-bar-value",
-		"baz": "dependency-include-input-baz-value",
-		"foo": "dependency-input-foo-value",
+		"bar":             "dependency-parent-bar-value",
+		"baz":             "dependency-include-input-baz-value",
+		"foo":             "dependency-input-foo-value",
+		"dep-cluster-id":  "test-id-value",
+		"dep-output-test": "test-value",
 	}
 
 	output := stdout.String()