Skip to content

Commit 21b43fc

Browse files
ChristopherHXlunnywxiaoguang
authored
Actions Runner rest api (#33873)
Implements runner apis based on https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#list-self-hosted-runners-for-an-organization - Add Post endpoints for registration-token, google/go-github revealed this as problem - We should deprecate Get Endpoints, leaving them for compatibility - Get endpoint of admin has api path /admin/runners/registration-token that feels wrong, /admin/actions/runners/registration-token seems more consistent with user/org/repo api - Get Runner Api - List Runner Api - Delete Runner Api - Tests admin / user / org / repo level endpoints Related to #33750 (implements point 1 and 2) Via needs discovered in #32461, this runner api is needed to allow cleanup of runners that are deallocated without user interaction. --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent ba0deab commit 21b43fc

File tree

15 files changed

+1519
-7
lines changed

15 files changed

+1519
-7
lines changed

models/actions/runner.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"code.gitea.io/gitea/models/shared/types"
1515
user_model "code.gitea.io/gitea/models/user"
1616
"code.gitea.io/gitea/modules/optional"
17+
"code.gitea.io/gitea/modules/setting"
1718
"code.gitea.io/gitea/modules/timeutil"
1819
"code.gitea.io/gitea/modules/translation"
1920
"code.gitea.io/gitea/modules/util"
@@ -123,8 +124,15 @@ func (r *ActionRunner) IsOnline() bool {
123124
return false
124125
}
125126

126-
// Editable checks if the runner is editable by the user
127-
func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
127+
// EditableInContext checks if the runner is editable by the "context" owner/repo
128+
// ownerID == 0 and repoID == 0 means "admin" context, any runner including global runners could be edited
129+
// ownerID == 0 and repoID != 0 means "repo" context, any runner belonging to the given repo could be edited
130+
// ownerID != 0 and repoID == 0 means "owner(org/user)" context, any runner belonging to the given user/org could be edited
131+
// ownerID != 0 and repoID != 0 means "owner" OR "repo" context, legacy behavior, but we should forbid using it
132+
func (r *ActionRunner) EditableInContext(ownerID, repoID int64) bool {
133+
if ownerID != 0 && repoID != 0 {
134+
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
135+
}
128136
if ownerID == 0 && repoID == 0 {
129137
return true
130138
}
@@ -168,6 +176,12 @@ func init() {
168176
db.RegisterModel(&ActionRunner{})
169177
}
170178

179+
// FindRunnerOptions
180+
// ownerID == 0 and repoID == 0 means any runner including global runners
181+
// repoID != 0 and WithAvailable == false means any runner for the given repo
182+
// repoID != 0 and WithAvailable == true means any runner for the given repo, parent user/org, and global runners
183+
// ownerID != 0 and repoID == 0 and WithAvailable == false means any runner for the given user/org
184+
// ownerID != 0 and repoID == 0 and WithAvailable == true means any runner for the given user/org and global runners
171185
type FindRunnerOptions struct {
172186
db.ListOptions
173187
IDs []int64

models/fixtures/action_runner.yml

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-
2+
id: 34346
3+
name: runner_to_be_deleted-user
4+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
5+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
6+
version: "1.0.0"
7+
owner_id: 1
8+
repo_id: 0
9+
description: "This runner is going to be deleted"
10+
agent_labels: '["runner_to_be_deleted","linux"]'
11+
-
12+
id: 34347
13+
name: runner_to_be_deleted-org
14+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
15+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
16+
version: "1.0.0"
17+
owner_id: 3
18+
repo_id: 0
19+
description: "This runner is going to be deleted"
20+
agent_labels: '["runner_to_be_deleted","linux"]'
21+
-
22+
id: 34348
23+
name: runner_to_be_deleted-repo1
24+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
25+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
26+
version: "1.0.0"
27+
owner_id: 0
28+
repo_id: 1
29+
description: "This runner is going to be deleted"
30+
agent_labels: '["runner_to_be_deleted","linux"]'
31+
-
32+
id: 34349
33+
name: runner_to_be_deleted
34+
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
35+
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
36+
version: "1.0.0"
37+
owner_id: 0
38+
repo_id: 0
39+
description: "This runner is going to be deleted"
40+
agent_labels: '["runner_to_be_deleted","linux"]'

modules/structs/repo_actions.go

+23
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,26 @@ type ActionWorkflowJob struct {
133133
// swagger:strfmt date-time
134134
CompletedAt time.Time `json:"completed_at,omitempty"`
135135
}
136+
137+
// ActionRunnerLabel represents a Runner Label
138+
type ActionRunnerLabel struct {
139+
ID int64 `json:"id"`
140+
Name string `json:"name"`
141+
Type string `json:"type"`
142+
}
143+
144+
// ActionRunner represents a Runner
145+
type ActionRunner struct {
146+
ID int64 `json:"id"`
147+
Name string `json:"name"`
148+
Status string `json:"status"`
149+
Busy bool `json:"busy"`
150+
Ephemeral bool `json:"ephemeral"`
151+
Labels []*ActionRunnerLabel `json:"labels"`
152+
}
153+
154+
// ActionRunnersResponse returns Runners
155+
type ActionRunnersResponse struct {
156+
Entries []*ActionRunner `json:"runners"`
157+
TotalCount int64 `json:"total_count"`
158+
}

routers/api/v1/admin/runners.go

+78
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,81 @@ func GetRegistrationToken(ctx *context.APIContext) {
2424

2525
shared.GetRegistrationToken(ctx, 0, 0)
2626
}
27+
28+
// CreateRegistrationToken returns the token to register global runners
29+
func CreateRegistrationToken(ctx *context.APIContext) {
30+
// swagger:operation POST /admin/actions/runners/registration-token admin adminCreateRunnerRegistrationToken
31+
// ---
32+
// summary: Get an global actions runner registration token
33+
// produces:
34+
// - application/json
35+
// parameters:
36+
// responses:
37+
// "200":
38+
// "$ref": "#/responses/RegistrationToken"
39+
40+
shared.GetRegistrationToken(ctx, 0, 0)
41+
}
42+
43+
// ListRunners get all runners
44+
func ListRunners(ctx *context.APIContext) {
45+
// swagger:operation GET /admin/actions/runners admin getAdminRunners
46+
// ---
47+
// summary: Get all runners
48+
// produces:
49+
// - application/json
50+
// responses:
51+
// "200":
52+
// "$ref": "#/definitions/ActionRunnersResponse"
53+
// "400":
54+
// "$ref": "#/responses/error"
55+
// "404":
56+
// "$ref": "#/responses/notFound"
57+
shared.ListRunners(ctx, 0, 0)
58+
}
59+
60+
// GetRunner get an global runner
61+
func GetRunner(ctx *context.APIContext) {
62+
// swagger:operation GET /admin/actions/runners/{runner_id} admin getAdminRunner
63+
// ---
64+
// summary: Get an global runner
65+
// produces:
66+
// - application/json
67+
// parameters:
68+
// - name: runner_id
69+
// in: path
70+
// description: id of the runner
71+
// type: string
72+
// required: true
73+
// responses:
74+
// "200":
75+
// "$ref": "#/definitions/ActionRunner"
76+
// "400":
77+
// "$ref": "#/responses/error"
78+
// "404":
79+
// "$ref": "#/responses/notFound"
80+
shared.GetRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
81+
}
82+
83+
// DeleteRunner delete an global runner
84+
func DeleteRunner(ctx *context.APIContext) {
85+
// swagger:operation DELETE /admin/actions/runners/{runner_id} admin deleteAdminRunner
86+
// ---
87+
// summary: Delete an global runner
88+
// produces:
89+
// - application/json
90+
// parameters:
91+
// - name: runner_id
92+
// in: path
93+
// description: id of the runner
94+
// type: string
95+
// required: true
96+
// responses:
97+
// "204":
98+
// description: runner has been deleted
99+
// "400":
100+
// "$ref": "#/responses/error"
101+
// "404":
102+
// "$ref": "#/responses/notFound"
103+
shared.DeleteRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
104+
}

routers/api/v1/api.go

+14
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,11 @@ func Routes() *web.Router {
912912
})
913913

914914
m.Group("/runners", func() {
915+
m.Get("", reqToken(), reqChecker, act.ListRunners)
915916
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
917+
m.Post("/registration-token", reqToken(), reqChecker, act.CreateRegistrationToken)
918+
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
919+
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
916920
})
917921
})
918922
}
@@ -1043,7 +1047,11 @@ func Routes() *web.Router {
10431047
})
10441048

