mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-06-19 15:40:50 +00:00
feat: make action runs available in api (#7699)
## Summary Inspired by https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository and https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#get-a-workflow-run ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/7699): <!--number 7699 --><!--line 0 --><!--description bWFrZSBhY3Rpb24gcnVucyBhdmFpbGFibGUgaW4gYXBp-->make action runs available in api<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7699 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: klausfyhn <klausfyhn@gmail.com> Co-committed-by: klausfyhn <klausfyhn@gmail.com>
This commit is contained in:
parent
85c054c412
commit
fc35915a28
36 changed files with 730 additions and 1 deletions
|
@ -54,6 +54,8 @@ type FindRunJobOptions struct {
|
||||||
CommitSHA string
|
CommitSHA string
|
||||||
Statuses []Status
|
Statuses []Status
|
||||||
UpdatedBefore timeutil.TimeStamp
|
UpdatedBefore timeutil.TimeStamp
|
||||||
|
Events []string // []webhook_module.HookEventType
|
||||||
|
RunNumber int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||||
|
@ -76,5 +78,11 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||||
if opts.UpdatedBefore > 0 {
|
if opts.UpdatedBefore > 0 {
|
||||||
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
||||||
}
|
}
|
||||||
|
if len(opts.Events) > 0 {
|
||||||
|
cond = cond.And(builder.In("event", opts.Events))
|
||||||
|
}
|
||||||
|
if opts.RunNumber > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
|
||||||
|
}
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,15 @@ var statusNames = map[Status]string{
|
||||||
StatusBlocked: "blocked",
|
StatusBlocked: "blocked",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nameToStatus = make(map[string]Status, len(statusNames))
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Populate name to status lookup map
|
||||||
|
for status, name := range statusNames {
|
||||||
|
nameToStatus[name] = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the string name of the Status
|
// String returns the string name of the Status
|
||||||
func (s Status) String() string {
|
func (s Status) String() string {
|
||||||
return statusNames[s]
|
return statusNames[s]
|
||||||
|
@ -102,3 +111,8 @@ func (s Status) AsResult() runnerv1.Result {
|
||||||
}
|
}
|
||||||
return runnerv1.Result_RESULT_UNSPECIFIED
|
return runnerv1.Result_RESULT_UNSPECIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StatusFromString(name string) (Status, bool) {
|
||||||
|
status, exists := nameToStatus[name]
|
||||||
|
return status, exists
|
||||||
|
}
|
||||||
|
|
|
@ -471,3 +471,64 @@
|
||||||
need_approval: 0
|
need_approval: 0
|
||||||
approved_by: 0
|
approved_by: 0
|
||||||
event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}'
|
event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}'
|
||||||
|
|
||||||
|
|
||||||
|
# GET action run(s) test
|
||||||
|
-
|
||||||
|
id: 892
|
||||||
|
title: "successful push run"
|
||||||
|
repo_id: 63
|
||||||
|
owner_id: 2
|
||||||
|
workflow_id: "success.yaml"
|
||||||
|
index: 1
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: "refs/heads/main"
|
||||||
|
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
|
||||||
|
event: "push"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 1 # success
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 893
|
||||||
|
title: "failed pull_request run"
|
||||||
|
repo_id: 63
|
||||||
|
owner_id: 2
|
||||||
|
workflow_id: "failed.yaml"
|
||||||
|
index: 2
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: "refs/heads/bugfix-1"
|
||||||
|
commit_sha: "35c5cddfc19397501ec8f4f7bb808a7c8f04445f"
|
||||||
|
event: "pull_request"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 2 # failure
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 894
|
||||||
|
title: "running workflow_dispatch run"
|
||||||
|
repo_id: 63
|
||||||
|
owner_id: 2
|
||||||
|
workflow_id: "running.yaml"
|
||||||
|
index: 3
|
||||||
|
trigger_user_id: 2
|
||||||
|
ref: "refs/heads/main"
|
||||||
|
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
|
||||||
|
event: "workflow_dispatch"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 6 # running
|
||||||
|
started: 1683636528
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
|
|
@ -795,3 +795,10 @@
|
||||||
type: 10
|
type: 10
|
||||||
config: "{}"
|
config: "{}"
|
||||||
created_unix: 946684810
|
created_unix: 946684810
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 115
|
||||||
|
repo_id: 63
|
||||||
|
type: 10
|
||||||
|
config: "{}"
|
||||||
|
created_unix: 946684810
|
||||||
|
|
|
@ -1889,3 +1889,35 @@
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
topics: '[]'
|
topics: '[]'
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 63
|
||||||
|
owner_id: 2
|
||||||
|
owner_name: user2
|
||||||
|
lower_name: test_action_run_search
|
||||||
|
name: test_action_run_search
|
||||||
|
default_branch: main
|
||||||
|
num_watches: 0
|
||||||
|
num_stars: 0
|
||||||
|
num_forks: 0
|
||||||
|
num_issues: 0
|
||||||
|
num_closed_issues: 0
|
||||||
|
num_pulls: 0
|
||||||
|
num_closed_pulls: 0
|
||||||
|
num_milestones: 0
|
||||||
|
num_closed_milestones: 0
|
||||||
|
num_projects: 0
|
||||||
|
num_closed_projects: 0
|
||||||
|
is_private: true
|
||||||
|
is_empty: false
|
||||||
|
is_archived: false
|
||||||
|
is_mirror: false
|
||||||
|
status: 0
|
||||||
|
is_fork: false
|
||||||
|
fork_id: 0
|
||||||
|
is_template: false
|
||||||
|
template_id: 0
|
||||||
|
size: 0
|
||||||
|
is_fsck_enabled: true
|
||||||
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
topics: '[]'
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
num_followers: 2
|
num_followers: 2
|
||||||
num_following: 1
|
num_following: 1
|
||||||
num_stars: 2
|
num_stars: 2
|
||||||
num_repos: 17
|
num_repos: 18
|
||||||
num_teams: 0
|
num_teams: 0
|
||||||
num_members: 0
|
num_members: 0
|
||||||
visibility: 0
|
visibility: 0
|
||||||
|
|
|
@ -32,3 +32,23 @@ type ActionTaskResponse struct {
|
||||||
Entries []*ActionTask `json:"workflow_runs"`
|
Entries []*ActionTask `json:"workflow_runs"`
|
||||||
TotalCount int64 `json:"total_count"`
|
TotalCount int64 `json:"total_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionRun represents an ActionRun
|
||||||
|
type ActionRun struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RunNumber int64 `json:"run_number"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
HeadBranch string `json:"head_branch"`
|
||||||
|
HeadSHA string `json:"head_sha"`
|
||||||
|
WorkflowID string `json:"workflow_id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
TriggeringActor *User `json:"triggering_actor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListActionRunResponse return a list of ActionRun
|
||||||
|
type ListActionRunResponse struct {
|
||||||
|
Entries []*ActionRun `json:"workflow_runs"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
}
|
||||||
|
|
|
@ -1172,6 +1172,10 @@ func Routes() *web.Route {
|
||||||
}, reqToken(), reqAdmin())
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("/tasks", repo.ListActionTasks)
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
|
m.Group("/runs", func() {
|
||||||
|
m.Get("", repo.ListActionRuns)
|
||||||
|
m.Get("/{run_id}", repo.GetActionRun)
|
||||||
|
})
|
||||||
|
|
||||||
m.Group("/workflows", func() {
|
m.Group("/workflows", func() {
|
||||||
m.Group("/{workflowname}", func() {
|
m.Group("/{workflowname}", func() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
actions_model "forgejo.org/models/actions"
|
actions_model "forgejo.org/models/actions"
|
||||||
|
@ -694,3 +695,160 @@ func DispatchWorkflow(ctx *context.APIContext) {
|
||||||
ctx.JSON(http.StatusNoContent, nil)
|
ctx.JSON(http.StatusNoContent, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListActionRuns return a filtered list of ActionRun
|
||||||
|
func ListActionRuns(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runs repository ListActionRuns
|
||||||
|
// ---
|
||||||
|
// summary: List a repository's action runs
|
||||||
|
// 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: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results, default maximum page size is 50
|
||||||
|
// type: integer
|
||||||
|
// - name: event
|
||||||
|
// in: query
|
||||||
|
// description: Returns workflow run triggered by the specified events. For example, `push`, `pull_request` or `workflow_dispatch`.
|
||||||
|
// type: array
|
||||||
|
// items:
|
||||||
|
// type: string
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: |
|
||||||
|
// Returns workflow runs with the check run status or conclusion that is specified. For example, a conclusion can be success or a status can be in_progress. Only Forgejo Actions can set a status of waiting, pending, or requested.
|
||||||
|
// type: array
|
||||||
|
// items:
|
||||||
|
// type: string
|
||||||
|
// enum: [unknown, waiting, running, success, failure, cancelled, skipped, blocked]
|
||||||
|
// - name: run_number
|
||||||
|
// in: query
|
||||||
|
// description: |
|
||||||
|
// Returns the workflow run associated with the run number.
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// - name: head_sha
|
||||||
|
// in: query
|
||||||
|
// description: Only returns workflow runs that are associated with the specified head_sha.
|
||||||
|
// type: string
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ActionRunList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
|
||||||
|
statusStrs := ctx.FormStrings("status")
|
||||||
|
statuses := make([]actions_model.Status, len(statusStrs))
|
||||||
|
for i, s := range statusStrs {
|
||||||
|
if status, exists := actions_model.StatusFromString(s); exists {
|
||||||
|
statuses[i] = status
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusBadRequest, "StatusFromString", fmt.Sprintf("unknown status: %s", s))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, &actions_model.FindRunJobOptions{
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
OwnerID: ctx.Repo.Owner.ID,
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
Events: ctx.FormStrings("event"),
|
||||||
|
Statuses: statuses,
|
||||||
|
RunNumber: ctx.FormInt64("run_number"),
|
||||||
|
CommitSHA: ctx.FormString("head_sha"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ListActionRuns", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := new(api.ListActionRunResponse)
|
||||||
|
res.TotalCount = total
|
||||||
|
|
||||||
|
res.Entries = make([]*api.ActionRun, len(runs))
|
||||||
|
for i, r := range runs {
|
||||||
|
cr, err := convert.ToActionRun(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ToActionRun", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.Entries[i] = cr
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActionRun get one action instance
|
||||||
|
func GetActionRun(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run_id} repository ActionRun
|
||||||
|
// ---
|
||||||
|
// summary: Get an action 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 repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: run_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the action run
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ActionRun"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
run, err := actions_model.GetRunByID(ctx, ctx.ParamsInt64(":run_id"))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetRunById", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetRunByID", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.ID != run.RepoID {
|
||||||
|
ctx.Error(http.StatusNotFound, "GetRunById", util.ErrNotExist)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := convert.ToActionRun(ctx, run)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ToActionRun", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, res)
|
||||||
|
}
|
||||||
|
|
|
@ -455,3 +455,17 @@ type swaggerSyncForkInfo struct {
|
||||||
// in:body
|
// in:body
|
||||||
Body []api.SyncForkInfo `json:"body"`
|
Body []api.SyncForkInfo `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionRunList
|
||||||
|
// swagger:response ActionRunList
|
||||||
|
type swaggerRepoActionRunList struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ListActionRunResponse `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionRun
|
||||||
|
// swagger:response ActionRun
|
||||||
|
type swaggerRepoActionRun struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionRun `json:"body"`
|
||||||
|
}
|
||||||
|
|
|
@ -222,6 +222,29 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToActionRun convert a actions_model.ActionRun to an api.ActionRun
|
||||||
|
func ToActionRun(ctx context.Context, r *actions_model.ActionRun) (*api.ActionRun, error) {
|
||||||
|
if err := r.LoadAttributes(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := strings.TrimSuffix(setting.AppURL, "/") + r.Link()
|
||||||
|
actor := ToUser(ctx, r.TriggerUser, nil)
|
||||||
|
|
||||||
|
return &api.ActionRun{
|
||||||
|
ID: r.ID,
|
||||||
|
Name: r.Title,
|
||||||
|
HeadBranch: r.PrettyRef(),
|
||||||
|
HeadSHA: r.CommitSHA,
|
||||||
|
RunNumber: r.Index,
|
||||||
|
Event: r.TriggerEvent,
|
||||||
|
Status: r.Status.String(),
|
||||||
|
WorkflowID: r.WorkflowID,
|
||||||
|
URL: url,
|
||||||
|
TriggeringActor: actor,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
||||||
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
|
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
|
||||||
verif := asymkey_model.ParseCommitWithSignature(ctx, c)
|
verif := asymkey_model.ParseCommitWithSignature(ctx, c)
|
||||||
|
|
221
templates/swagger/v1_json.tmpl
generated
221
templates/swagger/v1_json.tmpl
generated
|
@ -4904,6 +4904,148 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/runs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "List a repository's action runs",
|
||||||
|
"operationId": "ListActionRuns",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results, default maximum page size is 50",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Returns workflow run triggered by the specified events. For example, `push`, `pull_request` or `workflow_dispatch`.",
|
||||||
|
"name": "event",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"unknown",
|
||||||
|
"waiting",
|
||||||
|
"running",
|
||||||
|
"success",
|
||||||
|
"failure",
|
||||||
|
"cancelled",
|
||||||
|
"skipped",
|
||||||
|
"blocked"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Returns workflow runs with the check run status or conclusion that is specified. For example, a conclusion can be success or a status can be in_progress. Only Forgejo Actions can set a status of waiting, pending, or requested.\n",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "Returns the workflow run associated with the run number.\n",
|
||||||
|
"name": "run_number",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Only returns workflow runs that are associated with the specified head_sha.",
|
||||||
|
"name": "head_sha",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ActionRunList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/runs/{run_id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get an action run",
|
||||||
|
"operationId": "ActionRun",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "id of the action run",
|
||||||
|
"name": "run_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/ActionRun"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/secrets": {
|
"/repos/{owner}/{repo}/actions/secrets": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -20925,6 +21067,54 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "forgejo.org/modules/structs"
|
"x-go-package": "forgejo.org/modules/structs"
|
||||||
},
|
},
|
||||||
|
"ActionRun": {
|
||||||
|
"description": "ActionRun represents an ActionRun",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"event": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Event"
|
||||||
|
},
|
||||||
|
"head_branch": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "HeadBranch"
|
||||||
|
},
|
||||||
|
"head_sha": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "HeadSHA"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"run_number": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "RunNumber"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Status"
|
||||||
|
},
|
||||||
|
"triggering_actor": {
|
||||||
|
"$ref": "#/definitions/User"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "URL"
|
||||||
|
},
|
||||||
|
"workflow_id": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "WorkflowID"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "forgejo.org/modules/structs"
|
||||||
|
},
|
||||||
"ActionRunJob": {
|
"ActionRunJob": {
|
||||||
"description": "ActionRunJob represents a job of a run",
|
"description": "ActionRunJob represents a job of a run",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -25381,6 +25571,25 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "forgejo.org/modules/structs"
|
"x-go-package": "forgejo.org/modules/structs"
|
||||||
},
|
},
|
||||||
|
"ListActionRunResponse": {
|
||||||
|
"description": "ListActionRunResponse return a list of ActionRun",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"total_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "TotalCount"
|
||||||
|
},
|
||||||
|
"workflow_runs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/ActionRun"
|
||||||
|
},
|
||||||
|
"x-go-name": "Entries"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "forgejo.org/modules/structs"
|
||||||
|
},
|
||||||
"MarkdownOption": {
|
"MarkdownOption": {
|
||||||
"description": "MarkdownOption markdown options",
|
"description": "MarkdownOption markdown options",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -28585,6 +28794,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ActionRun": {
|
||||||
|
"description": "ActionRun",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionRun"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ActionRunList": {
|
||||||
|
"description": "ActionRunList",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ListActionRunResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ActionVariable": {
|
"ActionVariable": {
|
||||||
"description": "ActionVariable",
|
"description": "ActionVariable",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
fix: bug
|
|
@ -0,0 +1 @@
|
||||||
|
ref: refs/heads/main
|
|
@ -0,0 +1,5 @@
|
||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = true
|
||||||
|
bare = false
|
||||||
|
logallrefupdates = true
|
|
@ -0,0 +1 @@
|
||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
||||||
|
# git ls-files --others --exclude-from=.git/info/exclude
|
||||||
|
# Lines that start with '#' are comments.
|
||||||
|
# For a project mostly in C, the following would be a good set of
|
||||||
|
# exclude patterns (uncomment them if you want to use them):
|
||||||
|
# *.[oa]
|
||||||
|
# *~
|
|
@ -0,0 +1,7 @@
|
||||||
|
0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745918688 +0200 commit (initial): initial
|
||||||
|
97f29ee599c373c729132a5c46a046978311e0ee 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745921505 +0200 checkout: moving from master to main
|
||||||
|
97f29ee599c373c729132a5c46a046978311e0ee 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922102 +0200 checkout: moving from main to bugfix-1
|
||||||
|
97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 <user2@example.com> 1745922142 +0200 commit: fix: bug
|
||||||
|
35c5cddfc19397501ec8f4f7bb808a7c8f04445f 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922150 +0200 checkout: moving from bugfix-1 to main
|
||||||
|
97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 <user2@example.com> 1745922198 +0200 checkout: moving from main to bugfix-1
|
||||||
|
35c5cddfc19397501ec8f4f7bb808a7c8f04445f 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922272 +0200 checkout: moving from bugfix-1 to main
|
|
@ -0,0 +1,2 @@
|
||||||
|
0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922102 +0200 branch: Created from HEAD
|
||||||
|
97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 <user2@example.com> 1745922142 +0200 commit: fix: bug
|
|
@ -0,0 +1 @@
|
||||||
|
0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745921505 +0200 branch: Created from HEAD
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
35c5cddfc19397501ec8f4f7bb808a7c8f04445f
|
|
@ -0,0 +1 @@
|
||||||
|
97f29ee599c373c729132a5c46a046978311e0ee
|
|
@ -0,0 +1 @@
|
||||||
|
97f29ee599c373c729132a5c46a046978311e0ee
|
|
@ -101,3 +101,143 @@ jobs:
|
||||||
assert.Len(t, run.Jobs, 2)
|
assert.Len(t, run.Jobs, 2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIGetListActionRun(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
var (
|
||||||
|
runIDs = []int64{892, 893, 894}
|
||||||
|
dbRuns = make(map[int64]*actions_model.ActionRun, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, id := range runIDs {
|
||||||
|
dbRuns[id] = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: id})
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: dbRuns[runIDs[0]].RepoID})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
token := getUserToken(t, user.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
testqueries := []struct {
|
||||||
|
name string
|
||||||
|
query string
|
||||||
|
expectedIDs []int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "No query parameters",
|
||||||
|
query: "",
|
||||||
|
expectedIDs: runIDs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Search for workflow_dispatch events",
|
||||||
|
query: "?event=workflow_dispatch",
|
||||||
|
expectedIDs: []int64{894},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Search for multiple events",
|
||||||
|
query: "?event=workflow_dispatch&event=push",
|
||||||
|
expectedIDs: []int64{892, 894},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Search for failed status",
|
||||||
|
query: "?status=failure",
|
||||||
|
expectedIDs: []int64{893},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Search for multiple statuses",
|
||||||
|
query: "?status=failure&status=running",
|
||||||
|
expectedIDs: []int64{893, 894},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Search for num_nr",
|
||||||
|
query: "?run_number=1",
|
||||||
|
expectedIDs: []int64{892},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Search for sha",
|
||||||
|
query: "?head_sha=97f29ee599c373c729132a5c46a046978311e0ee",
|
||||||
|
expectedIDs: []int64{892, 894},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testqueries {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
req := NewRequest(t, http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs%s",
|
||||||
|
repo.OwnerName, repo.Name, tt.query,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
req.AddTokenAuth(token)
|
||||||
|
|
||||||
|
res := MakeRequest(t, req, http.StatusOK)
|
||||||
|
apiRuns := new(api.ListActionRunResponse)
|
||||||
|
DecodeJSON(t, res, apiRuns)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(len(tt.expectedIDs)), apiRuns.TotalCount)
|
||||||
|
assert.Len(t, apiRuns.Entries, len(tt.expectedIDs))
|
||||||
|
|
||||||
|
resultIDs := make([]int64, apiRuns.TotalCount)
|
||||||
|
for i, run := range apiRuns.Entries {
|
||||||
|
resultIDs[i] = run.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, tt.expectedIDs, resultIDs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetActionRun(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
token := getUserToken(t, user.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
testqueries := []struct {
|
||||||
|
name string
|
||||||
|
runID int64
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "existing return ok",
|
||||||
|
runID: 892,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non existing run",
|
||||||
|
runID: 9876543210, // I hope this run will not exists, else just change it to another.
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing run but wrong repo should not be found",
|
||||||
|
runID: 891,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testqueries {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
req := NewRequest(t, http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d",
|
||||||
|
repo.OwnerName, repo.Name, tt.runID,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
req.AddTokenAuth(token)
|
||||||
|
|
||||||
|
res := MakeRequest(t, req, tt.expectedStatus)
|
||||||
|
|
||||||
|
// Only interested in the data if 200 OK
|
||||||
|
if tt.expectedStatus != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dbRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: tt.runID})
|
||||||
|
apiRun := new(api.ActionRun)
|
||||||
|
DecodeJSON(t, res, apiRun)
|
||||||
|
|
||||||
|
assert.Equal(t, dbRun.Index, apiRun.RunNumber)
|
||||||
|
assert.Equal(t, dbRun.Status.String(), apiRun.Status)
|
||||||
|
assert.Equal(t, dbRun.CommitSHA, apiRun.HeadSHA)
|
||||||
|
assert.Equal(t, dbRun.TriggerUserID, apiRun.TriggeringActor.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue