Skip to content

Commit fc35915

Browse files
klausfyhnEarl Warren
authored andcommitted
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 <[email protected]> Co-authored-by: klausfyhn <[email protected]> Co-committed-by: klausfyhn <[email protected]>
1 parent 85c054c commit fc35915

36 files changed

+730
-1
lines changed

models/actions/run_job_list.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ type FindRunJobOptions struct {
5454
CommitSHA string
5555
Statuses []Status
5656
UpdatedBefore timeutil.TimeStamp
57+
Events []string // []webhook_module.HookEventType
58+
RunNumber int64
5759
}
5860

5961
func (opts FindRunJobOptions) ToConds() builder.Cond {
@@ -76,5 +78,11 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
7678
if opts.UpdatedBefore > 0 {
7779
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
7880
}
81+
if len(opts.Events) > 0 {
82+
cond = cond.And(builder.In("event", opts.Events))
83+
}
84+
if opts.RunNumber > 0 {
85+
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
86+
}
7987
return cond
8088
}

models/actions/status.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ var statusNames = map[Status]string{
3434
StatusBlocked: "blocked",
3535
}
3636

37+
var nameToStatus = make(map[string]Status, len(statusNames))
38+
39+
func init() {
40+
// Populate name to status lookup map
41+
for status, name := range statusNames {
42+
nameToStatus[name] = status
43+
}
44+
}
45+
3746
// String returns the string name of the Status
3847
func (s Status) String() string {
3948
return statusNames[s]
@@ -102,3 +111,8 @@ func (s Status) AsResult() runnerv1.Result {
102111
}
103112
return runnerv1.Result_RESULT_UNSPECIFIED
104113
}
114+
115+
func StatusFromString(name string) (Status, bool) {
116+
status, exists := nameToStatus[name]
117+
return status, exists
118+
}

models/fixtures/action_run.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,3 +471,64 @@
471471
need_approval: 0
472472
approved_by: 0
473473
event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}'
474+
475+
476+
# GET action run(s) test
477+
-
478+
id: 892
479+
title: "successful push run"
480+
repo_id: 63
481+
owner_id: 2
482+
workflow_id: "success.yaml"
483+
index: 1
484+
trigger_user_id: 2
485+
ref: "refs/heads/main"
486+
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
487+
event: "push"
488+
is_fork_pull_request: 0
489+
status: 1 # success
490+
started: 1683636528
491+
stopped: 1683636626
492+
created: 1683636108
493+
updated: 1683636626
494+
need_approval: 0
495+
approved_by: 0
496+
497+
-
498+
id: 893
499+
title: "failed pull_request run"
500+
repo_id: 63
501+
owner_id: 2
502+
workflow_id: "failed.yaml"
503+
index: 2
504+
trigger_user_id: 2
505+
ref: "refs/heads/bugfix-1"
506+
commit_sha: "35c5cddfc19397501ec8f4f7bb808a7c8f04445f"
507+
event: "pull_request"
508+
is_fork_pull_request: 0
509+
status: 2 # failure
510+
started: 1683636528
511+
stopped: 1683636626
512+
created: 1683636108
513+
updated: 1683636626
514+
need_approval: 0
515+
approved_by: 0
516+
517+
-
518+
id: 894
519+
title: "running workflow_dispatch run"
520+
repo_id: 63
521+
owner_id: 2
522+
workflow_id: "running.yaml"
523+
index: 3
524+
trigger_user_id: 2
525+
ref: "refs/heads/main"
526+
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
527+
event: "workflow_dispatch"
528+
is_fork_pull_request: 0
529+
status: 6 # running
530+
started: 1683636528
531+
created: 1683636108
532+
updated: 1683636626
533+
need_approval: 0
534+
approved_by: 0

models/fixtures/repo_unit.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,3 +795,10 @@
795795
type: 10
796796
config: "{}"
797797
created_unix: 946684810
798+
799+
-
800+
id: 115
801+
repo_id: 63
802+
type: 10
803+
config: "{}"
804+
created_unix: 946684810

