Skip to content

Commit

Permalink
Improved errors retry/ignore (#3795)
Browse files Browse the repository at this point in the history
* Add handling of multiline errors

* Simplified errors message cleanup

* Strict lint updates

* Log message update
  • Loading branch information
denis256 authored Jan 23, 2025
1 parent 2b1a3e3 commit a5c79a5
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 3 deletions.
20 changes: 17 additions & 3 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/gruntwork-io/terragrunt/internal/errors"
Expand Down Expand Up @@ -65,6 +66,9 @@ var (
"force-unlock",
"state",
}

// Pattern used to clean error message when looking for retry and ignore patterns.
errorCleanPattern = regexp.MustCompile(`[^a-zA-Z0-9./'"(): ]+`)
)

type ctxKey byte
Expand Down Expand Up @@ -962,7 +966,7 @@ func (opts *TerragruntOptions) RunWithErrorHandling(ctx context.Context, operati
}

// Process the error through our error handling configuration
action, processErr := opts.Errors.ProcessError(err, currentAttempt)
action, processErr := opts.Errors.ProcessError(opts, err, currentAttempt)
if processErr != nil {
return fmt.Errorf("error processing error handling rules: %w", processErr)
}
Expand Down Expand Up @@ -1041,14 +1045,16 @@ type ErrorAction struct {
}

// ProcessError evaluates an error against the configuration and returns the appropriate action
func (c *ErrorsConfig) ProcessError(err error, currentAttempt int) (*ErrorAction, error) {
func (c *ErrorsConfig) ProcessError(opts *TerragruntOptions, err error, currentAttempt int) (*ErrorAction, error) {
if err == nil {
return nil, nil
}

errStr := err.Error()
errStr := extractErrorMessage(err)
action := &ErrorAction{}

opts.Logger.Debugf("Processing error message: %s", errStr)

// First check ignore rules
for _, ignoreBlock := range c.Ignore {
isIgnorable := matchesAnyRegexpPattern(errStr, ignoreBlock.IgnorableErrors)
Expand Down Expand Up @@ -1087,6 +1093,14 @@ func (c *ErrorsConfig) ProcessError(err error, currentAttempt int) (*ErrorAction
return nil, err
}

func extractErrorMessage(err error) string {
// fetch the error string and remove any ASCII escape sequences
multilineText := log.RemoveAllASCISeq(err.Error())
errorText := errorCleanPattern.ReplaceAllString(multilineText, " ")

return strings.Join(strings.Fields(errorText), " ")
}

// matchesAnyRegexpPattern checks if the input string matches any of the provided compiled patterns
func matchesAnyRegexpPattern(input string, patterns []*ErrorsPattern) bool {
for _, pattern := range patterns {
Expand Down
17 changes: 17 additions & 0 deletions test/fixtures/errors/multi-line/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

resource "null_resource" "script_runner" {
provisioner "local-exec" {
command = "./script.sh"

interpreter = ["/bin/sh", "-c"]
on_failure = fail
}

triggers = {
always_run = timestamp()
}
}

output "value" {
value = "valid value from failing_dep"
}
4 changes: 4 additions & 0 deletions test/fixtures/errors/multi-line/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

echo "Error: creating Route in Route Table (rtb-46521694) with destination (10.0.0.0/8): operation error EC2: CreateRoute, https response error StatusCode: 400, RequestID: JD40-14127-2022, api error InvalidTransitGatewayID.NotFound: The transitGateway ID 'tgw-xxxxxxxxxxxxxx' does not exist."
exit 1
17 changes: 17 additions & 0 deletions test/fixtures/errors/multi-line/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
errors {
# Retry block for transient errors
retry "transient_errors" {
retryable_errors = [
"(?s).*cannot create resource \"storageclasses\" in API group.*",
]
max_attempts = 3
sleep_interval_sec = 20
}

ignore "transit_gateway_errors" {
ignorable_errors = [
".*creating Route in Route Table*"
]
message = "Ignoring transit gateway not found when creating internal route."
}
}
14 changes: 14 additions & 0 deletions test/integration_errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
testRetryFailErrors = "fixtures/errors/retry-fail"
testRunAllErrors = "fixtures/errors/run-all"
testNegativePatternErrors = "fixtures/errors/ignore-negative-pattern"
testMultiLineErrors = "fixtures/errors/multi-line"
)

func TestErrorsHandling(t *testing.T) {
Expand Down Expand Up @@ -159,3 +160,16 @@ func TestIgnoreNegativePattern(t *testing.T) {
require.Error(t, err)
assert.Contains(t, stdout, "Error: baz")
}

func TestHandleMultiLineErrors(t *testing.T) {
t.Parallel()

cleanupTerraformFolder(t, testMultiLineErrors)
tmpEnvPath := helpers.CopyEnvironment(t, testMultiLineErrors)
rootPath := util.JoinPath(tmpEnvPath, testMultiLineErrors)

_, stdout, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)

require.NoError(t, err)
assert.Contains(t, stdout, "Ignoring transit gateway not found when creating internal route")
}

0 comments on commit a5c79a5

Please sign in to comment.