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

Issue:
`terragrunt render-json` fails in certain cases
Running the latest build, `terragrunt render-json` fails with multiple errors like this:
```
│ Error: Backend initialization required, please run "terraform init"
│
│ Reason: Initial configuration of the requested backend "s3"
│
│ The "backend" is the interface that Terraform uses to store state,
│ perform operations, etc. If this message is showing up, it means that the
│ Terraform configuration you're using is using a custom configuration for
│ the Terraform backend.
│
│ Changes to backend configurations require reinitialization. This allows
│ Terraform to set up the new configuration, copy existing state, etc. Please
│ run
│ "terraform init" with either the "-reconfigure" or "-migrate-state" flags
│ to
│ use the current configuration.
│
│ If the change reason above is incorrect, please verify your configuration
│ hasn't changed and try again. At this point, no changes to your existing
│ configuration or state have been made.
```
Followed by another error at the end
```
ERRO[0002] Could not convert include to the execution context to evaluate additional locals
ERRO[0002] Encountered error while evaluating locals in file /Users/ana/code/infrastructure-live-variational/stage/eu-north-1/stage/services/streamer-cron/terragrunt.hcl
ERRO[0002] exit status 1
```


Discussion / hints:
hm, I get similar errors when terragrunt was executed without configured AWS account, once I configured access, it started to fail with
```
╷
│ Error: Failed to load state: AccessDenied: Access Denied
```

I think during evaluation of `render-json` is tried to read modules output which leads to errors for situations when modules weren't deployed, most probably should be used mock outputs from each dependency
Hey @denis256 thanks for the evaluation of this bug. Agreed, I don't think that AWS auth should be required to execute `render-json`. Would you be able to work on a fix?
Yes, looking to fix this issue - looks like execution of `terraform output` is trying to read the remote state... 
I don't know how did I close this issue. Reopening!
Hello @denis256, do you have any updates in this ticket? Unfortunately it is blocking our team now.

Fix patch:
diff --git a/config/dependency.go b/config/dependency.go
index 80a9ed64fe..ca6a06bfe2 100644
--- a/config/dependency.go
+++ b/config/dependency.go
@@ -31,6 +31,8 @@ import (
 	"github.com/gruntwork-io/terragrunt/util"
 )
 
