Skip to content

Commit 580ea4b

Browse files
committed
improve online runner check
1 parent a4e23b8 commit 580ea4b

File tree

4 files changed

+142
-103
lines changed

4 files changed

+142
-103
lines changed

models/actions/runner.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
repo_model "code.gitea.io/gitea/models/repo"
1515
"code.gitea.io/gitea/models/shared/types"
1616
user_model "code.gitea.io/gitea/models/user"
17+
"code.gitea.io/gitea/modules/container"
1718
"code.gitea.io/gitea/modules/optional"
1819
"code.gitea.io/gitea/modules/setting"
1920
"code.gitea.io/gitea/modules/timeutil"
@@ -173,6 +174,18 @@ func (r *ActionRunner) GenerateToken() (err error) {
173174
return err
174175
}
175176

177+
// CanMatchLabels checks whether the runner's labels can match a job's "runs-on"
178+
// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on
179+
func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool {
180+
runnerLabelSet := container.SetOf(r.AgentLabels...)
181+
for _, v := range jobRunsOn {
182+
if !runnerLabelSet.Contains(v) {
183+
return false
184+
}
185+
}
186+
return true
187+
}
188+
176189
func init() {
177190
db.RegisterModel(&ActionRunner{})
178191
}

models/actions/task.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
auth_model "code.gitea.io/gitea/models/auth"
1414
"code.gitea.io/gitea/models/db"
1515
"code.gitea.io/gitea/models/unit"
16-
"code.gitea.io/gitea/modules/container"
1716
"code.gitea.io/gitea/modules/log"
1817
"code.gitea.io/gitea/modules/setting"
1918
"code.gitea.io/gitea/modules/timeutil"
@@ -245,7 +244,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
245244
var job *ActionRunJob
246245
log.Trace("runner labels: %v", runner.AgentLabels)
247246
for _, v := range jobs {
248-
if isSubset(runner.AgentLabels, v.RunsOn) {
247+
if runner.CanMatchLabels(v.RunsOn) {
249248
job = v
250249
break
251250
}
@@ -475,20 +474,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
475474
Find(&tasks)
476475
}
477476

478-
func isSubset(set, subset []string) bool {
479-
m := make(container.Set[string], len(set))
480-
for _, v := range set {
481-
m.Add(v)
482-
}
483-
484-
for _, v := range subset {
485-
if !m.Contains(v) {
486-
return false
487-
}
488-
}
489-
return true
490-
}
491-
492477
func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
493478
if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
494479
return timeutil.TimeStamp(0)

routers/web/repo/actions/actions.go

Lines changed: 122 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"code.gitea.io/gitea/services/context"
2929
"code.gitea.io/gitea/services/convert"
3030

31-
"github.com/nektos/act/pkg/model"
31+
act_model "github.com/nektos/act/pkg/model"
3232
"gopkg.in/yaml.v3"
3333
)
3434

@@ -38,9 +38,10 @@ const (
3838
tplViewActions templates.TplName = "repo/actions/view"
3939
)
4040

41-
type Workflow struct {
42-
Entry git.TreeEntry
43-
ErrMsg string
41+
type WorkflowInfo struct {
42+
Entry git.TreeEntry
43+
ErrMsg string
44+
Workflow *act_model.Workflow
4445
}
4546

4647
// MustEnableActions check if actions are enabled in settings
@@ -77,7 +78,11 @@ func List(ctx *context.Context) {
7778
return
7879
}
7980

80-
workflows := prepareWorkflowDispatchTemplate(ctx, commit)
81+
workflows := prepareWorkflowTemplate(ctx, commit)
82+
if ctx.Written() {
83+
return
84+
}
85+
prepareWorkflowDispatchTemplate(ctx, workflows)
8186
if ctx.Written() {
8287
return
8388
}
@@ -112,55 +117,42 @@ func WorkflowDispatchInputs(ctx *context.Context) {
112117
ctx.ServerError("GetTagCommit/GetBranchCommit", err)
113118
return
114119
}
115-
prepareWorkflowDispatchTemplate(ctx, commit)
120+
workflows := prepareWorkflowTemplate(ctx, commit)
121+
if ctx.Written() {
122+
return
123+
}
124+
prepareWorkflowDispatchTemplate(ctx, workflows)
116125
if ctx.Written() {
117126
return
118127
}
119128
ctx.HTML(http.StatusOK, tplDispatchInputsActions)
120129
}
121130

