From 4f421edf6a21a3b53d6adae19825568076c4f7a2 Mon Sep 17 00:00:00 2001 From: Yousif Akbar <11247449+yhakbar@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:30:42 -0500 Subject: [PATCH] fix: Fixing report run duplication error --- internal/discovery/dependencydiscovery.go | 17 +- internal/report/report.go | 18 +- internal/report/report_test.go | 243 ++++++++++++---------- internal/runner/common/unit_runner.go | 14 +- internal/runner/runnerpool/runner.go | 19 +- options/options.go | 19 +- 6 files changed, 199 insertions(+), 131 deletions(-) diff --git a/internal/discovery/dependencydiscovery.go b/internal/discovery/dependencydiscovery.go index 09d96bf094..1455c7e337 100644 --- a/internal/discovery/dependencydiscovery.go +++ b/internal/discovery/dependencydiscovery.go @@ -290,8 +290,21 @@ func (dd *DependencyDiscovery) dependencyToDiscover( // Record in report as excluded external dependency if dd.report != nil { absPath := util.CleanPath(depPath) - run, _ := dd.report.EnsureRun(absPath) - _ = dd.report.EndRun(run.Path, report.WithResult(report.ResultExcluded), report.WithReason(report.ReasonExcludeExternal)) + + _, err := dd.report.EnsureRun(l, absPath) + if err != nil { + l.Errorf("Error ensuring run for excluded external dependency %s: %v", absPath, err) + } + + err = dd.report.EndRun( + l, + absPath, + report.WithResult(report.ResultExcluded), + report.WithReason(report.ReasonExcludeExternal), + ) + if err != nil { + l.Errorf("Error ending run for excluded external dependency %s: %v", absPath, err) + } } dd.markSeen(depPath) diff --git a/internal/report/report.go b/internal/report/report.go index e248e3a829..8b173e31b6 100644 --- a/internal/report/report.go +++ b/internal/report/report.go @@ -8,6 +8,8 @@ import ( "slices" "sync" "time" + + "github.com/gruntwork-io/terragrunt/pkg/log" ) // Report captures data for a report/summary. @@ -129,7 +131,7 @@ var ErrRunAlreadyExists = errors.New("run already exists") // AddRun adds a run to the report. // If the run already exists, it returns the ErrRunAlreadyExists error. -func (r *Report) AddRun(run *Run) error { +func (r *Report) AddRun(l log.Logger, run *Run) error { r.mu.Lock() defer r.mu.Unlock() @@ -139,6 +141,8 @@ func (r *Report) AddRun(run *Run) error { } } + l.Debugf("Adding report run %s", run.Path) + r.Runs = append(r.Runs, run) return nil @@ -169,9 +173,11 @@ func (r *Report) GetRun(path string) (*Run, error) { // EnsureRun tries to get a run from the report. // If the run does not exist, it creates a new run and adds it to the report, then returns the run. // This is useful when a run is being ended that might not have been started due to exclusion, etc. -func (r *Report) EnsureRun(path string) (*Run, error) { +func (r *Report) EnsureRun(l log.Logger, path string) (*Run, error) { run, err := r.GetRun(path) if err == nil { + l.Debugf("Report run %s already exists, returning existing run", path) + return run, nil } @@ -179,12 +185,14 @@ func (r *Report) EnsureRun(path string) (*Run, error) { return run, err } + l.Debugf("Report run %s not found, creating new run", path) + run, err = NewRun(path) if err != nil { return run, err } - if err = r.AddRun(run); err != nil { + if err = r.AddRun(l, run); err != nil { return run, err } @@ -195,7 +203,7 @@ func (r *Report) EnsureRun(path string) (*Run, error) { // If the run does not exist, it returns the ErrRunNotFound error. // By default, the run is assumed to have succeeded. To change this, pass WithResult to the function. // If the run has already ended from an early exit, it does nothing. -func (r *Report) EndRun(path string, endOptions ...EndOption) error { +func (r *Report) EndRun(l log.Logger, path string, endOptions ...EndOption) error { r.mu.Lock() defer r.mu.Unlock() @@ -231,6 +239,8 @@ func (r *Report) EndRun(path string, endOptions ...EndOption) error { endOption(run) } + l.Debugf("Ending report run %s with result %s", path, run.Result) + return nil } diff --git a/internal/report/report_test.go b/internal/report/report_test.go index f524fba761..b69f559672 100644 --- a/internal/report/report_test.go +++ b/internal/report/report_test.go @@ -13,7 +13,9 @@ import ( "time" "github.com/gruntwork-io/terragrunt/internal/report" + "github.com/gruntwork-io/terragrunt/pkg/log" "github.com/gruntwork-io/terragrunt/test/helpers" + "github.com/gruntwork-io/terragrunt/test/helpers/logger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/xeipuuv/gojsonschema" @@ -49,15 +51,17 @@ func TestAddRun(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + path := filepath.Join(tmp, "test-run") r := report.NewReport() - err := r.AddRun(newRun(t, path)) + err := r.AddRun(l, newRun(t, path)) require.NoError(t, err) assert.Len(t, r.Runs, 1) - err = r.AddRun(newRun(t, path)) + err = r.AddRun(l, newRun(t, path)) require.Error(t, err) assert.ErrorIs(t, err, report.ErrRunAlreadyExists) } @@ -67,9 +71,11 @@ func TestGetRun(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + r := report.NewReport() run := newRun(t, filepath.Join(tmp, "test-run")) - r.AddRun(run) + r.AddRun(l, run) tests := []struct { expectedErr error @@ -107,6 +113,8 @@ func TestEnsureRun(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + tests := []struct { expectedErrIs error setupFunc func(*report.Report) *report.Run @@ -128,7 +136,7 @@ func TestEnsureRun(t *testing.T) { expectError: false, setupFunc: func(r *report.Report) *report.Run { run := newRun(t, filepath.Join(tmp, "existing-run")) - err := r.AddRun(run) + err := r.AddRun(l, run) require.NoError(t, err) return run }, @@ -154,7 +162,7 @@ func TestEnsureRun(t *testing.T) { existingRun = tt.setupFunc(r) } - run, err := r.EnsureRun(tt.runName) + run, err := r.EnsureRun(l, tt.runName) if tt.expectError { require.Error(t, err) @@ -180,7 +188,7 @@ func TestEnsureRun(t *testing.T) { assert.Equal(t, run, retrievedRun) // Verify that calling EnsureRun again returns the same run - secondRun, err := r.EnsureRun(tt.runName) + secondRun, err := r.EnsureRun(l, tt.runName) require.NoError(t, err) assert.Equal(t, run, secondRun) } @@ -193,6 +201,8 @@ func TestEndRun(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + tests := []struct { wantReason *report.Reason wantCause *report.Cause @@ -248,10 +258,10 @@ func TestEndRun(t *testing.T) { if !tt.wantErr { run := newRun(t, tt.runName) - r.AddRun(run) + r.AddRun(l, run) } - err := r.EndRun(tt.runName, tt.options...) + err := r.EndRun(l, tt.runName, tt.options...) if tt.wantErr { require.Error(t, err) } else { @@ -283,6 +293,8 @@ func TestEndRunAlreadyEnded(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + tests := []struct { name string initialResult report.Result @@ -334,20 +346,20 @@ func TestEndRunAlreadyEnded(t *testing.T) { r := report.NewReport() runName := filepath.Join(tmp, tt.name) run := newRun(t, runName) - r.AddRun(run) + r.AddRun(l, run) // Set up initial options with the initial result initialOptions := append(tt.initialOptions, report.WithResult(tt.initialResult)) // End the run with the initial state - err := r.EndRun(runName, initialOptions...) + err := r.EndRun(l, runName, initialOptions...) require.NoError(t, err) // Set up second options with the second result secondOptions := append(tt.secondOptions, report.WithResult(tt.secondResult)) // Then try to end it again with a different state - err = r.EndRun(runName, secondOptions...) + err = r.EndRun(l, runName, secondOptions...) require.NoError(t, err) // Verify that the result is the expected one @@ -363,6 +375,8 @@ func TestSummarize(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + tests := []struct { name string results []struct { @@ -421,8 +435,8 @@ func TestSummarize(t *testing.T) { for _, result := range tt.results { run := newRun(t, result.name) - r.AddRun(run) - r.EndRun(result.name, report.WithResult(result.result)) + r.AddRun(l, run) + r.EndRun(l, result.name, report.WithResult(result.result)) } summary := r.Summarize() @@ -440,15 +454,15 @@ func TestWriteCSV(t *testing.T) { tests := []struct { name string - setup func(dir string, r *report.Report) + setup func(l log.Logger, dir string, r *report.Report) expected [][]string }{ { name: "single successful run", - setup: func(dir string, r *report.Report) { + setup: func(l log.Logger, dir string, r *report.Report) { run := newRun(t, filepath.Join(dir, "successful-run")) - r.AddRun(run) - r.EndRun(run.Path) + r.AddRun(l, run) + r.EndRun(l, run.Path) }, expected: [][]string{ {"Name", "Started", "Ended", "Result", "Reason", "Cause"}, @@ -457,26 +471,26 @@ func TestWriteCSV(t *testing.T) { }, { name: "complex mixed results", - setup: func(dir string, r *report.Report) { + setup: func(l log.Logger, dir string, r *report.Report) { // Add successful run successRun := newRun(t, filepath.Join(dir, "success-run")) - r.AddRun(successRun) - r.EndRun(successRun.Path) + r.AddRun(l, successRun) + r.EndRun(l, successRun.Path) // Add failed run with reason failedRun := newRun(t, filepath.Join(dir, "failed-run")) - r.AddRun(failedRun) - r.EndRun(failedRun.Path, report.WithResult(report.ResultFailed), report.WithReason(report.ReasonRunError)) + r.AddRun(l, failedRun) + r.EndRun(l, failedRun.Path, report.WithResult(report.ResultFailed), report.WithReason(report.ReasonRunError)) // Add excluded run with cause excludedRun := newRun(t, filepath.Join(dir, "excluded-run")) - r.AddRun(excludedRun) - r.EndRun(excludedRun.Path, report.WithResult(report.ResultExcluded), report.WithCauseRetryBlock("test-block")) + r.AddRun(l, excludedRun) + r.EndRun(l, excludedRun.Path, report.WithResult(report.ResultExcluded), report.WithCauseRetryBlock("test-block")) // Add early exit run with both reason and cause earlyExitRun := newRun(t, filepath.Join(dir, "early-exit-run")) - r.AddRun(earlyExitRun) - r.EndRun(earlyExitRun.Path, + r.AddRun(l, earlyExitRun) + r.EndRun(l, earlyExitRun.Path, report.WithResult(report.ResultEarlyExit), report.WithReason(report.ReasonRunError), report.WithCauseRetryBlock("another-block"), @@ -498,6 +512,8 @@ func TestWriteCSV(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + // Create a temporary file for the CSV csvFile := filepath.Join(tmp, "report.csv") file, err := os.Create(csvFile) @@ -507,7 +523,7 @@ func TestWriteCSV(t *testing.T) { // Setup and write the report r := report.NewReport().WithWorkingDir(tmp) - tt.setup(tmp, r) + tt.setup(l, tmp, r) err = r.WriteCSV(file) require.NoError(t, err) @@ -564,17 +580,19 @@ func TestWriteCSV(t *testing.T) { func TestWriteJSON(t *testing.T) { t.Parallel() + l := logger.CreateLogger() + tests := []struct { name string - setup func(dir string, r *report.Report) + setup func(l log.Logger, dir string, r *report.Report) expected string }{ { name: "single successful run", - setup: func(dir string, r *report.Report) { + setup: func(l log.Logger, dir string, r *report.Report) { run := newRun(t, filepath.Join(dir, "successful-run")) - r.AddRun(run) - r.EndRun(run.Path) + r.AddRun(l, run) + r.EndRun(l, run.Path) }, expected: `[ { @@ -587,16 +605,17 @@ func TestWriteJSON(t *testing.T) { }, { name: "complex mixed results", - setup: func(dir string, r *report.Report) { + setup: func(l log.Logger, dir string, r *report.Report) { // Add successful run successRun := newRun(t, filepath.Join(dir, "success-run")) - r.AddRun(successRun) - r.EndRun(successRun.Path) + r.AddRun(l, successRun) + r.EndRun(l, successRun.Path) // Add failed run with reason failedRun := newRun(t, filepath.Join(dir, "failed-run")) - r.AddRun(failedRun) + r.AddRun(l, failedRun) r.EndRun( + l, failedRun.Path, report.WithResult(report.ResultFailed), report.WithReason(report.ReasonRunError), @@ -604,8 +623,9 @@ func TestWriteJSON(t *testing.T) { // Add excluded run with cause retriedRun := newRun(t, filepath.Join(dir, "retried-run")) - r.AddRun(retriedRun) + r.AddRun(l, retriedRun) r.EndRun( + l, retriedRun.Path, report.WithResult(report.ResultSucceeded), report.WithReason(report.ReasonRetrySucceeded), @@ -613,8 +633,9 @@ func TestWriteJSON(t *testing.T) { // Add excluded run with cause excludedRun := newRun(t, filepath.Join(dir, "excluded-run")) - r.AddRun(excludedRun) + r.AddRun(l, excludedRun) r.EndRun( + l, excludedRun.Path, report.WithResult(report.ResultExcluded), report.WithReason(report.ReasonExcludeBlock), @@ -669,7 +690,7 @@ func TestWriteJSON(t *testing.T) { // Setup and write the report r := report.NewReport().WithWorkingDir(tmp) - tt.setup(tmp, r) + tt.setup(l, tmp, r) err = r.WriteJSON(file) require.NoError(t, err) @@ -883,17 +904,19 @@ func TestWriteSummary(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + tests := []struct { name string - setup func(*report.Report) + setup func(l log.Logger, r *report.Report) expected string }{ { name: "single successful run", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { run := newRun(t, filepath.Join(tmp, "successful-run")) - r.AddRun(run) - r.EndRun(run.Path) + r.AddRun(l, run) + r.EndRun(l, run.Path) }, expected: ` ❯❯ Run Summary 1 units x @@ -903,42 +926,42 @@ func TestWriteSummary(t *testing.T) { }, { name: "complex mixed results", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { // Add successful runs firstSuccessfulRun := newRun(t, filepath.Join(tmp, "first-successful-run")) - r.AddRun(firstSuccessfulRun) - r.EndRun(firstSuccessfulRun.Path) + r.AddRun(l, firstSuccessfulRun) + r.EndRun(l, firstSuccessfulRun.Path) secondSuccessfulRun := newRun(t, filepath.Join(tmp, "second-successful-run")) - r.AddRun(secondSuccessfulRun) - r.EndRun(secondSuccessfulRun.Path) + r.AddRun(l, secondSuccessfulRun) + r.EndRun(l, secondSuccessfulRun.Path) // Add failed runs firstFailedRun := newRun(t, filepath.Join(tmp, "first-failed-run")) - r.AddRun(firstFailedRun) - r.EndRun(firstFailedRun.Path, report.WithResult(report.ResultFailed)) + r.AddRun(l, firstFailedRun) + r.EndRun(l, firstFailedRun.Path, report.WithResult(report.ResultFailed)) secondFailedRun := newRun(t, filepath.Join(tmp, "second-failed-run")) - r.AddRun(secondFailedRun) - r.EndRun(secondFailedRun.Path, report.WithResult(report.ResultFailed)) + r.AddRun(l, secondFailedRun) + r.EndRun(l, secondFailedRun.Path, report.WithResult(report.ResultFailed)) // Add excluded runs firstExcludedRun := newRun(t, filepath.Join(tmp, "first-excluded-run")) - r.AddRun(firstExcludedRun) - r.EndRun(firstExcludedRun.Path, report.WithResult(report.ResultExcluded)) + r.AddRun(l, firstExcludedRun) + r.EndRun(l, firstExcludedRun.Path, report.WithResult(report.ResultExcluded)) secondExcludedRun := newRun(t, filepath.Join(tmp, "second-excluded-run")) - r.AddRun(secondExcludedRun) - r.EndRun(secondExcludedRun.Path, report.WithResult(report.ResultExcluded)) + r.AddRun(l, secondExcludedRun) + r.EndRun(l, secondExcludedRun.Path, report.WithResult(report.ResultExcluded)) // Add early exit runs firstEarlyExitRun := newRun(t, filepath.Join(tmp, "first-early-exit-run")) - r.AddRun(firstEarlyExitRun) - r.EndRun(firstEarlyExitRun.Path, report.WithResult(report.ResultEarlyExit)) + r.AddRun(l, firstEarlyExitRun) + r.EndRun(l, firstEarlyExitRun.Path, report.WithResult(report.ResultEarlyExit)) secondEarlyExitRun := newRun(t, filepath.Join(tmp, "second-early-exit-run")) - r.AddRun(secondEarlyExitRun) - r.EndRun(secondEarlyExitRun.Path, report.WithResult(report.ResultEarlyExit)) + r.AddRun(l, secondEarlyExitRun) + r.EndRun(l, secondEarlyExitRun.Path, report.WithResult(report.ResultEarlyExit)) }, expected: ` ❯❯ Run Summary 8 units x @@ -956,7 +979,7 @@ func TestWriteSummary(t *testing.T) { t.Parallel() r := report.NewReport().WithDisableColor() - tt.setup(r) + tt.setup(l, r) var buf bytes.Buffer @@ -981,20 +1004,22 @@ func TestSchemaIsValid(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + // Create a new report with working directory r := report.NewReport().WithWorkingDir(tmp) // Add a simple run that succeeds simpleRun := newRun(t, filepath.Join(tmp, "simple-run")) - r.AddRun(simpleRun) - r.EndRun(simpleRun.Path, + r.AddRun(l, simpleRun) + r.EndRun(l, simpleRun.Path, report.WithResult(report.ResultSucceeded), ) // Add a complex run that tests all possible fields and states complexRun := newRun(t, filepath.Join(tmp, "complex-run")) - r.AddRun(complexRun) - r.EndRun(complexRun.Path, + r.AddRun(l, complexRun) + r.EndRun(l, complexRun.Path, report.WithResult(report.ResultFailed), report.WithReason(report.ReasonRunError), report.WithCauseAncestorExit("some-error"), @@ -1002,8 +1027,8 @@ func TestSchemaIsValid(t *testing.T) { // Create an excluded run with exclude block excludedRun := newRun(t, filepath.Join(tmp, "excluded-run")) - r.AddRun(excludedRun) - r.EndRun(excludedRun.Path, + r.AddRun(l, excludedRun) + r.EndRun(l, excludedRun.Path, report.WithResult(report.ResultExcluded), report.WithReason(report.ReasonExcludeBlock), report.WithCauseExcludeBlock("test-block"), @@ -1011,8 +1036,8 @@ func TestSchemaIsValid(t *testing.T) { // Create a retry run that succeeded retryRun := newRun(t, filepath.Join(tmp, "retry-run")) - r.AddRun(retryRun) - r.EndRun(retryRun.Path, + r.AddRun(l, retryRun) + r.EndRun(l, retryRun.Path, report.WithResult(report.ResultSucceeded), report.WithReason(report.ReasonRetrySucceeded), report.WithCauseRetryBlock("retry-block"), @@ -1020,8 +1045,8 @@ func TestSchemaIsValid(t *testing.T) { // Create an early exit run earlyExitRun := newRun(t, filepath.Join(tmp, "early-exit-run")) - r.AddRun(earlyExitRun) - r.EndRun(earlyExitRun.Path, + r.AddRun(l, earlyExitRun) + r.EndRun(l, earlyExitRun.Path, report.WithResult(report.ResultEarlyExit), report.WithReason(report.ReasonAncestorError), report.WithCauseAncestorExit("parent-unit"), @@ -1029,8 +1054,8 @@ func TestSchemaIsValid(t *testing.T) { // Create a run with ignored error ignoredRun := newRun(t, filepath.Join(tmp, "ignored-run")) - r.AddRun(ignoredRun) - r.EndRun(ignoredRun.Path, + r.AddRun(l, ignoredRun) + r.EndRun(l, ignoredRun.Path, report.WithResult(report.ResultSucceeded), report.WithReason(report.ReasonErrorIgnored), report.WithCauseIgnoreBlock("ignore-block"), @@ -1148,24 +1173,26 @@ func TestWriteUnitLevelSummary(t *testing.T) { tmp := helpers.TmpDirWOSymlinks(t) + l := logger.CreateLogger() + tests := []struct { name string - setup func(*report.Report) + setup func(l log.Logger, r *report.Report) expected string }{ { name: "empty runs", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { // No runs added }, expected: ``, }, { name: "single run", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { run := newRun(t, filepath.Join(tmp, "single-run")) - r.AddRun(run) - r.EndRun(run.Path) + r.AddRun(l, run) + r.EndRun(l, run.Path) }, expected: ` ❯❯ Run Summary 1 units x @@ -1176,35 +1203,35 @@ func TestWriteUnitLevelSummary(t *testing.T) { }, { name: "multiple runs sorted by duration", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { // Use syntest.Test so that we can artificially manipulate the clock for duration testing. synctest.Test(t, func(t *testing.T) { t.Helper() longRun := newRun(t, filepath.Join(tmp, "long-run")) - r.AddRun(longRun) + r.AddRun(l, longRun) time.Sleep(1 * time.Second) mediumRun := newRun(t, filepath.Join(tmp, "medium-run")) - r.AddRun(mediumRun) + r.AddRun(l, mediumRun) time.Sleep(1 * time.Second) shortRun := newRun(t, filepath.Join(tmp, "short-run")) - r.AddRun(shortRun) + r.AddRun(l, shortRun) time.Sleep(1 * time.Second) - r.EndRun(shortRun.Path) + r.EndRun(l, shortRun.Path) time.Sleep(1 * time.Second) - r.EndRun(mediumRun.Path) + r.EndRun(l, mediumRun.Path) time.Sleep(1 * time.Second) - r.EndRun(longRun.Path) + r.EndRun(l, longRun.Path) }) }, expected: ` @@ -1218,7 +1245,7 @@ func TestWriteUnitLevelSummary(t *testing.T) { }, { name: "mixed results grouped by category", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { // Use syntest.Test so that we can artificially manipulate the clock for duration testing. synctest.Test(t, func(t *testing.T) { t.Helper() @@ -1237,31 +1264,31 @@ func TestWriteUnitLevelSummary(t *testing.T) { excludedRun := newRun(t, filepath.Join(tmp, "excluded-run")) - r.AddRun(successRun1) + r.AddRun(l, successRun1) time.Sleep(1 * time.Second) - r.AddRun(successRun2) + r.AddRun(l, successRun2) time.Sleep(1 * time.Second) - r.AddRun(failRun) + r.AddRun(l, failRun) time.Sleep(1 * time.Second) - r.AddRun(excludedRun) + r.AddRun(l, excludedRun) time.Sleep(1 * time.Second) - r.EndRun(successRun1.Path) + r.EndRun(l, successRun1.Path) time.Sleep(1 * time.Second) - r.EndRun(successRun2.Path) + r.EndRun(l, successRun2.Path) time.Sleep(1 * time.Second) - r.EndRun(failRun.Path, report.WithResult(report.ResultFailed)) + r.EndRun(l, failRun.Path, report.WithResult(report.ResultFailed)) time.Sleep(1 * time.Second) - r.EndRun(excludedRun.Path, report.WithResult(report.ResultExcluded)) + r.EndRun(l, excludedRun.Path, report.WithResult(report.ResultExcluded)) }) }, expected: ` @@ -1278,7 +1305,7 @@ func TestWriteUnitLevelSummary(t *testing.T) { }, { name: "very short unit names", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { // Use syntest.Test so that we can artificially manipulate the clock for duration testing. synctest.Test(t, func(t *testing.T) { t.Helper() @@ -1293,27 +1320,27 @@ func TestWriteUnitLevelSummary(t *testing.T) { c := newRun(t, filepath.Join(tmp, "c")) - r.AddRun(a) + r.AddRun(l, a) time.Sleep(1 * time.Second) - r.AddRun(b) + r.AddRun(l, b) time.Sleep(1 * time.Second) - r.AddRun(c) + r.AddRun(l, c) time.Sleep(1 * time.Second) - r.EndRun(a.Path) + r.EndRun(l, a.Path) time.Sleep(1 * time.Second) - r.EndRun(b.Path) + r.EndRun(l, b.Path) time.Sleep(1 * time.Second) - r.EndRun(c.Path) + r.EndRun(l, c.Path) }) }, expected: ` @@ -1327,7 +1354,7 @@ func TestWriteUnitLevelSummary(t *testing.T) { }, { name: "very long unit names", - setup: func(r *report.Report) { + setup: func(l log.Logger, r *report.Report) { // Use syntest.Test so that we can artificially manipulate the clock for duration testing. synctest.Test(t, func(t *testing.T) { t.Helper() @@ -1344,27 +1371,27 @@ func TestWriteUnitLevelSummary(t *testing.T) { time.Sleep(1 * time.Second) - r.AddRun(longName1) + r.AddRun(l, longName1) time.Sleep(1 * time.Second) - r.AddRun(longName2) + r.AddRun(l, longName2) time.Sleep(1 * time.Second) - r.AddRun(longName3) + r.AddRun(l, longName3) time.Sleep(1 * time.Second) - r.EndRun(longName1.Path) + r.EndRun(l, longName1.Path) time.Sleep(1 * time.Second) - r.EndRun(longName2.Path) + r.EndRun(l, longName2.Path) time.Sleep(1 * time.Second) - r.EndRun(longName3.Path) + r.EndRun(l, longName3.Path) }) }, expected: ` @@ -1387,7 +1414,7 @@ func TestWriteUnitLevelSummary(t *testing.T) { WithShowUnitLevelSummary(). WithWorkingDir(tmp) - tt.setup(r) + tt.setup(l, r) var buf bytes.Buffer diff --git a/internal/runner/common/unit_runner.go b/internal/runner/common/unit_runner.go index 5e50142fce..4d2639ada2 100644 --- a/internal/runner/common/unit_runner.go +++ b/internal/runner/common/unit_runner.go @@ -59,12 +59,7 @@ func (runner *UnitRunner) runTerragrunt(ctx context.Context, opts *options.Terra unitPath := runner.Unit.AbsolutePath() unitPath = util.CleanPath(unitPath) - run, err := report.NewRun(unitPath) - if err != nil { - return err - } - - if err := r.AddRun(run); err != nil { + if _, err := r.EnsureRun(runner.Unit.Execution.Logger, unitPath); err != nil { return err } } @@ -91,6 +86,7 @@ func (runner *UnitRunner) runTerragrunt(ctx context.Context, opts *options.Terra if runErr != nil { if endErr := r.EndRun( + runner.Unit.Execution.Logger, unitPath, report.WithResult(report.ResultFailed), report.WithReason(report.ReasonRunError), @@ -99,7 +95,11 @@ func (runner *UnitRunner) runTerragrunt(ctx context.Context, opts *options.Terra runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", unitPath, endErr) } } else { - if endErr := r.EndRun(unitPath, report.WithResult(report.ResultSucceeded)); endErr != nil { + if endErr := r.EndRun( + runner.Unit.Execution.Logger, + unitPath, + report.WithResult(report.ResultSucceeded), + ); endErr != nil { runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", unitPath, endErr) } } diff --git a/internal/runner/runnerpool/runner.go b/internal/runner/runnerpool/runner.go index 5cacd78082..6db98a5448 100644 --- a/internal/runner/runnerpool/runner.go +++ b/internal/runner/runnerpool/runner.go @@ -333,12 +333,20 @@ func NewRunnerPoolStack( } } - run, err := runner.Stack.Execution.Report.EnsureRun(absPath) + run, err := runner.Stack.Execution.Report.EnsureRun(l, absPath) if err != nil { continue } - _ = runner.Stack.Execution.Report.EndRun(run.Path, report.WithResult(report.ResultExcluded), report.WithReason(report.ReasonExcludeDir)) + err = runner.Stack.Execution.Report.EndRun( + l, + run.Path, + report.WithResult(report.ResultExcluded), + report.WithReason(report.ReasonExcludeDir), + ) + if err != nil { + l.Errorf("Error ending run for unit %s: %v", absPath, err) + } } } } @@ -490,7 +498,7 @@ func (r *Runner) Run(ctx context.Context, l log.Logger, opts *options.Terragrunt // Ensure path is absolute for reporting unitPath := u.AbsolutePath() - run, err := r.Stack.Execution.Report.EnsureRun(unitPath) + run, err := r.Stack.Execution.Report.EnsureRun(l, unitPath) if err != nil { l.Errorf("Error ensuring run for unit %s: %v", unitPath, err) continue @@ -508,6 +516,7 @@ func (r *Runner) Run(ctx context.Context, l log.Logger, opts *options.Terragrunt } if err := r.Stack.Execution.Report.EndRun( + l, run.Path, report.WithResult(report.ResultExcluded), report.WithReason(reason), @@ -578,7 +587,7 @@ func (r *Runner) Run(ctx context.Context, l log.Logger, opts *options.Terragrunt // Ensure path is absolute for reporting unitPath := unit.AbsolutePath() - run, reportErr := r.Stack.Execution.Report.EnsureRun(unitPath) + run, reportErr := r.Stack.Execution.Report.EnsureRun(l, unitPath) if reportErr != nil { l.Errorf("Error ensuring run for early exit unit %s: %v", unitPath, reportErr) continue @@ -609,7 +618,7 @@ func (r *Runner) Run(ctx context.Context, l log.Logger, opts *options.Terragrunt endOpts = append(endOpts, report.WithCauseAncestorExit(failedAncestor)) } - if endErr := r.Stack.Execution.Report.EndRun(run.Path, endOpts...); endErr != nil { + if endErr := r.Stack.Execution.Report.EndRun(l, run.Path, endOpts...); endErr != nil { l.Errorf("Error ending run for early exit unit %s: %v", unitPath, endErr) } } diff --git a/options/options.go b/options/options.go index 21adbbf90b..9caf28a2c1 100644 --- a/options/options.go +++ b/options/options.go @@ -676,19 +676,26 @@ type ErrorsPattern struct { } // RunWithErrorHandling runs the given operation and handles any errors according to the configuration. -func (opts *TerragruntOptions) RunWithErrorHandling(ctx context.Context, l log.Logger, r *report.Report, operation func() error) error { +func (opts *TerragruntOptions) RunWithErrorHandling( + ctx context.Context, + l log.Logger, + r *report.Report, + operation func() error, +) error { if opts.Errors == nil { return operation() } currentAttempt := 1 - // convert working dir to an absolute path for reporting - absWorkingDir, err := filepath.Abs(opts.WorkingDir) + // convert working dir to a clean, absolute path for reporting + reportDir, err := filepath.Abs(opts.WorkingDir) if err != nil { return err } + reportDir = util.CleanPath(reportDir) + for { err := operation() if err == nil { @@ -715,12 +722,13 @@ func (opts *TerragruntOptions) RunWithErrorHandling(ctx context.Context, l log.L } } - run, err := r.EnsureRun(absWorkingDir) + run, err := r.EnsureRun(l, reportDir) if err != nil { return err } if err := r.EndRun( + l, run.Path, report.WithResult(report.ResultSucceeded), report.WithReason(report.ReasonErrorIgnored), @@ -747,12 +755,13 @@ func (opts *TerragruntOptions) RunWithErrorHandling(ctx context.Context, l log.L ) // Record that a retry will be attempted without prematurely marking success. - run, err := r.EnsureRun(absWorkingDir) + run, err := r.EnsureRun(l, reportDir) if err != nil { return err } if err := r.EndRun( + l, run.Path, report.WithResult(report.ResultSucceeded), report.WithReason(report.ReasonRetrySucceeded),