mirror of https://github.com/go-gitea/gitea.git
Improve the handling of `jobs.<job_id>.if` (#31070)
Fix #25897
Fix #30322
#29464 cannot handle some complex `if` conditions correctly because it
only checks `always()` literally. In fact, it's not easy to evaluate the
`if` condition on the Gitea side because evaluating it requires a series
of contexts. But act_runner is able to evaluate the `if` condition
before running the job (for more information, see
[`gitea/act`](517d11c671/pkg/runner/run_context.go (L739-L753)
))
. So we can use act_runner to check the `if` condition.
In this PR, how to handle a blocked job depends on its `needs` and `if`:
- If not all jobs in `needs` completed successfully and the job's `if`
is empty, set the job status to `StatusSkipped`
- In other cases, the job status will be set to `StatusWaiting`, and
then act_runner will check the `if` condition and run the job if the
condition is met
This commit is contained in:
parent
e695ba4755
commit
31a0c4dfb4
|
@ -7,7 +7,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -141,18 +140,19 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
|
||||||
if allSucceed {
|
if allSucceed {
|
||||||
ret[id] = actions_model.StatusWaiting
|
ret[id] = actions_model.StatusWaiting
|
||||||
} else {
|
} else {
|
||||||
// If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed.
|
// Check if the job has an "if" condition
|
||||||
// See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
|
hasIf := false
|
||||||
always := false
|
|
||||||
if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
|
if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
|
||||||
_, wfJob := wfJobs[0].Job()
|
_, wfJob := wfJobs[0].Job()
|
||||||
expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}"))
|
hasIf = len(wfJob.If.Value) > 0
|
||||||
always = expr == "always()"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if always {
|
if hasIf {
|
||||||
|
// act_runner will check the "if" condition
|
||||||
ret[id] = actions_model.StatusWaiting
|
ret[id] = actions_model.StatusWaiting
|
||||||
} else {
|
} else {
|
||||||
|
// If the "if" condition is empty and not all dependent jobs completed successfully,
|
||||||
|
// the job should be skipped.
|
||||||
ret[id] = actions_model.StatusSkipped
|
ret[id] = actions_model.StatusSkipped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,9 +71,9 @@ func Test_jobStatusResolver_Resolve(t *testing.T) {
|
||||||
want: map[int64]actions_model.Status{},
|
want: map[int64]actions_model.Status{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with ${{ always() }} condition",
|
name: "`if` is not empty and all jobs in `needs` completed successfully",
|
||||||
jobs: actions_model.ActionJobList{
|
jobs: actions_model.ActionJobList{
|
||||||
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
{ID: 1, JobID: "job1", Status: actions_model.StatusSuccess, Needs: []string{}},
|
||||||
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||||
`
|
`
|
||||||
name: test
|
name: test
|
||||||
|
@ -82,15 +82,15 @@ jobs:
|
||||||
job2:
|
job2:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: job1
|
needs: job1
|
||||||
if: ${{ always() }}
|
if: ${{ always() && needs.job1.result == 'success' }}
|
||||||
steps:
|
steps:
|
||||||
- run: echo "always run"
|
- run: echo "will be checked by act_runner"
|
||||||
`)},
|
`)},
|
||||||
},
|
},
|
||||||
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with always() condition",
|
name: "`if` is not empty and not all jobs in `needs` completed successfully",
|
||||||
jobs: actions_model.ActionJobList{
|
jobs: actions_model.ActionJobList{
|
||||||
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||||
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||||
|
@ -101,15 +101,15 @@ jobs:
|
||||||
job2:
|
job2:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: job1
|
needs: job1
|
||||||
if: always()
|
if: ${{ always() && needs.job1.result == 'failure' }}
|
||||||
steps:
|
steps:
|
||||||
- run: echo "always run"
|
- run: echo "will be checked by act_runner"
|
||||||
`)},
|
`)},
|
||||||
},
|
},
|
||||||
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "without always() condition",
|
name: "`if` is empty and not all jobs in `needs` completed successfully",
|
||||||
jobs: actions_model.ActionJobList{
|
jobs: actions_model.ActionJobList{
|
||||||
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
|
||||||
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
|
||||||
|
@ -121,7 +121,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: job1
|
needs: job1
|
||||||
steps:
|
steps:
|
||||||
- run: echo "not always run"
|
- run: echo "should be skipped"
|
||||||
`)},
|
`)},
|
||||||
},
|
},
|
||||||
want: map[int64]actions_model.Status{2: actions_model.StatusSkipped},
|
want: map[int64]actions_model.Status{2: actions_model.StatusSkipped},
|
||||||
|
|
Loading…
Reference in New Issue