mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-12 22:11:07 +00:00
feat(api): Add GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs (#37196)
- Add GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs
endpoint, matching the
https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2026-03-10#list-workflow-runs-for-a-workflow
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
@@ -99,5 +99,5 @@ func ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.ListRuns(ctx, 0, 0)
|
||||
shared.ListRuns(ctx, 0, 0, "")
|
||||
}
|
||||
|
||||
@@ -1188,6 +1188,7 @@ func Routes() *web.Router {
|
||||
m.Group("/actions/workflows", func() {
|
||||
m.Get("", repo.ActionsListRepositoryWorkflows)
|
||||
m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
|
||||
m.Get("/{workflow_id}/runs", repo.ActionsListWorkflowRuns)
|
||||
m.Put("/{workflow_id}/disable", reqRepoWriter(unit.TypeActions), repo.ActionsDisableWorkflow)
|
||||
m.Put("/{workflow_id}/enable", reqRepoWriter(unit.TypeActions), repo.ActionsEnableWorkflow)
|
||||
m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
|
||||
|
||||
@@ -679,7 +679,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
|
||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0, "")
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
|
||||
@@ -772,6 +772,11 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: exclude_pull_requests
|
||||
// in: query
|
||||
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||
// type: boolean
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
@@ -790,7 +795,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
|
||||
shared.ListRuns(ctx, 0, repoID)
|
||||
shared.ListRuns(ctx, 0, repoID, "")
|
||||
}
|
||||
|
||||
var _ actions_service.API = new(Action)
|
||||
@@ -967,6 +972,97 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, workflow)
|
||||
}
|
||||
|
||||
func ActionsListWorkflowRuns(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs repository ActionsListWorkflowRuns
|
||||
// ---
|
||||
// summary: List runs for a workflow
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: workflow_id
|
||||
// in: path
|
||||
// description: id of the workflow, must be the workflow file name (e.g. `build.yml`)
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: event
|
||||
// in: query
|
||||
// description: workflow event name
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: branch
|
||||
// in: query
|
||||
// description: workflow branch
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: actor
|
||||
// in: query
|
||||
// description: triggered by user
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: head_sha
|
||||
// in: query
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: exclude_pull_requests
|
||||
// in: query
|
||||
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||
// type: boolean
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowRunsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
workflowID := ctx.PathParam("workflow_id")
|
||||
// Existing runs prove the workflow is/was valid and cover historical workflows
|
||||
// whose file was later removed. Fall back to a git lookup for never-run workflows.
|
||||
runExists, err := db.Exist[actions_model.ActionRun](ctx, actions_model.FindRunOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WorkflowID: workflowID,
|
||||
}.ToConds())
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !runExists {
|
||||
if _, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID); err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
shared.ListRuns(ctx, 0, ctx.Repo.Repository.ID, workflowID)
|
||||
}
|
||||
|
||||
func ActionsDisableWorkflow(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
|
||||
// ---
|
||||
@@ -1238,7 +1334,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1287,7 +1383,7 @@ func GetWorkflowRunAttempt(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1342,7 +1438,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -135,8 +135,9 @@ func convertToInternal(s string) ([]actions_model.Status, error) {
|
||||
// ownerID == 0 and repoID != 0 means all runs for the given repo
|
||||
// ownerID != 0 and repoID == 0 means all runs for the given user/org
|
||||
// ownerID != 0 and repoID != 0 undefined behavior
|
||||
// workflowID filters runs by workflow file name (e.g. "build.yml"), empty means no filter
|
||||
// Access rights are checked at the API route level
|
||||
func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
func ListRuns(ctx *context.APIContext, ownerID, repoID int64, workflowID string) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
@@ -144,6 +145,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
opts := actions_model.FindRunOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
WorkflowID: workflowID,
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
@@ -172,6 +174,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
if headSHA := ctx.FormString("head_sha"); headSHA != "" {
|
||||
opts.CommitSHA = headSHA
|
||||
}
|
||||
excludePullRequests := ctx.FormBool("exclude_pull_requests")
|
||||
|
||||
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
|
||||
if err != nil {
|
||||
@@ -203,7 +206,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
|
||||
for i := range runs {
|
||||
// TODO: load run attempts in batch
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil, excludePullRequests)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -407,7 +407,7 @@ func ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRuns(ctx, ctx.Doer.ID, 0)
|
||||
shared.ListRuns(ctx, ctx.Doer.ID, 0, "")
|
||||
}
|
||||
|
||||
// ListWorkflowJobs lists workflow jobs
|
||||
|
||||
Reference in New Issue
Block a user