From 5867027e6146f5f328581240d43b43c0b71704e6 Mon Sep 17 00:00:00 2001 From: Rodrigo Fior Kuntzer Date: Tue, 20 Aug 2024 17:55:04 +0200 Subject: [PATCH] fix: make `terragrunt-fetch-dependency-output-from-state` to work with not applied dependencies (#3350) * fix: make `terragrunt-fetch-dependency-output-from-state` to work with not applied dependencies Signed-off-by: Rodrigo Fior Kuntzer * fix: remove terraform only checks in the integration tests Signed-off-by: Rodrigo Fior Kuntzer * fix: applying code suggestions Signed-off-by: Rodrigo Fior Kuntzer --------- Signed-off-by: Rodrigo Fior Kuntzer --- config/dependency.go | 13 ++++++++++++- test/integration_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/config/dependency.go b/config/dependency.go index 7a66d62da9..6b5b9f98be 100644 --- a/config/dependency.go +++ b/config/dependency.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "encoding/json" + goErrors "errors" "fmt" "io" "os" @@ -11,6 +12,8 @@ import ( "strings" "sync" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/gruntwork-io/terragrunt/internal/cache" "github.com/aws/aws-sdk-go/aws" @@ -500,7 +503,7 @@ func getTerragruntOutput(ctx *ParsingContext, dependencyConfig Dependency) (*cty jsonBytes, err := getOutputJsonWithCaching(ctx, targetConfigPath) if err != nil { - if !isRenderJsonCommand(ctx) { + if !isRenderJsonCommand(ctx) && !isAwsS3NoSuchKey(err) { return nil, true, err } ctx.TerragruntOptions.Logger.Warnf("Failed to read outputs from %s referenced in %s as %s, fallback to mock outputs. Error: %v", targetConfigPath, ctx.TerragruntOptions.TerragruntConfigPath, dependencyConfig.Name, err) @@ -524,6 +527,14 @@ func getTerragruntOutput(ctx *ParsingContext, dependencyConfig Dependency) (*cty return &convertedOutput, isEmpty, errors.WithStackTrace(err) } +func isAwsS3NoSuchKey(err error) bool { + var awsErr awserr.Error + if goErrors.As(errors.Unwrap(err), &awsErr) { + return awsErr.Code() == "NoSuchKey" + } + return false +} + // isRenderJsonCommand This function will true if terragrunt was invoked with render-json func isRenderJsonCommand(ctx *ParsingContext) bool { return util.ListContainsElement(ctx.TerragruntOptions.TerraformCliArgs, renderJsonCommand) diff --git a/test/integration_test.go b/test/integration_test.go index dfb969cafe..c1d289e1e2 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -5337,6 +5337,35 @@ func TestTerragruntOutputFromRemoteState(t *testing.T) { //nolint: paralleltest assert.True(t, (strings.Index(output, "app3 output") < strings.Index(output, "app1 output")) && (strings.Index(output, "app1 output") < strings.Index(output, "app2 output"))) } +func TestTerragruntMockOutputsFromRemoteState(t *testing.T) { //nolint: paralleltest + // NOTE: We can't run this test in parallel because there are other tests that also call `config.ClearOutputCache()`, but this function uses a global variable and sometimes it throws an unexpected error: + // "fixture-output-from-remote-state/env1/app2/terragrunt.hcl:23,38-48: Unsupported attribute; This object does not have an attribute named "app3_text"." + // t.Parallel() + + s3BucketName := "terragrunt-test-bucket-" + strings.ToLower(uniqueId()) + defer deleteS3Bucket(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) + + tmpEnvPath := copyEnvironment(t, TEST_FIXTURE_OUTPUT_FROM_REMOTE_STATE) + + rootTerragruntConfigPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_OUTPUT_FROM_REMOTE_STATE, config.DefaultTerragruntConfigPath) + copyTerragruntConfigAndFillPlaceholders(t, rootTerragruntConfigPath, rootTerragruntConfigPath, s3BucketName, "not-used", "not-used") + + environmentPath := filepath.Join(tmpEnvPath, TEST_FIXTURE_OUTPUT_FROM_REMOTE_STATE, "env1") + + // applying only the app1 dependency, the app3 dependency was purposely not applied and should be mocked when running the app2 module + runTerragrunt(t, fmt.Sprintf("terragrunt apply --terragrunt-fetch-dependency-output-from-state --auto-approve --terragrunt-non-interactive --terragrunt-working-dir %s/app1", environmentPath)) + // Now delete dependencies cached state + config.ClearOutputCache() + require.NoError(t, os.Remove(filepath.Join(environmentPath, "/app1/.terraform/terraform.tfstate"))) + require.NoError(t, os.RemoveAll(filepath.Join(environmentPath, "/app1/.terraform"))) + + _, stderr, err := runTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt init --terragrunt-fetch-dependency-output-from-state --terragrunt-non-interactive --terragrunt-working-dir %s/app2", environmentPath)) + require.NoError(t, err) + + assert.True(t, strings.Contains(stderr, "Failed to read outputs")) + assert.True(t, strings.Contains(stderr, "fallback to mock outputs")) +} + func TestShowErrorWhenRunAllInvokedWithoutArguments(t *testing.T) { t.Parallel()