122-
func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (workflows []Workflow) {
131+
func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflows []WorkflowInfo) {
123132
workflowID := ctx.FormString("workflow")
124133
ctx.Data["CurWorkflow"] = workflowID
125-
ctx.Data["CurWorkflowExists"] = false
126-
127-
var curWorkflow *model.Workflow
128134

129135
_, entries, err := actions.ListWorkflows(commit)
130136
if err != nil {
131137
ctx.ServerError("ListWorkflows", err)
132138
return nil
133139
}
134140

135-
// Get all runner labels
136-
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
137-
RepoID: ctx.Repo.Repository.ID,
138-
IsOnline: optional.Some(true),
139-
WithAvailable: true,
140-
})
141-
if err != nil {
142-
ctx.ServerError("FindRunners", err)
143-
return nil
144-
}
145-
allRunnerLabels := make(container.Set[string])
146-
for _, r := range runners {
147-
allRunnerLabels.AddMultiple(r.AgentLabels...)
148-
}
149-
150-
workflows = make([]Workflow, 0, len(entries))
141+
workflows = make([]WorkflowInfo, 0, len(entries))
151142
for _, entry := range entries {
152-
workflow := Workflow{Entry: *entry}
143+
workflow := WorkflowInfo{Entry: *entry}
153144
content, err := actions.GetContentFromEntry(entry)
154145
if err != nil {
155146
ctx.ServerError("GetContentFromEntry", err)
156147
return nil
157148
}
158-
wf, err := model.ReadWorkflow(bytes.NewReader(content))
149+
wf, err := act_model.ReadWorkflow(bytes.NewReader(content))
159150
if err != nil {
160151
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
161152
workflows = append(workflows, workflow)
162153
continue
163154
}
155+
workflow.Workflow = wf
164156
// The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
165157
hasJobWithoutNeeds := false
166158
// Check whether you have matching runner and a job without "needs"
@@ -173,22 +165,6 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (
173165
if !hasJobWithoutNeeds && len(j.Needs()) == 0 {
174166
hasJobWithoutNeeds = true
175167
}
176-
runsOnList := j.RunsOn()
177-
for _, ro := range runsOnList {
178-
if strings.Contains(ro, "${{") {
179-
// Skip if it contains expressions.
180-
// The expressions could be very complex and could not be evaluated here,
181-
// so just skip it, it's OK since it's just a tooltip message.
182-
continue
183-
}
184-
if !allRunnerLabels.Contains(ro) {
185-
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro)
186-
break
187-
}
188-
}
189-
if workflow.ErrMsg != "" {
190-
break
191-
}
192168
}
193169
if !hasJobWithoutNeeds {
194170
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs")
@@ -197,61 +173,81 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (
197173
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job")
198174
}
199175
workflows = append(workflows, workflow)
200-
201-
if workflow.Entry.Name() == workflowID {
202-
curWorkflow = wf
203-
ctx.Data["CurWorkflowExists"] = true
204-
}
205176
}
206177

207178
ctx.Data["workflows"] = workflows
208179
ctx.Data["RepoLink"] = ctx.Repo.Repository.Link()
209-
180+
ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.IsAdmin()
210181
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
211182
ctx.Data["ActionsConfig"] = actionsConfig
212183

