mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-10 05:24:18 +09:00
fix(actions): dynamic matrix expansion correctness fixes (#38690)
Follow-up to https://github.com/go-gitea/gitea/pull/36564 (dynamic matrix) and https://github.com/go-gitea/gitea/pull/36357 (max-parallel), fixing issues found reviewing the two features together. - **A placeholder could stall its run forever.** Its payload keeps the raw matrix but loses its `needs`, so `ParseJob` re-expanded it instead of reading it back — fatal for `include: ${{ fromJson(needs.*.outputs.*) }}`. - **An `if:` reading `matrix.*` skipped the whole job**, with or without the `${{ }}`. It now reduces to the needs gate, except under `always()`/`failure()`/`cancelled()`, and each combination is decided on its own values once the matrix expands. - **Dependents could be skipped before the combinations ran**, since inserted siblings are absent from the resolver's job set. The pass now stops after an insert and defers to the re-emit it schedules. - **Expansion failures stranded the placeholder.** A retryable one is returned so the queue retries it; a malformed payload fails the job instead of requeueing forever. - **Rerun could rewind a pass-through row** into a raw placeholder keeping its old terminal status, which nothing expands. Now gated on the anchor itself. Plus: `max-parallel` distinguishes an unevaluated `${{ }}` (debug) from a non-numeric literal (warn — it silently drops the cap). Co-authored-by: Zettat123 <zettat123@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
co-authored by
Zettat123
silverwind
parent
e80a62f555
commit
11d0ed699b
@@ -192,6 +192,16 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
|
||||
|
||||
// ParseJob parses the job structure from the ActionRunJob.WorkflowPayload
|
||||
func (job *ActionRunJob) ParseJob() (*jobparser.Job, error) {
|
||||
if job.IsMatrixDeferred {
|
||||
// The needs were erased before the placeholder was persisted, so jobparser.Parse no longer
|
||||
// recognises the raw matrix it still carries and would re-expand it: see ParseRawSingleWorkflow.
|
||||
_, workflowJob, err := jobparser.ParseRawSingleWorkflow(job.WorkflowPayload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("job %d deferred matrix placeholder: unable to parse: %w", job.ID, err)
|
||||
}
|
||||
return workflowJob, nil
|
||||
}
|
||||
|
||||
// job.WorkflowPayload is a SingleWorkflow created from an ActionRun's workflow, which exactly contains this job's YAML definition.
|
||||
// Ideally it shouldn't be called "Workflow", it is just a job with global workflow fields + trigger
|
||||
parsedWorkflows, err := jobparser.Parse(job.WorkflowPayload)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
@@ -197,3 +198,43 @@ func TestCancelJobs_NestedBlockedReusableCaller(t *testing.T) {
|
||||
gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID})
|
||||
assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked")
|
||||
}
|
||||
|
||||
func TestParseJobDeferredMatrixPlaceholder(t *testing.T) {
|
||||
// A placeholder is persisted with the raw matrix and without its needs, so routing its payload
|
||||
// through jobparser.Parse re-expands that matrix. The job emitter reads `if:` (and so ParseJob)
|
||||
// before it can expand the placeholder, and it only logs a parse failure: getting this wrong
|
||||
// leaves the job Blocked on every pass, the run never finishes and its concurrency group is
|
||||
// never released.
|
||||
payload := func(matrix string) []byte {
|
||||
return fmt.Appendf(nil, `name: test
|
||||
on: push
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
%s
|
||||
steps:
|
||||
- run: echo
|
||||
`, matrix)
|
||||
}
|
||||
|
||||
// The shape Parse happens to survive, so only the name tells the two paths apart. What Parse makes
|
||||
// of every other shape is asserted in TestParseRawSingleWorkflowRoundTripsDeferredPlaceholder.
|
||||
t.Run("a placeholder is read back, not re-expanded", func(t *testing.T) {
|
||||
job := &ActionRunJob{ID: 1, JobID: "build", IsMatrixDeferred: true, WorkflowPayload: payload("version: ${{ fromJson(needs.setup.outputs.m) }}")}
|
||||
parsed, err := job.ParseJob()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parsed)
|
||||
assert.Equal(t, "build", parsed.Name)
|
||||
})
|
||||
|
||||
t.Run("an expanded job still goes through the full parse", func(t *testing.T) {
|
||||
job := &ActionRunJob{ID: 1, JobID: "build", WorkflowPayload: payload("version: [1]")}
|
||||
parsed, err := job.ParseJob()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parsed)
|
||||
// Parse bakes the combination into the name, ParseRawSingleWorkflow would not.
|
||||
assert.Equal(t, "build (1)", parsed.Name)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user