Skip to content

Commit ad3d9cc

Browse files
authored
feat: removal of deprecated hcl attributes skip, retryable_errors (#5033)
* feat: removal of deprecated hcl attributes * removal of deprecated hcl attributes * added retries validation * added migration guide * Failing tests fixes * Integration test fix * docs attributes fixes * Add tests for missing functionality * chore: tests simplification * chrore: tests simplifications * improved error checking * docs: deprecation docs cleanup * PR comments * Fixed links
1 parent adad0cb commit ad3d9cc

File tree

69 files changed

+569
-1011
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+569
-1011
lines changed

cli/commands/run/errors.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package run
22

33
import (
44
"fmt"
5-
"strings"
65

76
"github.com/gruntwork-io/terragrunt/options"
87
)
@@ -50,13 +49,7 @@ func (err ModuleIsProtected) Error() string {
5049
return fmt.Sprintf("Unit is protected by the prevent_destroy flag in %s. Set it to false or remove it to allow destruction of the unit.", err.Opts.TerragruntConfigPath)
5150
}
5251

53-
type MaxRetriesExceeded struct {
54-
Opts *options.TerragruntOptions
55-
}
56-
57-
func (err MaxRetriesExceeded) Error() string {
58-
return fmt.Sprintf("Exhausted retries (%v) for command %v %v", err.Opts.RetryMaxAttempts, err.Opts.TFPath, strings.Join(err.Opts.TerraformCliArgs, " "))
59-
}
52+
// Legacy retry error removed in favor of error handling via options.Errors
6053

6154
type RunAllDisabledErr struct {
6255
command string

cli/commands/run/run.go

Lines changed: 9 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"regexp"
1111
"strings"
1212
"sync"
13-
"time"
1413

1514
"github.com/gruntwork-io/terragrunt/internal/runner"
1615

@@ -147,15 +146,6 @@ func run(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, r *
147146
return target.runErrorCallback(l, opts, terragruntConfig, err)
148147
}
149148

150-
if terragruntConfig.Skip != nil && *terragruntConfig.Skip {
151-
l.Infof(
152-
"Skipping terragrunt module %s due to skip = true.",
153-
opts.TerragruntConfigPath,
154-
)
155-
156-
return nil
157-
}
158-
159149
// We merge the OriginalIAMRoleOptions into the one from the config, because the CLI passed IAMRoleOptions has
160150
// precedence.
161151
opts.IAMRoleOptions = options.MergeIAMRoleOptions(
@@ -181,27 +171,6 @@ func run(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, r *
181171
opts.DownloadDir = terragruntConfig.DownloadDir
182172
}
183173

184-
// Override the default value of retryable errors using the value set in the config file
185-
if terragruntConfig.RetryableErrors != nil {
186-
opts.RetryableErrors = terragruntConfig.RetryableErrors
187-
}
188-
189-
if terragruntConfig.RetryMaxAttempts != nil {
190-
if *terragruntConfig.RetryMaxAttempts < 1 {
191-
return fmt.Errorf("cannot have less than 1 max retry, but you specified %d", *terragruntConfig.RetryMaxAttempts)
192-
}
193-
194-
opts.RetryMaxAttempts = *terragruntConfig.RetryMaxAttempts
195-
}
196-
197-
if terragruntConfig.RetrySleepIntervalSec != nil {
198-
if *terragruntConfig.RetrySleepIntervalSec < 0 {
199-
return fmt.Errorf("cannot sleep for less than 0 seconds, but you specified %d", *terragruntConfig.RetrySleepIntervalSec)
200-
}
201-
202-
opts.RetrySleepInterval = time.Duration(*terragruntConfig.RetrySleepIntervalSec) * time.Second
203-
}
204-
205174
updatedTerragruntOptions := opts
206175

207176
sourceURL, err := config.GetTerraformSourceURL(opts, terragruntConfig)
@@ -365,7 +334,8 @@ func runTerragruntWithConfig(
365334
}
366335

367336
return RunActionWithHooks(ctx, l, "terraform", opts, cfg, r, func(ctx context.Context) error {
368-
runTerraformError := RunTerraformWithRetry(ctx, l, opts, r)
337+
// Execute the underlying command once; retries and ignores are handled by outer RunWithErrorHandling
338+
out, runTerraformError := tf.RunCommandWithOutput(ctx, l, opts, opts.TerraformCliArgs...)
369339

370340
var lockFileError error
371341
if ShouldCopyLockFile(opts.TerraformCliArgs, cfg.Terraform) {
@@ -379,6 +349,13 @@ func runTerragruntWithConfig(
379349
lockFileError = config.CopyLockFile(l, opts, opts.WorkingDir, originalOpts.WorkingDir)
380350
}
381351

352+
// If command failed, log a helpful message
353+
if runTerraformError != nil {
354+
if out == nil {
355+
l.Errorf("%s invocation failed in %s", opts.TerraformImplementation, opts.WorkingDir)
356+
}
357+
}
358+
382359
return multierror.Append(runTerraformError, lockFileError).ErrorOrNil()
383360
})
384361
}
@@ -493,54 +470,6 @@ func SetTerragruntInputsAsEnvVars(l log.Logger, opts *options.TerragruntOptions,
493470
return nil
494471
}
495472

496-
func RunTerraformWithRetry(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, r *report.Report) error {
497-
// Retry the command configurable time with sleep in between
498-
for range opts.RetryMaxAttempts {
499-
if out, err := tf.RunCommandWithOutput(ctx, l, opts, opts.TerraformCliArgs...); err != nil {
500-
if out == nil || !IsRetryable(opts, out) {
501-
l.Errorf("%s invocation failed in %s", opts.TerraformImplementation, opts.WorkingDir)
502-
503-
return err
504-
} else {
505-
l.Infof("Encountered an error eligible for retrying. Sleeping %v before retrying.\n", opts.RetrySleepInterval)
506-
// Reset the exit code to success so that we can retry the command
507-
if exitCode := tf.DetailedExitCodeFromContext(ctx); exitCode != nil {
508-
exitCode.ResetSuccess()
509-
510-
// Also assume this retry will succeed for now. If it doesn't, we'll update this later.
511-
if err := r.EndRun(
512-
opts.WorkingDir,
513-
report.WithResult(report.ResultSucceeded),
514-
report.WithReason(report.ReasonRetrySucceeded),
515-
); err != nil {
516-
l.Errorf("Error ending run for unit %s: %v", opts.WorkingDir, err)
517-
}
518-
}
519-
520-
select {
521-
case <-time.After(opts.RetrySleepInterval):
522-
// try again
523-
case <-ctx.Done():
524-
return errors.New(ctx.Err())
525-
}
526-
}
527-
} else {
528-
return nil
529-
}
530-
}
531-
532-
return errors.New(MaxRetriesExceeded{opts})
533-
}
534-
535-
// IsRetryable checks whether there was an error and if the output matches any of the configured RetryableErrors
536-
func IsRetryable(opts *options.TerragruntOptions, out *util.CmdOutput) bool {
537-
if !opts.AutoRetry {
538-
return false
539-
}
540-
// When -json is enabled, Terraform will send all output, errors included, to stdout.
541-
return util.MatchesAny(opts.RetryableErrors, out.Stderr.String()) || util.MatchesAny(opts.RetryableErrors, out.Stdout.String())
542-
}
543-
544473
// Prepare for running 'terraform init' by initializing remote state storage and adding backend configuration arguments
545474
// to the TerraformCliArgs
546475
func prepareInitCommand(ctx context.Context, l log.Logger, terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) error {

cli/commands/run/run_test.go

Lines changed: 1 addition & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package run_test
22

33
import (
4-
"bytes"
54
"os"
65
"path/filepath"
76
"testing"
87

98
"github.com/gruntwork-io/terragrunt/cli/commands/run"
109
"github.com/gruntwork-io/terragrunt/config"
1110
"github.com/gruntwork-io/terragrunt/internal/errors"
12-
"github.com/gruntwork-io/terragrunt/internal/report"
1311
"github.com/gruntwork-io/terragrunt/options"
1412
"github.com/gruntwork-io/terragrunt/pkg/log"
1513
"github.com/gruntwork-io/terragrunt/test/helpers/logger"
@@ -160,120 +158,7 @@ func TestTerragruntTerraformCodeCheck(t *testing.T) {
160158
}
161159
}
162160

163-
func TestErrorRetryableOnStdoutError(t *testing.T) {
164-
t.Parallel()
165-
166-
tgOptions, err := options.NewTerragruntOptionsForTest("")
167-
require.NoError(t, err)
168-
169-
retryableErrors := []string{".*error.*"}
170-
tgOptions.RetryableErrors = retryableErrors
171-
tgOptions.AutoRetry = true
172-
173-
out := new(util.CmdOutput)
174-
out.Stderr = *bytes.NewBufferString("error is here")
175-
176-
retryable := run.IsRetryable(tgOptions, out)
177-
require.True(t, retryable, "The error should have retried")
178-
}
179-
180-
func TestErrorMultipleRetryableOnStderrError(t *testing.T) {
181-
t.Parallel()
182-
183-
tgOptions, err := options.NewTerragruntOptionsForTest("")
184-
require.NoError(t, err)
185-
186-
retryableErrors := []string{"no match", ".*error.*"}
187-
tgOptions.RetryableErrors = retryableErrors
188-
tgOptions.AutoRetry = true
189-
190-
out := new(util.CmdOutput)
191-
out.Stderr = *bytes.NewBufferString("error is here")
192-
193-
retryable := run.IsRetryable(tgOptions, out)
194-
require.True(t, retryable, "The error should have retried")
195-
}
196-
197-
func TestEmptyRetryablesOnStderrError(t *testing.T) {
198-
t.Parallel()
199-
200-
tgOptions, err := options.NewTerragruntOptionsForTest("")
201-
require.NoError(t, err)
202-
203-
retryableErrors := []string{}
204-
tgOptions.RetryableErrors = retryableErrors
205-
tgOptions.AutoRetry = true
206-
207-
out := new(util.CmdOutput)
208-
out.Stderr = *bytes.NewBufferString("error is here")
209-
210-
retryable := run.IsRetryable(tgOptions, out)
211-
require.False(t, retryable, "The error should not have retried, the list of retryable errors was empty")
212-
}
213-
214-
func TestErrorRetryableOnStderrError(t *testing.T) {
215-
t.Parallel()
216-
217-
tgOptions, err := options.NewTerragruntOptionsForTest("")
218-
require.NoError(t, err)
219-
220-
retryableErrors := []string{".*error.*"}
221-
tgOptions.RetryableErrors = retryableErrors
222-
tgOptions.AutoRetry = true
223-
224-
out := new(util.CmdOutput)
225-
out.Stderr = *bytes.NewBufferString("error is here")
226-
227-
retryable := run.IsRetryable(tgOptions, out)
228-
require.True(t, retryable, "The error should have retried")
229-
}
230-
231-
func TestErrorNotRetryableOnStdoutError(t *testing.T) {
232-
t.Parallel()
233-
234-
tgOptions, err := options.NewTerragruntOptionsForTest("")
235-
require.NoError(t, err)
236-
237-
retryableErrors := []string{"not the error"}
238-
tgOptions.RetryableErrors = retryableErrors
239-
tgOptions.AutoRetry = true
240-
241-
out := new(util.CmdOutput)
242-
out.Stdout = *bytes.NewBufferString("error is here")
243-
244-
retryable := run.IsRetryable(tgOptions, out)
245-
require.False(t, retryable, "The error should not retry")
246-
}
247-
248-
func TestErrorNotRetryableOnStderrError(t *testing.T) {
249-
t.Parallel()
250-
251-
tgOptions, err := options.NewTerragruntOptionsForTest("")
252-
require.NoError(t, err)
253-
254-
retryableErrors := []string{"not the error"}
255-
tgOptions.RetryableErrors = retryableErrors
256-
tgOptions.AutoRetry = true
257-
258-
out := new(util.CmdOutput)
259-
out.Stderr = *bytes.NewBufferString("error is here")
260-
261-
retryable := run.IsRetryable(tgOptions, out)
262-
require.False(t, retryable, "The error should not retry")
263-
}
264-
265-
func TestTerragruntHandlesCatastrophicTerraformFailure(t *testing.T) {
266-
t.Parallel()
267-
268-
tgOptions, err := options.NewTerragruntOptionsForTest("")
269-
require.NoError(t, err)
270-
271-
// Use a path that doesn't exist to induce error
272-
tgOptions.TFPath = "i-dont-exist"
273-
l := logger.CreateLogger()
274-
err = run.RunTerraformWithRetry(t.Context(), l, tgOptions, report.NewReport())
275-
require.Error(t, err)
276-
}
161+
// Legacy retry tests removed; retries now handled via errors blocks
277162

278163
func TestToTerraformEnvVars(t *testing.T) {
279164
t.Parallel()

0 commit comments

Comments
 (0)