mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-06 16:15:52 +00:00
a65f422b89
Implements the missing REST API endpoints for Actions workflow run
management:
1. `POST /actions/runs/{run}/cancel` cancels a run and its jobs, `409`
when it already finished
1. `POST /actions/runs/{run}/approve` approves a run awaiting approval,
idempotent, `409` when it never awaited one
1. `GET /actions/runs/{run}/logs` downloads the latest attempt's job
logs as a zip archive
`ActionWorkflowRun` gains `created_at`, `updated_at` and the `jobs_url`,
`logs_url`, `artifacts_url`, `cancel_url` and `rerun_url` fields, and
now always emits `conclusion` and `head_branch`.
Cancellation is shared with the web handler in `services/actions`.
Fixes https://github.com/go-gitea/gitea/issues/35176
Fixes https://github.com/go-gitea/gitea/issues/36554
---------
Co-authored-by: Claude Sonnet 4.6 <claude-sonnet-4-6@anthropic.com>
Co-authored-by: OpenCode Agent <opencode@rossgolder.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
205 lines
5.0 KiB
Go
205 lines
5.0 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
actions_model "gitea.dev/models/actions"
|
|
"gitea.dev/routers/common"
|
|
actions_service "gitea.dev/services/actions"
|
|
"gitea.dev/services/context"
|
|
)
|
|
|
|
func DownloadActionsRunJobLogs(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs repository downloadActionsRunJobLogs
|
|
// ---
|
|
// summary: Downloads the job logs for a workflow run
|
|
// 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 repository
|
|
// type: string
|
|
// required: true
|
|
// - name: job_id
|
|
// in: path
|
|
// description: id of the job
|
|
// type: integer
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: output blob content
|
|
// "400":
|
|
// "$ref": "#/responses/error"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
jobID := ctx.PathParamInt64("job_id")
|
|
curJob, err := actions_model.GetRunJobByRepoAndID(ctx, ctx.Repo.Repository.ID, jobID)
|
|
if err != nil {
|
|
ctx.APIErrorAuto(err)
|
|
return
|
|
}
|
|
err = common.DownloadActionsRunJobLogs(ctx.Base, ctx.Repo.Repository, curJob)
|
|
if err != nil {
|
|
ctx.APIErrorAuto(err)
|
|
}
|
|
}
|
|
|
|
func CancelWorkflowRun(ctx *context.APIContext) {
|
|
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/cancel repository cancelWorkflowRun
|
|
// ---
|
|
// summary: Cancel a workflow run and its jobs
|
|
// 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 repository
|
|
// type: string
|
|
// required: true
|
|
// - name: run
|
|
// in: path
|
|
// description: run ID
|
|
// type: integer
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/WorkflowRun"
|
|
// "400":
|
|
// "$ref": "#/responses/error"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
// "409":
|
|
// "$ref": "#/responses/conflict"
|
|
|
|
run, jobs := getCurrentRepoActionRunJobsByID(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
|
|
// Cancelling a finished run would change nothing, so report a conflict instead of a false success.
|
|
if run.Status.IsDone() {
|
|
ctx.APIError(http.StatusConflict, "run is already completed")
|
|
return
|
|
}
|
|
|
|
run, err := actions_service.CancelRun(ctx, run, jobs)
|
|
if err != nil {
|
|
ctx.APIErrorAuto(err)
|
|
return
|
|
}
|
|
respondRepoActionWorkflowRun(ctx, run)
|
|
}
|
|
|
|
func ApproveWorkflowRun(ctx *context.APIContext) {
|
|
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/approve repository approveWorkflowRun
|
|
// ---
|
|
// summary: Approve a workflow run that requires approval
|
|
// 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 repository
|
|
// type: string
|
|
// required: true
|
|
// - name: run
|
|
// in: path
|
|
// description: run ID
|
|
// type: integer
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/WorkflowRun"
|
|
// "400":
|
|
// "$ref": "#/responses/error"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
// "409":
|
|
// "$ref": "#/responses/conflict"
|
|
|
|
run := getCurrentRepoActionRunByID(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
|
|
if !run.NeedApproval {
|
|
// Approving twice is idempotent, but a run that never awaited approval gets 409 rather
|
|
// than GitHub's 403, which would be indistinguishable from a permission denial.
|
|
if run.ApprovedBy == 0 {
|
|
ctx.APIError(http.StatusConflict, "run does not require approval")
|
|
return
|
|
}
|
|
respondRepoActionWorkflowRun(ctx, run)
|
|
return
|
|
}
|
|
|
|
approvedRuns, err := actions_service.ApproveRuns(ctx, ctx.Repo.Repository, ctx.Doer, []int64{run.ID})
|
|
if err != nil {
|
|
ctx.APIErrorAuto(err)
|
|
return
|
|
}
|
|
respondRepoActionWorkflowRun(ctx, approvedRuns[0])
|
|
}
|
|
|
|
func GetWorkflowRunLogs(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/logs repository getWorkflowRunLogs
|
|
// ---
|
|
// summary: Download workflow run logs as archive
|
|
// produces:
|
|
// - application/zip
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repository
|
|
// type: string
|
|
// required: true
|
|
// - name: run
|
|
// in: path
|
|
// description: run ID
|
|
// type: integer
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: Logs archive
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
run := getCurrentRepoActionRunByID(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
|
|
if err := common.DownloadActionsRunAllJobLogs(ctx.Base, run); err != nil {
|
|
ctx.APIErrorAuto(err)
|
|
}
|
|
}
|