0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-07-20 23:28:28 +02:00
This commit is contained in:
Christopher Homberger 2025-03-17 17:27:25 +01:00
parent edb24592fc
commit 013a0af385
4 changed files with 390 additions and 1 deletions

View File

@ -89,10 +89,12 @@ type ActionWorkflowRun struct {
ID int64 `json:"id"` ID int64 `json:"id"`
URL string `json:"url"` URL string `json:"url"`
HTMLURL string `json:"html_url"` HTMLURL string `json:"html_url"`
DisplayTitle string `json:"display_title"`
Path string `json:"path"`
Event string `json:"event"` Event string `json:"event"`
RunAttempt int64 `json:"run_attempt"` RunAttempt int64 `json:"run_attempt"`
RunNumber int64 `json:"run_number"` RunNumber int64 `json:"run_number"`
RepositoryID int64 `json:"repository_id"` RepositoryID int64 `json:"repository_id,omitempty"`
HeadSha string `json:"head_sha"` HeadSha string `json:"head_sha"`
HeadBranch string `json:"head_branch,omitempty"` HeadBranch string `json:"head_branch,omitempty"`
Status string `json:"status"` Status string `json:"status"`
@ -103,6 +105,18 @@ type ActionWorkflowRun struct {
CompletedAt time.Time `json:"completed_at,omitempty"` CompletedAt time.Time `json:"completed_at,omitempty"`
} }
// ActionArtifactsResponse returns ActionArtifacts
type ActionWorkflowRunsResponse struct {
Entries []*ActionWorkflowRun `json:"workflow_runs"`
TotalCount int64 `json:"total_count"`
}
// ActionArtifactsResponse returns ActionArtifacts
type ActionWorkflowJobsResponse struct {
Entries []*ActionWorkflowJob `json:"workflow_jobs"`
TotalCount int64 `json:"total_count"`
}
// ActionArtifactsResponse returns ActionArtifacts // ActionArtifactsResponse returns ActionArtifacts
type ActionArtifactsResponse struct { type ActionArtifactsResponse struct {
Entries []*ActionArtifact `json:"artifacts"` Entries []*ActionArtifact `json:"artifacts"`

View File

@ -1243,6 +1243,8 @@ func Routes() *web.Router {
}, reqToken(), reqAdmin()) }, reqToken(), reqAdmin())
m.Group("/actions", func() { m.Group("/actions", func() {
m.Get("/tasks", repo.ListActionTasks) m.Get("/tasks", repo.ListActionTasks)
m.Get("/runs", repo.GetWorkflowRuns)
m.Get("/runs/{run}/jobs", repo.GetWorkflowJobs)
m.Get("/runs/{run}/artifacts", repo.GetArtifactsOfRun) m.Get("/runs/{run}/artifacts", repo.GetArtifactsOfRun)
m.Get("/artifacts", repo.GetArtifacts) m.Get("/artifacts", repo.GetArtifacts)
m.Group("/artifacts/{artifact_id}", func() { m.Group("/artifacts/{artifact_id}", func() {

View File

@ -21,11 +21,13 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
secret_model "code.gitea.io/gitea/models/secret" secret_model "code.gitea.io/gitea/models/secret"
"code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/routers/api/v1/shared" "code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
actions_service "code.gitea.io/gitea/services/actions" actions_service "code.gitea.io/gitea/services/actions"
@ -868,6 +870,264 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
} }
func convertToInternal(s string) actions_model.Status {
switch s {
case "pending":
return actions_model.StatusBlocked
case "queued":
return actions_model.StatusWaiting
case "in_progress":
return actions_model.StatusRunning
case "failure":
return actions_model.StatusFailure
case "success":
return actions_model.StatusSuccess
case "skipped":
return actions_model.StatusSkipped
default:
return actions_model.StatusUnknown
}
}
// GetArtifacts Lists all artifacts for a repository.
func GetWorkflowRuns(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs repository getWorkflowRuns
// ---
// summary: Lists all runs for a repository run
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: name of the owner
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repository
// type: string
// required: true
// - name: run
// in: path
// description: runid of the workflow run
// type: integer
// required: true
// - name: name
// in: query
// description: name of the artifact
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/ArtifactsList"
// "400":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
repoID := ctx.Repo.Repository.ID
opts := actions_model.FindRunOptions{
RepoID: repoID,
ListOptions: utils.GetListOptions(ctx),
}
if event := ctx.Req.URL.Query().Get("event"); event != "" {
opts.TriggerEvent = webhook.HookEventType(event)
}
if branch := ctx.Req.URL.Query().Get("branch"); branch != "" {
opts.Ref = string(git.RefNameFromBranch(branch))
}
if status := ctx.Req.URL.Query().Get("status"); status != "" {
opts.Status = []actions_model.Status{convertToInternal(status)}
}
// if actor := ctx.Req.URL.Query().Get("actor"); actor != "" {
// user_model.
// opts.TriggerUserID =
// }
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
if err != nil {
ctx.APIErrorInternal(err)
return
}
res := new(api.ActionWorkflowRunsResponse)
res.TotalCount = total
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
for i := range runs {
convertedRun, err := convert.ToActionWorkflowRun(ctx.Repo.Repository, runs[i])
if err != nil {
ctx.APIErrorInternal(err)
return
}
res.Entries[i] = convertedRun
}
ctx.JSON(http.StatusOK, &res)
}
// GetWorkflowRun Gets a specific workflow run.
func GetWorkflowRun(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
// ---
// summary: Gets a specific workflow run
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: name of the owner
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repository
// type: string
// required: true
// - name: run
// in: path
// description: id of the run
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Artifact"
// "400":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
runID := ctx.PathParamInt64("run")
job, _, _ := db.GetByID[actions_model.ActionRun](ctx, runID)
if job.RepoID != ctx.Repo.Repository.ID {
ctx.APIError(http.StatusNotFound, util.ErrNotExist)
}
convertedArtifact, err := convert.ToActionWorkflowRun(ctx.Repo.Repository, job)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convertedArtifact)
return
}
// GetWorkflowJobs Lists all jobs for a workflow run.
func GetWorkflowJobs(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/jobs repository getWorkflowJobs
// ---
// summary: Lists all jobs for a workflow run
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: name of the owner
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repository
// type: string
// required: true
// - name: run
// in: path
// description: runid of the workflow run
// type: integer
// required: true
// - name: name
// in: query
// description: name of the artifact
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/ArtifactsList"
// "400":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
repoID := ctx.Repo.Repository.ID
runID := ctx.PathParamInt64("run")
artifacts, total, err := db.FindAndCount[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{
RepoID: repoID,
RunID: runID,
ListOptions: utils.GetListOptions(ctx),
})
if err != nil {
ctx.APIErrorInternal(err)
return
}
res := new(api.ActionWorkflowJobsResponse)
res.TotalCount = total
res.Entries = make([]*api.ActionWorkflowJob, len(artifacts))
for i := range artifacts {
convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, artifacts[i])
if err != nil {
ctx.APIErrorInternal(err)
return
}
res.Entries[i] = convertedWorkflowJob
}
ctx.JSON(http.StatusOK, &res)
}
// GetWorkflowJob Gets a specific workflow job for a workflow run.
func GetWorkflowJob(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id} repository getWorkflowJob
// ---
// summary: Gets a specific workflow job for a workflow run
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: name of the owner
// 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: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Artifact"
// "400":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
jobID := ctx.PathParamInt64("job_id")
job, _, _ := db.GetByID[actions_model.ActionRunJob](ctx, jobID)
if job.RepoID != ctx.Repo.Repository.ID {
ctx.APIError(http.StatusNotFound, util.ErrNotExist)
}
convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, job)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, convertedWorkflowJob)
return
}
// GetArtifacts Lists all artifacts for a repository. // GetArtifacts Lists all artifacts for a repository.
func GetArtifactsOfRun(ctx *context.APIContext) { func GetArtifactsOfRun(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/artifacts repository getArtifactsOfRun // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/artifacts repository getArtifactsOfRun

View File

@ -14,6 +14,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
@ -230,6 +231,118 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
}, nil }, nil
} }
func ToActionWorkflowRun(repo *repo_model.Repository, run *actions_model.ActionRun) (*api.ActionWorkflowRun, error) {
status, conclusion := toActionStatus(run.Status)
return &api.ActionWorkflowRun{
ID: run.ID,
URL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), run.ID),
HTMLURL: run.HTMLURL(),
RunNumber: run.Index,
StartedAt: run.Started.AsLocalTime(),
CompletedAt: run.Stopped.AsLocalTime(),
Event: run.TriggerEvent,
DisplayTitle: run.Title,
HeadBranch: git.RefName(run.Ref).BranchName(),
HeadSha: run.CommitSHA,
Status: status,
Conclusion: conclusion,
Path: fmt.Sprint("%s@%s", run.WorkflowID, run.Ref),
}, nil
}
func toActionStatus(status actions_model.Status) (string, string) {
var action string
var conclusion string
switch status {
// This is a naming conflict of the webhook between Gitea and GitHub Actions
case actions_model.StatusWaiting:
action = "queued"
case actions_model.StatusBlocked:
action = "waiting"
case actions_model.StatusRunning:
action = "in_progress"
}
if status.IsDone() {
action = "completed"
switch status {
case actions_model.StatusSuccess:
conclusion = "success"
case actions_model.StatusCancelled:
conclusion = "cancelled"
case actions_model.StatusFailure:
conclusion = "failure"
}
}
return action, conclusion
}
func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, job *actions_model.ActionRunJob) (*api.ActionWorkflowJob, error) {
err := job.LoadAttributes(ctx)
if err != nil {
return nil, err
}
jobIndex := 0
jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
if err != nil {
return nil, err
}
for i, j := range jobs {
if j.ID == job.ID {
jobIndex = i
break
}
}
status, conclusion := toActionStatus(job.Status)
var runnerID int64
var runnerName string
var steps []*api.ActionWorkflowStep
if job.TaskID != 0 {
task, _, _ := db.GetByID[actions_model.ActionTask](ctx, job.TaskID)
runnerID = task.RunnerID
if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
runnerName = runner.Name
}
for i, step := range task.Steps {
stepStatus, stepConclusion := toActionStatus(job.Status)
steps = append(steps, &api.ActionWorkflowStep{
Name: step.Name,
Number: int64(i),
Status: stepStatus,
Conclusion: stepConclusion,
StartedAt: step.Started.AsTime().UTC(),
CompletedAt: step.Stopped.AsTime().UTC(),
})
}
}
return &api.ActionWorkflowJob{
ID: job.ID,
// missing api endpoint for this location
URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), job.ID),
HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), jobIndex),
RunID: job.RunID,
// Missing api endpoint for this location, artifacts are available under a nested url
RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID),
Name: job.Name,
Labels: job.RunsOn,
RunAttempt: job.Attempt,
HeadSha: job.Run.CommitSHA,
HeadBranch: git.RefName(job.Run.Ref).BranchName(),
Status: status,
Conclusion: conclusion,
RunnerID: runnerID,
RunnerName: runnerName,
Steps: steps,
CreatedAt: job.Created.AsTime().UTC(),
StartedAt: job.Started.AsTime().UTC(),
CompletedAt: job.Stopped.AsTime().UTC(),
}, nil
}
// ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact // ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact
func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) { func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) {
url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID) url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID)