]> source.dussan.org Git - gitea.git/commitdiff
feat(api): enhance Actions Secrets Management API for repository (#30656)
authorBo-Yi Wu <appleboy.tw@gmail.com>
Fri, 26 Apr 2024 13:11:49 +0000 (21:11 +0800)
committerGitHub <noreply@github.com>
Fri, 26 Apr 2024 13:11:49 +0000 (21:11 +0800)
- 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>
routers/api/v1/api.go
routers/api/v1/org/action.go [new file with mode: 0644]
routers/api/v1/org/runners.go [deleted file]
routers/api/v1/org/secrets.go [deleted file]
routers/api/v1/org/variables.go [deleted file]
routers/api/v1/repo/action.go
routers/api/v1/repo/runners.go [deleted file]
services/actions/interface.go [new file with mode: 0644]
templates/swagger/v1_json.tmpl
tests/integration/api_repo_secrets_test.go

index 5358906f27d5120d5042b20813632406368a2022..73071aa8df7ee489f39462691ae14563b32df521 100644 (file)
@@ -93,6 +93,7 @@ import (
        "code.gitea.io/gitea/routers/api/v1/settings"
        "code.gitea.io/gitea/routers/api/v1/user"
        "code.gitea.io/gitea/routers/common"
+       "code.gitea.io/gitea/services/actions"
        "code.gitea.io/gitea/services/auth"
        "code.gitea.io/gitea/services/context"
        "code.gitea.io/gitea/services/forms"
@@ -835,6 +836,34 @@ func Routes() *web.Route {
                SignInRequired: setting.Service.RequireSignInView,
        }))
 
