Skip to content

Commit 852547d

Browse files
authored
feat(api): enhance Actions Secrets Management API for repository (#30656)
- Add endpoint to list repository action secrets in API routes - Implement `ListActionsSecrets` function to retrieve action secrets from the database - Update Swagger documentation to include the new `/repos/{owner}/{repo}/actions/secrets` endpoint - Add `actions` package import and define new routes for actions, secrets, variables, and runners in `api.go`. - Refactor action-related API functions into `Action` struct methods in `org/action.go` and `repo/action.go`. - Remove `actionAPI` struct and related functions, replacing them with `NewAction()` calls. - Rename `variables.go` to `action.go` in `org` directory. - Delete `runners.go` and `secrets.go` in both `org` and `repo` directories, consolidating their content into `action.go`. - Update copyright year and add new imports in `org/action.go`. - Implement `API` interface in `services/actions/interface.go` for action-related methods. - Remove individual action-related functions and replace them with methods on the `Action` struct in `repo/action.go`. --------- Signed-off-by: Bo-Yi Wu <[email protected]> Signed-off-by: appleboy <[email protected]>
1 parent 993736d commit 852547d

File tree

9 files changed

+410
-285
lines changed

9 files changed

+410
-285
lines changed

routers/api/v1/api.go

+39-41
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import (
9393
"code.gitea.io/gitea/routers/api/v1/settings"
9494
"code.gitea.io/gitea/routers/api/v1/user"
9595
"code.gitea.io/gitea/routers/common"
96+
"code.gitea.io/gitea/services/actions"
9697
"code.gitea.io/gitea/services/auth"
9798
"code.gitea.io/gitea/services/context"
9899
"code.gitea.io/gitea/services/forms"
@@ -835,6 +836,34 @@ func Routes() *web.Route {
835836
SignInRequired: setting.Service.RequireSignInView,
836837
}))
837838

