close #27801 --------- Co-authored-by: silverwind <me@silverwind.io>tags/v1.22.0-rc1
@@ -6,13 +6,11 @@ package actions | |||
import ( | |||
"context" | |||
"errors" | |||
"fmt" | |||
"strings" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"code.gitea.io/gitea/modules/util" | |||
"xorm.io/builder" | |||
) | |||
@@ -55,24 +53,24 @@ type FindVariablesOpts struct { | |||
db.ListOptions | |||
OwnerID int64 | |||
RepoID int64 | |||
Name string | |||
} | |||
func (opts FindVariablesOpts) ToConds() builder.Cond { | |||
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{"repo_id": opts.RepoID}) | |||
if opts.Name != "" { | |||
cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)}) | |||
} | |||
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) { | |||
@@ -84,6 +82,13 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) | |||
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) { | |||
variables := map[string]string{} | |||
@@ -0,0 +1,37 @@ | |||
// 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"` | |||
} |
@@ -221,3 +221,12 @@ func IfZero[T comparable](v, def T) T { | |||
} | |||
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") | |||
} |
@@ -235,3 +235,8 @@ func TestToPointer(t *testing.T) { | |||
val123 := 123 | |||
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") | |||
} |
@@ -955,6 +955,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) | |||
}) | |||
@@ -1073,6 +1082,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) | |||
}) | |||
@@ -1452,6 +1470,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) | |||
}) |
@@ -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) | |||
} |
@@ -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) | |||
} |
@@ -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"` | |||
} |
@@ -193,4 +193,10 @@ type swaggerParameterBodies struct { | |||
// in:body | |||
UserBadgeOption api.UserBadgeOption | |||
// in:body | |||
CreateVariableOption api.CreateVariableOption | |||
// in:body | |||
UpdateVariableOption api.UpdateVariableOption | |||
} |
@@ -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) | |||
} |
@@ -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") | |||
} |
@@ -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")) |
@@ -0,0 +1,100 @@ | |||
// 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 | |||
} |
@@ -1844,6 +1844,232 @@ | |||
} | |||
} | |||
}, | |||
"/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": { | |||
"get": { | |||
"produces": [ | |||
@@ -3587,53 +3813,305 @@ | |||
}, | |||
{ | |||
"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", | |||
"required": true | |||
}, | |||
{ | |||
"description": "Properties of a repo that you can edit", | |||
"name": "body", | |||
"in": "body", | |||
"schema": { | |||
"$ref": "#/definitions/EditRepoOption" | |||
"$ref": "#/definitions/UpdateVariableOption" | |||
} | |||
} | |||
], | |||
"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": { | |||
"$ref": "#/responses/notFound" | |||
}, | |||
"422": { | |||
"$ref": "#/responses/validationError" | |||
} | |||
} | |||
} | |||
}, | |||
"/repos/{owner}/{repo}/actions/secrets/{secretname}": { | |||
"put": { | |||
"consumes": [ | |||
"application/json" | |||
], | |||
}, | |||
"post": { | |||
"produces": [ | |||
"application/json" | |||
], | |||
"tags": [ | |||
"repository" | |||
], | |||
"summary": "Create or Update a secret value in a repository", | |||
"operationId": "updateRepoSecret", | |||
"summary": "Create a repo-level variable", | |||
"operationId": "createRepoVariable", | |||
"parameters": [ | |||
{ | |||
"type": "string", | |||
"description": "owner of the repository", | |||
"description": "name of the owner", | |||
"name": "owner", | |||
"in": "path", | |||
"required": true | |||
@@ -3647,8 +4125,8 @@ | |||
}, | |||
{ | |||
"type": "string", | |||
"description": "name of the secret", | |||
"name": "secretname", | |||
"description": "name of the variable", | |||
"name": "variablename", | |||
"in": "path", | |||
"required": true | |||
}, | |||
@@ -3656,16 +4134,16 @@ | |||
"name": "body", | |||
"in": "body", | |||
"schema": { | |||
"$ref": "#/definitions/CreateOrUpdateSecretOption" | |||
"$ref": "#/definitions/CreateVariableOption" | |||
} | |||
} | |||
], | |||
"responses": { | |||
"201": { | |||
"description": "response when creating a secret" | |||
"description": "response when creating a repo-level variable" | |||
}, | |||
"204": { | |||
"description": "response when updating a secret" | |||
"description": "response when creating a repo-level variable" | |||
}, | |||
"400": { | |||
"$ref": "#/responses/error" | |||
@@ -3676,21 +4154,18 @@ | |||
} | |||
}, | |||
"delete": { | |||
"consumes": [ | |||
"application/json" | |||
], | |||
"produces": [ | |||
"application/json" | |||
], | |||
"tags": [ | |||
"repository" | |||
], | |||
"summary": "Delete a secret in a repository", | |||
"operationId": "deleteRepoSecret", | |||
"summary": "Delete a repo-level variable", | |||
"operationId": "deleteRepoVariable", | |||
"parameters": [ | |||
{ | |||
"type": "string", | |||
"description": "owner of the repository", | |||
"description": "name of the owner", | |||
"name": "owner", | |||
"in": "path", | |||
"required": true | |||
@@ -3704,15 +4179,21 @@ | |||
}, | |||
{ | |||
"type": "string", | |||
"description": "name of the secret", | |||
"name": "secretname", | |||
"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": "delete one secret of the organization" | |||
"description": "response when deleting a variable" | |||
}, | |||
"400": { | |||
"$ref": "#/responses/error" | |||
@@ -15050,6 +15531,194 @@ | |||
} | |||
} | |||
}, | |||
"/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": { | |||
"get": { | |||
"produces": [ | |||
@@ -17193,6 +17862,35 @@ | |||
}, | |||
"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": { | |||
"type": "object", | |||
"properties": { | |||
@@ -19079,6 +19777,21 @@ | |||
}, | |||
"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": { | |||
"description": "CreateWikiPageOptions form for creating wiki", | |||
"type": "object", | |||
@@ -23371,6 +24084,26 @@ | |||
}, | |||
"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": { | |||
"description": "User represents a user", | |||
"type": "object", | |||
@@ -23752,6 +24485,12 @@ | |||
} | |||
} | |||
}, | |||
"ActionVariable": { | |||
"description": "ActionVariable", | |||
"schema": { | |||
"$ref": "#/definitions/ActionVariable" | |||
} | |||
}, | |||
"ActivityFeedsList": { | |||
"description": "ActivityFeedsList", | |||
"schema": { | |||
@@ -24635,6 +25374,15 @@ | |||
} | |||
} | |||
}, | |||
"VariableList": { | |||
"description": "VariableList", | |||
"schema": { | |||
"type": "array", | |||
"items": { | |||
"$ref": "#/definitions/ActionVariable" | |||
} | |||
} | |||
}, | |||
"WatchInfo": { | |||
"description": "WatchInfo", | |||
"schema": { | |||
@@ -24710,7 +25458,7 @@ | |||
"parameterBodies": { | |||
"description": "parameterBodies", | |||
"schema": { | |||
"$ref": "#/definitions/UserBadgeOption" | |||
"$ref": "#/definitions/UpdateVariableOption" | |||
} | |||
}, | |||
"redirect": { |
@@ -0,0 +1,149 @@ | |||
// 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) | |||
}) | |||
} |
@@ -0,0 +1,144 @@ | |||
// 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) | |||
}) | |||
} |