models/fixtures/repository.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,3 +1889,35 @@
18891889
is_fsck_enabled: true
18901890
close_issues_via_commit_in_any_branch: false
18911891
topics: '[]'
1892+
1893+
-
1894+
id: 63
1895+
owner_id: 2
1896+
owner_name: user2
1897+
lower_name: test_action_run_search
1898+
name: test_action_run_search
1899+
default_branch: main
1900+
num_watches: 0
1901+
num_stars: 0
1902+
num_forks: 0
1903+
num_issues: 0
1904+
num_closed_issues: 0
1905+
num_pulls: 0
1906+
num_closed_pulls: 0
1907+
num_milestones: 0
1908+
num_closed_milestones: 0
1909+
num_projects: 0
1910+
num_closed_projects: 0
1911+
is_private: true
1912+
is_empty: false
1913+
is_archived: false
1914+
is_mirror: false
1915+
status: 0
1916+
is_fork: false
1917+
fork_id: 0
1918+
is_template: false
1919+
template_id: 0
1920+
size: 0
1921+
is_fsck_enabled: true
1922+
close_issues_via_commit_in_any_branch: false
1923+
topics: '[]'

models/fixtures/user.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
num_followers: 2
7171
num_following: 1
7272
num_stars: 2
73-
num_repos: 17
73+
num_repos: 18
7474
num_teams: 0
7575
num_members: 0
7676
visibility: 0

modules/structs/repo_actions.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,23 @@ type ActionTaskResponse struct {
3232
Entries []*ActionTask `json:"workflow_runs"`
3333
TotalCount int64 `json:"total_count"`
3434
}
35+
36+
// ActionRun represents an ActionRun
37+
type ActionRun struct {
38+
ID int64 `json:"id"`
39+
Name string `json:"name"`
40+
RunNumber int64 `json:"run_number"`
41+
Event string `json:"event"`
42+
Status string `json:"status"`
43+
HeadBranch string `json:"head_branch"`
44+
HeadSHA string `json:"head_sha"`
45+
WorkflowID string `json:"workflow_id"`
46+
URL string `json:"url"`
47+
TriggeringActor *User `json:"triggering_actor"`
48+
}
49+
50+
// ListActionRunResponse return a list of ActionRun
51+
type ListActionRunResponse struct {
52+
Entries []*ActionRun `json:"workflow_runs"`
53+
TotalCount int64 `json:"total_count"`
54+
}

