diff --git a/services/actions/job_emitter.go b/services/actions/job_emitter.go index fe3cc1b38a..bf4881ea04 100644 --- a/services/actions/job_emitter.go +++ b/services/actions/job_emitter.go @@ -324,7 +324,7 @@ func tryHandleIncompleteMatrix(ctx context.Context, blockedJob *actions_model.Ac // it to be a "persistent incomplete" job with some error that needs to be reported to the user. If the // re-evaluated job has a different job ID, then it's likely an expanded job -- such as from a reusable workflow // -- which could have it's own `needs` that allows it to expand into a correct job in the future. - jobID, _ := swf.Job() + jobID, job := swf.Job() if jobID == blockedJob.JobID { if swf.IncompleteMatrix { errorCode, errorDetails := persistentIncompleteMatrixError(blockedJob, swf.IncompleteMatrixNeeds) @@ -349,6 +349,20 @@ func tryHandleIncompleteMatrix(ctx context.Context, blockedJob *actions_model.Ac return behaviourIgnoreAllJobsInRun, nil } } + + // Original job had a `needs: ...blockedJob.Needs...`. Even though we've now expanded that job, which would + // evaluate any ${{ needs.... }} reference that is required for expansion, this job could still have other + // reasons to require acccess to those needs variables. We need to reinsert those `needs` into the new job so + // that those job's outputs and results are made available to this new job. + newNeeds := append(job.Needs(), blockedJob.Needs...) + err := job.RawNeeds.Encode(newNeeds) + if err != nil { + return behaviourError, fmt.Errorf("failure to encode newNeeds: %w", err) + } + err = swf.SetJob(jobID, job) + if err != nil { + return behaviourError, fmt.Errorf("failure to reencode updated job: %w", err) + } } err = db.WithTx(ctx, func(ctx context.Context) error { diff --git a/services/actions/job_emitter_test.go b/services/actions/job_emitter_test.go index caccf75434..6899508f37 100644 --- a/services/actions/job_emitter_test.go +++ b/services/actions/job_emitter_test.go @@ -326,6 +326,12 @@ func Test_tryHandleIncompleteMatrix(t *testing.T) { runJobID: 601, consumed: true, runJobNames: []string{"define-matrix", "produce-artifacts (blue)", "produce-artifacts (green)", "produce-artifacts (red)"}, + needs: map[string][]string{ + "define-matrix": nil, + "produce-artifacts (blue)": {"define-matrix"}, + "produce-artifacts (green)": {"define-matrix"}, + "produce-artifacts (red)": {"define-matrix"}, + }, }, { name: "needs an incomplete job", @@ -490,8 +496,8 @@ func Test_tryHandleIncompleteMatrix(t *testing.T) { }, needs: map[string][]string{ "define-workflow-call": nil, - "inner my-workflow-input": nil, - "perform-workflow-call": {"perform-workflow-call.inner_job"}, + "inner my-workflow-input": {"define-workflow-call"}, + "perform-workflow-call": {"define-workflow-call", "perform-workflow-call.inner_job"}, }, }, // Before reusable workflow expansion, there weren't any cases where evaluating a job in the job emitter could @@ -515,9 +521,10 @@ func Test_tryHandleIncompleteMatrix(t *testing.T) { }, needs: map[string][]string{ "define-workflow-call": nil, - "inner define-runs-on my-workflow-input": nil, - "inner incomplete-job my-workflow-input": {"perform-workflow-call.define-runs-on"}, + "inner define-runs-on my-workflow-input": {"define-workflow-call"}, + "inner incomplete-job my-workflow-input": {"define-workflow-call", "perform-workflow-call.define-runs-on"}, "perform-workflow-call": { + "define-workflow-call", "perform-workflow-call.define-runs-on", "perform-workflow-call.scalar-job", }, @@ -661,7 +668,7 @@ func Test_tryHandleIncompleteMatrix(t *testing.T) { if tt.needs != nil { for _, j := range allJobsInRun { expected, ok := tt.needs[j.Name] - if assert.Truef(t, ok, "unable to find runsOn[%q] in test case", j.Name) { + if assert.Truef(t, ok, "unable to find needs[%q] in test case", j.Name) { slices.Sort(j.Needs) slices.Sort(expected) assert.Equalf(t, expected, j.Needs, "comparing needs expectations for job %q", j.Name)