10451049
m.Group("/runners", func() {
1050+
m.Get("", reqToken(), user.ListRunners)
10461051
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
1052+
m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
1053+
m.Get("/{runner_id}", reqToken(), user.GetRunner)
1054+
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
10471055
})
10481056
})
10491057

@@ -1689,6 +1697,12 @@ func Routes() *web.Router {
16891697
Patch(bind(api.EditHookOption{}), admin.EditHook).
16901698
Delete(admin.DeleteHook)
16911699
})
1700+
m.Group("/actions/runners", func() {
1701+
m.Get("", admin.ListRunners)
1702+
m.Post("/registration-token", admin.CreateRegistrationToken)
1703+
m.Get("/{runner_id}", admin.GetRunner)
1704+
m.Delete("/{runner_id}", admin.DeleteRunner)
1705+
})
16921706
m.Group("/runners", func() {
16931707
m.Get("/registration-token", admin.GetRegistrationToken)
16941708
})

routers/api/v1/org/action.go

+100
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,27 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
190190
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
191191
}
192192

193+
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
194+
// CreateRegistrationToken returns the token to register org runners
195+
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
196+
// swagger:operation POST /orgs/{org}/actions/runners/registration-token organization orgCreateRunnerRegistrationToken
197+
// ---
198+
// summary: Get an organization's actions runner registration token
199+
// produces:
200+
// - application/json
201+
// parameters:
202+
// - name: org
203+
// in: path
204+
// description: name of the organization
205+
// type: string
206+
// required: true
207+
// responses:
208+
// "200":
209+
// "$ref": "#/responses/RegistrationToken"
210+
211+
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
212+
}
213+
193214
// ListVariables list org-level variables
194215
func (Action) ListVariables(ctx *context.APIContext) {
195216
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
@@ -470,6 +491,85 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
470491
ctx.Status(http.StatusNoContent)
471492
}
472493

