From b91057b172dea07a9db1bf96a32d2ab25a0e030d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 30 Aug 2023 04:54:49 +0800 Subject: [PATCH] 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 Co-authored-by: Giteabot --- models/secret/secret.go | 28 +++++++++++++ routers/api/v1/api.go | 6 ++- routers/api/v1/org/action.go | 23 ++++------- routers/api/v1/repo/action.go | 75 ++++++++++++++++++++++++++++++++++ templates/swagger/v1_json.tmpl | 59 ++++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 routers/api/v1/repo/action.go diff --git a/models/secret/secret.go b/models/secret/secret.go index 410cb3770eb..1cb816e9db2 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -160,3 +160,31 @@ func DeleteSecret(ctx context.Context, orgID, repoID int64, name string) error { 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 +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 6424931a473..32e5a10bbe7 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -933,6 +933,10 @@ func Routes() *web.Route { m.Post("/accept", repo.AcceptTransfer) m.Post("/reject", repo.RejectTransfer) }, reqToken()) + m.Group("/actions/secrets", func() { + m.Combo("/{secretname}"). + Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret) + }) m.Group("/hooks/git", func() { m.Combo("").Get(repo.ListGitHooks) m.Group("/{id}", func() { @@ -1301,7 +1305,7 @@ func Routes() *web.Route { m.Group("/actions/secrets", func() { m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets) m.Combo("/{secretname}"). - Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateOrgSecret). + Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret). Delete(reqToken(), reqOrgOwnership(), org.DeleteOrgSecret) }) m.Group("/public_members", func() { diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go index ee18cca26de..0bf741e8256 100644 --- a/routers/api/v1/org/action.go +++ b/routers/api/v1/org/action.go @@ -74,7 +74,7 @@ func listActionsSecrets(ctx *context.APIContext) { } // 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 // --- // summary: Create or Update a secret value in an organization @@ -108,26 +108,17 @@ func CreateOrUpdateOrgSecret(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" secretName := ctx.Params(":secretname") if err := actions.NameRegexMatch(secretName); err != nil { - ctx.Error(http.StatusBadRequest, "CreateOrUpdateOrgSecret", err) + ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err) return } opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption) - err := secret_model.UpdateSecret( - ctx, ctx.Org.Organization.ID, 0, secretName, opt.Data, - ) - 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) + isCreated, err := secret_model.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, secretName, opt.Data) + if err != nil { + ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err) return } - if err != nil { - ctx.Error(http.StatusInternalServerError, "UpdateSecret", err) + if isCreated { + ctx.Status(http.StatusCreated) return } diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go new file mode 100644 index 00000000000..015c731a75d --- /dev/null +++ b/routers/api/v1/repo/action.go @@ -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) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index d37f4463f55..78491de2e10 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -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": { "get": { "produces": [