diff options
author | sillyguodong <33891828+sillyguodong@users.noreply.github.com> | 2024-03-28 21:40:35 +0100 |
---|---|---|
committer | Gergely Nagy <forgejo@gergo.csillger.hu> | 2024-04-07 10:57:53 +0200 |
commit | 16696a42f557dd65f335a44f55881d27a3247f97 (patch) | |
tree | 1b7fd5fcd4a0a7f80d379b8a8cea949bf11ce1d8 /routers | |
parent | Fix `DEFAULT_SHOW_FULL_NAME=false` has no effect in commit list and commit gr... (diff) | |
download | forgejo-16696a42f557dd65f335a44f55881d27a3247f97.tar.xz forgejo-16696a42f557dd65f335a44f55881d27a3247f97.zip |
Add API for `Variables` (#29520)
close #27801
---------
Co-authored-by: silverwind <me@silverwind.io>
(cherry picked from commit 62b073e6f31645e446c7e8d6b5a506f61b47924e)
Conflicts:
- modules/util/util.go
Trivial resolution, only picking the newly introduced function
- routers/api/v1/swagger/options.go
Trivial resolution. We don't have UserBadges, don't pick that part.
- templates/swagger/v1_json.tmpl
Regenerated.
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/v1/api.go | 27 | ||||
-rw-r--r-- | routers/api/v1/org/variables.go | 291 | ||||
-rw-r--r-- | routers/api/v1/repo/action.go | 296 | ||||
-rw-r--r-- | routers/api/v1/swagger/action.go | 14 | ||||
-rw-r--r-- | routers/api/v1/swagger/options.go | 6 | ||||
-rw-r--r-- | routers/api/v1/user/action.go | 250 | ||||
-rw-r--r-- | routers/web/shared/actions/variables.go | 67 | ||||
-rw-r--r-- | routers/web/shared/secrets/secrets.go | 4 |
8 files changed, 893 insertions, 62 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index b202e32e4e..80f2243ef0 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -871,6 +871,15 @@ func Routes() *web.Route { Delete(user.DeleteSecret) }) + m.Group("/variables", func() { + m.Get("", user.ListVariables) + m.Combo("/{variablename}"). + Get(user.GetVariable). + Delete(user.DeleteVariable). + Post(bind(api.CreateVariableOption{}), user.CreateVariable). + Put(bind(api.UpdateVariableOption{}), user.UpdateVariable) + }) + m.Group("/runners", func() { m.Get("/registration-token", reqToken(), user.GetRegistrationToken) }) @@ -990,6 +999,15 @@ func Routes() *web.Route { 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) }) @@ -1393,6 +1411,15 @@ func Routes() *web.Route { 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) }) diff --git a/routers/api/v1/org/variables.go b/routers/api/v1/org/variables.go new file mode 100644 index 0000000000..eaf7bdc45b --- /dev/null +++ b/routers/api/v1/org/variables.go @@ -0,0 +1,291 @@ +// 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) +} diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index e0af276c71..03321d956d 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -7,9 +7,13 @@ 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" secret_service "code.gitea.io/gitea/services/secrets" ) @@ -127,3 +131,295 @@ func DeleteSecret(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } + +// GetVariable get a repo-level variable +func GetVariable(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable + // --- + // summary: Get a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // 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{ + RepoID: ctx.Repo.Repository.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 a repo-level variable +func DeleteVariable(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable + // --- + // summary: Delete a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // 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, 0, ctx.Repo.Repository.ID, 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 a repo-level variable +func CreateVariable(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable + // --- + // summary: Create a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // 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 a repo-level variable + // "204": + // description: response when creating a repo-level variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.CreateVariableOption) + + repoID := ctx.Repo.Repository.ID + variableName := ctx.Params("variablename") + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + RepoID: repoID, + 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, 0, repoID, 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 a repo-level variable +func UpdateVariable(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable + // --- + // summary: Update a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // 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 a repo-level variable + // "204": + // description: response when updating a repo-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{ + RepoID: ctx.Repo.Repository.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) +} + +// ListVariables list repo-level variables +func ListVariables(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList + // --- + // summary: Get repo-level variables list + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // 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/VariableList" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ + RepoID: ctx.Repo.Repository.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, + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, variables) +} diff --git a/routers/api/v1/swagger/action.go b/routers/api/v1/swagger/action.go index 3771780718..665f4d0b85 100644 --- a/routers/api/v1/swagger/action.go +++ b/routers/api/v1/swagger/action.go @@ -18,3 +18,17 @@ type swaggerResponseSecret struct { // in:body Body api.Secret `json:"body"` } + +// ActionVariable +// swagger:response ActionVariable +type swaggerResponseActionVariable struct { + // in:body + Body api.ActionVariable `json:"body"` +} + +// VariableList +// swagger:response VariableList +type swaggerResponseVariableList struct { + // in:body + Body []api.ActionVariable `json:"body"` +} diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 2886b865e8..6a94c21002 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -199,4 +199,10 @@ type swaggerParameterBodies struct { // in:body CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption + + // in:body + CreateVariableOption api.CreateVariableOption + + // in:body + UpdateVariableOption api.UpdateVariableOption } diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go index babb8c0cf7..bf78c2c864 100644 --- a/routers/api/v1/user/action.go +++ b/routers/api/v1/user/action.go @@ -7,9 +7,13 @@ 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" secret_service "code.gitea.io/gitea/services/secrets" ) @@ -101,3 +105,249 @@ func DeleteSecret(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } + +// CreateVariable create a user-level variable +func CreateVariable(ctx *context.APIContext) { + // swagger:operation POST /user/actions/variables/{variablename} user createUserVariable + // --- + // summary: Create a user-level variable + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - 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 a variable + // "204": + // description: response when creating a variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.CreateVariableOption) + + ownerID := ctx.Doer.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 a user-level variable which is created by current doer +func UpdateVariable(ctx *context.APIContext) { + // swagger:operation PUT /user/actions/variables/{variablename} user updateUserVariable + // --- + // summary: Update a user-level variable which is created by current doer + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - 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 a variable + // "204": + // description: response when updating a 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.Doer.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) +} + +// DeleteVariable delete a user-level variable which is created by current doer +func DeleteVariable(ctx *context.APIContext) { + // swagger:operation DELETE /user/actions/variables/{variablename} user deleteUserVariable + // --- + // summary: Delete a user-level variable which is created by current doer + // produces: + // - application/json + // parameters: + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // responses: + // "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.Doer.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) +} + +// GetVariable get a user-level variable which is created by current doer +func GetVariable(ctx *context.APIContext) { + // swagger:operation GET /user/actions/variables/{variablename} user getUserVariable + // --- + // summary: Get a user-level variable which is created by current doer + // produces: + // - application/json + // parameters: + // - 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.Doer.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) +} + +// ListVariables list user-level variables +func ListVariables(ctx *context.APIContext) { + // swagger:operation GET /user/actions/variables user getUserVariablesList + // --- + // summary: Get the user-level list of variables which is created by current doer + // produces: + // - application/json + // parameters: + // - 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.Doer.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) +} diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go index 0f705399c9..79c03e4e8c 100644 --- a/routers/web/shared/actions/variables.go +++ b/routers/web/shared/actions/variables.go @@ -4,17 +4,13 @@ package actions import ( - "errors" - "regexp" - "strings" - actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/web" + actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" - secret_service "code.gitea.io/gitea/services/secrets" ) func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { @@ -29,41 +25,16 @@ func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { ctx.Data["Variables"] = variables } -// some regular expression of `variables` and `secrets` -// reference to: -// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables -// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets -var ( - forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI") -) - -func envNameCIRegexMatch(name string) error { - if forbiddenEnvNameCIRx.MatchString(name) { - log.Error("Env Name cannot be ci") - return errors.New("env name cannot be ci") - } - return nil -} - func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) { form := web.GetForm(ctx).(*forms.EditVariableForm) - if err := secret_service.ValidateName(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - if err := envNameCIRegexMatch(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - v, err := actions_model.InsertVariable(ctx, ownerID, repoID, form.Name, ReserveLineBreakForTextarea(form.Data)) + v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data) if err != nil { - log.Error("InsertVariable error: %v", err) + log.Error("CreateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } + ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name)) ctx.JSONRedirect(redirectURL) } @@ -72,23 +43,8 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { id := ctx.ParamsInt64(":variable_id") form := web.GetForm(ctx).(*forms.EditVariableForm) - if err := secret_service.ValidateName(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - if err := envNameCIRegexMatch(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - ok, err := actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{ - ID: id, - Name: strings.ToUpper(form.Name), - Data: ReserveLineBreakForTextarea(form.Data), - }) - if err != nil || !ok { - log.Error("UpdateVariable error: %v", err) + if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok { + log.Error("UpdateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.update.failed")) return } @@ -99,7 +55,7 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { func DeleteVariable(ctx *context.Context, redirectURL string) { id := ctx.ParamsInt64(":variable_id") - if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil { + if err := actions_service.DeleteVariableByID(ctx, id); err != nil { log.Error("Delete variable [%d] failed: %v", id, err) ctx.JSONError(ctx.Tr("actions.variables.deletion.failed")) return @@ -107,12 +63,3 @@ func DeleteVariable(ctx *context.Context, redirectURL string) { ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success")) ctx.JSONRedirect(redirectURL) } - -func ReserveLineBreakForTextarea(input string) string { - // Since the content is from a form which is a textarea, the line endings are \r\n. - // It's a standard behavior of HTML. - // But we want to store them as \n like what GitHub does. - // And users are unlikely to really need to keep the \r. - // Other than this, we should respect the original content, even leading or trailing spaces. - return strings.ReplaceAll(input, "\r\n", "\n") -} diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go index 73505ec372..3bd421f86a 100644 --- a/routers/web/shared/secrets/secrets.go +++ b/routers/web/shared/secrets/secrets.go @@ -7,8 +7,8 @@ import ( "code.gitea.io/gitea/models/db" secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/routers/web/shared/actions" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" secret_service "code.gitea.io/gitea/services/secrets" @@ -27,7 +27,7 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) { func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) { form := web.GetForm(ctx).(*forms.AddSecretForm) - s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, actions.ReserveLineBreakForTextarea(form.Data)) + s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data)) if err != nil { log.Error("CreateOrUpdateSecret failed: %v", err) ctx.JSONError(ctx.Tr("secrets.creation.failed")) |