494+
// ListRunners get org-level runners
495+
func (Action) ListRunners(ctx *context.APIContext) {
496+
// swagger:operation GET /orgs/{org}/actions/runners organization getOrgRunners
497+
// ---
498+
// summary: Get org-level runners
499+
// produces:
500+
// - application/json
501+
// parameters:
502+
// - name: org
503+
// in: path
504+
// description: name of the organization
505+
// type: string
506+
// required: true
507+
// responses:
508+
// "200":
509+
// "$ref": "#/definitions/ActionRunnersResponse"
510+
// "400":
511+
// "$ref": "#/responses/error"
512+
// "404":
513+
// "$ref": "#/responses/notFound"
514+
shared.ListRunners(ctx, ctx.Org.Organization.ID, 0)
515+
}
516+
517+
// GetRunner get an org-level runner
518+
func (Action) GetRunner(ctx *context.APIContext) {
519+
// swagger:operation GET /orgs/{org}/actions/runners/{runner_id} organization getOrgRunner
520+
// ---
521+
// summary: Get an org-level runner
522+
// produces:
523+
// - application/json
524+
// parameters:
525+
// - name: org
526+
// in: path
527+
// description: name of the organization
528+
// type: string
529+
// required: true
530+
// - name: runner_id
531+
// in: path
532+
// description: id of the runner
533+
// type: string
534+
// required: true
535+
// responses:
536+
// "200":
537+
// "$ref": "#/definitions/ActionRunner"
538+
// "400":
539+
// "$ref": "#/responses/error"
540+
// "404":
541+
// "$ref": "#/responses/notFound"
542+
shared.GetRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
543+
}
544+
545+
// DeleteRunner delete an org-level runner
546+
func (Action) DeleteRunner(ctx *context.APIContext) {
547+
// swagger:operation DELETE /orgs/{org}/actions/runners/{runner_id} organization deleteOrgRunner
548+
// ---
549+
// summary: Delete an org-level runner
550+
// produces:
551+
// - application/json
552+
// parameters:
553+
// - name: org
554+
// in: path
555+
// description: name of the organization
556+
// type: string
557+
// required: true
558+
// - name: runner_id
559+
// in: path
560+
// description: id of the runner
561+
// type: string
562+
// required: true
563+
// responses:
564+
// "204":
565+
// description: runner has been deleted
566+
// "400":
567+
// "$ref": "#/responses/error"
568+
// "404":
569+
// "$ref": "#/responses/notFound"
570+
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
571+
}
572+
473573
var _ actions_service.API = new(Action)
474574

475575
// Action implements actions_service.API

0 commit comments

Comments
 (0)