mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-29 04:08:00 +01:00
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 <appleboy.tw@gmail.com> Signed-off-by: appleboy <appleboy.tw@gmail.com> (cherry picked from commit 852547d0dc70299589c7bf8d00ea462ed709b8e5) Conflicts: routers/api/v1/api.go trivial conflict because of Fix #2512 /api/forgejo/v1/version auth check (#2582)
This commit is contained in:
parent
089e95f250
commit
7b456a28d1
9 changed files with 410 additions and 285 deletions
|
@ -93,6 +93,7 @@ import (
|
||||||
"code.gitea.io/gitea/routers/api/v1/repo"
|
"code.gitea.io/gitea/routers/api/v1/repo"
|
||||||
"code.gitea.io/gitea/routers/api/v1/settings"
|
"code.gitea.io/gitea/routers/api/v1/settings"
|
||||||
"code.gitea.io/gitea/routers/api/v1/user"
|
"code.gitea.io/gitea/routers/api/v1/user"
|
||||||
|
"code.gitea.io/gitea/services/actions"
|
||||||
"code.gitea.io/gitea/services/auth"
|
"code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
@ -753,6 +754,34 @@ func Routes() *web.Route {
|
||||||
|
|
||||||
m.Use(shared.Middlewares()...)
|
m.Use(shared.Middlewares()...)
|
||||||
|
|
||||||
|
addActionsRoutes := func(
|
||||||
|
m *web.Route,
|
||||||
|
reqChecker func(ctx *context.APIContext),
|
||||||
|
act actions.API,
|
||||||
|
) {
|
||||||
|
m.Group("/actions", func() {
|
||||||
|
m.Group("/secrets", func() {
|
||||||
|
m.Get("", reqToken(), reqChecker, act.ListActionsSecrets)
|
||||||
|
m.Combo("/{secretname}").
|
||||||
|
Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret).
|
||||||
|
Delete(reqToken(), reqChecker, act.DeleteSecret)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Group("/variables", func() {
|
||||||
|
m.Get("", reqToken(), reqChecker, act.ListVariables)
|
||||||
|
m.Combo("/{variablename}").
|
||||||
|
Get(reqToken(), reqChecker, act.GetVariable).
|
||||||
|
Delete(reqToken(), reqChecker, act.DeleteVariable).
|
||||||
|
Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable).
|
||||||
|
Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.Group("/runners", func() {
|
||||||
|
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
// Miscellaneous (no scope required)
|
// Miscellaneous (no scope required)
|
||||||
if setting.API.EnableSwagger {
|
if setting.API.EnableSwagger {
|
||||||
|
@ -994,26 +1023,11 @@ func Routes() *web.Route {
|
||||||
m.Post("/accept", repo.AcceptTransfer)
|
m.Post("/accept", repo.AcceptTransfer)
|
||||||
m.Post("/reject", repo.RejectTransfer)
|
m.Post("/reject", repo.RejectTransfer)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
m.Group("/actions", func() {
|
addActionsRoutes(
|
||||||
m.Group("/secrets", func() {
|
m,
|
||||||
m.Combo("/{secretname}").
|
reqOwner(),
|
||||||
Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
|
repo.NewAction(),
|
||||||
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/variables", func() {
|
|
||||||
m.Get("", reqToken(), reqOwner(), repo.ListVariables)
|
|
||||||
m.Combo("/{variablename}").
|
|
||||||
Get(reqToken(), reqOwner(), repo.GetVariable).
|
|
||||||
Delete(reqToken(), reqOwner(), repo.DeleteVariable).
|
|
||||||
Post(reqToken(), reqOwner(), bind(api.CreateVariableOption{}), repo.CreateVariable).
|
|
||||||
Put(reqToken(), reqOwner(), bind(api.UpdateVariableOption{}), repo.UpdateVariable)
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
|
||||||
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
m.Group("/hooks/git", func() {
|
m.Group("/hooks/git", func() {
|
||||||
m.Combo("").Get(repo.ListGitHooks)
|
m.Combo("").Get(repo.ListGitHooks)
|
||||||
m.Group("/{id}", func() {
|
m.Group("/{id}", func() {
|
||||||
|
@ -1405,27 +1419,11 @@ func Routes() *web.Route {
|
||||||
m.Combo("/{username}").Get(reqToken(), org.IsMember).
|
m.Combo("/{username}").Get(reqToken(), org.IsMember).
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
|
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
|
||||||
})
|
})
|
||||||
m.Group("/actions", func() {
|
addActionsRoutes(
|
||||||
m.Group("/secrets", func() {
|
m,
|
||||||
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
|
reqOrgOwnership(),
|
||||||
m.Combo("/{secretname}").
|
org.NewAction(),
|
||||||
Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
|
)
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/variables", func() {
|
|
||||||
m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables)
|
|
||||||
m.Combo("/{variablename}").
|
|
||||||
Get(reqToken(), reqOrgOwnership(), org.GetVariable).
|
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable).
|
|
||||||
Post(reqToken(), reqOrgOwnership(), bind(api.CreateVariableOption{}), org.CreateVariable).
|
|
||||||
Put(reqToken(), reqOrgOwnership(), bind(api.UpdateVariableOption{}), org.UpdateVariable)
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
|
||||||
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
m.Group("/public_members", func() {
|
m.Group("/public_members", func() {
|
||||||
m.Get("", org.ListPublicMembers)
|
m.Get("", org.ListPublicMembers)
|
||||||
m.Combo("/{username}").Get(org.IsPublicMember).
|
m.Combo("/{username}").Get(org.IsPublicMember).
|
||||||
|
|
|
@ -9,16 +9,188 @@ import (
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
secret_model "code.gitea.io/gitea/models/secret"
|
||||||
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/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"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ListActionsSecrets list an organization's actions secrets
|
||||||
|
func (Action) ListActionsSecrets(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
|
||||||
|
// ---
|
||||||
|
// summary: List an organization's actions secrets
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// 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
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/SecretList"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opts := &secret_model.FindSecretsOptions{
|
||||||
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSecrets := make([]*api.Secret, len(secrets))
|
||||||
|
for k, v := range secrets {
|
||||||
|
apiSecrets[k] = &api.Secret{
|
||||||
|
Name: v.Name,
|
||||||
|
Created: v.CreatedUnix.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, apiSecrets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create or update one secret of the organization
|
||||||
|
func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
|
||||||
|
// ---
|
||||||
|
// summary: Create or Update a secret value in an organization
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: secretname
|
||||||
|
// in: path
|
||||||
|
// description: name of the secret
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// description: response when creating a secret
|
||||||
|
// "204":
|
||||||
|
// description: response when updating a secret
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
||||||
|
|
||||||
|
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
ctx.Status(http.StatusCreated)
|
||||||
|
} else {
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSecret delete one secret of the organization
|
||||||
|
func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
|
||||||
|
// ---
|
||||||
|
// summary: Delete a secret in an organization
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: secretname
|
||||||
|
// in: path
|
||||||
|
// description: name of the secret
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: delete one secret of the organization
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
|
||||||
|
} else if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Error(http.StatusNotFound, "DeleteSecret", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||||
|
// GetRegistrationToken returns the token to register org runners
|
||||||
|
func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get an organization's actions runner registration token
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// ListVariables list org-level variables
|
// ListVariables list org-level variables
|
||||||
func ListVariables(ctx *context.APIContext) {
|
func (Action) ListVariables(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
||||||
// ---
|
// ---
|
||||||
// summary: Get an org-level variables list
|
// summary: Get an org-level variables list
|
||||||
|
@ -70,7 +242,7 @@ func ListVariables(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVariable get an org-level variable
|
// GetVariable get an org-level variable
|
||||||
func GetVariable(ctx *context.APIContext) {
|
func (Action) GetVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
|
// swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Get an org-level variable
|
// summary: Get an org-level variable
|
||||||
|
@ -119,7 +291,7 @@ func GetVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVariable delete an org-level variable
|
// DeleteVariable delete an org-level variable
|
||||||
func DeleteVariable(ctx *context.APIContext) {
|
func (Action) DeleteVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
|
// swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete an org-level variable
|
// summary: Delete an org-level variable
|
||||||
|
@ -163,7 +335,7 @@ func DeleteVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVariable create an org-level variable
|
// CreateVariable create an org-level variable
|
||||||
func CreateVariable(ctx *context.APIContext) {
|
func (Action) CreateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
|
// swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Create an org-level variable
|
// summary: Create an org-level variable
|
||||||
|
@ -227,7 +399,7 @@ func CreateVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateVariable update an org-level variable
|
// UpdateVariable update an org-level variable
|
||||||
func UpdateVariable(ctx *context.APIContext) {
|
func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
|
// swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Update an org-level variable
|
// summary: Update an org-level variable
|
||||||
|
@ -289,3 +461,13 @@ func UpdateVariable(ctx *context.APIContext) {
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
|
// Action implements actions_service.API
|
||||||
|
type Action struct{}
|
||||||
|
|
||||||
|
// NewAction creates a new Action service
|
||||||
|
func NewAction() actions_service.API {
|
||||||
|
return Action{}
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package org
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
|
||||||
|
|
||||||
// GetRegistrationToken returns the token to register org runners
|
|
||||||
func GetRegistrationToken(ctx *context.APIContext) {
|
|
||||||
// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
|
|
||||||
// ---
|
|
||||||
// summary: Get an organization's actions runner registration token
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of the organization
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// responses:
|
|
||||||
// "200":
|
|
||||||
// "$ref": "#/responses/RegistrationToken"
|
|
||||||
|
|
||||||
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package org
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
secret_model "code.gitea.io/gitea/models/secret"
|
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListActionsSecrets list an organization's actions secrets
|
|
||||||
func ListActionsSecrets(ctx *context.APIContext) {
|
|
||||||
// swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
|
|
||||||
// ---
|
|
||||||
// summary: List an organization's actions secrets
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of the organization
|
|
||||||
// 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
|
|
||||||
// type: integer
|
|
||||||
// responses:
|
|
||||||
// "200":
|
|
||||||
// "$ref": "#/responses/SecretList"
|
|
||||||
// "404":
|
|
||||||
// "$ref": "#/responses/notFound"
|
|
||||||
|
|
||||||
opts := &secret_model.FindSecretsOptions{
|
|
||||||
OwnerID: ctx.Org.Organization.ID,
|
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
|
||||||
}
|
|
||||||
|
|
||||||
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
ctx.InternalServerError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
apiSecrets := make([]*api.Secret, len(secrets))
|
|
||||||
for k, v := range secrets {
|
|
||||||
apiSecrets[k] = &api.Secret{
|
|
||||||
Name: v.Name,
|
|
||||||
Created: v.CreatedUnix.AsTime(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetTotalCountHeader(count)
|
|
||||||
ctx.JSON(http.StatusOK, apiSecrets)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create or update one secret of the organization
|
|
||||||
func CreateOrUpdateSecret(ctx *context.APIContext) {
|
|
||||||
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
|
|
||||||
// ---
|
|
||||||
// summary: Create or Update a secret value in an organization
|
|
||||||
// consumes:
|
|
||||||
// - application/json
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of organization
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: secretname
|
|
||||||
// in: path
|
|
||||||
// description: name of the secret
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: body
|
|
||||||
// in: body
|
|
||||||
// schema:
|
|
||||||
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
|
|
||||||
// responses:
|
|
||||||
// "201":
|
|
||||||
// description: response when creating a secret
|
|
||||||
// "204":
|
|
||||||
// description: response when updating a secret
|
|
||||||
// "400":
|
|
||||||
// "$ref": "#/responses/error"
|
|
||||||
// "404":
|
|
||||||
// "$ref": "#/responses/notFound"
|
|
||||||
|
|
||||||
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
|
||||||
|
|
||||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, util.ErrInvalidArgument) {
|
|
||||||
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
|
|
||||||
} else if errors.Is(err, util.ErrNotExist) {
|
|
||||||
ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
|
|
||||||
} else {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if created {
|
|
||||||
ctx.Status(http.StatusCreated)
|
|
||||||
} else {
|
|
||||||
ctx.Status(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSecret delete one secret of the organization
|
|
||||||
func DeleteSecret(ctx *context.APIContext) {
|
|
||||||
// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
|
|
||||||
// ---
|
|
||||||
// summary: Delete a secret in an organization
|
|
||||||
// consumes:
|
|
||||||
// - application/json
|
|
||||||
// produces:
|
|
||||||
// - application/json
|
|
||||||
// parameters:
|
|
||||||
// - name: org
|
|
||||||
// in: path
|
|
||||||
// description: name of organization
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// - name: secretname
|
|
||||||
// in: path
|
|
||||||
// description: name of the secret
|
|
||||||
// type: string
|
|
||||||
// required: true
|
|
||||||
// responses:
|
|
||||||
// "204":
|
|
||||||
// description: delete one secret of the organization
|
|
||||||
// "400":
|
|
||||||
// "$ref": "#/responses/error"
|
|
||||||
// "404":
|
|
||||||
// "$ref": "#/responses/notFound"
|
|
||||||
|
|
||||||
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, util.ErrInvalidArgument) {
|
|
||||||
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
|
|
||||||
} else if errors.Is(err, util.ErrNotExist) {
|
|
||||||
ctx.Error(http.StatusNotFound, "DeleteSecret", err)
|
|
||||||
} else {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
|
||||||
}
|
|
|
@ -9,17 +9,76 @@ import (
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
secret_model "code.gitea.io/gitea/models/secret"
|
||||||
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/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"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
secret_service "code.gitea.io/gitea/services/secrets"
|
secret_service "code.gitea.io/gitea/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ListActionsSecrets list an repo's actions secrets
|
||||||
|
func (Action) ListActionsSecrets(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/secrets repository repoListActionsSecrets
|
||||||
|
// ---
|
||||||
|
// summary: List an repo's actions secrets
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// 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
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/SecretList"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
opts := &secret_model.FindSecretsOptions{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSecrets := make([]*api.Secret, len(secrets))
|
||||||
|
for k, v := range secrets {
|
||||||
|
apiSecrets[k] = &api.Secret{
|
||||||
|
Name: v.Name,
|
||||||
|
Created: v.CreatedUnix.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, apiSecrets)
|
||||||
|
}
|
||||||
|
|
||||||
// create or update one secret of the repository
|
// create or update one secret of the repository
|
||||||
func CreateOrUpdateSecret(ctx *context.APIContext) {
|
func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
|
// swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
|
||||||
// ---
|
// ---
|
||||||
// summary: Create or Update a secret value in a repository
|
// summary: Create or Update a secret value in a repository
|
||||||
|
@ -82,7 +141,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSecret delete one secret of the repository
|
// DeleteSecret delete one secret of the repository
|
||||||
func DeleteSecret(ctx *context.APIContext) {
|
func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete a secret in a repository
|
// summary: Delete a secret in a repository
|
||||||
|
@ -133,7 +192,7 @@ func DeleteSecret(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVariable get a repo-level variable
|
// GetVariable get a repo-level variable
|
||||||
func GetVariable(ctx *context.APIContext) {
|
func (Action) GetVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
|
// swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Get a repo-level variable
|
// summary: Get a repo-level variable
|
||||||
|
@ -186,7 +245,7 @@ func GetVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVariable delete a repo-level variable
|
// DeleteVariable delete a repo-level variable
|
||||||
func DeleteVariable(ctx *context.APIContext) {
|
func (Action) DeleteVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Delete a repo-level variable
|
// summary: Delete a repo-level variable
|
||||||
|
@ -235,7 +294,7 @@ func DeleteVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVariable create a repo-level variable
|
// CreateVariable create a repo-level variable
|
||||||
func CreateVariable(ctx *context.APIContext) {
|
func (Action) CreateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
|
// swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Create a repo-level variable
|
// summary: Create a repo-level variable
|
||||||
|
@ -302,7 +361,7 @@ func CreateVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateVariable update a repo-level variable
|
// UpdateVariable update a repo-level variable
|
||||||
func UpdateVariable(ctx *context.APIContext) {
|
func (Action) UpdateVariable(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
|
// swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable
|
||||||
// ---
|
// ---
|
||||||
// summary: Update a repo-level variable
|
// summary: Update a repo-level variable
|
||||||
|
@ -369,7 +428,7 @@ func UpdateVariable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListVariables list repo-level variables
|
// ListVariables list repo-level variables
|
||||||
func ListVariables(ctx *context.APIContext) {
|
func (Action) ListVariables(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
|
// swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList
|
||||||
// ---
|
// ---
|
||||||
// summary: Get repo-level variables list
|
// summary: Get repo-level variables list
|
||||||
|
@ -423,3 +482,38 @@ func ListVariables(ctx *context.APIContext) {
|
||||||
ctx.SetTotalCountHeader(count)
|
ctx.SetTotalCountHeader(count)
|
||||||
ctx.JSON(http.StatusOK, variables)
|
ctx.JSON(http.StatusOK, variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRegistrationToken returns the token to register repo runners
|
||||||
|
func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get a repository's actions runner registration token
|
||||||
|
// 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
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
|
// Action implements actions_service.API
|
||||||
|
type Action struct{}
|
||||||
|
|
||||||
|
// NewAction creates a new Action service
|
||||||
|
func NewAction() actions_service.API {
|
||||||
|
return Action{}
|
||||||
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/gitea/routers/api/v1/shared"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetRegistrationToken returns the token to register repo runners
|
|
||||||
func GetRegistrationToken(ctx *context.APIContext) {
|
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
|
|
||||||
// ---
|
|
||||||
// summary: Get a repository's actions runner registration token
|
|
||||||
// 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
|
|
||||||
// responses:
|
|
||||||
// "200":
|
|
||||||
// "$ref": "#/responses/RegistrationToken"
|
|
||||||
|
|
||||||
shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
|
||||||
}
|
|
28
services/actions/interface.go
Normal file
28
services/actions/interface.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import "code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
|
// API for actions of a repository or organization
|
||||||
|
type API interface {
|
||||||
|
// ListActionsSecrets list secrets
|
||||||
|
ListActionsSecrets(*context.APIContext)
|
||||||
|
// CreateOrUpdateSecret create or update a secret
|
||||||
|
CreateOrUpdateSecret(*context.APIContext)
|
||||||
|
// DeleteSecret delete a secret
|
||||||
|
DeleteSecret(*context.APIContext)
|
||||||
|
// ListVariables list variables
|
||||||
|
ListVariables(*context.APIContext)
|
||||||
|
// GetVariable get a variable
|
||||||
|
GetVariable(*context.APIContext)
|
||||||
|
// DeleteVariable delete a variable
|
||||||
|
DeleteVariable(*context.APIContext)
|
||||||
|
// CreateVariable create a variable
|
||||||
|
CreateVariable(*context.APIContext)
|
||||||
|
// UpdateVariable update a variable
|
||||||
|
UpdateVariable(*context.APIContext)
|
||||||
|
// GetRegistrationToken get registration token
|
||||||
|
GetRegistrationToken(*context.APIContext)
|
||||||
|
}
|
48
templates/swagger/v1_json.tmpl
generated
48
templates/swagger/v1_json.tmpl
generated
|
@ -3711,6 +3711,54 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/secrets": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "List an repo's actions secrets",
|
||||||
|
"operationId": "repoListActionsSecrets",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repository",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repository",
|
||||||
|
"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",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/SecretList"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/secrets/{secretname}": {
|
"/repos/{owner}/{repo}/actions/secrets/{secretname}": {
|
||||||
"put": {
|
"put": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
|
|
@ -24,6 +24,12 @@ func TestAPIRepoSecrets(t *testing.T) {
|
||||||
session := loginUser(t, user.Name)
|
session := loginUser(t, user.Name)
|
||||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
t.Run("List", func(t *testing.T) {
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/secrets", repo.FullName())).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Create", func(t *testing.T) {
|
t.Run("Create", func(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -31,7 +37,7 @@ func TestAPIRepoSecrets(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "",
|
Name: "",
|
||||||
ExpectedStatus: http.StatusNotFound,
|
ExpectedStatus: http.StatusMethodNotAllowed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "-",
|
Name: "-",
|
||||||
|
|
Loading…
Reference in a new issue