routers/api/v1/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,10 @@ func Routes() *web.Route {
11721172
}, reqToken(), reqAdmin())
11731173
m.Group("/actions", func() {
11741174
m.Get("/tasks", repo.ListActionTasks)
1175+
m.Group("/runs", func() {
1176+
m.Get("", repo.ListActionRuns)
1177+
m.Get("/{run_id}", repo.GetActionRun)
1178+
})
11751179

11761180
m.Group("/workflows", func() {
11771181
m.Group("/{workflowname}", func() {

routers/api/v1/repo/action.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package repo
55

66
import (
77
"errors"
8+
"fmt"
89
"net/http"
910

1011
actions_model "forgejo.org/models/actions"
@@ -694,3 +695,160 @@ func DispatchWorkflow(ctx *context.APIContext) {
694695
ctx.JSON(http.StatusNoContent, nil)
695696
}
696697
}
698+
699+
// ListActionRuns return a filtered list of ActionRun
700+
func ListActionRuns(ctx *context.APIContext) {
701+
// swagger:operation GET /repos/{owner}/{repo}/actions/runs repository ListActionRuns
702+
// ---
703+
// summary: List a repository's action runs
704+
// produces:
705+
// - application/json
706+
// parameters:
707+
// - name: owner
708+
// in: path
709+
// description: owner of the repo
710+
// type: string
711+
// required: true
712+
// - name: repo
713+
// in: path
714+
// description: name of the repo
715+
// type: string
716+
// required: true
717+
// - name: page
718+
// in: query
719+
// description: page number of results to return (1-based)
720+
// type: integer
721+
// - name: limit
722+
// in: query
723+
// description: page size of results, default maximum page size is 50
724+
// type: integer
725+
// - name: event
726+
// in: query
727+
// description: Returns workflow run triggered by the specified events. For example, `push`, `pull_request` or `workflow_dispatch`.
728+
// type: array
729+
// items:
730+
// type: string
731+
// - name: status
732+
// in: query
733+
// description: |
734+
// 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.
735+
// type: array
736+
// items:
737+
// type: string
738+
// enum: [unknown, waiting, running, success, failure, cancelled, skipped, blocked]
739+
// - name: run_number
740+
// in: query
741+
// description: |
742+
// Returns the workflow run associated with the run number.
743+
// type: integer
744+
// format: int64
745+
// - name: head_sha
746+
// in: query
747+
// description: Only returns workflow runs that are associated with the specified head_sha.
748+
// type: string
749+
// responses:
750+
// "200":
751+
// "$ref": "#/responses/ActionRunList"
752+
// "400":
753+
// "$ref": "#/responses/error"
754+
// "403":
755+
// "$ref": "#/responses/forbidden"
756+
757+
statusStrs := ctx.FormStrings("status")
758+
statuses := make([]actions_model.Status, len(statusStrs))
759+
for i, s := range statusStrs {
760+
if status, exists := actions_model.StatusFromString(s); exists {
761+
statuses[i] = status
762+
} else {
763+
ctx.Error(http.StatusBadRequest, "StatusFromString", fmt.Sprintf("unknown status: %s", s))
764+
return
765+
}
766+
}
767+
768+
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, &actions_model.FindRunJobOptions{
769+
ListOptions: utils.GetListOptions(ctx),
770+
OwnerID: ctx.Repo.Owner.ID,
771+
RepoID: ctx.Repo.Repository.ID,
772+
Events: ctx.FormStrings("event"),
773+
Statuses: statuses,
774+
RunNumber: ctx.FormInt64("run_number"),
775+
CommitSHA: ctx.FormString("head_sha"),
776+
})
777+
if err != nil {
778+
ctx.Error(http.StatusInternalServerError, "ListActionRuns", err)
779+
return
780+
}
781+
782+
res := new(api.ListActionRunResponse)
783+
res.TotalCount = total
784+
785+
res.Entries = make([]*api.ActionRun, len(runs))
786+
for i, r := range runs {
787+
cr, err := convert.ToActionRun(ctx, r)
788+
if err != nil {
789+
ctx.Error(http.StatusInternalServerError, "ToActionRun", err)
790+
return
791+
}
792+
res.Entries[i] = cr
793+
}
794+
795+
ctx.JSON(http.StatusOK, &res)
796+
}
797+
798+
// GetActionRun get one action instance
799+
func GetActionRun(ctx *context.APIContext) {
800+
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run_id} repository ActionRun
801+
// ---
802+
// summary: Get an action run
803+
// produces:
804+
// - application/json
805+
// parameters:
806+
// - name: owner
807+
// in: path
808+
// description: owner of the repo
809+
// type: string
810+
// required: true
811+
// - name: repo
812+
// in: path
813+
// description: name of the repo
814+
// type: string
815+
// required: true
816+
// - name: run_id
817+
// in: path
818+
// description: id of the action run
819+
// type: integer
820+
// format: int64
821+
// required: true
822+
// responses:
823+
// "200":
824+
// "$ref": "#/responses/ActionRun"
825+
// "400":
826+
// "$ref": "#/responses/error"
827+
// "403":
828+
// "$ref": "#/responses/forbidden"
829+
// "404":
830+
// "$ref": "#/responses/notFound"
831+
832+
run, err := actions_model.GetRunByID(ctx, ctx.ParamsInt64(":run_id"))
833+
if err != nil {
834+
if errors.Is(err, util.ErrNotExist) {
835+
ctx.Error(http.StatusNotFound, "GetRunById", err)
836+
} else {
837+
ctx.Error(http.StatusInternalServerError, "GetRunByID", err)
838+
}
839+
return
840+
}
841+
842+
if ctx.Repo.Repository.ID != run.RepoID {
843+
ctx.Error(http.StatusNotFound, "GetRunById", util.ErrNotExist)
844+
return
845+
}
846+
847+
res, err := convert.ToActionRun(ctx, run)
848+
if err != nil {
849+
ctx.Error(http.StatusInternalServerError, "ToActionRun", err)
850+
return
851+
}
852+
853+
ctx.JSON(http.StatusOK, res)
854+
}

routers/api/v1/swagger/repo.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,17 @@ type swaggerSyncForkInfo struct {
455455
// in:body
456456
Body []api.SyncForkInfo `json:"body"`
457457
}
458+
459+
// ActionRunList
460+
// swagger:response ActionRunList
461+
type swaggerRepoActionRunList struct {
462+
// in:body
463+
Body api.ListActionRunResponse `json:"body"`
464+
}
465+
466+
// ActionRun
467+
// swagger:response ActionRun
468+
type swaggerRepoActionRun struct {
469+
// in:body
470+
Body api.ActionRun `json:"body"`
471+
}

0 commit comments

Comments
 (0)