mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Add API for Variables (#29520)
				
					
				
			close #27801 --------- Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
		| @@ -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{} | ||||
|  | ||||
|   | ||||
							
								
								
									
										37
									
								
								modules/structs/variable.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								modules/structs/variable.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 				}) | ||||
|   | ||||
							
								
								
									
										291
									
								
								routers/api/v1/org/variables.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								routers/api/v1/org/variables.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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")) | ||||
|   | ||||
							
								
								
									
										100
									
								
								services/actions/variables.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								services/actions/variables.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										750
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										750
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							| @@ -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": [ | ||||
| @@ -3723,6 +3949,261 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/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 | ||||
|           }, | ||||
|           { | ||||
|             "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" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "post": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Create a repo-level variable", | ||||
|         "operationId": "createRepoVariable", | ||||
|         "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 | ||||
|           }, | ||||
|           { | ||||
|             "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" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "delete": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Delete a repo-level variable", | ||||
|         "operationId": "deleteRepoVariable", | ||||
|         "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" | ||||
|           }, | ||||
|           "201": { | ||||
|             "description": "response when deleting a variable" | ||||
|           }, | ||||
|           "204": { | ||||
|             "description": "response when deleting a variable" | ||||
|           }, | ||||
|           "400": { | ||||
|             "$ref": "#/responses/error" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/repos/{owner}/{repo}/activities/feeds": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
| @@ -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": { | ||||
|   | ||||
							
								
								
									
										149
									
								
								tests/integration/api_repo_variables_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								tests/integration/api_repo_variables_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										144
									
								
								tests/integration/api_user_variables_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								tests/integration/api_user_variables_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	}) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user