213-
if len(workflowID) > 0 && ctx.Repo.CanWrite(unit.TypeActions) {
214-
ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.IsAdmin()
215-
isWorkflowDisabled := actionsConfig.IsWorkflowDisabled(workflowID)
216-
ctx.Data["CurWorkflowDisabled"] = isWorkflowDisabled
217-
218-
if !isWorkflowDisabled && curWorkflow != nil {
219-
workflowDispatchConfig := workflowDispatchConfig(curWorkflow)
220-
if workflowDispatchConfig != nil {
221-
ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig
222-
223-
branchOpts := git_model.FindBranchOptions{
224-
RepoID: ctx.Repo.Repository.ID,
225-
IsDeletedBranch: optional.Some(false),
226-
ListOptions: db.ListOptions{
227-
ListAll: true,
228-
},
229-
}
230-
branches, err := git_model.FindBranchNames(ctx, branchOpts)
231-
if err != nil {
232-
ctx.ServerError("FindBranchNames", err)
233-
return nil
234-
}
235-
// always put default branch on the top if it exists
236-
if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) {
237-
branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
238-
branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
239-
}
240-
ctx.Data["Branches"] = branches
184+
if len(workflowID) > 0 {
185+
ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflowID)
186+
}
241187

242-
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
243-
if err != nil {
244-
ctx.ServerError("GetTagNamesByRepoID", err)
245-
return nil
246-
}
247-
ctx.Data["Tags"] = tags
188+
return workflows
189+
}
190+
191+
func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []WorkflowInfo) {
192+
workflowID := ctx.FormString("workflow")
193+
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
194+
if len(workflowID) == 0 || !ctx.Repo.CanWrite(unit.TypeActions) || actionsConfig.IsWorkflowDisabled(workflowID) {
195+
return
196+
}
197+
198+
ctx.Data["CurWorkflowExists"] = false
199+
var curWorkflow *act_model.Workflow
200+
for _, workflowInfo := range workflowInfos {
201+
if workflowInfo.Entry.Name() == workflowID {
202+
if workflowInfo.Workflow == nil {
203+
log.Error("%s workflowInfo.Workflow is nil", workflowID)
204+
return
248205
}
206+
curWorkflow = workflowInfo.Workflow
207+
ctx.Data["CurWorkflowExists"] = true
208+
break
249209
}
250210
}
251-
return workflows
211+
212+
if curWorkflow == nil {
213+
return
214+
}
215+
216+
workflowDispatchConfig := workflowDispatchConfig(curWorkflow)
217+
if workflowDispatchConfig == nil {
218+
return
219+
}
220+
221+
ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig
222+
223+
branchOpts := git_model.FindBranchOptions{
224+
RepoID: ctx.Repo.Repository.ID,
225+
IsDeletedBranch: optional.Some(false),
226+
ListOptions: db.ListOptions{
227+
ListAll: true,
228+
},
229+
}
230+
branches, err := git_model.FindBranchNames(ctx, branchOpts)
231+
if err != nil {
232+
ctx.ServerError("FindBranchNames", err)
233+
return
234+
}
235+
// always put default branch on the top if it exists
236+
if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) {
237+
branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
238+
branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
239+
}
240+
ctx.Data["Branches"] = branches
241+
242+
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
243+
if err != nil {
244+
ctx.ServerError("GetTagNamesByRepoID", err)
245+
return
246+
}
247+
ctx.Data["Tags"] = tags
252248
}
253249

254-
func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
250+
func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) {
255251
actorID := ctx.FormInt64("actor")
256252
status := ctx.FormInt("status")
257253
workflowID := ctx.FormString("workflow")
@@ -302,6 +298,45 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
302298
log.Error("LoadIsRefDeleted", err)
303299
}
304300

301+
// Check for each run if there is at least one online runner that can run its jobs
302+
runErrors := make(map[int64]string)
303+
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
304+
RepoID: ctx.Repo.Repository.ID,
305+
IsOnline: optional.Some(true),
306+
WithAvailable: true,
307+
})
308+
if err != nil {
309+
ctx.ServerError("FindRunners", err)
310+
return
311+
}
312+
for _, run := range runs {
313+
if !run.Status.In(actions_model.StatusWaiting, actions_model.StatusRunning) {
314+
continue
315+
}
316+
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
317+
if err != nil {
318+
ctx.ServerError("GetRunJobsByRunID", err)
319+
return
320+
}
321+
for _, job := range jobs {
322+
if !job.Status.IsWaiting() {
323+
continue
324+
}
325+
hasOnlineRunner := false
326+
for _, runner := range runners {
327+
if runner.CanMatchLabels(job.RunsOn) {
328+
hasOnlineRunner = true
329+
break
330+
}
331+
}
332+
if !hasOnlineRunner {
333+
runErrors[run.ID] = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", strings.Join(job.RunsOn, ","))
334+
break
335+
}
336+
}
337+
}
338+
ctx.Data["RunErrors"] = runErrors
339+
305340
ctx.Data["Runs"] = runs
306341

307342
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
@@ -362,7 +397,7 @@ type WorkflowDispatch struct {
362397
Inputs []WorkflowDispatchInput
363398
}
364399

365-
func workflowDispatchConfig(w *model.Workflow) *WorkflowDispatch {
400+
func workflowDispatchConfig(w *act_model.Workflow) *WorkflowDispatch {
366401
switch w.RawOn.Kind {
367402
case yaml.ScalarNode:
368403
var val string

templates/repo/actions/runs_list.tmpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
{{ctx.Locale.Tr "actions.runs.pushed_by"}}
2525
<a href="{{$run.TriggerUser.HomeLink}}">{{$run.TriggerUser.GetDisplayName}}</a>
2626
{{- end -}}
27+
{{$errMsg := index $.RunErrors $run.ID}}
28+
{{if $errMsg}}
29+
<span data-tooltip-content="{{$errMsg}}">
30+
{{svg "octicon-alert" 16 "text red"}}
31+
</span>
32+
{{end}}
2733
</div>
2834
</div>
2935
<div class="flex-item-trailing">

0 commit comments

Comments
 (0)