+const renderJsonCommand = "render-json"
+
 type Dependency struct {
 	Name                                string     `hcl:",label" cty:"name"`
 	ConfigPath                          string     `hcl:"config_path,attr" cty:"config_path"`
@@ -379,7 +381,7 @@ func (dependencyConfig Dependency) shouldReturnMockOutputs(terragruntOptions *op
 		dependencyConfig.MockOutputsAllowedTerraformCommands == nil ||
 			len(*dependencyConfig.MockOutputsAllowedTerraformCommands) == 0 ||
 			util.ListContainsElement(*dependencyConfig.MockOutputsAllowedTerraformCommands, terragruntOptions.OriginalTerraformCommand)
-	return defaultOutputsSet && allowedCommand
+	return defaultOutputsSet && allowedCommand || isRenderJsonCommand(terragruntOptions)
 }
 
 // Return the output from the state of another module, managed by terragrunt. This function will parse the provided
@@ -395,7 +397,14 @@ func getTerragruntOutput(dependencyConfig Dependency, terragruntOptions *options
 
 	jsonBytes, err := getOutputJsonWithCaching(targetConfig, terragruntOptions)
 	if err != nil {
-		return nil, true, err
+		if !isRenderJsonCommand(terragruntOptions) {
+			return nil, true, err
+		}
+		terragruntOptions.Logger.Warnf("Failed to read outputs from %s referenced in %s as %s, fallback to mock outputs. Error: %v", targetConfig, terragruntOptions.TerragruntConfigPath, dependencyConfig.Name, err)
+		jsonBytes, err = json.Marshal(dependencyConfig.MockOutputs)
+		if err != nil {
+			return nil, true, err
+		}
 	}
 	isEmpty := string(jsonBytes) == "{}"
 
@@ -412,6 +421,11 @@ func getTerragruntOutput(dependencyConfig Dependency, terragruntOptions *options
 	return &convertedOutput, isEmpty, errors.WithStackTrace(err)
 }
 
+// This function will true if terragrunt was invoked with renderJsonCommand
+func isRenderJsonCommand(terragruntOptions *options.TerragruntOptions) bool {
+	return util.ListContainsElement(terragruntOptions.TerraformCliArgs, renderJsonCommand)
+}
+
 // getOutputJsonWithCaching will run terragrunt output on the target config if it is not already cached.
 func getOutputJsonWithCaching(targetConfig string, terragruntOptions *options.TerragruntOptions) ([]byte, error) {
 	// Acquire synchronization lock to ensure only one instance of output is called per config.


Test patch:
diff --git a/test/fixture-render-json-mock-outputs/app/main.tf b/test/fixture-render-json-mock-outputs/app/main.tf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixture-render-json-mock-outputs/app/terragrunt.hcl b/test/fixture-render-json-mock-outputs/app/terragrunt.hcl
new file mode 100644
index 0000000000..7287c1cf10
--- /dev/null
+++ b/test/fixture-render-json-mock-outputs/app/terragrunt.hcl
@@ -0,0 +1,13 @@
+include "root" {
+  path = find_in_parent_folders()
+}
+
+dependency "module" {
+  config_path = "../dependency"
+
+  mock_outputs = {
+    security_group_id = "sg-abcd1234"
+    bastion_host_security_group_id = "123"
+  }
+  mock_outputs_allowed_terraform_commands = ["validate" ]
+}
diff --git a/test/fixture-render-json-mock-outputs/dependency/main.tf b/test/fixture-render-json-mock-outputs/dependency/main.tf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixture-render-json-mock-outputs/dependency/terragrunt.hcl b/test/fixture-render-json-mock-outputs/dependency/terragrunt.hcl
new file mode 100644
index 0000000000..e147285f72
--- /dev/null
+++ b/test/fixture-render-json-mock-outputs/dependency/terragrunt.hcl
@@ -0,0 +1,3 @@
+include "root" {
+  path = find_in_parent_folders()
+}
diff --git a/test/fixture-render-json-mock-outputs/terragrunt.hcl b/test/fixture-render-json-mock-outputs/terragrunt.hcl
new file mode 100644
index 0000000000..11d031ffaa
--- /dev/null
+++ b/test/fixture-render-json-mock-outputs/terragrunt.hcl
@@ -0,0 +1,14 @@
+remote_state {
+  backend = "s3"
+  config = {
+    encrypt                   = true
+    bucket                    = "test-tf-state"
+    key                       = "${path_relative_to_include()}/terraform.tfstate"
+    dynamodb_table            = "test-terraform-locks"
+    accesslogging_bucket_name = "test-tf-logs"
+  }
+  generate = {
+    path      = "backend.tf"
+    if_exists = "overwrite_terragrunt"
+  }
+}
diff --git a/test/integration_test.go b/test/integration_test.go
index eea65bfbf3..9df3b70f5f 100644
--- a/test/integration_test.go
+++ b/test/integration_test.go
@@ -130,6 +130,7 @@ const (
 	TEST_FIXTURE_BROKEN_LOCALS                              = "fixture-broken-locals"
 	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"
 	TERRAFORM_BINARY                                        = "terraform"
 	TERRAFORM_FOLDER                                        = ".terraform"
 	TERRAFORM_STATE                                         = "terraform.tfstate"
@@ -4699,6 +4700,55 @@ func TestRenderJsonMetadataDependencies(t *testing.T) {
 	assert.True(t, reflect.DeepEqual(expectedDependencies, dependencies))
 }
 
+func TestRenderJsonWithMockOutputs(t *testing.T) {
+	t.Parallel()
+
+	tmpEnvPath := copyEnvironment(t, TEST_FIXTURE_RENDER_JSON_MOCK_OUTPUTS)
+	cleanupTerraformFolder(t, tmpEnvPath)
+	tmpDir := util.JoinPath(tmpEnvPath, TEST_FIXTURE_RENDER_JSON_MOCK_OUTPUTS, "app")
+
+	var expectedMetadata = map[string]interface{}{
+		"found_in_file": util.JoinPath(tmpDir, "terragrunt.hcl"),
+	}
+
+	jsonOut := filepath.Join(tmpDir, "terragrunt_rendered.json")
+
+	runTerragrunt(t, fmt.Sprintf("terragrunt render-json --with-metadata --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir %s  --terragrunt-json-out %s", tmpDir, jsonOut))
+
+	jsonBytes, err := ioutil.ReadFile(jsonOut)
+	require.NoError(t, err)
+
+	var renderedJson = map[string]interface{}{}
+	require.NoError(t, json.Unmarshal(jsonBytes, &renderedJson))
+
+	dependency := renderedJson[config.MetadataDependency]
+
+	var expectedDependency = map[string]interface{}{
+		"module": map[string]interface{}{
+			"metadata": expectedMetadata,
+			"value": map[string]interface{}{
+				"config_path": "../dependency",
+				"mock_outputs": map[string]interface{}{
+					"bastion_host_security_group_id": "123",
+					"security_group_id":              "sg-abcd1234",
+				},
+				"mock_outputs_allowed_terraform_commands": [1]string{"validate"},
+				"mock_outputs_merge_strategy_with_state":  nil,
+				"mock_outputs_merge_with_state":           nil,
+				"name":                                    "module",
+				"outputs":                                 nil,
+				"skip":                                    nil,
+			},
+		},
+	}
+	serializedDependency, err := json.Marshal(dependency)
+	assert.NoError(t, err)
+
+	serializedExpectedDependency, err := json.Marshal(expectedDependency)
+	assert.NoError(t, err)
+	assert.Equal(t, string(serializedExpectedDependency), string(serializedDependency))
+}
+
 func TestRenderJsonMetadataIncludes(t *testing.T) {
 	t.Parallel()