close #27801 --------- Co-authored-by: silverwind <me@silverwind.io>tags/v1.22.0-rc1
import ( | import ( | ||||
"context" | "context" | ||||
"errors" | "errors" | ||||
"fmt" | |||||
"strings" | "strings" | ||||
"code.gitea.io/gitea/models/db" | "code.gitea.io/gitea/models/db" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/modules/timeutil" | "code.gitea.io/gitea/modules/timeutil" | ||||
"code.gitea.io/gitea/modules/util" | |||||
"xorm.io/builder" | "xorm.io/builder" | ||||
) | ) | ||||
db.ListOptions | db.ListOptions | ||||
OwnerID int64 | OwnerID int64 | ||||
RepoID int64 | RepoID int64 | ||||
Name string | |||||
} | } | ||||
func (opts FindVariablesOpts) ToConds() builder.Cond { | func (opts FindVariablesOpts) ToConds() builder.Cond { | ||||
cond := builder.NewCond() | cond := builder.NewCond() | ||||
// Since we now support instance-level variables, | |||||
// there is no need to check for null values for `owner_id` and `repo_id` | |||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) | cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) | ||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) | cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) | ||||
if opts.Name != "" { | |||||
cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)}) | |||||
} | |||||
return cond | return cond | ||||
} | } | ||||
func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) { | |||||
var variable ActionVariable | |||||
has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable) | |||||
if err != nil { | |||||
return nil, err | |||||
} else if !has { | |||||
return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist) | |||||
} | |||||
return &variable, nil | |||||
func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) { | |||||
return db.Find[ActionVariable](ctx, opts) | |||||
} | } | ||||
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) { | func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) { | ||||
return count != 0, err | return count != 0, err | ||||
} | } | ||||
func DeleteVariable(ctx context.Context, id int64) error { | |||||
if _, err := db.DeleteByID[ActionVariable](ctx, id); err != nil { | |||||
return err | |||||
} | |||||
return nil | |||||
} | |||||
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { | func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { | ||||
variables := map[string]string{} | variables := map[string]string{} | ||||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||||
// SPDX-License-Identifier: MIT | |||||
package structs | |||||
// CreateVariableOption the option when creating variable | |||||
// swagger:model | |||||
type CreateVariableOption struct { | |||||
// Value of the variable to create | |||||
// | |||||
// required: true | |||||
Value string `json:"value" binding:"Required"` | |||||
} | |||||
// UpdateVariableOption the option when updating variable | |||||
// swagger:model | |||||
type UpdateVariableOption struct { | |||||
// New name for the variable. If the field is empty, the variable name won't be updated. | |||||
Name string `json:"name"` | |||||
// Value of the variable to update | |||||
// | |||||
// required: true | |||||
Value string `json:"value" binding:"Required"` | |||||
} | |||||
// ActionVariable return value of the query API | |||||
// swagger:model | |||||
type ActionVariable struct { | |||||
// the owner to which the variable belongs | |||||
OwnerID int64 `json:"owner_id"` | |||||
// the repository to which the variable belongs | |||||
RepoID int64 `json:"repo_id"` | |||||
// the name of the variable | |||||
Name string `json:"name"` | |||||
// the value of the variable | |||||
Data string `json:"data"` | |||||
} |
} | } | ||||
return v | return v | ||||
} | } | ||||
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") | |||||
} |
val123 := 123 | val123 := 123 | ||||
assert.False(t, &val123 == ToPointer(val123)) | assert.False(t, &val123 == ToPointer(val123)) | ||||
} | } | ||||
func TestReserveLineBreakForTextarea(t *testing.T) { | |||||
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata"), "test\ndata") | |||||
assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata\r\n"), "test\ndata\n") | |||||
} |
Delete(user.DeleteSecret) | 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.Group("/runners", func() { | ||||
m.Get("/registration-token", reqToken(), user.GetRegistrationToken) | m.Get("/registration-token", reqToken(), user.GetRegistrationToken) | ||||
}) | }) | ||||
Delete(reqToken(), reqOwner(), repo.DeleteSecret) | 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.Group("/runners", func() { | ||||
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken) | m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken) | ||||
}) | }) | ||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret) | 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.Group("/runners", func() { | ||||
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken) | m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken) | ||||
}) | }) |
// 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) | |||||
} |
"errors" | "errors" | ||||
"net/http" | "net/http" | ||||
actions_model "code.gitea.io/gitea/models/actions" | |||||
"code.gitea.io/gitea/models/db" | |||||
api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
"code.gitea.io/gitea/modules/util" | "code.gitea.io/gitea/modules/util" | ||||
"code.gitea.io/gitea/modules/web" | "code.gitea.io/gitea/modules/web" | ||||
"code.gitea.io/gitea/routers/api/v1/utils" | |||||
actions_service "code.gitea.io/gitea/services/actions" | |||||
"code.gitea.io/gitea/services/context" | "code.gitea.io/gitea/services/context" | ||||
secret_service "code.gitea.io/gitea/services/secrets" | secret_service "code.gitea.io/gitea/services/secrets" | ||||
) | ) | ||||
ctx.Status(http.StatusNoContent) | 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) | |||||
} |
// in:body | // in:body | ||||
Body api.Secret `json:"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"` | |||||
} |
// in:body | // in:body | ||||
UserBadgeOption api.UserBadgeOption | UserBadgeOption api.UserBadgeOption | ||||
// in:body | |||||
CreateVariableOption api.CreateVariableOption | |||||
// in:body | |||||
UpdateVariableOption api.UpdateVariableOption | |||||
} | } |
"errors" | "errors" | ||||
"net/http" | "net/http" | ||||
actions_model "code.gitea.io/gitea/models/actions" | |||||
"code.gitea.io/gitea/models/db" | |||||
api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
"code.gitea.io/gitea/modules/util" | "code.gitea.io/gitea/modules/util" | ||||
"code.gitea.io/gitea/modules/web" | "code.gitea.io/gitea/modules/web" | ||||
"code.gitea.io/gitea/routers/api/v1/utils" | |||||
actions_service "code.gitea.io/gitea/services/actions" | |||||
"code.gitea.io/gitea/services/context" | "code.gitea.io/gitea/services/context" | ||||
secret_service "code.gitea.io/gitea/services/secrets" | secret_service "code.gitea.io/gitea/services/secrets" | ||||
) | ) | ||||
ctx.Status(http.StatusNoContent) | 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) | |||||
} |
package actions | package actions | ||||
import ( | import ( | ||||
"errors" | |||||
"regexp" | |||||
"strings" | |||||
actions_model "code.gitea.io/gitea/models/actions" | actions_model "code.gitea.io/gitea/models/actions" | ||||
"code.gitea.io/gitea/models/db" | "code.gitea.io/gitea/models/db" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/modules/web" | "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/context" | ||||
"code.gitea.io/gitea/services/forms" | "code.gitea.io/gitea/services/forms" | ||||
secret_service "code.gitea.io/gitea/services/secrets" | |||||
) | ) | ||||
func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { | func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { | ||||
ctx.Data["Variables"] = variables | 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) { | func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) { | ||||
form := web.GetForm(ctx).(*forms.EditVariableForm) | 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 { | if err != nil { | ||||
log.Error("InsertVariable error: %v", err) | |||||
log.Error("CreateVariable: %v", err) | |||||
ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) | ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) | ||||
return | return | ||||
} | } | ||||
ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name)) | ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name)) | ||||
ctx.JSONRedirect(redirectURL) | ctx.JSONRedirect(redirectURL) | ||||
} | } | ||||
id := ctx.ParamsInt64(":variable_id") | id := ctx.ParamsInt64(":variable_id") | ||||
form := web.GetForm(ctx).(*forms.EditVariableForm) | 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")) | ctx.JSONError(ctx.Tr("actions.variables.update.failed")) | ||||
return | return | ||||
} | } | ||||
func DeleteVariable(ctx *context.Context, redirectURL string) { | func DeleteVariable(ctx *context.Context, redirectURL string) { | ||||
id := ctx.ParamsInt64(":variable_id") | 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) | log.Error("Delete variable [%d] failed: %v", id, err) | ||||
ctx.JSONError(ctx.Tr("actions.variables.deletion.failed")) | ctx.JSONError(ctx.Tr("actions.variables.deletion.failed")) | ||||
return | return | ||||
ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success")) | ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success")) | ||||
ctx.JSONRedirect(redirectURL) | 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") | |||||
} |
"code.gitea.io/gitea/models/db" | "code.gitea.io/gitea/models/db" | ||||
secret_model "code.gitea.io/gitea/models/secret" | secret_model "code.gitea.io/gitea/models/secret" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/modules/util" | |||||
"code.gitea.io/gitea/modules/web" | "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/context" | ||||
"code.gitea.io/gitea/services/forms" | "code.gitea.io/gitea/services/forms" | ||||
secret_service "code.gitea.io/gitea/services/secrets" | secret_service "code.gitea.io/gitea/services/secrets" | ||||
func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) { | func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) { | ||||
form := web.GetForm(ctx).(*forms.AddSecretForm) | 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 { | if err != nil { | ||||
log.Error("CreateOrUpdateSecret failed: %v", err) | log.Error("CreateOrUpdateSecret failed: %v", err) | ||||
ctx.JSONError(ctx.Tr("secrets.creation.failed")) | ctx.JSONError(ctx.Tr("secrets.creation.failed")) |
// Copyright 2024 The Gitea Authors. All rights reserved. | |||||
// SPDX-License-Identifier: MIT | |||||
package actions | |||||
import ( | |||||
"context" | |||||
"regexp" | |||||
"strings" | |||||
actions_model "code.gitea.io/gitea/models/actions" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/util" | |||||
secret_service "code.gitea.io/gitea/services/secrets" | |||||
) | |||||
func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*actions_model.ActionVariable, error) { | |||||
if err := secret_service.ValidateName(name); err != nil { | |||||
return nil, err | |||||
} | |||||
if err := envNameCIRegexMatch(name); err != nil { | |||||
return nil, err | |||||
} | |||||
v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data)) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return v, nil | |||||
} | |||||
func UpdateVariable(ctx context.Context, variableID int64, name, data string) (bool, error) { | |||||
if err := secret_service.ValidateName(name); err != nil { | |||||
return false, err | |||||
} | |||||
if err := envNameCIRegexMatch(name); err != nil { | |||||
return false, err | |||||
} | |||||
return actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{ | |||||
ID: variableID, | |||||
Name: strings.ToUpper(name), | |||||
Data: util.ReserveLineBreakForTextarea(data), | |||||
}) | |||||
} | |||||
func DeleteVariableByID(ctx context.Context, variableID int64) error { | |||||
return actions_model.DeleteVariable(ctx, variableID) | |||||
} | |||||
func DeleteVariableByName(ctx context.Context, ownerID, repoID int64, name string) error { | |||||
if err := secret_service.ValidateName(name); err != nil { | |||||
return err | |||||
} | |||||
if err := envNameCIRegexMatch(name); err != nil { | |||||
return err | |||||
} | |||||
v, err := GetVariable(ctx, actions_model.FindVariablesOpts{ | |||||
OwnerID: ownerID, | |||||
RepoID: repoID, | |||||
Name: name, | |||||
}) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return actions_model.DeleteVariable(ctx, v.ID) | |||||
} | |||||
func GetVariable(ctx context.Context, opts actions_model.FindVariablesOpts) (*actions_model.ActionVariable, error) { | |||||
vars, err := actions_model.FindVariables(ctx, opts) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if len(vars) != 1 { | |||||
return nil, util.NewNotExistErrorf("variable not found") | |||||
} | |||||
return vars[0], nil | |||||
} | |||||
// 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 util.NewInvalidArgumentErrorf("env name cannot be ci") | |||||
} | |||||
return nil | |||||
} |
} | } | ||||
} | } | ||||
}, | }, | ||||
"/orgs/{org}/actions/variables": { | |||||
"get": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"organization" | |||||
], | |||||
"summary": "Get an org-level variables list", | |||||
"operationId": "getOrgVariablesList", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the organization", | |||||
"name": "org", | |||||
"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/VariableList" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/orgs/{org}/actions/variables/{variablename}": { | |||||
"get": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"organization" | |||||
], | |||||
"summary": "Get an org-level variable", | |||||
"operationId": "getOrgVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the organization", | |||||
"name": "org", | |||||
"in": "path", | |||||
"required": true | |||||
}, | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"required": true | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"$ref": "#/responses/ActionVariable" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
}, | |||||
"put": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"organization" | |||||
], | |||||
"summary": "Update an org-level variable", | |||||
"operationId": "updateOrgVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the organization", | |||||
"name": "org", | |||||
"in": "path", | |||||
"required": true | |||||
}, | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"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" | |||||
} | |||||
} | |||||
}, | |||||
"post": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"organization" | |||||
], | |||||
"summary": "Create an org-level variable", | |||||
"operationId": "createOrgVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the organization", | |||||
"name": "org", | |||||
"in": "path", | |||||
"required": true | |||||
}, | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"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" | |||||
} | |||||
} | |||||
}, | |||||
"delete": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"organization" | |||||
], | |||||
"summary": "Delete an org-level variable", | |||||
"operationId": "deleteOrgVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the organization", | |||||
"name": "org", | |||||
"in": "path", | |||||
"required": true | |||||
}, | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"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" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/orgs/{org}/activities/feeds": { | "/orgs/{org}/activities/feeds": { | ||||
"get": { | "get": { | ||||
"produces": [ | "produces": [ | ||||
}, | }, | ||||
{ | { | ||||
"type": "string", | "type": "string", | ||||
"description": "name of the repo to edit", | |||||
"name": "repo", | |||||
"description": "name of the repo to edit", | |||||
"name": "repo", | |||||
"in": "path", | |||||
"required": true | |||||
}, | |||||
{ | |||||
"description": "Properties of a repo that you can edit", | |||||
"name": "body", | |||||
"in": "body", | |||||
"schema": { | |||||
"$ref": "#/definitions/EditRepoOption" | |||||
} | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"$ref": "#/responses/Repository" | |||||
}, | |||||
"403": { | |||||
"$ref": "#/responses/forbidden" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
}, | |||||
"422": { | |||||
"$ref": "#/responses/validationError" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/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" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
}, | |||||
"delete": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"repository" | |||||
], | |||||
"summary": "Delete a secret in a repository", | |||||
"operationId": "deleteRepoSecret", | |||||
"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 | |||||
} | |||||
], | |||||
"responses": { | |||||
"204": { | |||||
"description": "delete one secret of the organization" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/repos/{owner}/{repo}/actions/variables": { | |||||
"get": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"repository" | |||||
], | |||||
"summary": "Get repo-level variables list", | |||||
"operationId": "getRepoVariablesList", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the owner", | |||||
"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/VariableList" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/repos/{owner}/{repo}/actions/variables/{variablename}": { | |||||
"get": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"repository" | |||||
], | |||||
"summary": "Get a repo-level variable", | |||||
"operationId": "getRepoVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the owner", | |||||
"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 variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"required": true | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"$ref": "#/responses/ActionVariable" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
}, | |||||
"put": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"repository" | |||||
], | |||||
"summary": "Update a repo-level variable", | |||||
"operationId": "updateRepoVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the owner", | |||||
"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 variable", | |||||
"name": "variablename", | |||||
"in": "path", | "in": "path", | ||||
"required": true | "required": true | ||||
}, | }, | ||||
{ | { | ||||
"description": "Properties of a repo that you can edit", | |||||
"name": "body", | "name": "body", | ||||
"in": "body", | "in": "body", | ||||
"schema": { | "schema": { | ||||
"$ref": "#/definitions/EditRepoOption" | |||||
"$ref": "#/definitions/UpdateVariableOption" | |||||
} | } | ||||
} | } | ||||
], | ], | ||||
"responses": { | "responses": { | ||||
"200": { | |||||
"$ref": "#/responses/Repository" | |||||
"201": { | |||||
"description": "response when updating a repo-level variable" | |||||
}, | }, | ||||
"403": { | |||||
"$ref": "#/responses/forbidden" | |||||
"204": { | |||||
"description": "response when updating a repo-level variable" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | }, | ||||
"404": { | "404": { | ||||
"$ref": "#/responses/notFound" | "$ref": "#/responses/notFound" | ||||
}, | |||||
"422": { | |||||
"$ref": "#/responses/validationError" | |||||
} | } | ||||
} | } | ||||
} | |||||
}, | |||||
"/repos/{owner}/{repo}/actions/secrets/{secretname}": { | |||||
"put": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
}, | |||||
"post": { | |||||
"produces": [ | "produces": [ | ||||
"application/json" | "application/json" | ||||
], | ], | ||||
"tags": [ | "tags": [ | ||||
"repository" | "repository" | ||||
], | ], | ||||
"summary": "Create or Update a secret value in a repository", | |||||
"operationId": "updateRepoSecret", | |||||
"summary": "Create a repo-level variable", | |||||
"operationId": "createRepoVariable", | |||||
"parameters": [ | "parameters": [ | ||||
{ | { | ||||
"type": "string", | "type": "string", | ||||
"description": "owner of the repository", | |||||
"description": "name of the owner", | |||||
"name": "owner", | "name": "owner", | ||||
"in": "path", | "in": "path", | ||||
"required": true | "required": true | ||||
}, | }, | ||||
{ | { | ||||
"type": "string", | "type": "string", | ||||
"description": "name of the secret", | |||||
"name": "secretname", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | "in": "path", | ||||
"required": true | "required": true | ||||
}, | }, | ||||
"name": "body", | "name": "body", | ||||
"in": "body", | "in": "body", | ||||
"schema": { | "schema": { | ||||
"$ref": "#/definitions/CreateOrUpdateSecretOption" | |||||
"$ref": "#/definitions/CreateVariableOption" | |||||
} | } | ||||
} | } | ||||
], | ], | ||||
"responses": { | "responses": { | ||||
"201": { | "201": { | ||||
"description": "response when creating a secret" | |||||
"description": "response when creating a repo-level variable" | |||||
}, | }, | ||||
"204": { | "204": { | ||||
"description": "response when updating a secret" | |||||
"description": "response when creating a repo-level variable" | |||||
}, | }, | ||||
"400": { | "400": { | ||||
"$ref": "#/responses/error" | "$ref": "#/responses/error" | ||||
} | } | ||||
}, | }, | ||||
"delete": { | "delete": { | ||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | "produces": [ | ||||
"application/json" | "application/json" | ||||
], | ], | ||||
"tags": [ | "tags": [ | ||||
"repository" | "repository" | ||||
], | ], | ||||
"summary": "Delete a secret in a repository", | |||||
"operationId": "deleteRepoSecret", | |||||
"summary": "Delete a repo-level variable", | |||||
"operationId": "deleteRepoVariable", | |||||
"parameters": [ | "parameters": [ | ||||
{ | { | ||||
"type": "string", | "type": "string", | ||||
"description": "owner of the repository", | |||||
"description": "name of the owner", | |||||
"name": "owner", | "name": "owner", | ||||
"in": "path", | "in": "path", | ||||
"required": true | "required": true | ||||
}, | }, | ||||
{ | { | ||||
"type": "string", | "type": "string", | ||||
"description": "name of the secret", | |||||
"name": "secretname", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | "in": "path", | ||||
"required": true | "required": true | ||||
} | } | ||||
], | ], | ||||
"responses": { | "responses": { | ||||
"200": { | |||||
"$ref": "#/responses/ActionVariable" | |||||
}, | |||||
"201": { | |||||
"description": "response when deleting a variable" | |||||
}, | |||||
"204": { | "204": { | ||||
"description": "delete one secret of the organization" | |||||
"description": "response when deleting a variable" | |||||
}, | }, | ||||
"400": { | "400": { | ||||
"$ref": "#/responses/error" | "$ref": "#/responses/error" | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"/user/actions/variables": { | |||||
"get": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"user" | |||||
], | |||||
"summary": "Get the user-level list of variables which is created by current doer", | |||||
"operationId": "getUserVariablesList", | |||||
"parameters": [ | |||||
{ | |||||
"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/VariableList" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/user/actions/variables/{variablename}": { | |||||
"get": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"user" | |||||
], | |||||
"summary": "Get a user-level variable which is created by current doer", | |||||
"operationId": "getUserVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"required": true | |||||
} | |||||
], | |||||
"responses": { | |||||
"200": { | |||||
"$ref": "#/responses/ActionVariable" | |||||
}, | |||||
"400": { | |||||
"$ref": "#/responses/error" | |||||
}, | |||||
"404": { | |||||
"$ref": "#/responses/notFound" | |||||
} | |||||
} | |||||
}, | |||||
"put": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"user" | |||||
], | |||||
"summary": "Update a user-level variable which is created by current doer", | |||||
"operationId": "updateUserVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"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" | |||||
} | |||||
} | |||||
}, | |||||
"post": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"user" | |||||
], | |||||
"summary": "Create a user-level variable", | |||||
"operationId": "createUserVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"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" | |||||
} | |||||
} | |||||
}, | |||||
"delete": { | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"user" | |||||
], | |||||
"summary": "Delete a user-level variable which is created by current doer", | |||||
"operationId": "deleteUserVariable", | |||||
"parameters": [ | |||||
{ | |||||
"type": "string", | |||||
"description": "name of the variable", | |||||
"name": "variablename", | |||||
"in": "path", | |||||
"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" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/user/applications/oauth2": { | "/user/applications/oauth2": { | ||||
"get": { | "get": { | ||||
"produces": [ | "produces": [ | ||||
}, | }, | ||||
"x-go-package": "code.gitea.io/gitea/modules/structs" | "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
}, | }, | ||||
"ActionVariable": { | |||||
"description": "ActionVariable return value of the query API", | |||||
"type": "object", | |||||
"properties": { | |||||
"data": { | |||||
"description": "the value of the variable", | |||||
"type": "string", | |||||
"x-go-name": "Data" | |||||
}, | |||||
"name": { | |||||
"description": "the name of the variable", | |||||
"type": "string", | |||||
"x-go-name": "Name" | |||||
}, | |||||
"owner_id": { | |||||
"description": "the owner to which the variable belongs", | |||||
"type": "integer", | |||||
"format": "int64", | |||||
"x-go-name": "OwnerID" | |||||
}, | |||||
"repo_id": { | |||||
"description": "the repository to which the variable belongs", | |||||
"type": "integer", | |||||
"format": "int64", | |||||
"x-go-name": "RepoID" | |||||
} | |||||
}, | |||||
"x-go-package": "code.gitea.io/gitea/modules/structs" | |||||
}, | |||||
"Activity": { | "Activity": { | ||||
"type": "object", | "type": "object", | ||||
"properties": { | "properties": { | ||||
}, | }, | ||||
"x-go-package": "code.gitea.io/gitea/modules/structs" | "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
}, | }, | ||||
"CreateVariableOption": { | |||||
"description": "CreateVariableOption the option when creating variable", | |||||
"type": "object", | |||||
"required": [ | |||||
"value" | |||||
], | |||||
"properties": { | |||||
"value": { | |||||
"description": "Value of the variable to create", | |||||
"type": "string", | |||||
"x-go-name": "Value" | |||||
} | |||||
}, | |||||
"x-go-package": "code.gitea.io/gitea/modules/structs" | |||||
}, | |||||
"CreateWikiPageOptions": { | "CreateWikiPageOptions": { | ||||
"description": "CreateWikiPageOptions form for creating wiki", | "description": "CreateWikiPageOptions form for creating wiki", | ||||
"type": "object", | "type": "object", | ||||
}, | }, | ||||
"x-go-package": "code.gitea.io/gitea/modules/structs" | "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
}, | }, | ||||
"UpdateVariableOption": { | |||||
"description": "UpdateVariableOption the option when updating variable", | |||||
"type": "object", | |||||
"required": [ | |||||
"value" | |||||
], | |||||
"properties": { | |||||
"name": { | |||||
"description": "New name for the variable. If the field is empty, the variable name won't be updated.", | |||||
"type": "string", | |||||
"x-go-name": "Name" | |||||
}, | |||||
"value": { | |||||
"description": "Value of the variable to update", | |||||
"type": "string", | |||||
"x-go-name": "Value" | |||||
} | |||||
}, | |||||
"x-go-package": "code.gitea.io/gitea/modules/structs" | |||||
}, | |||||
"User": { | "User": { | ||||
"description": "User represents a user", | "description": "User represents a user", | ||||
"type": "object", | "type": "object", | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"ActionVariable": { | |||||
"description": "ActionVariable", | |||||
"schema": { | |||||
"$ref": "#/definitions/ActionVariable" | |||||
} | |||||
}, | |||||
"ActivityFeedsList": { | "ActivityFeedsList": { | ||||
"description": "ActivityFeedsList", | "description": "ActivityFeedsList", | ||||
"schema": { | "schema": { | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
"VariableList": { | |||||
"description": "VariableList", | |||||
"schema": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/definitions/ActionVariable" | |||||
} | |||||
} | |||||
}, | |||||
"WatchInfo": { | "WatchInfo": { | ||||
"description": "WatchInfo", | "description": "WatchInfo", | ||||
"schema": { | "schema": { | ||||
"parameterBodies": { | "parameterBodies": { | ||||
"description": "parameterBodies", | "description": "parameterBodies", | ||||
"schema": { | "schema": { | ||||
"$ref": "#/definitions/UserBadgeOption" | |||||
"$ref": "#/definitions/UpdateVariableOption" | |||||
} | } | ||||
}, | }, | ||||
"redirect": { | "redirect": { |
// Copyright 2024 The Gitea Authors. All rights reserved. | |||||
// SPDX-License-Identifier: MIT | |||||
package integration | |||||
import ( | |||||
"fmt" | |||||
"net/http" | |||||
"testing" | |||||
auth_model "code.gitea.io/gitea/models/auth" | |||||
repo_model "code.gitea.io/gitea/models/repo" | |||||
"code.gitea.io/gitea/models/unittest" | |||||
user_model "code.gitea.io/gitea/models/user" | |||||
api "code.gitea.io/gitea/modules/structs" | |||||
"code.gitea.io/gitea/tests" | |||||
) | |||||
func TestAPIRepoVariables(t *testing.T) { | |||||
defer tests.PrepareTestEnv(t)() | |||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) | |||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) | |||||
session := loginUser(t, user.Name) | |||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | |||||
t.Run("CreateRepoVariable", func(t *testing.T) { | |||||
cases := []struct { | |||||
Name string | |||||
ExpectedStatus int | |||||
}{ | |||||
{ | |||||
Name: "-", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "_", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
{ | |||||
Name: "TEST_VAR", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
{ | |||||
Name: "test_var", | |||||
ExpectedStatus: http.StatusConflict, | |||||
}, | |||||
{ | |||||
Name: "ci", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "123var", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "var@test", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "github_var", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "gitea_var", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
} | |||||
for _, c := range cases { | |||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.CreateVariableOption{ | |||||
Value: "value", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, c.ExpectedStatus) | |||||
} | |||||
}) | |||||
t.Run("UpdateRepoVariable", func(t *testing.T) { | |||||
variableName := "test_update_var" | |||||
url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName) | |||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ | |||||
Value: "initial_val", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNoContent) | |||||
cases := []struct { | |||||
Name string | |||||
UpdateName string | |||||
ExpectedStatus int | |||||
}{ | |||||
{ | |||||
Name: "not_found_var", | |||||
ExpectedStatus: http.StatusNotFound, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "1invalid", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "invalid@name", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "ci", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "updated_var_name", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
ExpectedStatus: http.StatusNotFound, | |||||
}, | |||||
{ | |||||
Name: "updated_var_name", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
} | |||||
for _, c := range cases { | |||||
req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.UpdateVariableOption{ | |||||
Name: c.UpdateName, | |||||
Value: "updated_val", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, c.ExpectedStatus) | |||||
} | |||||
}) | |||||
t.Run("DeleteRepoVariable", func(t *testing.T) { | |||||
variableName := "test_delete_var" | |||||
url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName) | |||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ | |||||
Value: "initial_val", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNoContent) | |||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNoContent) | |||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNotFound) | |||||
}) | |||||
} |
// Copyright 2024 The Gitea Authors. All rights reserved. | |||||
// SPDX-License-Identifier: MIT | |||||
package integration | |||||
import ( | |||||
"fmt" | |||||
"net/http" | |||||
"testing" | |||||
auth_model "code.gitea.io/gitea/models/auth" | |||||
api "code.gitea.io/gitea/modules/structs" | |||||
"code.gitea.io/gitea/tests" | |||||
) | |||||
func TestAPIUserVariables(t *testing.T) { | |||||
defer tests.PrepareTestEnv(t)() | |||||
session := loginUser(t, "user1") | |||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) | |||||
t.Run("CreateRepoVariable", func(t *testing.T) { | |||||
cases := []struct { | |||||
Name string | |||||
ExpectedStatus int | |||||
}{ | |||||
{ | |||||
Name: "-", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "_", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
{ | |||||
Name: "TEST_VAR", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
{ | |||||
Name: "test_var", | |||||
ExpectedStatus: http.StatusConflict, | |||||
}, | |||||
{ | |||||
Name: "ci", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "123var", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "var@test", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "github_var", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: "gitea_var", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
} | |||||
for _, c := range cases { | |||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.CreateVariableOption{ | |||||
Value: "value", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, c.ExpectedStatus) | |||||
} | |||||
}) | |||||
t.Run("UpdateRepoVariable", func(t *testing.T) { | |||||
variableName := "test_update_var" | |||||
url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) | |||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ | |||||
Value: "initial_val", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNoContent) | |||||
cases := []struct { | |||||
Name string | |||||
UpdateName string | |||||
ExpectedStatus int | |||||
}{ | |||||
{ | |||||
Name: "not_found_var", | |||||
ExpectedStatus: http.StatusNotFound, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "1invalid", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "invalid@name", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "ci", | |||||
ExpectedStatus: http.StatusBadRequest, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
UpdateName: "updated_var_name", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
{ | |||||
Name: variableName, | |||||
ExpectedStatus: http.StatusNotFound, | |||||
}, | |||||
{ | |||||
Name: "updated_var_name", | |||||
ExpectedStatus: http.StatusNoContent, | |||||
}, | |||||
} | |||||
for _, c := range cases { | |||||
req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.UpdateVariableOption{ | |||||
Name: c.UpdateName, | |||||
Value: "updated_val", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, c.ExpectedStatus) | |||||
} | |||||
}) | |||||
t.Run("DeleteRepoVariable", func(t *testing.T) { | |||||
variableName := "test_delete_var" | |||||
url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) | |||||
req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ | |||||
Value: "initial_val", | |||||
}).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNoContent) | |||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNoContent) | |||||
req = NewRequest(t, "DELETE", url).AddTokenAuth(token) | |||||
MakeRequest(t, req, http.StatusNotFound) | |||||
}) | |||||
} |