feat(API): add route and implementation for creating/updating repository secret (#26766)

spec:
https://docs.github.com/en/rest/actions/secrets?apiVersion=2022-11-28#create-or-update-a-repository-secret

- Add a new route for creating or updating a secret value in a
repository
- Create a new file `routers/api/v1/repo/action.go` with the
implementation of the `CreateOrUpdateSecret` function
- Update the Swagger documentation for the `updateRepoSecret` operation
in the `v1_json.tmpl` template file

---------

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
Bo-Yi Wu 2023-08-30 04:54:49 +08:00 committed by GitHub
parent 2d9249b6d9
commit b91057b172
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 174 additions and 17 deletions

View file

@ -160,3 +160,31 @@ func DeleteSecret(ctx context.Context, orgID, repoID int64, name string) error {
return nil return nil
} }
// CreateOrUpdateSecret creates or updates a secret and returns true if it was created
func CreateOrUpdateSecret(ctx context.Context, orgID, repoID int64, name, data string) (bool, error) {
sc := new(Secret)
name = strings.ToUpper(name)
has, err := db.GetEngine(ctx).
Where("owner_id=?", orgID).
And("repo_id=?", repoID).
And("name=?", name).
Get(sc)
if err != nil {
return false, err
}
if !has {
_, err = InsertEncryptedSecret(ctx, orgID, repoID, name, data)
if err != nil {
return false, err
}
return true, nil
}
if err := UpdateSecret(ctx, orgID, repoID, name, data); err != nil {
return false, err
}
return false, nil
}

View file

@ -933,6 +933,10 @@ 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/secrets", func() {
m.Combo("/{secretname}").
Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret)
})
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() {
@ -1301,7 +1305,7 @@ func Routes() *web.Route {
m.Group("/actions/secrets", func() { m.Group("/actions/secrets", func() {
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets) m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
m.Combo("/{secretname}"). m.Combo("/{secretname}").
Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateOrgSecret). Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
Delete(reqToken(), reqOrgOwnership(), org.DeleteOrgSecret) Delete(reqToken(), reqOrgOwnership(), org.DeleteOrgSecret)
}) })
m.Group("/public_members", func() { m.Group("/public_members", func() {

View file

@ -74,7 +74,7 @@ func listActionsSecrets(ctx *context.APIContext) {
} }
// create or update one secret of the organization // create or update one secret of the organization
func CreateOrUpdateOrgSecret(ctx *context.APIContext) { func CreateOrUpdateSecret(ctx *context.APIContext) {
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret // swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
// --- // ---
// summary: Create or Update a secret value in an organization // summary: Create or Update a secret value in an organization
@ -108,26 +108,17 @@ func CreateOrUpdateOrgSecret(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
secretName := ctx.Params(":secretname") secretName := ctx.Params(":secretname")
if err := actions.NameRegexMatch(secretName); err != nil { if err := actions.NameRegexMatch(secretName); err != nil {
ctx.Error(http.StatusBadRequest, "CreateOrUpdateOrgSecret", err) ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
return return
} }
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption) opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
err := secret_model.UpdateSecret( isCreated, err := secret_model.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, secretName, opt.Data)
ctx, ctx.Org.Organization.ID, 0, secretName, opt.Data, if err != nil {
) ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
if secret_model.IsErrSecretNotFound(err) {
_, err := secret_model.InsertEncryptedSecret(
ctx, ctx.Org.Organization.ID, 0, secretName, actions.ReserveLineBreakForTextarea(opt.Data),
)
if err != nil {
ctx.Error(http.StatusInternalServerError, "InsertEncryptedSecret", err)
return
}
ctx.Status(http.StatusCreated)
return return
} }
if err != nil { if isCreated {
ctx.Error(http.StatusInternalServerError, "UpdateSecret", err) ctx.Status(http.StatusCreated)
return return
} }

View file

@ -0,0 +1,75 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"net/http"
secret_model "code.gitea.io/gitea/models/secret"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/shared/actions"
)
// create or update one secret of the repository
func 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
// consumes:
// - application/json
// 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: 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"
// "403":
// "$ref": "#/responses/forbidden"
owner := ctx.Repo.Owner
repo := ctx.Repo.Repository
secretName := ctx.Params(":secretname")
if err := actions.NameRegexMatch(secretName); err != nil {
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
return
}
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
isCreated, err := secret_model.CreateOrUpdateSecret(ctx, owner.ID, repo.ID, secretName, opt.Data)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
return
}
if isCreated {
ctx.Status(http.StatusCreated)
return
}
ctx.Status(http.StatusNoContent)
}

View file

@ -3230,6 +3230,65 @@
} }
} }
}, },
"/repos/{owner}/{repo}/actions/secrets/{secretname}": {
"put": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Create or Update a secret value in a repository",
"operationId": "updateRepoSecret",
"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": "string",
"description": "name of the secret",
"name": "secretname",
"in": "path",
"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"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/repos/{owner}/{repo}/activities/feeds": { "/repos/{owner}/{repo}/activities/feeds": {
"get": { "get": {
"produces": [ "produces": [