From 580ea4b0375a71019b0be26495f14a2ec20ba9e4 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Tue, 21 Oct 2025 14:03:15 -0600 Subject: [PATCH 1/8] improve online runner check --- models/actions/runner.go | 13 ++ models/actions/task.go | 17 +-- routers/web/repo/actions/actions.go | 209 +++++++++++++++----------- templates/repo/actions/runs_list.tmpl | 6 + 4 files changed, 142 insertions(+), 103 deletions(-) diff --git a/models/actions/runner.go b/models/actions/runner.go index 81d4249ae0b85..ba7c0771640c4 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -14,6 +14,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/shared/types" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -173,6 +174,18 @@ func (r *ActionRunner) GenerateToken() (err error) { return err } +// CanMatchLabels checks whether the runner's labels can match a job's "runs-on" +// See https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idruns-on +func (r *ActionRunner) CanMatchLabels(jobRunsOn []string) bool { + runnerLabelSet := container.SetOf(r.AgentLabels...) + for _, v := range jobRunsOn { + if !runnerLabelSet.Contains(v) { + return false + } + } + return true +} + func init() { db.RegisterModel(&ActionRunner{}) } diff --git a/models/actions/task.go b/models/actions/task.go index 7417af8b45186..8b4ecf28f7a0b 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -13,7 +13,6 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" - "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -245,7 +244,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask var job *ActionRunJob log.Trace("runner labels: %v", runner.AgentLabels) for _, v := range jobs { - if isSubset(runner.AgentLabels, v.RunsOn) { + if runner.CanMatchLabels(v.RunsOn) { job = v break } @@ -475,20 +474,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim Find(&tasks) } -func isSubset(set, subset []string) bool { - m := make(container.Set[string], len(set)) - for _, v := range set { - m.Add(v) - } - - for _, v := range subset { - if !m.Contains(v) { - return false - } - } - return true -} - func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp { if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 { return timeutil.TimeStamp(0) diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 202da407d298b..cf48d11e26f8a 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -28,7 +28,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" - "github.com/nektos/act/pkg/model" + act_model "github.com/nektos/act/pkg/model" "gopkg.in/yaml.v3" ) @@ -38,9 +38,10 @@ const ( tplViewActions templates.TplName = "repo/actions/view" ) -type Workflow struct { - Entry git.TreeEntry - ErrMsg string +type WorkflowInfo struct { + Entry git.TreeEntry + ErrMsg string + Workflow *act_model.Workflow } // MustEnableActions check if actions are enabled in settings @@ -77,7 +78,11 @@ func List(ctx *context.Context) { return } - workflows := prepareWorkflowDispatchTemplate(ctx, commit) + workflows := prepareWorkflowTemplate(ctx, commit) + if ctx.Written() { + return + } + prepareWorkflowDispatchTemplate(ctx, workflows) if ctx.Written() { return } @@ -112,19 +117,20 @@ func WorkflowDispatchInputs(ctx *context.Context) { ctx.ServerError("GetTagCommit/GetBranchCommit", err) return } - prepareWorkflowDispatchTemplate(ctx, commit) + workflows := prepareWorkflowTemplate(ctx, commit) + if ctx.Written() { + return + } + prepareWorkflowDispatchTemplate(ctx, workflows) if ctx.Written() { return } ctx.HTML(http.StatusOK, tplDispatchInputsActions) } -func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) (workflows []Workflow) { +func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflows []WorkflowInfo) { workflowID := ctx.FormString("workflow") ctx.Data["CurWorkflow"] = workflowID - ctx.Data["CurWorkflowExists"] = false - - var curWorkflow *model.Workflow _, entries, err := actions.ListWorkflows(commit) if err != nil { @@ -132,35 +138,21 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) ( return nil } - // Get all runner labels - runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{ - RepoID: ctx.Repo.Repository.ID, - IsOnline: optional.Some(true), - WithAvailable: true, - }) - if err != nil { - ctx.ServerError("FindRunners", err) - return nil - } - allRunnerLabels := make(container.Set[string]) - for _, r := range runners { - allRunnerLabels.AddMultiple(r.AgentLabels...) - } - - workflows = make([]Workflow, 0, len(entries)) + workflows = make([]WorkflowInfo, 0, len(entries)) for _, entry := range entries { - workflow := Workflow{Entry: *entry} + workflow := WorkflowInfo{Entry: *entry} content, err := actions.GetContentFromEntry(entry) if err != nil { ctx.ServerError("GetContentFromEntry", err) return nil } - wf, err := model.ReadWorkflow(bytes.NewReader(content)) + wf, err := act_model.ReadWorkflow(bytes.NewReader(content)) if err != nil { workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) workflows = append(workflows, workflow) continue } + workflow.Workflow = wf // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run. hasJobWithoutNeeds := false // Check whether you have matching runner and a job without "needs" @@ -173,22 +165,6 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) ( if !hasJobWithoutNeeds && len(j.Needs()) == 0 { hasJobWithoutNeeds = true } - runsOnList := j.RunsOn() - for _, ro := range runsOnList { - if strings.Contains(ro, "${{") { - // Skip if it contains expressions. - // The expressions could be very complex and could not be evaluated here, - // so just skip it, it's OK since it's just a tooltip message. - continue - } - if !allRunnerLabels.Contains(ro) { - workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro) - break - } - } - if workflow.ErrMsg != "" { - break - } } if !hasJobWithoutNeeds { workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") @@ -197,61 +173,81 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, commit *git.Commit) ( workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job") } workflows = append(workflows, workflow) - - if workflow.Entry.Name() == workflowID { - curWorkflow = wf - ctx.Data["CurWorkflowExists"] = true - } } ctx.Data["workflows"] = workflows ctx.Data["RepoLink"] = ctx.Repo.Repository.Link() - + ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.IsAdmin() actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() ctx.Data["ActionsConfig"] = actionsConfig - if len(workflowID) > 0 && ctx.Repo.CanWrite(unit.TypeActions) { - ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.IsAdmin() - isWorkflowDisabled := actionsConfig.IsWorkflowDisabled(workflowID) - ctx.Data["CurWorkflowDisabled"] = isWorkflowDisabled - - if !isWorkflowDisabled && curWorkflow != nil { - workflowDispatchConfig := workflowDispatchConfig(curWorkflow) - if workflowDispatchConfig != nil { - ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig - - branchOpts := git_model.FindBranchOptions{ - RepoID: ctx.Repo.Repository.ID, - IsDeletedBranch: optional.Some(false), - ListOptions: db.ListOptions{ - ListAll: true, - }, - } - branches, err := git_model.FindBranchNames(ctx, branchOpts) - if err != nil { - ctx.ServerError("FindBranchNames", err) - return nil - } - // always put default branch on the top if it exists - if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) { - branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch) - branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...) - } - ctx.Data["Branches"] = branches + if len(workflowID) > 0 { + ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflowID) + } - tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) - if err != nil { - ctx.ServerError("GetTagNamesByRepoID", err) - return nil - } - ctx.Data["Tags"] = tags + return workflows +} + +func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []WorkflowInfo) { + workflowID := ctx.FormString("workflow") + actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() + if len(workflowID) == 0 || !ctx.Repo.CanWrite(unit.TypeActions) || actionsConfig.IsWorkflowDisabled(workflowID) { + return + } + + ctx.Data["CurWorkflowExists"] = false + var curWorkflow *act_model.Workflow + for _, workflowInfo := range workflowInfos { + if workflowInfo.Entry.Name() == workflowID { + if workflowInfo.Workflow == nil { + log.Error("%s workflowInfo.Workflow is nil", workflowID) + return } + curWorkflow = workflowInfo.Workflow + ctx.Data["CurWorkflowExists"] = true + break } } - return workflows + + if curWorkflow == nil { + return + } + + workflowDispatchConfig := workflowDispatchConfig(curWorkflow) + if workflowDispatchConfig == nil { + return + } + + ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig + + branchOpts := git_model.FindBranchOptions{ + RepoID: ctx.Repo.Repository.ID, + IsDeletedBranch: optional.Some(false), + ListOptions: db.ListOptions{ + ListAll: true, + }, + } + branches, err := git_model.FindBranchNames(ctx, branchOpts) + if err != nil { + ctx.ServerError("FindBranchNames", err) + return + } + // always put default branch on the top if it exists + if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) { + branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch) + branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...) + } + ctx.Data["Branches"] = branches + + tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) + if err != nil { + ctx.ServerError("GetTagNamesByRepoID", err) + return + } + ctx.Data["Tags"] = tags } -func prepareWorkflowList(ctx *context.Context, workflows []Workflow) { +func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) { actorID := ctx.FormInt64("actor") status := ctx.FormInt("status") workflowID := ctx.FormString("workflow") @@ -302,6 +298,45 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) { log.Error("LoadIsRefDeleted", err) } + // Check for each run if there is at least one online runner that can run its jobs + runErrors := make(map[int64]string) + runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{ + RepoID: ctx.Repo.Repository.ID, + IsOnline: optional.Some(true), + WithAvailable: true, + }) + if err != nil { + ctx.ServerError("FindRunners", err) + return + } + for _, run := range runs { + if !run.Status.In(actions_model.StatusWaiting, actions_model.StatusRunning) { + continue + } + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) + if err != nil { + ctx.ServerError("GetRunJobsByRunID", err) + return + } + for _, job := range jobs { + if !job.Status.IsWaiting() { + continue + } + hasOnlineRunner := false + for _, runner := range runners { + if runner.CanMatchLabels(job.RunsOn) { + hasOnlineRunner = true + break + } + } + if !hasOnlineRunner { + runErrors[run.ID] = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", strings.Join(job.RunsOn, ",")) + break + } + } + } + ctx.Data["RunErrors"] = runErrors + ctx.Data["Runs"] = runs actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID) @@ -362,7 +397,7 @@ type WorkflowDispatch struct { Inputs []WorkflowDispatchInput } -func workflowDispatchConfig(w *model.Workflow) *WorkflowDispatch { +func workflowDispatchConfig(w *act_model.Workflow) *WorkflowDispatch { switch w.RawOn.Kind { case yaml.ScalarNode: var val string diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl index 1e8ab4c16b037..33a7764c77697 100644 --- a/templates/repo/actions/runs_list.tmpl +++ b/templates/repo/actions/runs_list.tmpl @@ -24,6 +24,12 @@ {{ctx.Locale.Tr "actions.runs.pushed_by"}} {{$run.TriggerUser.GetDisplayName}} {{- end -}} + {{$errMsg := index $.RunErrors $run.ID}} + {{if $errMsg}} + + {{svg "octicon-alert" 16 "text red"}} + + {{end}}
From 550437d9de6539889ea2f291a90f39e50464dff6 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Tue, 21 Oct 2025 21:27:50 -0600 Subject: [PATCH 2/8] fix comment --- routers/web/repo/actions/actions.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index cf48d11e26f8a..e00278b735879 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -232,11 +232,9 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []Workf ctx.ServerError("FindBranchNames", err) return } - // always put default branch on the top if it exists - if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) { - branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch) - branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...) - } + // always put default branch on the top + branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch) + branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...) ctx.Data["Branches"] = branches tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) From 29652f7e9b4e188ba441f0483a12c822428d1f43 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Wed, 22 Oct 2025 15:03:44 -0600 Subject: [PATCH 3/8] icon center vertically --- templates/repo/actions/list.tmpl | 4 +++- templates/repo/actions/runs_list.tmpl | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 7d782c0adebdf..13698a21936b4 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -13,7 +13,9 @@ {{.Entry.Name}} {{if .ErrMsg}} - {{svg "octicon-alert" 16 "text red"}} + + {{svg "octicon-alert" 16 "text red"}} + {{end}} diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl index 33a7764c77697..3b44e0fc0f6e6 100644 --- a/templates/repo/actions/runs_list.tmpl +++ b/templates/repo/actions/runs_list.tmpl @@ -27,7 +27,9 @@ {{$errMsg := index $.RunErrors $run.ID}} {{if $errMsg}} - {{svg "octicon-alert" 16 "text red"}} +
+ {{svg "octicon-alert" 16 "text red"}} +
{{end}}
From 07fee463f51c47cd680005de7329b8580f0b5ee6 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Thu, 23 Oct 2025 11:03:25 -0600 Subject: [PATCH 4/8] fix class --- templates/repo/actions/list.tmpl | 6 ++---- templates/repo/actions/runs_list.tmpl | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 13698a21936b4..954ed8d8dcb4d 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -12,10 +12,8 @@ {{range .workflows}} {{.Entry.Name}} {{if .ErrMsg}} - - - {{svg "octicon-alert" 16 "text red"}} - + + {{svg "octicon-alert" 16 "text red"}} {{end}} diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl index 3b44e0fc0f6e6..9a8712466c64f 100644 --- a/templates/repo/actions/runs_list.tmpl +++ b/templates/repo/actions/runs_list.tmpl @@ -26,10 +26,8 @@ {{- end -}} {{$errMsg := index $.RunErrors $run.ID}} {{if $errMsg}} - -
- {{svg "octicon-alert" 16 "text red"}} -
+ + {{svg "octicon-alert" 16 "text red"}} {{end}} From fb296a34dcc600927913b8d57c070db6ce4d1caa Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Thu, 23 Oct 2025 19:30:43 -0600 Subject: [PATCH 5/8] correct flex --- templates/repo/actions/list.tmpl | 21 ++++++++++-------- templates/repo/actions/runs_list.tmpl | 32 ++++++++++++++------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 954ed8d8dcb4d..b0a18bee17f4a 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -10,16 +10,19 @@
diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl index 9a8712466c64f..340937148dad2 100644 --- a/templates/repo/actions/runs_list.tmpl +++ b/templates/repo/actions/runs_list.tmpl @@ -15,21 +15,23 @@ {{or $run.Title (ctx.Locale.Tr "actions.runs.empty_commit_message")}}
- {{if not $.CurWorkflow}}{{$run.WorkflowID}} {{end}}#{{$run.Index}}: - {{- if $run.ScheduleID -}} - {{ctx.Locale.Tr "actions.runs.scheduled"}} - {{- else -}} - {{ctx.Locale.Tr "actions.runs.commit"}} - {{ShortSha $run.CommitSHA}} - {{ctx.Locale.Tr "actions.runs.pushed_by"}} - {{$run.TriggerUser.GetDisplayName}} - {{- end -}} - {{$errMsg := index $.RunErrors $run.ID}} - {{if $errMsg}} - - {{svg "octicon-alert" 16 "text red"}} - - {{end}} +
+ {{if not $.CurWorkflow}}{{$run.WorkflowID}} {{end}}#{{$run.Index}}: + {{- if $run.ScheduleID -}} + {{ctx.Locale.Tr "actions.runs.scheduled"}} + {{- else -}} + {{ctx.Locale.Tr "actions.runs.commit"}} + {{ShortSha $run.CommitSHA}} + {{ctx.Locale.Tr "actions.runs.pushed_by"}} + {{$run.TriggerUser.GetDisplayName}} + {{- end -}} + {{$errMsg := index $.RunErrors $run.ID}} + {{if $errMsg}} + + {{svg "octicon-alert" 16 "text red"}} + + {{end}} +
From f6fbeeb06c49f933f7877ae8b4f43582152c9629 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 24 Oct 2025 12:50:27 +0800 Subject: [PATCH 6/8] Update list.tmpl --- templates/repo/actions/list.tmpl | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index b0a18bee17f4a..385ca54a329a4 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -7,22 +7,20 @@ {{if .HasWorkflowsOrRuns}}
-