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

Issue:
`render-json` fails when there is not a fallback to the output
**Describe the bug**
This issue is related to #2288. When the Terragrunt file has a dependency in another resource's output, `render-json` errors and returns no output.

The error message:
```
> terragrunt render-json --terragrunt-json-out hello.json --with-metadata
WARN[0001] Failed to read outputs from /Users/marina/go/src/github.com/gruntwork-io/dogfood-infrastructure-live/prod/us-east-1/prod/vpc/terragrunt.hcl referenced in /Users/marina/go/src/github.com/gruntwork-io/dogfood-infrastructure-live/prod/us-east-1/prod/services/docs-sourcer/lambda/terragrunt.hcl as vpc, fallback to mock outputs. Error: signal: killed
ERRO[0001] /Users/marina/go/src/github.com/gruntwork-io/dogfood-infrastructure-live/_envcommon/services/lambda-app.hcl:76,55-62: Unsupported attribute; This object does not have an attribute named "vpc_id"., and 1 other diagnostic(s)
ERRO[0001] Unable to determine underlying exit code, so Terragrunt will exit with error code 1
```

**To Reproduce**

`terragrunt.hcl`:

```hcl
terraform {
  source = "${local.source_base_url}?ref=v0.88.1"
}

dependency "vpc" {
  config_path = "${dirname(find_in_parent_folders("env.yaml"))}/vpc"
}

inputs = {
  # ... some removed inputs
  run_in_vpc                  = true
  vpc_id                         = dependency.vpc.outputs.vpc_id
  subnet_ids                  = dependency.vpc.outputs.private_app_subnet_ids

}
```

**Expected behavior**

Is there a way to ignore this variable and build the `render-json` without it?


**Versions**
- Terragrunt version: v0.45.10
- Terraform version: v1.4.6
- Environment details (Ubuntu 20.04, Windows 10, etc.): macOS 13.4.1




Discussion / hints:

Fix patch:
diff --git a/config/config.go b/config/config.go
index 6e457b8f56..1f4d79358c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -10,6 +10,8 @@ import (
 	"reflect"
 	"strings"
 
+	"github.com/zclconf/go-cty/cty/gocty"
+
 	"github.com/hashicorp/go-getter"
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/hclparse"
@@ -791,6 +793,25 @@ func decodeAsTerragruntConfigFile(
 ) (*terragruntConfigFile, error) {
 	terragruntConfig := terragruntConfigFile{}
 	err := decodeHcl(file, filename, &terragruntConfig, terragruntOptions, extensions)
+	// in case of render-json command and inputs reference error, we update the inputs with default value
+	if diagErr, ok := err.(hcl.Diagnostics); ok && isRenderJsonCommand(terragruntOptions) && isAttributeAccessError(diagErr) {
+		terragruntOptions.Logger.Warnf("Failed to decode inputs %v", diagErr)
+		// update unknown inputs with default value
+		updatedValue := map[string]cty.Value{}
+		for key, value := range terragruntConfig.Inputs.AsValueMap() {
+			if value.IsKnown() {
+				updatedValue[key] = value
+			} else {
+				updatedValue[key] = cty.StringVal("")
+			}
+		}
+		value, err := gocty.ToCtyValue(updatedValue, terragruntConfig.Inputs.Type())
+		if err != nil {
+			return nil, err
+		}
+		terragruntConfig.Inputs = &value
+		return &terragruntConfig, nil
+	}
 	if err != nil {
 		return nil, err
 	}
@@ -808,6 +829,16 @@ func getIndexOfHookWithName(hooks []Hook, name string) int {
 	return -1
 }
 
+// isAttributeAccessError returns true if the given diagnostics indicate an error accessing an attribute
+func isAttributeAccessError(diagnostics hcl.Diagnostics) bool {
+	for _, diagnostic := range diagnostics {
+		if diagnostic.Severity == hcl.DiagError && strings.Contains(diagnostic.Summary, "Unsupported attribute") {
+			return true
+		}
+	}
+	return false
+}
+
 // Returns the index of the ErrorHook with the given name,
 // or -1 if no Hook have the given name.
 // TODO: Figure out more DRY way to do this
diff --git a/config/dependency.go b/config/dependency.go
index f04f5878f8..d2f48d061e 100644
--- a/config/dependency.go
+++ b/config/dependency.go
@@ -424,7 +424,7 @@ func getTerragruntOutput(dependencyConfig Dependency, terragruntOptions *options
 	return &convertedOutput, isEmpty, errors.WithStackTrace(err)
 }
 
-// This function will true if terragrunt was invoked with renderJsonCommand
+// isRenderJsonCommand This function will true if terragrunt was invoked with render-json
 func isRenderJsonCommand(terragruntOptions *options.TerragruntOptions) bool {
 	return util.ListContainsElement(terragruntOptions.TerraformCliArgs, renderJsonCommand)
 }


Test patch:
diff --git a/test/fixture-render-json-inputs/app/main.tf b/test/fixture-render-json-inputs/app/main.tf
new file mode 100644
index 0000000000..340d590276
--- /dev/null
+++ b/test/fixture-render-json-inputs/app/main.tf
@@ -0,0 +1,1 @@
+variable "x" {}
diff --git a/test/fixture-render-json-inputs/app/terragrunt.hcl b/test/fixture-render-json-inputs/app/terragrunt.hcl
new file mode 100644
index 0000000000..88c47d1602
--- /dev/null
+++ b/test/fixture-render-json-inputs/app/terragrunt.hcl
@@ -0,0 +1,11 @@
+
+dependency "dep" {
+  config_path = "../dependency"
+
+}
+
+inputs = {
+  static_value = "static_value"
+  value = dependency.dep.outputs.value
+  not_existing_value = dependency.dep.outputs.not_existing_value
+}
diff --git a/test/fixture-render-json-inputs/dependency/main.tf b/test/fixture-render-json-inputs/dependency/main.tf
new file mode 100644
index 0000000000..c76aabcfbe
--- /dev/null
+++ b/test/fixture-render-json-inputs/dependency/main.tf
@@ -0,0 +1,4 @@
+
+output "value" {
+  value = "output_value"
+}
diff --git a/test/fixture-render-json-inputs/dependency/terragrunt.hcl b/test/fixture-render-json-inputs/dependency/terragrunt.hcl
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/integration_test.go b/test/integration_test.go
index 564fcc7e82..b27d1832d2 100644
--- a/test/integration_test.go
+++ b/test/integration_test.go
@@ -134,6 +134,7 @@ const (
 	TEST_FIXTURE_BROKEN_DEPENDENCY                                           = "fixture-broken-dependency"
 	TEST_FIXTURE_RENDER_JSON_METADATA                                        = "fixture-render-json-metadata"
 	TEST_FIXTURE_RENDER_JSON_MOCK_OUTPUTS                                    = "fixture-render-json-mock-outputs"
+	TEST_FIXTURE_RENDER_JSON_INPUTS                                          = "fixture-render-json-inputs"
 	TEST_FIXTURE_STARTSWITH                                                  = "fixture-startswith"
 	TEST_FIXTURE_TIMECMP                                                     = "fixture-timecmp"
 	TEST_FIXTURE_TIMECMP_INVALID_TIMESTAMP                                   = "fixture-timecmp-errors/invalid-timestamp"
@@ -5636,6 +5637,47 @@ func TestInitSkipCache(t *testing.T) {
 	assert.Contains(t, stderr.String(), "Running command: terraform init")
 }
 
+func TestRenderJsonWithInputsNotExistingOutput(t *testing.T) {
+	t.Parallel()
+
+	tmpEnvPath := copyEnvironment(t, TEST_FIXTURE_RENDER_JSON_INPUTS)
+	cleanupTerraformFolder(t, tmpEnvPath)
+	dependencyPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_RENDER_JSON_INPUTS, "dependency")
+	appPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_RENDER_JSON_INPUTS, "app")
+
+	runTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-working-dir %s", dependencyPath))
+	runTerragrunt(t, fmt.Sprintf("terragrunt render-json --with-metadata --terragrunt-non-interactive --terragrunt-working-dir %s", appPath))
+
+	jsonOut := filepath.Join(appPath, "terragrunt_rendered.json")
+
+	jsonBytes, err := ioutil.ReadFile(jsonOut)
+	require.NoError(t, err)
+
+	var renderedJson = map[string]interface{}{}
+	require.NoError(t, json.Unmarshal(jsonBytes, &renderedJson))
+
+	var includeMetadata = map[string]interface{}{
+		"found_in_file": util.JoinPath(appPath, "terragrunt.hcl"),
+	}
+
+	var inputs = renderedJson[config.MetadataInputs]
+	var expectedInputs = map[string]interface{}{
+		"static_value": map[string]interface{}{
+			"metadata": includeMetadata,
+			"value":    "static_value",
+		},
+		"value": map[string]interface{}{
+			"metadata": includeMetadata,
+			"value":    "output_value",
+		},
+		"not_existing_value": map[string]interface{}{
+			"metadata": includeMetadata,
+			"value":    "",
+		},
+	}
+	assert.True(t, reflect.DeepEqual(expectedInputs, inputs))
+}
+
 func validateOutput(t *testing.T, outputs map[string]TerraformOutput, key string, value interface{}) {
 	t.Helper()
 	output, hasPlatform := outputs[key]