diff --git a/services/actions/TestRerun_RerunAllJobs/action_artifact.yml b/services/actions/TestRerun_RerunAllJobs/action_artifact.yml new file mode 100644 index 0000000000..58ffe67e8d --- /dev/null +++ b/services/actions/TestRerun_RerunAllJobs/action_artifact.yml @@ -0,0 +1,11 @@ +- id: 59220 + run_id: 455620 + repo_id: 62 # test_workflows + owner_id: 2 # user2 + status: 2 # ArtifactStatusUploadConfirmed + +- id: 59230 + run_id: 455630 + repo_id: 62 # test_workflows + owner_id: 2 # user2 + status: 2 # ArtifactStatusUploadConfirmed \ No newline at end of file diff --git a/services/actions/TestRerun_RerunJob/action_artifact.yml b/services/actions/TestRerun_RerunJob/action_artifact.yml new file mode 100644 index 0000000000..5f59c87183 --- /dev/null +++ b/services/actions/TestRerun_RerunJob/action_artifact.yml @@ -0,0 +1,11 @@ +- id: 59240 + run_id: 455640 + repo_id: 62 # test_workflows + owner_id: 2 # user2 + status: 2 # ArtifactStatusUploadConfirmed + +- id: 59250 + run_id: 455650 + repo_id: 62 # test_workflows + owner_id: 2 # user2 + status: 2 # ArtifactStatusUploadConfirmed \ No newline at end of file diff --git a/services/actions/rerun.go b/services/actions/rerun.go index 59838e5f57..4ca32276d1 100644 --- a/services/actions/rerun.go +++ b/services/actions/rerun.go @@ -74,6 +74,12 @@ func RerunAllJobs(ctx context.Context, run *actions_model.ActionRun) ([]*actions return fmt.Errorf("cannot prepare next attempt because run %d is active: %s", run.ID, run.Status.String()) } + // Wipe all artifacts before a rerun to prevent stale artifacts from polluting artifacts collected during the + // rerun. + if err := actions_model.SetArtifactsOfRunDeleted(ctx, run.ID); err != nil { + return fmt.Errorf("cannot remove artifacts of previous run of run %d: %w", run.ID, err) + } + run.PreviousDuration = run.Duration() run.Status = actions_model.StatusWaiting @@ -138,6 +144,14 @@ func RerunJob(ctx context.Context, job *actions_model.ActionRunJob) ([]*actions_ return fmt.Errorf("could not load jobs of run %d: %w", job.RunID, err) } + // Wipe all artifacts before a rerun to prevent stale artifacts from polluting the artifacts collected during + // the rerun. Because artifacts are bound to a run and not to a job, it is not possible to only remove the + // artifacts of the jobs that are going to be rerun. That means that artifacts created by jobs that are not + // rerun will be lost. That matches GitHub Actions' behaviour as of May 2026. + if err := actions_model.SetArtifactsOfRunDeleted(ctx, job.RunID); err != nil { + return fmt.Errorf("cannot remove artifacts of previous run of run %d: %w", job.RunID, err) + } + for _, jobToRerun := range GetAllRerunJobs(job, jobs) { canBeRerun, err := jobToRerun.CanBeRerun(ctx) if err != nil { diff --git a/services/actions/rerun_test.go b/services/actions/rerun_test.go index 93a000afd1..fcd638e448 100644 --- a/services/actions/rerun_test.go +++ b/services/actions/rerun_test.go @@ -59,6 +59,11 @@ func TestRerun_RerunAllJobs(t *testing.T) { run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455620}) + unittest.AssertCount(t, &actions_model.ActionArtifact{RunID: run.ID}, 1) + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: run.ID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 0) + rerunJobs, err := RerunAllJobs(t.Context(), run) require.NoError(t, err) @@ -81,6 +86,10 @@ func TestRerun_RerunAllJobs(t *testing.T) { assert.Equal(t, actions_model.StatusWaiting, rerunJobs[1].Status) assert.Equal(t, timeutil.TimeStamp(0), rerunJobs[1].Started) assert.Equal(t, timeutil.TimeStamp(0), rerunJobs[1].Stopped) + + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: run.ID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 1) }) t.Run("Error if workflow running", func(t *testing.T) { @@ -89,6 +98,11 @@ func TestRerun_RerunAllJobs(t *testing.T) { run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 455630}) + unittest.AssertCount(t, &actions_model.ActionArtifact{RunID: run.ID}, 1) + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: run.ID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 0) + rerunJobs, err := RerunAllJobs(t.Context(), run) require.ErrorContains(t, err, "workflow is still running") @@ -100,6 +114,11 @@ func TestRerun_RerunAllJobs(t *testing.T) { assert.Equal(t, time.Duration(0), run.PreviousDuration) assert.Empty(t, rerunJobs) + + unittest.AssertCount(t, &actions_model.ActionArtifact{RunID: run.ID}, 1) + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: run.ID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 0) }) t.Run("Error if workflow invalid", func(t *testing.T) { @@ -153,6 +172,11 @@ func TestRerun_RerunJob(t *testing.T) { job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683910}) + unittest.AssertCount(t, &actions_model.ActionArtifact{RunID: job.RunID}, 1) + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: job.RunID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 0) + rerunJobs, err := RerunJob(t.Context(), job) require.NoError(t, err) @@ -166,6 +190,10 @@ func TestRerun_RerunJob(t *testing.T) { assert.Equal(t, actions_model.StatusWaiting, job.Status) assert.Equal(t, timeutil.TimeStamp(0), job.Started) assert.Equal(t, timeutil.TimeStamp(0), job.Stopped) + + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: job.RunID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 1) }) t.Run("Rerun job needed by others", func(t *testing.T) { @@ -225,6 +253,11 @@ func TestRerun_RerunJob(t *testing.T) { job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 683900}) + unittest.AssertCount(t, &actions_model.ActionArtifact{RunID: job.RunID}, 1) + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: job.RunID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 0) + rerunJobs, err := RerunJob(t.Context(), job) require.ErrorContains(t, err, "workflow is invalid") @@ -236,6 +269,11 @@ func TestRerun_RerunJob(t *testing.T) { assert.Equal(t, actions_model.StatusFailure, job.Status) assert.Equal(t, timeutil.TimeStamp(0), job.Started) assert.Equal(t, timeutil.TimeStamp(0), job.Stopped) + + unittest.AssertCount(t, &actions_model.ActionArtifact{RunID: job.RunID}, 1) + unittest.AssertCount(t, &actions_model.ActionArtifact{ + RunID: job.RunID, Status: int64(actions_model.ArtifactStatusPendingDeletion), + }, 0) }) t.Run("Error if workflow disabled", func(t *testing.T) {