Skip to content

Commit d0fe6ea

Browse files
Zettat123GiteaBot
andauthored
The job should always run when if is always() (#29464)
Fix #27906 According to GitHub's [documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds), a job should always run when its `if` is `always()` > If you would like a job to run even if a job it is dependent on did not succeed, use the `always()` conditional expression in `jobs.<job_id>.if`. --------- Co-authored-by: Giteabot <[email protected]>
1 parent d557fbc commit d0fe6ea

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

services/actions/job_emitter.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import (
77
"context"
88
"errors"
99
"fmt"
10+
"strings"
1011

1112
actions_model "code.gitea.io/gitea/models/actions"
1213
"code.gitea.io/gitea/models/db"
1314
"code.gitea.io/gitea/modules/graceful"
1415
"code.gitea.io/gitea/modules/queue"
1516

17+
"github.com/nektos/act/pkg/jobparser"
1618
"xorm.io/builder"
1719
)
1820

@@ -76,12 +78,15 @@ func checkJobsOfRun(ctx context.Context, runID int64) error {
7678
type jobStatusResolver struct {
7779
statuses map[int64]actions_model.Status
7880
needs map[int64][]int64
81+
jobMap map[int64]*actions_model.ActionRunJob
7982
}
8083

8184
func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
8285
idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
86+
jobMap := make(map[int64]*actions_model.ActionRunJob)
8387
for _, job := range jobs {
8488
idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
89+
jobMap[job.ID] = job
8590
}
8691

8792
statuses := make(map[int64]actions_model.Status, len(jobs))
@@ -97,6 +102,7 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
97102
return &jobStatusResolver{
98103
statuses: statuses,
99104
needs: needs,
105+
jobMap: jobMap,
100106
}
101107
}
102108

@@ -135,7 +141,20 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
135141
if allSucceed {
136142
ret[id] = actions_model.StatusWaiting
137143
} else {
138-
ret[id] = actions_model.StatusSkipped
144+
// If a job's "if" condition is "always()", the job should always run even if some of its dependencies did not succeed.
145+
// See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
146+
always := false
147+
if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
148+
_, wfJob := wfJobs[0].Job()
149+
expr := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(wfJob.If.Value, "${{"), "}}"))
150+
always = expr == "always()"
151+
}
152+
153+
if always {
154+
ret[id] = actions_model.StatusWaiting
155+
} else {
156+
ret[id] = actions_model.StatusSkipped
157+
}
139158
}
140159
}
141160
}

services/actions/job_emitter_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,62 @@ func Test_jobStatusResolver_Resolve(t *testing.T) {
7070
},
7171
want: map[int64]actions_model.Status{},
7272
},
73+
{
74+
name: "with ${{ always() }} condition",
75+
jobs: actions_model.ActionJobList{
76+
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
77+
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
78+
`
79+
name: test
80+
on: push
81+
jobs:
82+
job2:
83+
runs-on: ubuntu-latest
84+
needs: job1
85+
if: ${{ always() }}
86+
steps:
87+
- run: echo "always run"
88+
`)},
89+
},
90+
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
91+
},
92+
{
93+
name: "with always() condition",
94+
jobs: actions_model.ActionJobList{
95+
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
96+
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
97+
`
98+
name: test
99+
on: push
100+
jobs:
101+
job2:
102+
runs-on: ubuntu-latest
103+
needs: job1
104+
if: always()
105+
steps:
106+
- run: echo "always run"
107+
`)},
108+
},
109+
want: map[int64]actions_model.Status{2: actions_model.StatusWaiting},
110+
},
111+
{
112+
name: "without always() condition",
113+
jobs: actions_model.ActionJobList{
114+
{ID: 1, JobID: "job1", Status: actions_model.StatusFailure, Needs: []string{}},
115+
{ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte(
116+
`
117+
name: test
118+
on: push
119+
jobs:
120+
job2:
121+
runs-on: ubuntu-latest
122+
needs: job1
123+
steps:
124+
- run: echo "not always run"
125+
`)},
126+
},
127+
want: map[int64]actions_model.Status{2: actions_model.StatusSkipped},
128+
},
73129
}
74130
for _, tt := range tests {
75131
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)