839+
addActionsRoutes := func(
840+
m *web.Route,
841+
reqChecker func(ctx *context.APIContext),
842+
act actions.API,
843+
) {
844+
m.Group("/actions", func() {
845+
m.Group("/secrets", func() {
846+
m.Get("", reqToken(), reqChecker, act.ListActionsSecrets)
847+
m.Combo("/{secretname}").
848+
Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret).
849+
Delete(reqToken(), reqChecker, act.DeleteSecret)
850+
})
851+
852+
m.Group("/variables", func() {
853+
m.Get("", reqToken(), reqChecker, act.ListVariables)
854+
m.Combo("/{variablename}").
855+
Get(reqToken(), reqChecker, act.GetVariable).
856+
Delete(reqToken(), reqChecker, act.DeleteVariable).
857+
Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable).
858+
Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable)
859+
})
860+
861+
m.Group("/runners", func() {
862+
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
863+
})
864+
})
865+
}
866+
838867
m.Group("", func() {
839868
// Miscellaneous (no scope required)
840869
if setting.API.EnableSwagger {
@@ -1073,26 +1102,11 @@ func Routes() *web.Route {
10731102
m.Post("/accept", repo.AcceptTransfer)
10741103
m.Post("/reject", repo.RejectTransfer)
10751104
}, reqToken())
1076-
m.Group("/actions", func() {
1077-
m.Group("/secrets", func() {
1078-
m.Combo("/{secretname}").
1079-
Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
1080-
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
1081-
})
1082-
1083-
m.Group("/variables", func() {
1084-
m.Get("", reqToken(), reqOwner(), repo.ListVariables)
1085-
m.Combo("/{variablename}").
1086-
Get(reqToken(), reqOwner(), repo.GetVariable).
1087-
Delete(reqToken(), reqOwner(), repo.DeleteVariable).
1088-
Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable).
1089-
Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable)
1090-
})
1091-
1092-
m.Group("/runners", func() {
1093-
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
1094-
})
1095-
})
1105+
addActionsRoutes(
1106+
m,
1107+
reqOwner(),
1108+
repo.NewAction(),
1109+
)
10961110
m.Group("/hooks/git", func() {
10971111
m.Combo("").Get(repo.ListGitHooks)
10981112
m.Group("/{id}", func() {
@@ -1460,27 +1474,11 @@ func Routes() *web.Route {
14601474
m.Combo("/{username}").Get(reqToken(), org.IsMember).
14611475
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
14621476
})
1463-
m.Group("/actions", func() {
1464-
m.Group("/secrets", func() {
1465-
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
1466-
m.Combo("/{secretname}").
1467-
Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
1468-
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
1469-
})
1470-
1471-
m.Group("/variables", func() {
1472-
m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables)
1473-
m.Combo("/{variablename}").
1474-
Get(reqToken(), reqOrgOwnership(), org.GetVariable).
1475-
Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable).
1476-
Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable).
1477-
Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable)
1478-
})
1479-
1480-
m.Group("/runners", func() {
1481-
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
1482-
})
1483-
})
1477+
addActionsRoutes(
1478+
m,
1479+
reqOrgOwnership(),
1480+
org.NewAction(),
1481+
)
14841482
m.Group("/public_members", func() {
14851483
m.Get("", org.ListPublicMembers)
14861484
m.Combo("/{username}").Get(org.IsPublicMember).

routers/api/v1/org/variables.go renamed to routers/api/v1/org/action.go

+187-5
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,188 @@ import (
99

1010
actions_model "code.gitea.io/gitea/models/actions"
1111
"code.gitea.io/gitea/models/db"
12+
secret_model "code.gitea.io/gitea/models/secret"
1213
api "code.gitea.io/gitea/modules/structs"
1314
"code.gitea.io/gitea/modules/util"
1415
"code.gitea.io/gitea/modules/web"
16+
"code.gitea.io/gitea/routers/api/v1/shared"
1517
"code.gitea.io/gitea/routers/api/v1/utils"
1618
actions_service "code.gitea.io/gitea/services/actions"
1719
"code.gitea.io/gitea/services/context"
20+
secret_service "code.gitea.io/gitea/services/secrets"
1821
)
1922

23+
// ListActionsSecrets list an organization's actions secrets
24+
func (Action) ListActionsSecrets(ctx *context.APIContext) {
25+
// swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
26+
// ---
27+
// summary: List an organization's actions secrets
28+
// produces:
29+
// - application/json
30+
// parameters:
31+
// - name: org
32+
// in: path
33+
// description: name of the organization
34+
// type: string
35+
// required: true
36+
// - name: page
37+
// in: query
38+
// description: page number of results to return (1-based)
39+
// type: integer
40+
// - name: limit
41+
// in: query
42+
// description: page size of results
43+
// type: integer
44+
// responses:
45+
// "200":
46+
// "$ref": "#/responses/SecretList"
47+
// "404":
48+
// "$ref": "#/responses/notFound"
49+
50+
opts := &secret_model.FindSecretsOptions{
51+
OwnerID: ctx.Org.Organization.ID,
52+
ListOptions: utils.GetListOptions(ctx),
53+
}
54+
55+
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
56+
if err != nil {
57+
ctx.InternalServerError(err)
58+
return
59+
}
60+
61+
apiSecrets := make([]*api.Secret, len(secrets))
62+
for k, v := range secrets {
63+
apiSecrets[k] = &api.Secret{
64+
Name: v.Name,
65+
Created: v.CreatedUnix.AsTime(),
66+
}
67+
}
68+
69+
ctx.SetTotalCountHeader(count)
70+
ctx.JSON(http.StatusOK, apiSecrets)
71+
}
72+
73+
// create or update one secret of the organization
74+
func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
75+
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
76+
// ---
77+
// summary: Create or Update a secret value in an organization
78+
// consumes:
79+
// - application/json
80+
// produces:
81+
// - application/json
82+
// parameters:
83+
// - name: org
84+
// in: path
85+
// description: name of organization
86+
// type: string
87+
// required: true
88+
// - name: secretname
89+
// in: path
90+
// description: name of the secret
91+
// type: string
92+
// required: true
93+
// - name: body
94+
// in: body
95+
// schema:
96+
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
97+
// responses:
98+
// "201":
99+
// description: response when creating a secret
100+
// "204":
101+
// description: response when updating a secret
102+
// "400":
103+
// "$ref": "#/responses/error"
104+
// "404":
105+
// "$ref": "#/responses/notFound"
106+
107+
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
108+
109+
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
110+
if err != nil {
111+
if errors.Is(err, util.ErrInvalidArgument) {
112+
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
113+
} else if errors.Is(err, util.ErrNotExist) {
114+
ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
115+
} else {
116+
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
117+
}
118+
return
119+
}
120+
121+
if created {
122+
ctx.Status(http.StatusCreated)
123+
} else {
124+
ctx.Status(http.StatusNoContent)
125+
}
126+
}
127+
128+
// DeleteSecret delete one secret of the organization
129+
func (Action) DeleteSecret(ctx *context.APIContext) {
130+
// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
131+
// ---
132+
// summary: Delete a secret in an organization
133+
// consumes:
134+
// - application/json
135+
// produces:
136+
// - application/json
137+
// parameters:
138+
// - name: org
139+
// in: path
140+
// description: name of organization
141+
// type: string
142+
// required: true
143+
// - name: secretname
144+
// in: path
145+
// description: name of the secret
146+
// type: string
147+
// required: true
148+
// responses:
149+
// "204":
150+
// description: delete one secret of the organization
151+
// "400":
152+
// "$ref": "#/responses/error"
153+
// "404":
154+
// "$ref": "#/responses/notFound"
155+
156+
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
157+
if err != nil {
158+
if errors.Is(err, util.ErrInvalidArgument) {
159+
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
160+
} else if errors.Is(err, util.ErrNotExist) {
161+
ctx.Error(http.StatusNotFound, "DeleteSecret", err)
162+
} else {
163+
ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
164+
}
165+
return
166+
}
167+
168+
ctx.Status(http.StatusNoContent)
169+
}
170+
171+
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
172+
// GetRegistrationToken returns the token to register org runners
173+
func (Action) GetRegistrationToken(ctx *context.APIContext) {
174+
// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
175+
// ---
176+
// summary: Get an organization's actions runner registration token
177+
// produces:
178+
// - application/json
179+
// parameters:
180+
// - name: org
181+
// in: path
182+
// description: name of the organization
183+
// type: string
184+
// required: true
185+
// responses:
186+
// "200":
187+
// "$ref": "#/responses/RegistrationToken"
188+
189+
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
190+
}
191+
20192
// ListVariables list org-level variables
21-
func ListVariables(ctx *context.APIContext) {
193+
func (Action) ListVariables(ctx *context.APIContext) {
22194
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
23195
// ---
24196
// summary: Get an org-level variables list
@@ -70,7 +242,7 @@ func ListVariables(ctx *context.APIContext) {
70242
}
71243

72244
// GetVariable get an org-level variable
73-
func GetVariable(ctx *context.APIContext) {
245+
func (Action) GetVariable(ctx *context.APIContext) {
74246
// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
75247
// ---
76248
// summary: Get an org-level variable
@@ -119,7 +291,7 @@ func GetVariable(ctx *context.APIContext) {
119291
}
120292

121293
// DeleteVariable delete an org-level variable
122-
func DeleteVariable(ctx *context.APIContext) {
294+
func (Action) DeleteVariable(ctx *context.APIContext) {
123295
// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
124296
// ---
125297
// summary: Delete an org-level variable
@@ -163,7 +335,7 @@ func DeleteVariable(ctx *context.APIContext) {
163335
}
164336

165337
// CreateVariable create an org-level variable
166-
func CreateVariable(ctx *context.APIContext) {
338+
func (Action) CreateVariable(ctx *context.APIContext) {
167339
// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
168340
// ---
169341
// summary: Create an org-level variable
@@ -227,7 +399,7 @@ func CreateVariable(ctx *context.APIContext) {
227399
}
228400

229401
// UpdateVariable update an org-level variable
230-
func UpdateVariable(ctx *context.APIContext) {
402+
func (Action) UpdateVariable(ctx *context.APIContext) {
231403
// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
232404
// ---
233405
// summary: Update an org-level variable
@@ -289,3 +461,13 @@ func UpdateVariable(ctx *context.APIContext) {
289461

290462
ctx.Status(http.StatusNoContent)
291463
}
464+
465+
var _ actions_service.API = new(Action)
466+
467+
// Action implements actions_service.API
468+
type Action struct{}
469+
470+
// NewAction creates a new Action service
471+
func NewAction() actions_service.API {
472+
return Action{}
473+
}

routers/api/v1/org/runners.go

-31
This file was deleted.

0 commit comments

Comments
 (0)