+       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() {
                // Miscellaneous (no scope required)
                if setting.API.EnableSwagger {
@@ -1073,26 +1102,11 @@ func Routes() *web.Route {
                                        m.Post("/accept", repo.AcceptTransfer)
                                        m.Post("/reject", repo.RejectTransfer)
                                }, reqToken())
-                               m.Group("/actions", func() {
-                                       m.Group("/secrets", func() {
-                                               m.Combo("/{secretname}").
-                                                       Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
-                                                       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)
-                                       })
-                               })
+                               addActionsRoutes(
+                                       m,
+                                       reqOwner(),
+                                       repo.NewAction(),
+                               )
                                m.Group("/hooks/git", func() {
                                        m.Combo("").Get(repo.ListGitHooks)
                                        m.Group("/{id}", func() {
@@ -1460,27 +1474,11 @@ func Routes() *web.Route {
                                m.Combo("/{username}").Get(reqToken(), org.IsMember).
                                        Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
                        })
-                       m.Group("/actions", func() {
-                               m.Group("/secrets", func() {
-                                       m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
-                                       m.Combo("/{secretname}").
-                                               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)
-                               })
-                       })
+                       addActionsRoutes(
+                               m,
+                               reqOrgOwnership(),
+                               org.NewAction(),
+                       )
                        m.Group("/public_members", func() {
                                m.Get("", org.ListPublicMembers)
                                m.Combo("/{username}").Get(org.IsPublicMember).
diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go
new file mode 100644 (file)
index 0000000..03a1fa8
--- /dev/null
@@ -0,0 +1,473 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+       "errors"
+       "net/http"
+
+       actions_model "code.gitea.io/gitea/models/actions"
+       "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/shared"
+       "code.gitea.io/gitea/routers/api/v1/utils"
+       actions_service "code.gitea.io/gitea/services/actions"
+       "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
+func (Action) ListVariables(ctx *context.APIContext) {
+       // swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
+       // ---
+       // summary: Get an org-level variables list
+       // 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/VariableList"
+       //   "400":
+       //     "$ref": "#/responses/error"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+
+       vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
+               OwnerID:     ctx.Org.Organization.ID,
+               ListOptions: utils.GetListOptions(ctx),
+       })
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "FindVariables", err)
+               return
+       }
+
+       variables := make([]*api.ActionVariable, len(vars))
+       for i, v := range vars {
+               variables[i] = &api.ActionVariable{
+                       OwnerID: v.OwnerID,
+                       RepoID:  v.RepoID,
+                       Name:    v.Name,
+                       Data:    v.Data,
+               }
+       }
+
+       ctx.SetTotalCountHeader(count)
+       ctx.JSON(http.StatusOK, variables)
+}
+
+// GetVariable get an org-level variable
+func (Action) GetVariable(ctx *context.APIContext) {
+       // swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
+       // ---
+       // summary: Get an org-level variable
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: org
+       //   in: path
+       //   description: name of the organization
+       //   type: string
+       //   required: true
+       // - name: variablename
+       //   in: path
+       //   description: name of the variable
+       //   type: string
+       //   required: true
+       // responses:
+       //   "200":
+       //               "$ref": "#/responses/ActionVariable"
+       //   "400":
+       //     "$ref": "#/responses/error"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+
+       v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
+               OwnerID: ctx.Org.Organization.ID,
+               Name:    ctx.Params("variablename"),
+       })
+       if err != nil {
+               if errors.Is(err, util.ErrNotExist) {
+                       ctx.Error(http.StatusNotFound, "GetVariable", err)
+               } else {
+                       ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+               }
+               return
+       }
+
+       variable := &api.ActionVariable{
+               OwnerID: v.OwnerID,
+               RepoID:  v.RepoID,
+               Name:    v.Name,
+               Data:    v.Data,
+       }
+
+       ctx.JSON(http.StatusOK, variable)
+}
+
+// DeleteVariable delete an org-level variable
+func (Action) DeleteVariable(ctx *context.APIContext) {
+       // swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
+       // ---
+       // summary: Delete an org-level variable
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: org
+       //   in: path
+       //   description: name of the organization
+       //   type: string
+       //   required: true
+       // - name: variablename
+       //   in: path
+       //   description: name of the variable
+       //   type: string
+       //   required: true
+       // responses:
+       //   "200":
+       //                      "$ref": "#/responses/ActionVariable"
+       //   "201":
+       //     description: response when deleting a variable
+       //   "204":
+       //     description: response when deleting a variable
+       //   "400":
+       //     "$ref": "#/responses/error"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+
+       if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil {
+               if errors.Is(err, util.ErrInvalidArgument) {
+                       ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
+               } else if errors.Is(err, util.ErrNotExist) {
+                       ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
+               } else {
+                       ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
+               }
+               return
+       }
+
+       ctx.Status(http.StatusNoContent)
+}
+
+// CreateVariable create an org-level variable
+func (Action) CreateVariable(ctx *context.APIContext) {
+       // swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
+       // ---
+       // summary: Create an org-level variable
+       // consumes:
+       // - application/json
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: org
+       //   in: path
+       //   description: name of the organization
+       //   type: string
+       //   required: true
+       // - name: variablename
+       //   in: path
+       //   description: name of the variable
+       //   type: string
+       //   required: true
+       // - name: body
+       //   in: body
+       //   schema:
+       //     "$ref": "#/definitions/CreateVariableOption"
+       // responses:
+       //   "201":
+       //     description: response when creating an org-level variable
+       //   "204":
+       //     description: response when creating an org-level variable
+       //   "400":
+       //     "$ref": "#/responses/error"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+
+       opt := web.GetForm(ctx).(*api.CreateVariableOption)
+
+       ownerID := ctx.Org.Organization.ID
+       variableName := ctx.Params("variablename")
+
+       v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
+               OwnerID: ownerID,
+               Name:    variableName,
+       })
+       if err != nil && !errors.Is(err, util.ErrNotExist) {
+               ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+               return
+       }
+       if v != nil && v.ID > 0 {
+               ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
+               return
+       }
+
+       if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
+               if errors.Is(err, util.ErrInvalidArgument) {
+                       ctx.Error(http.StatusBadRequest, "CreateVariable", err)
+               } else {
+                       ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
+               }
+               return
+       }
+
+       ctx.Status(http.StatusNoContent)
+}
+
+// UpdateVariable update an org-level variable
+func (Action) UpdateVariable(ctx *context.APIContext) {
+       // swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
+       // ---
+       // summary: Update an org-level variable
+       // consumes:
+       // - application/json
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: org
+       //   in: path
+       //   description: name of the organization
+       //   type: string
+       //   required: true
+       // - name: variablename
+       //   in: path
+       //   description: name of the variable
+       //   type: string
+       //   required: true
+       // - name: body
+       //   in: body
+       //   schema:
+       //     "$ref": "#/definitions/UpdateVariableOption"
+       // responses:
+       //   "201":
+       //     description: response when updating an org-level variable
+       //   "204":
+       //     description: response when updating an org-level variable
+       //   "400":
+       //     "$ref": "#/responses/error"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+
+       opt := web.GetForm(ctx).(*api.UpdateVariableOption)
+
+       v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
+               OwnerID: ctx.Org.Organization.ID,
+               Name:    ctx.Params("variablename"),
+       })
+       if err != nil {
+               if errors.Is(err, util.ErrNotExist) {
+                       ctx.Error(http.StatusNotFound, "GetVariable", err)
+               } else {
+                       ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+               }
+               return
+       }
+
+       if opt.Name == "" {
+               opt.Name = ctx.Params("variablename")
+       }
+       if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
+               if errors.Is(err, util.ErrInvalidArgument) {
+                       ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
+               } else {
+                       ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
+               }
+               return
+       }
+
+       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{}
+}
diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go
deleted file mode 100644 (file)
index 2a52bd8..0000000
+++ /dev/null
@@ -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)
-}
diff --git a/routers/api/v1/org/secrets.go b/routers/api/v1/org/secrets.go
deleted file mode 100644 (file)
index abb6bb2..0000000
+++ /dev/null
@@ -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)
-}
diff --git a/routers/api/v1/org/variables.go b/routers/api/v1/org/variables.go
deleted file mode 100644 (file)
index eaf7bdc..0000000
+++ /dev/null
@@ -1,291 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package org
-
-import (
-       "errors"
-       "net/http"
-
-       actions_model "code.gitea.io/gitea/models/actions"
-       "code.gitea.io/gitea/models/db"
-       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"
-       actions_service "code.gitea.io/gitea/services/actions"
-       "code.gitea.io/gitea/services/context"
-)
-
-// ListVariables list org-level variables
-func ListVariables(ctx *context.APIContext) {
-       // swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
-       // ---
-       // summary: Get an org-level variables list
-       // 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/VariableList"
-       //   "400":
-       //     "$ref": "#/responses/error"
-       //   "404":
-       //     "$ref": "#/responses/notFound"
-
-       vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
-               OwnerID:     ctx.Org.Organization.ID,
-               ListOptions: utils.GetListOptions(ctx),
-       })
-       if err != nil {
-               ctx.Error(http.StatusInternalServerError, "FindVariables", err)
-               return
-       }
-
-       variables := make([]*api.ActionVariable, len(vars))
-       for i, v := range vars {
-               variables[i] = &api.ActionVariable{
-                       OwnerID: v.OwnerID,
-                       RepoID:  v.RepoID,
-                       Name:    v.Name,
-                       Data:    v.Data,
-               }
-       }
-
-       ctx.SetTotalCountHeader(count)
-       ctx.JSON(http.StatusOK, variables)
-}
-
-// GetVariable get an org-level variable
-func GetVariable(ctx *context.APIContext) {
-       // swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
-       // ---
-       // summary: Get an org-level variable
-       // produces:
-       // - application/json
-       // parameters:
-       // - name: org
-       //   in: path
-       //   description: name of the organization
-       //   type: string
-       //   required: true
-       // - name: variablename
-       //   in: path
-       //   description: name of the variable
-       //   type: string
-       //   required: true
-       // responses:
-       //   "200":
-       //               "$ref": "#/responses/ActionVariable"
-       //   "400":
-       //     "$ref": "#/responses/error"
-       //   "404":
-       //     "$ref": "#/responses/notFound"
-
-       v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
-               OwnerID: ctx.Org.Organization.ID,
-               Name:    ctx.Params("variablename"),
-       })
-       if err != nil {
-               if errors.Is(err, util.ErrNotExist) {
-                       ctx.Error(http.StatusNotFound, "GetVariable", err)
-               } else {
-                       ctx.Error(http.StatusInternalServerError, "GetVariable", err)
-               }
-               return
-       }
-
-       variable := &api.ActionVariable{
-               OwnerID: v.OwnerID,
-               RepoID:  v.RepoID,
-               Name:    v.Name,
-               Data:    v.Data,
-       }
-
-       ctx.JSON(http.StatusOK, variable)
-}
-
-// DeleteVariable delete an org-level variable
-func DeleteVariable(ctx *context.APIContext) {
-       // swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
-       // ---
-       // summary: Delete an org-level variable
-       // produces:
-       // - application/json
-       // parameters:
-       // - name: org
-       //   in: path
-       //   description: name of the organization
-       //   type: string
-       //   required: true
-       // - name: variablename
-       //   in: path
-       //   description: name of the variable
-       //   type: string
-       //   required: true
-       // responses:
-       //   "200":
-       //                      "$ref": "#/responses/ActionVariable"
-       //   "201":
-       //     description: response when deleting a variable
-       //   "204":
-       //     description: response when deleting a variable
-       //   "400":
-       //     "$ref": "#/responses/error"
-       //   "404":
-       //     "$ref": "#/responses/notFound"
-
-       if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil {
-               if errors.Is(err, util.ErrInvalidArgument) {
-                       ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
-               } else if errors.Is(err, util.ErrNotExist) {
-                       ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
-               } else {
-                       ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
-               }
-               return
-       }
-
-       ctx.Status(http.StatusNoContent)
-}
-
-// CreateVariable create an org-level variable
-func CreateVariable(ctx *context.APIContext) {
-       // swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
-       // ---
-       // summary: Create an org-level variable
-       // consumes:
-       // - application/json
-       // produces:
-       // - application/json
-       // parameters:
-       // - name: org
-       //   in: path
-       //   description: name of the organization
-       //   type: string
-       //   required: true
-       // - name: variablename
-       //   in: path
-       //   description: name of the variable
-       //   type: string
-       //   required: true
-       // - name: body
-       //   in: body
-       //   schema:
-       //     "$ref": "#/definitions/CreateVariableOption"
-       // responses:
-       //   "201":
-       //     description: response when creating an org-level variable
-       //   "204":
-       //     description: response when creating an org-level variable
-       //   "400":
-       //     "$ref": "#/responses/error"
-       //   "404":
-       //     "$ref": "#/responses/notFound"
-
-       opt := web.GetForm(ctx).(*api.CreateVariableOption)
-
-       ownerID := ctx.Org.Organization.ID
-       variableName := ctx.Params("variablename")
-
-       v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
-               OwnerID: ownerID,
-               Name:    variableName,
-       })
-       if err != nil && !errors.Is(err, util.ErrNotExist) {
-               ctx.Error(http.StatusInternalServerError, "GetVariable", err)
-               return
-       }
-       if v != nil && v.ID > 0 {
-               ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
-               return
-       }
-
-       if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
-               if errors.Is(err, util.ErrInvalidArgument) {
-                       ctx.Error(http.StatusBadRequest, "CreateVariable", err)
-               } else {
-                       ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
-               }
-               return
-       }
-
-       ctx.Status(http.StatusNoContent)
-}
-
-// UpdateVariable update an org-level variable
-func UpdateVariable(ctx *context.APIContext) {
-       // swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
-       // ---
-       // summary: Update an org-level variable
-       // consumes:
-       // - application/json
-       // produces:
-       // - application/json
-       // parameters:
-       // - name: org
-       //   in: path
-       //   description: name of the organization
-       //   type: string
-       //   required: true
-       // - name: variablename
-       //   in: path
-       //   description: name of the variable
-       //   type: string
-       //   required: true
-       // - name: body
-       //   in: body
-       //   schema:
-       //     "$ref": "#/definitions/UpdateVariableOption"
-       // responses:
-       //   "201":
-       //     description: response when updating an org-level variable
-       //   "204":
-       //     description: response when updating an org-level variable
-       //   "400":
-       //     "$ref": "#/responses/error"
-       //   "404":
-       //     "$ref": "#/responses/notFound"
-
-       opt := web.GetForm(ctx).(*api.UpdateVariableOption)
-
-       v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
-               OwnerID: ctx.Org.Organization.ID,
-               Name:    ctx.Params("variablename"),
-       })
-       if err != nil {
-               if errors.Is(err, util.ErrNotExist) {
-                       ctx.Error(http.StatusNotFound, "GetVariable", err)
-               } else {
-                       ctx.Error(http.StatusInternalServerError, "GetVariable", err)
-               }
-               return
-       }
-
-       if opt.Name == "" {
-               opt.Name = ctx.Params("variablename")
-       }
-       if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
-               if errors.Is(err, util.ErrInvalidArgument) {
-                       ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
-               } else {
-                       ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
-               }
-               return
-       }
-
-       ctx.Status(http.StatusNoContent)
-}
index 03321d956d7cbc14a63056535ea2fca133bc3e1d..311cfca6e91ba01c94037f7d38ea5c2d9eeb885b 100644 (file)
@@ -9,17 +9,76 @@ import (
 
        actions_model "code.gitea.io/gitea/models/actions"
        "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/shared"
        "code.gitea.io/gitea/routers/api/v1/utils"
        actions_service "code.gitea.io/gitea/services/actions"
        "code.gitea.io/gitea/services/context"
        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
-func CreateOrUpdateSecret(ctx *context.APIContext) {
+func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
        // swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret
        // ---
        // 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
-func DeleteSecret(ctx *context.APIContext) {
+func (Action) DeleteSecret(ctx *context.APIContext) {
        // swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret
        // ---
        // summary: Delete a secret in a repository
@@ -133,7 +192,7 @@ func DeleteSecret(ctx *context.APIContext) {
 }
 
 // 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
        // ---
        // summary: Get a repo-level variable
@@ -186,7 +245,7 @@ func GetVariable(ctx *context.APIContext) {
 }
 
 // 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
        // ---
        // summary: Delete a repo-level variable
@@ -235,7 +294,7 @@ func DeleteVariable(ctx *context.APIContext) {
 }
 
 // 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
        // ---
        // summary: Create a repo-level variable
@@ -302,7 +361,7 @@ func CreateVariable(ctx *context.APIContext) {
 }
 
 // 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
        // ---
        // summary: Update a repo-level variable
@@ -369,7 +428,7 @@ func UpdateVariable(ctx *context.APIContext) {
 }
 
 // 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
        // ---
        // summary: Get repo-level variables list
@@ -423,3 +482,38 @@ func ListVariables(ctx *context.APIContext) {
        ctx.SetTotalCountHeader(count)
        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{}
+}
diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go
deleted file mode 100644 (file)
index fe133b3..0000000
+++ /dev/null
@@ -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)
-}
diff --git a/services/actions/interface.go b/services/actions/interface.go
new file mode 100644 (file)
index 0000000..d4fa782
--- /dev/null
@@ -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)
+}
index faf57454d7a62646eceacf9508adabedd8ddf620..3ed4e43e6d8965c372d8c0fbceed9fbec5120c17 100644 (file)
         }
       }
     },
+    "/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}": {
       "put": {
         "consumes": [
index feb9bae2b22c7915a6c086c4bf1331df82e5d244..c3074d9eceedd0957e2174a767b6c8591f28195c 100644 (file)
@@ -24,6 +24,12 @@ func TestAPIRepoSecrets(t *testing.T) {
        session := loginUser(t, user.Name)
        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) {
                cases := []struct {
                        Name           string
@@ -31,7 +37,7 @@ func TestAPIRepoSecrets(t *testing.T) {
                }{
                        {
                                Name:           "",
-                               ExpectedStatus: http.StatusNotFound,
+                               ExpectedStatus: http.StatusMethodNotAllowed,
                        },
                        {
                                Name:           "-",