|
| 1 | +// Copyright 2025 The Gitea Authors. All rights reserved. |
| 2 | +// SPDX-License-Identifier: MIT |
| 3 | + |
| 4 | +package shared |
| 5 | + |
| 6 | +import ( |
| 7 | + "fmt" |
| 8 | + "net/http" |
| 9 | + |
| 10 | + actions_model "code.gitea.io/gitea/models/actions" |
| 11 | + "code.gitea.io/gitea/models/db" |
| 12 | + repo_model "code.gitea.io/gitea/models/repo" |
| 13 | + user_model "code.gitea.io/gitea/models/user" |
| 14 | + "code.gitea.io/gitea/modules/git" |
| 15 | + "code.gitea.io/gitea/modules/setting" |
| 16 | + api "code.gitea.io/gitea/modules/structs" |
| 17 | + "code.gitea.io/gitea/modules/webhook" |
| 18 | + "code.gitea.io/gitea/routers/api/v1/utils" |
| 19 | + "code.gitea.io/gitea/services/context" |
| 20 | + "code.gitea.io/gitea/services/convert" |
| 21 | +) |
| 22 | + |
| 23 | +// ListJobs lists jobs for api route validated ownerID and repoID |
| 24 | +// ownerID == 0 and repoID == 0 means all jobs |
| 25 | +// ownerID == 0 and repoID != 0 means all jobs for the given repo |
| 26 | +// ownerID != 0 and repoID == 0 means all jobs for the given user/org |
| 27 | +// ownerID != 0 and repoID != 0 undefined behavior |
| 28 | +// runID == 0 means all jobs |
| 29 | +// Access rights are checked at the API route level |
| 30 | +func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) { |
| 31 | + if ownerID != 0 && repoID != 0 { |
| 32 | + setting.PanicInDevOrTesting("ownerID and repoID should not be both set") |
| 33 | + } |
| 34 | + opts := actions_model.FindRunJobOptions{ |
| 35 | + OwnerID: ownerID, |
| 36 | + RepoID: repoID, |
| 37 | + RunID: runID, |
| 38 | + ListOptions: utils.GetListOptions(ctx), |
| 39 | + } |
| 40 | + if statuses, ok := ctx.Req.URL.Query()["status"]; ok { |
| 41 | + for _, status := range statuses { |
| 42 | + values, err := convertToInternal(status) |
| 43 | + if err != nil { |
| 44 | + ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status)) |
| 45 | + return |
| 46 | + } |
| 47 | + opts.Statuses = append(opts.Statuses, values...) |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + jobs, total, err := db.FindAndCount[actions_model.ActionRunJob](ctx, opts) |
| 52 | + if err != nil { |
| 53 | + ctx.APIErrorInternal(err) |
| 54 | + return |
| 55 | + } |
| 56 | + |
| 57 | + res := new(api.ActionWorkflowJobsResponse) |
| 58 | + res.TotalCount = total |
| 59 | + |
| 60 | + res.Entries = make([]*api.ActionWorkflowJob, len(jobs)) |
| 61 | + |
| 62 | + isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID |
| 63 | + for i := range jobs { |
| 64 | + var repository *repo_model.Repository |
| 65 | + if isRepoLevel { |
| 66 | + repository = ctx.Repo.Repository |
| 67 | + } else { |
| 68 | + repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID) |
| 69 | + if err != nil { |
| 70 | + ctx.APIErrorInternal(err) |
| 71 | + return |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i]) |
| 76 | + if err != nil { |
| 77 | + ctx.APIErrorInternal(err) |
| 78 | + return |
| 79 | + } |
| 80 | + res.Entries[i] = convertedWorkflowJob |
| 81 | + } |
| 82 | + |
| 83 | + ctx.JSON(http.StatusOK, &res) |
| 84 | +} |
| 85 | + |
| 86 | +func convertToInternal(s string) ([]actions_model.Status, error) { |
| 87 | + switch s { |
| 88 | + case "pending", "waiting", "requested", "action_required": |
| 89 | + return []actions_model.Status{actions_model.StatusBlocked}, nil |
| 90 | + case "queued": |
| 91 | + return []actions_model.Status{actions_model.StatusWaiting}, nil |
| 92 | + case "in_progress": |
| 93 | + return []actions_model.Status{actions_model.StatusRunning}, nil |
| 94 | + case "completed": |
| 95 | + return []actions_model.Status{ |
| 96 | + actions_model.StatusSuccess, |
| 97 | + actions_model.StatusFailure, |
| 98 | + actions_model.StatusSkipped, |
| 99 | + actions_model.StatusCancelled, |
| 100 | + }, nil |
| 101 | + case "failure": |
| 102 | + return []actions_model.Status{actions_model.StatusFailure}, nil |
| 103 | + case "success": |
| 104 | + return []actions_model.Status{actions_model.StatusSuccess}, nil |
| 105 | + case "skipped", "neutral": |
| 106 | + return []actions_model.Status{actions_model.StatusSkipped}, nil |
| 107 | + case "cancelled", "timed_out": |
| 108 | + return []actions_model.Status{actions_model.StatusCancelled}, nil |
| 109 | + default: |
| 110 | + return nil, fmt.Errorf("invalid status %s", s) |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +// ListRuns lists jobs for api route validated ownerID and repoID |
| 115 | +// ownerID == 0 and repoID == 0 means all runs |
| 116 | +// ownerID == 0 and repoID != 0 means all runs for the given repo |
| 117 | +// ownerID != 0 and repoID == 0 means all runs for the given user/org |
| 118 | +// ownerID != 0 and repoID != 0 undefined behavior |
| 119 | +// Access rights are checked at the API route level |
| 120 | +func ListRuns(ctx *context.APIContext, ownerID, repoID int64) { |
| 121 | + if ownerID != 0 && repoID != 0 { |
| 122 | + setting.PanicInDevOrTesting("ownerID and repoID should not be both set") |
| 123 | + } |
| 124 | + opts := actions_model.FindRunOptions{ |
| 125 | + OwnerID: ownerID, |
| 126 | + RepoID: repoID, |
| 127 | + ListOptions: utils.GetListOptions(ctx), |
| 128 | + } |
| 129 | + |
| 130 | + if event := ctx.Req.URL.Query().Get("event"); event != "" { |
| 131 | + opts.TriggerEvent = webhook.HookEventType(event) |
| 132 | + } |
| 133 | + if branch := ctx.Req.URL.Query().Get("branch"); branch != "" { |
| 134 | + opts.Ref = string(git.RefNameFromBranch(branch)) |
| 135 | + } |
| 136 | + if statuses, ok := ctx.Req.URL.Query()["status"]; ok { |
| 137 | + for _, status := range statuses { |
| 138 | + values, err := convertToInternal(status) |
| 139 | + if err != nil { |
| 140 | + ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status)) |
| 141 | + return |
| 142 | + } |
| 143 | + opts.Status = append(opts.Status, values...) |
| 144 | + } |
| 145 | + } |
| 146 | + if actor := ctx.Req.URL.Query().Get("actor"); actor != "" { |
| 147 | + user, err := user_model.GetUserByName(ctx, actor) |
| 148 | + if err != nil { |
| 149 | + ctx.APIErrorInternal(err) |
| 150 | + return |
| 151 | + } |
| 152 | + opts.TriggerUserID = user.ID |
| 153 | + } |
| 154 | + if headSHA := ctx.Req.URL.Query().Get("head_sha"); headSHA != "" { |
| 155 | + opts.CommitSHA = headSHA |
| 156 | + } |
| 157 | + |
| 158 | + runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts) |
| 159 | + if err != nil { |
| 160 | + ctx.APIErrorInternal(err) |
| 161 | + return |
| 162 | + } |
| 163 | + |
| 164 | + res := new(api.ActionWorkflowRunsResponse) |
| 165 | + res.TotalCount = total |
| 166 | + |
| 167 | + res.Entries = make([]*api.ActionWorkflowRun, len(runs)) |
| 168 | + isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID |
| 169 | + for i := range runs { |
| 170 | + var repository *repo_model.Repository |
| 171 | + if isRepoLevel { |
| 172 | + repository = ctx.Repo.Repository |
| 173 | + } else { |
| 174 | + repository, err = repo_model.GetRepositoryByID(ctx, runs[i].RepoID) |
| 175 | + if err != nil { |
| 176 | + ctx.APIErrorInternal(err) |
| 177 | + return |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i]) |
| 182 | + if err != nil { |
| 183 | + ctx.APIErrorInternal(err) |
| 184 | + return |
| 185 | + } |
| 186 | + res.Entries[i] = convertedRun |
| 187 | + } |
| 188 | + |
| 189 | + ctx.JSON(http.StatusOK, &res) |
| 190 | +} |
0 commit comments