mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 20:36:07 +01:00 
			
		
		
		
	Added Description Field for Secrets and Variables (#33526)
Fixes #33484 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -6,10 +6,12 @@ package actions
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"xorm.io/builder"
 | 
						"xorm.io/builder"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -32,26 +34,39 @@ type ActionVariable struct {
 | 
				
			|||||||
	RepoID      int64              `xorm:"INDEX UNIQUE(owner_repo_name)"`
 | 
						RepoID      int64              `xorm:"INDEX UNIQUE(owner_repo_name)"`
 | 
				
			||||||
	Name        string             `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
 | 
						Name        string             `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
 | 
				
			||||||
	Data        string             `xorm:"LONGTEXT NOT NULL"`
 | 
						Data        string             `xorm:"LONGTEXT NOT NULL"`
 | 
				
			||||||
 | 
						Description string             `xorm:"TEXT"`
 | 
				
			||||||
	CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
 | 
						CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
 | 
				
			||||||
	UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
 | 
						UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						VariableDataMaxLength        = 65536
 | 
				
			||||||
 | 
						VariableDescriptionMaxLength = 4096
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
	db.RegisterModel(new(ActionVariable))
 | 
						db.RegisterModel(new(ActionVariable))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*ActionVariable, error) {
 | 
					func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, description string) (*ActionVariable, error) {
 | 
				
			||||||
	if ownerID != 0 && repoID != 0 {
 | 
						if ownerID != 0 && repoID != 0 {
 | 
				
			||||||
		// It's trying to create a variable that belongs to a repository, but OwnerID has been set accidentally.
 | 
							// It's trying to create a variable that belongs to a repository, but OwnerID has been set accidentally.
 | 
				
			||||||
		// Remove OwnerID to avoid confusion; it's not worth returning an error here.
 | 
							// Remove OwnerID to avoid confusion; it's not worth returning an error here.
 | 
				
			||||||
		ownerID = 0
 | 
							ownerID = 0
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if utf8.RuneCountInString(data) > VariableDataMaxLength {
 | 
				
			||||||
 | 
							return nil, util.NewInvalidArgumentErrorf("data too long")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						description = util.TruncateRunes(description, VariableDescriptionMaxLength)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	variable := &ActionVariable{
 | 
						variable := &ActionVariable{
 | 
				
			||||||
		OwnerID:     ownerID,
 | 
							OwnerID:     ownerID,
 | 
				
			||||||
		RepoID:      repoID,
 | 
							RepoID:      repoID,
 | 
				
			||||||
		Name:        strings.ToUpper(name),
 | 
							Name:        strings.ToUpper(name),
 | 
				
			||||||
		Data:        data,
 | 
							Data:        data,
 | 
				
			||||||
 | 
							Description: description,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return variable, db.Insert(ctx, variable)
 | 
						return variable, db.Insert(ctx, variable)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -96,6 +111,12 @@ func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariab
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func UpdateVariableCols(ctx context.Context, variable *ActionVariable, cols ...string) (bool, error) {
 | 
					func UpdateVariableCols(ctx context.Context, variable *ActionVariable, cols ...string) (bool, error) {
 | 
				
			||||||
 | 
						if utf8.RuneCountInString(variable.Data) > VariableDataMaxLength {
 | 
				
			||||||
 | 
							return false, util.NewInvalidArgumentErrorf("data too long")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						variable.Description = util.TruncateRunes(variable.Description, VariableDescriptionMaxLength)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	variable.Name = strings.ToUpper(variable.Name)
 | 
						variable.Name = strings.ToUpper(variable.Name)
 | 
				
			||||||
	count, err := db.GetEngine(ctx).
 | 
						count, err := db.GetEngine(ctx).
 | 
				
			||||||
		ID(variable.ID).
 | 
							ID(variable.ID).
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -376,6 +376,7 @@ func prepareMigrationTasks() []*migration {
 | 
				
			|||||||
		newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin),
 | 
							newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin),
 | 
				
			||||||
		newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
 | 
							newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
 | 
				
			||||||
		newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
 | 
							newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
 | 
				
			||||||
 | 
							newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return preparedMigrations
 | 
						return preparedMigrations
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								models/migrations/v1_24/v316.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								models/migrations/v1_24/v316.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package v1_24 //nolint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddDescriptionForSecretsAndVariables(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						type Secret struct {
 | 
				
			||||||
 | 
							Description string `xorm:"TEXT"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type ActionVariable struct {
 | 
				
			||||||
 | 
							Description string `xorm:"TEXT"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return x.Sync(new(Secret), new(ActionVariable))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -40,9 +40,15 @@ type Secret struct {
 | 
				
			|||||||
	RepoID      int64              `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"`
 | 
						RepoID      int64              `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"`
 | 
				
			||||||
	Name        string             `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
 | 
						Name        string             `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
 | 
				
			||||||
	Data        string             `xorm:"LONGTEXT"` // encrypted data
 | 
						Data        string             `xorm:"LONGTEXT"` // encrypted data
 | 
				
			||||||
 | 
						Description string             `xorm:"TEXT"`
 | 
				
			||||||
	CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
 | 
						CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						SecretDataMaxLength        = 65536
 | 
				
			||||||
 | 
						SecretDescriptionMaxLength = 4096
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ErrSecretNotFound represents a "secret not found" error.
 | 
					// ErrSecretNotFound represents a "secret not found" error.
 | 
				
			||||||
type ErrSecretNotFound struct {
 | 
					type ErrSecretNotFound struct {
 | 
				
			||||||
	Name string
 | 
						Name string
 | 
				
			||||||
@@ -57,7 +63,7 @@ func (err ErrSecretNotFound) Unwrap() error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database
 | 
					// InsertEncryptedSecret Creates, encrypts, and validates a new secret with yet unencrypted data and insert into database
 | 
				
			||||||
func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, data string) (*Secret, error) {
 | 
					func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, data, description string) (*Secret, error) {
 | 
				
			||||||
	if ownerID != 0 && repoID != 0 {
 | 
						if ownerID != 0 && repoID != 0 {
 | 
				
			||||||
		// It's trying to create a secret that belongs to a repository, but OwnerID has been set accidentally.
 | 
							// It's trying to create a secret that belongs to a repository, but OwnerID has been set accidentally.
 | 
				
			||||||
		// Remove OwnerID to avoid confusion; it's not worth returning an error here.
 | 
							// Remove OwnerID to avoid confusion; it's not worth returning an error here.
 | 
				
			||||||
@@ -67,15 +73,23 @@ func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, dat
 | 
				
			|||||||
		return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument)
 | 
							return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(data) > SecretDataMaxLength {
 | 
				
			||||||
 | 
							return nil, util.NewInvalidArgumentErrorf("data too long")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						description = util.TruncateRunes(description, SecretDescriptionMaxLength)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
 | 
						encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	secret := &Secret{
 | 
						secret := &Secret{
 | 
				
			||||||
		OwnerID:     ownerID,
 | 
							OwnerID:     ownerID,
 | 
				
			||||||
		RepoID:      repoID,
 | 
							RepoID:      repoID,
 | 
				
			||||||
		Name:        strings.ToUpper(name),
 | 
							Name:        strings.ToUpper(name),
 | 
				
			||||||
		Data:        encrypted,
 | 
							Data:        encrypted,
 | 
				
			||||||
 | 
							Description: description,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return secret, db.Insert(ctx, secret)
 | 
						return secret, db.Insert(ctx, secret)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -114,7 +128,13 @@ func (opts FindSecretsOptions) ToConds() builder.Cond {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateSecret changes org or user reop secret.
 | 
					// UpdateSecret changes org or user reop secret.
 | 
				
			||||||
func UpdateSecret(ctx context.Context, secretID int64, data string) error {
 | 
					func UpdateSecret(ctx context.Context, secretID int64, data, description string) error {
 | 
				
			||||||
 | 
						if len(data) > SecretDataMaxLength {
 | 
				
			||||||
 | 
							return util.NewInvalidArgumentErrorf("data too long")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						description = util.TruncateRunes(description, SecretDescriptionMaxLength)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
 | 
						encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@@ -122,8 +142,9 @@ func UpdateSecret(ctx context.Context, secretID int64, data string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	s := &Secret{
 | 
						s := &Secret{
 | 
				
			||||||
		Data:        encrypted,
 | 
							Data:        encrypted,
 | 
				
			||||||
 | 
							Description: description,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	affected, err := db.GetEngine(ctx).ID(secretID).Cols("data").Update(s)
 | 
						affected, err := db.GetEngine(ctx).ID(secretID).Cols("data", "description").Update(s)
 | 
				
			||||||
	if affected != 1 {
 | 
						if affected != 1 {
 | 
				
			||||||
		return ErrSecretNotFound{}
 | 
							return ErrSecretNotFound{}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,8 @@ import "time"
 | 
				
			|||||||
type Secret struct {
 | 
					type Secret struct {
 | 
				
			||||||
	// the secret's name
 | 
						// the secret's name
 | 
				
			||||||
	Name string `json:"name"`
 | 
						Name string `json:"name"`
 | 
				
			||||||
 | 
						// the secret's description
 | 
				
			||||||
 | 
						Description string `json:"description"`
 | 
				
			||||||
	// swagger:strfmt date-time
 | 
						// swagger:strfmt date-time
 | 
				
			||||||
	Created time.Time `json:"created_at"`
 | 
						Created time.Time `json:"created_at"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -21,4 +23,9 @@ type CreateOrUpdateSecretOption struct {
 | 
				
			|||||||
	//
 | 
						//
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	Data string `json:"data" binding:"Required"`
 | 
						Data string `json:"data" binding:"Required"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Description of the secret to update
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// required: false
 | 
				
			||||||
 | 
						Description string `json:"description"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,11 @@ type CreateVariableOption struct {
 | 
				
			|||||||
	//
 | 
						//
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	Value string `json:"value" binding:"Required"`
 | 
						Value string `json:"value" binding:"Required"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Description of the variable to create
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// required: false
 | 
				
			||||||
 | 
						Description string `json:"description"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateVariableOption the option when updating variable
 | 
					// UpdateVariableOption the option when updating variable
 | 
				
			||||||
@@ -21,6 +26,11 @@ type UpdateVariableOption struct {
 | 
				
			|||||||
	//
 | 
						//
 | 
				
			||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	Value string `json:"value" binding:"Required"`
 | 
						Value string `json:"value" binding:"Required"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Description of the variable to update
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// required: false
 | 
				
			||||||
 | 
						Description string `json:"description"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ActionVariable return value of the query API
 | 
					// ActionVariable return value of the query API
 | 
				
			||||||
@@ -34,4 +44,6 @@ type ActionVariable struct {
 | 
				
			|||||||
	Name string `json:"name"`
 | 
						Name string `json:"name"`
 | 
				
			||||||
	// the value of the variable
 | 
						// the value of the variable
 | 
				
			||||||
	Data string `json:"data"`
 | 
						Data string `json:"data"`
 | 
				
			||||||
 | 
						// the description of the variable
 | 
				
			||||||
 | 
						Description string `json:"description"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3712,8 +3712,10 @@ secrets = Secrets
 | 
				
			|||||||
description = Secrets will be passed to certain actions and cannot be read otherwise.
 | 
					description = Secrets will be passed to certain actions and cannot be read otherwise.
 | 
				
			||||||
none = There are no secrets yet.
 | 
					none = There are no secrets yet.
 | 
				
			||||||
creation = Add Secret
 | 
					creation = Add Secret
 | 
				
			||||||
 | 
					creation.description = Description
 | 
				
			||||||
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
 | 
					creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
 | 
				
			||||||
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
 | 
					creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
 | 
				
			||||||
 | 
					creation.description_placeholder = Enter short description (optional).
 | 
				
			||||||
creation.success = The secret "%s" has been added.
 | 
					creation.success = The secret "%s" has been added.
 | 
				
			||||||
creation.failed = Failed to add secret.
 | 
					creation.failed = Failed to add secret.
 | 
				
			||||||
deletion = Remove secret
 | 
					deletion = Remove secret
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,6 +62,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
 | 
				
			|||||||
	for k, v := range secrets {
 | 
						for k, v := range secrets {
 | 
				
			||||||
		apiSecrets[k] = &api.Secret{
 | 
							apiSecrets[k] = &api.Secret{
 | 
				
			||||||
			Name:        v.Name,
 | 
								Name:        v.Name,
 | 
				
			||||||
 | 
								Description: v.Description,
 | 
				
			||||||
			Created:     v.CreatedUnix.AsTime(),
 | 
								Created:     v.CreatedUnix.AsTime(),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -106,7 +107,7 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
 | 
						opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data)
 | 
						_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, err)
 | 
								ctx.APIError(http.StatusBadRequest, err)
 | 
				
			||||||
@@ -234,6 +235,7 @@ func (Action) ListVariables(ctx *context.APIContext) {
 | 
				
			|||||||
			RepoID:      v.RepoID,
 | 
								RepoID:      v.RepoID,
 | 
				
			||||||
			Name:        v.Name,
 | 
								Name:        v.Name,
 | 
				
			||||||
			Data:        v.Data,
 | 
								Data:        v.Data,
 | 
				
			||||||
 | 
								Description: v.Description,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -285,6 +287,7 @@ func (Action) GetVariable(ctx *context.APIContext) {
 | 
				
			|||||||
		RepoID:      v.RepoID,
 | 
							RepoID:      v.RepoID,
 | 
				
			||||||
		Name:        v.Name,
 | 
							Name:        v.Name,
 | 
				
			||||||
		Data:        v.Data,
 | 
							Data:        v.Data,
 | 
				
			||||||
 | 
							Description: v.Description,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.JSON(http.StatusOK, variable)
 | 
						ctx.JSON(http.StatusOK, variable)
 | 
				
			||||||
@@ -386,7 +389,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
 | 
						if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, err)
 | 
								ctx.APIError(http.StatusBadRequest, err)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
@@ -453,6 +456,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	v.Name = opt.Name
 | 
						v.Name = opt.Name
 | 
				
			||||||
	v.Data = opt.Value
 | 
						v.Data = opt.Value
 | 
				
			||||||
 | 
						v.Description = opt.Description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
 | 
						if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -85,6 +85,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
 | 
				
			|||||||
	for k, v := range secrets {
 | 
						for k, v := range secrets {
 | 
				
			||||||
		apiSecrets[k] = &api.Secret{
 | 
							apiSecrets[k] = &api.Secret{
 | 
				
			||||||
			Name:        v.Name,
 | 
								Name:        v.Name,
 | 
				
			||||||
 | 
								Description: v.Description,
 | 
				
			||||||
			Created:     v.CreatedUnix.AsTime(),
 | 
								Created:     v.CreatedUnix.AsTime(),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -136,7 +137,7 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
 | 
						opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data)
 | 
						_, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data, opt.Description)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, err)
 | 
								ctx.APIError(http.StatusBadRequest, err)
 | 
				
			||||||
@@ -253,6 +254,7 @@ func (Action) GetVariable(ctx *context.APIContext) {
 | 
				
			|||||||
		RepoID:      v.RepoID,
 | 
							RepoID:      v.RepoID,
 | 
				
			||||||
		Name:        v.Name,
 | 
							Name:        v.Name,
 | 
				
			||||||
		Data:        v.Data,
 | 
							Data:        v.Data,
 | 
				
			||||||
 | 
							Description: v.Description,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.JSON(http.StatusOK, variable)
 | 
						ctx.JSON(http.StatusOK, variable)
 | 
				
			||||||
@@ -362,7 +364,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value); err != nil {
 | 
						if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value, opt.Description); err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, err)
 | 
								ctx.APIError(http.StatusBadRequest, err)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
@@ -432,6 +434,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	v.Name = opt.Name
 | 
						v.Name = opt.Name
 | 
				
			||||||
	v.Data = opt.Value
 | 
						v.Data = opt.Value
 | 
				
			||||||
 | 
						v.Description = opt.Description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
 | 
						if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
@@ -494,6 +497,8 @@ func (Action) ListVariables(ctx *context.APIContext) {
 | 
				
			|||||||
			OwnerID:     v.OwnerID,
 | 
								OwnerID:     v.OwnerID,
 | 
				
			||||||
			RepoID:      v.RepoID,
 | 
								RepoID:      v.RepoID,
 | 
				
			||||||
			Name:        v.Name,
 | 
								Name:        v.Name,
 | 
				
			||||||
 | 
								Data:        v.Data,
 | 
				
			||||||
 | 
								Description: v.Description,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
 | 
						opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data)
 | 
						_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, err)
 | 
								ctx.APIError(http.StatusBadRequest, err)
 | 
				
			||||||
@@ -153,7 +153,7 @@ func CreateVariable(ctx *context.APIContext) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
 | 
						if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, err)
 | 
								ctx.APIError(http.StatusBadRequest, err)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
@@ -215,6 +215,7 @@ func UpdateVariable(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	v.Name = opt.Name
 | 
						v.Name = opt.Name
 | 
				
			||||||
	v.Data = opt.Value
 | 
						v.Data = opt.Value
 | 
				
			||||||
 | 
						v.Description = opt.Description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
 | 
						if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
 | 
				
			||||||
		if errors.Is(err, util.ErrInvalidArgument) {
 | 
							if errors.Is(err, util.ErrInvalidArgument) {
 | 
				
			||||||
@@ -304,6 +305,7 @@ func GetVariable(ctx *context.APIContext) {
 | 
				
			|||||||
		RepoID:      v.RepoID,
 | 
							RepoID:      v.RepoID,
 | 
				
			||||||
		Name:        v.Name,
 | 
							Name:        v.Name,
 | 
				
			||||||
		Data:        v.Data,
 | 
							Data:        v.Data,
 | 
				
			||||||
 | 
							Description: v.Description,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.JSON(http.StatusOK, variable)
 | 
						ctx.JSON(http.StatusOK, variable)
 | 
				
			||||||
@@ -349,6 +351,7 @@ func ListVariables(ctx *context.APIContext) {
 | 
				
			|||||||
			RepoID:      v.RepoID,
 | 
								RepoID:      v.RepoID,
 | 
				
			||||||
			Name:        v.Name,
 | 
								Name:        v.Name,
 | 
				
			||||||
			Data:        v.Data,
 | 
								Data:        v.Data,
 | 
				
			||||||
 | 
								Description: v.Description,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,7 +106,8 @@ func Variables(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["Variables"] = variables
 | 
						ctx.Data["Variables"] = variables
 | 
				
			||||||
 | 
						ctx.Data["DataMaxLength"] = actions_model.VariableDataMaxLength
 | 
				
			||||||
 | 
						ctx.Data["DescriptionMaxLength"] = actions_model.VariableDescriptionMaxLength
 | 
				
			||||||
	ctx.HTML(http.StatusOK, vCtx.VariablesTemplate)
 | 
						ctx.HTML(http.StatusOK, vCtx.VariablesTemplate)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -124,7 +125,7 @@ func VariableCreate(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	form := web.GetForm(ctx).(*forms.EditVariableForm)
 | 
						form := web.GetForm(ctx).(*forms.EditVariableForm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	v, err := actions_service.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, form.Name, form.Data)
 | 
						v, err := actions_service.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, form.Name, form.Data, form.Description)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("CreateVariable: %v", err)
 | 
							log.Error("CreateVariable: %v", err)
 | 
				
			||||||
		ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
 | 
							ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
 | 
				
			||||||
@@ -157,6 +158,7 @@ func VariableUpdate(ctx *context.Context) {
 | 
				
			|||||||
	form := web.GetForm(ctx).(*forms.EditVariableForm)
 | 
						form := web.GetForm(ctx).(*forms.EditVariableForm)
 | 
				
			||||||
	variable.Name = form.Name
 | 
						variable.Name = form.Name
 | 
				
			||||||
	variable.Data = form.Data
 | 
						variable.Data = form.Data
 | 
				
			||||||
 | 
						variable.Description = form.Description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ok, err := actions_service.UpdateVariableNameData(ctx, variable); err != nil || !ok {
 | 
						if ok, err := actions_service.UpdateVariableNameData(ctx, variable); err != nil || !ok {
 | 
				
			||||||
		log.Error("UpdateVariable: %v", err)
 | 
							log.Error("UpdateVariable: %v", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,12 +22,14 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["Secrets"] = secrets
 | 
						ctx.Data["Secrets"] = secrets
 | 
				
			||||||
 | 
						ctx.Data["DataMaxLength"] = secret_model.SecretDataMaxLength
 | 
				
			||||||
 | 
						ctx.Data["DescriptionMaxLength"] = secret_model.SecretDescriptionMaxLength
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
 | 
					func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
 | 
				
			||||||
	form := web.GetForm(ctx).(*forms.AddSecretForm)
 | 
						form := web.GetForm(ctx).(*forms.AddSecretForm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data))
 | 
						s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data), form.Description)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("CreateOrUpdateSecret failed: %v", err)
 | 
							log.Error("CreateOrUpdateSecret failed: %v", err)
 | 
				
			||||||
		ctx.JSONError(ctx.Tr("secrets.creation.failed"))
 | 
							ctx.JSONError(ctx.Tr("secrets.creation.failed"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ import (
 | 
				
			|||||||
	secret_service "code.gitea.io/gitea/services/secrets"
 | 
						secret_service "code.gitea.io/gitea/services/secrets"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*actions_model.ActionVariable, error) {
 | 
					func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data, description string) (*actions_model.ActionVariable, error) {
 | 
				
			||||||
	if err := secret_service.ValidateName(name); err != nil {
 | 
						if err := secret_service.ValidateName(name); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -22,7 +22,7 @@ func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data strin
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data))
 | 
						v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data), description)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -41,7 +41,7 @@ func UpdateVariableNameData(ctx context.Context, variable *actions_model.ActionV
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	variable.Data = util.ReserveLineBreakForTextarea(variable.Data)
 | 
						variable.Data = util.ReserveLineBreakForTextarea(variable.Data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return actions_model.UpdateVariableCols(ctx, variable, "name", "data")
 | 
						return actions_model.UpdateVariableCols(ctx, variable, "name", "data", "description")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func DeleteVariableByID(ctx context.Context, variableID int64) error {
 | 
					func DeleteVariableByID(ctx context.Context, variableID int64) error {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -325,6 +325,7 @@ func (f *AddKeyForm) Validate(req *http.Request, errs binding.Errors) binding.Er
 | 
				
			|||||||
type AddSecretForm struct {
 | 
					type AddSecretForm struct {
 | 
				
			||||||
	Name        string `binding:"Required;MaxSize(255)"`
 | 
						Name        string `binding:"Required;MaxSize(255)"`
 | 
				
			||||||
	Data        string `binding:"Required;MaxSize(65535)"`
 | 
						Data        string `binding:"Required;MaxSize(65535)"`
 | 
				
			||||||
 | 
						Description string `binding:"MaxSize(65535)"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Validate validates the fields
 | 
					// Validate validates the fields
 | 
				
			||||||
@@ -336,6 +337,7 @@ func (f *AddSecretForm) Validate(req *http.Request, errs binding.Errors) binding
 | 
				
			|||||||
type EditVariableForm struct {
 | 
					type EditVariableForm struct {
 | 
				
			||||||
	Name        string `binding:"Required;MaxSize(255)"`
 | 
						Name        string `binding:"Required;MaxSize(255)"`
 | 
				
			||||||
	Data        string `binding:"Required;MaxSize(65535)"`
 | 
						Data        string `binding:"Required;MaxSize(65535)"`
 | 
				
			||||||
 | 
						Description string `binding:"MaxSize(65535)"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
 | 
					func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ import (
 | 
				
			|||||||
	secret_model "code.gitea.io/gitea/models/secret"
 | 
						secret_model "code.gitea.io/gitea/models/secret"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CreateOrUpdateSecret(ctx context.Context, ownerID, repoID int64, name, data string) (*secret_model.Secret, bool, error) {
 | 
					func CreateOrUpdateSecret(ctx context.Context, ownerID, repoID int64, name, data, description string) (*secret_model.Secret, bool, error) {
 | 
				
			||||||
	if err := ValidateName(name); err != nil {
 | 
						if err := ValidateName(name); err != nil {
 | 
				
			||||||
		return nil, false, err
 | 
							return nil, false, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -25,14 +25,14 @@ func CreateOrUpdateSecret(ctx context.Context, ownerID, repoID int64, name, data
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(s) == 0 {
 | 
						if len(s) == 0 {
 | 
				
			||||||
		s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, name, data)
 | 
							s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, name, data, description)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, false, err
 | 
								return nil, false, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return s, true, nil
 | 
							return s, true, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := secret_model.UpdateSecret(ctx, s[0].ID, data); err != nil {
 | 
						if err := secret_model.UpdateSecret(ctx, s[0].ID, data, description); err != nil {
 | 
				
			||||||
		return nil, false, err
 | 
							return nil, false, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,9 @@
 | 
				
			|||||||
				<div class="flex-item-title">
 | 
									<div class="flex-item-title">
 | 
				
			||||||
					{{.Name}}
 | 
										{{.Name}}
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
									<div class="flex-item-body">
 | 
				
			||||||
 | 
										{{if .Description}}{{.Description}}{{else}}-{{end}}
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
				<div class="flex-item-body">
 | 
									<div class="flex-item-body">
 | 
				
			||||||
					******
 | 
										******
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
@@ -72,9 +75,20 @@
 | 
				
			|||||||
				<textarea required
 | 
									<textarea required
 | 
				
			||||||
					id="secret-data"
 | 
										id="secret-data"
 | 
				
			||||||
					name="data"
 | 
										name="data"
 | 
				
			||||||
 | 
										maxlength="{{.DataMaxLength}}"
 | 
				
			||||||
					placeholder="{{ctx.Locale.Tr "secrets.creation.value_placeholder"}}"
 | 
										placeholder="{{ctx.Locale.Tr "secrets.creation.value_placeholder"}}"
 | 
				
			||||||
				></textarea>
 | 
									></textarea>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="field">
 | 
				
			||||||
 | 
									<label for="secret-description">{{ctx.Locale.Tr "secrets.creation.description"}}</label>
 | 
				
			||||||
 | 
									<textarea
 | 
				
			||||||
 | 
										id="secret-description"
 | 
				
			||||||
 | 
										name="description"
 | 
				
			||||||
 | 
										rows="2"
 | 
				
			||||||
 | 
										maxlength="{{.DescriptionMaxLength}}"
 | 
				
			||||||
 | 
										placeholder="{{ctx.Locale.Tr "secrets.creation.description_placeholder"}}"
 | 
				
			||||||
 | 
									></textarea>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
 | 
							{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
 | 
				
			||||||
	</form>
 | 
						</form>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@
 | 
				
			|||||||
			data-modal-header="{{ctx.Locale.Tr "actions.variables.creation"}}"
 | 
								data-modal-header="{{ctx.Locale.Tr "actions.variables.creation"}}"
 | 
				
			||||||
			data-modal-dialog-variable-name=""
 | 
								data-modal-dialog-variable-name=""
 | 
				
			||||||
			data-modal-dialog-variable-data=""
 | 
								data-modal-dialog-variable-data=""
 | 
				
			||||||
 | 
								data-modal-dialog-variable-description=""
 | 
				
			||||||
		>
 | 
							>
 | 
				
			||||||
			{{ctx.Locale.Tr "actions.variables.creation"}}
 | 
								{{ctx.Locale.Tr "actions.variables.creation"}}
 | 
				
			||||||
		</button>
 | 
							</button>
 | 
				
			||||||
@@ -24,6 +25,9 @@
 | 
				
			|||||||
				<div class="flex-item-title">
 | 
									<div class="flex-item-title">
 | 
				
			||||||
					{{.Name}}
 | 
										{{.Name}}
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
									<div class="flex-item-body">
 | 
				
			||||||
 | 
										{{if .Description}}{{.Description}}{{else}}-{{end}}
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
				<div class="flex-item-body">
 | 
									<div class="flex-item-body">
 | 
				
			||||||
					{{.Data}}
 | 
										{{.Data}}
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
@@ -39,6 +43,7 @@
 | 
				
			|||||||
					data-modal-header="{{ctx.Locale.Tr "actions.variables.edit"}}"
 | 
										data-modal-header="{{ctx.Locale.Tr "actions.variables.edit"}}"
 | 
				
			||||||
					data-modal-dialog-variable-name="{{.Name}}"
 | 
										data-modal-dialog-variable-name="{{.Name}}"
 | 
				
			||||||
					data-modal-dialog-variable-data="{{.Data}}"
 | 
										data-modal-dialog-variable-data="{{.Data}}"
 | 
				
			||||||
 | 
										data-modal-dialog-variable-description="{{.Description}}"
 | 
				
			||||||
				>
 | 
									>
 | 
				
			||||||
					{{svg "octicon-pencil"}}
 | 
										{{svg "octicon-pencil"}}
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
@@ -82,9 +87,20 @@
 | 
				
			|||||||
				<textarea required
 | 
									<textarea required
 | 
				
			||||||
					name="data"
 | 
										name="data"
 | 
				
			||||||
					id="dialog-variable-data"
 | 
										id="dialog-variable-data"
 | 
				
			||||||
 | 
										maxlength="{{.DataMaxLength}}"
 | 
				
			||||||
					placeholder="{{ctx.Locale.Tr "secrets.creation.value_placeholder"}}"
 | 
										placeholder="{{ctx.Locale.Tr "secrets.creation.value_placeholder"}}"
 | 
				
			||||||
				></textarea>
 | 
									></textarea>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="field">
 | 
				
			||||||
 | 
									<label for="dialog-variable-description">{{ctx.Locale.Tr "secrets.creation.description"}}</label>
 | 
				
			||||||
 | 
									<textarea
 | 
				
			||||||
 | 
										name="description"
 | 
				
			||||||
 | 
										id="dialog-variable-description"
 | 
				
			||||||
 | 
										rows="2"
 | 
				
			||||||
 | 
										maxlength="{{.DescriptionMaxLength}}"
 | 
				
			||||||
 | 
										placeholder="{{ctx.Locale.Tr "secrets.creation.description_placeholder"}}"
 | 
				
			||||||
 | 
									></textarea>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
 | 
							{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
 | 
				
			||||||
	</form>
 | 
						</form>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							@@ -19325,6 +19325,11 @@
 | 
				
			|||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "Data"
 | 
					          "x-go-name": "Data"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "description": {
 | 
				
			||||||
 | 
					          "description": "the description of the variable",
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Description"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "name": {
 | 
					        "name": {
 | 
				
			||||||
          "description": "the name of the variable",
 | 
					          "description": "the name of the variable",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
@@ -20988,6 +20993,11 @@
 | 
				
			|||||||
          "description": "Data of the secret to update",
 | 
					          "description": "Data of the secret to update",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "Data"
 | 
					          "x-go-name": "Data"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "description": {
 | 
				
			||||||
 | 
					          "description": "Description of the secret to update",
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Description"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
@@ -21498,6 +21508,11 @@
 | 
				
			|||||||
        "value"
 | 
					        "value"
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "properties": {
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "description": {
 | 
				
			||||||
 | 
					          "description": "Description of the variable to create",
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Description"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "value": {
 | 
					        "value": {
 | 
				
			||||||
          "description": "Value of the variable to create",
 | 
					          "description": "Value of the variable to create",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
@@ -25459,6 +25474,11 @@
 | 
				
			|||||||
          "format": "date-time",
 | 
					          "format": "date-time",
 | 
				
			||||||
          "x-go-name": "Created"
 | 
					          "x-go-name": "Created"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "description": {
 | 
				
			||||||
 | 
					          "description": "the secret's description",
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Description"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "name": {
 | 
					        "name": {
 | 
				
			||||||
          "description": "the secret's name",
 | 
					          "description": "the secret's name",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
@@ -26034,6 +26054,11 @@
 | 
				
			|||||||
        "value"
 | 
					        "value"
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "properties": {
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "description": {
 | 
				
			||||||
 | 
					          "description": "Description of the variable to update",
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Description"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "name": {
 | 
					        "name": {
 | 
				
			||||||
          "description": "New name for the variable. If the field is empty, the variable name won't be updated.",
 | 
					          "description": "New name for the variable. If the field is empty, the variable name won't be updated.",
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,21 +27,21 @@ func TestActionsVariables(t *testing.T) {
 | 
				
			|||||||
	require.NoError(t, db.DeleteAllRecords("action_variable"))
 | 
						require.NoError(t, db.DeleteAllRecords("action_variable"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
						user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
				
			||||||
	_, _ = actions_model.InsertVariable(ctx, user2.ID, 0, "VAR", "user2-var")
 | 
						_, _ = actions_model.InsertVariable(ctx, user2.ID, 0, "VAR", "user2-var", "user2-var-description")
 | 
				
			||||||
	user2Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{OwnerID: user2.ID, Name: "VAR"})
 | 
						user2Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{OwnerID: user2.ID, Name: "VAR"})
 | 
				
			||||||
	userWebURL := "/user/settings/actions/variables"
 | 
						userWebURL := "/user/settings/actions/variables"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization})
 | 
						org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization})
 | 
				
			||||||
	_, _ = actions_model.InsertVariable(ctx, org3.ID, 0, "VAR", "org3-var")
 | 
						_, _ = actions_model.InsertVariable(ctx, org3.ID, 0, "VAR", "org3-var", "org3-var-description")
 | 
				
			||||||
	org3Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{OwnerID: org3.ID, Name: "VAR"})
 | 
						org3Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{OwnerID: org3.ID, Name: "VAR"})
 | 
				
			||||||
	orgWebURL := "/org/org3/settings/actions/variables"
 | 
						orgWebURL := "/org/org3/settings/actions/variables"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
						repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
				
			||||||
	_, _ = actions_model.InsertVariable(ctx, 0, repo1.ID, "VAR", "repo1-var")
 | 
						_, _ = actions_model.InsertVariable(ctx, 0, repo1.ID, "VAR", "repo1-var", "repo1-var-description")
 | 
				
			||||||
	repo1Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{RepoID: repo1.ID, Name: "VAR"})
 | 
						repo1Var := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{RepoID: repo1.ID, Name: "VAR"})
 | 
				
			||||||
	repoWebURL := "/user2/repo1/settings/actions/variables"
 | 
						repoWebURL := "/user2/repo1/settings/actions/variables"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, _ = actions_model.InsertVariable(ctx, 0, 0, "VAR", "global-var")
 | 
						_, _ = actions_model.InsertVariable(ctx, 0, 0, "VAR", "global-var", "global-var-description")
 | 
				
			||||||
	globalVar := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{Name: "VAR", Data: "global-var"})
 | 
						globalVar := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionVariable{Name: "VAR", Data: "global-var"})
 | 
				
			||||||
	adminWebURL := "/-/admin/actions/variables"
 | 
						adminWebURL := "/-/admin/actions/variables"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -73,6 +73,33 @@ func TestAPIRepoSecrets(t *testing.T) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("CreateWithDescription", func(t *testing.T) {
 | 
				
			||||||
 | 
							cases := []struct {
 | 
				
			||||||
 | 
								Name           string
 | 
				
			||||||
 | 
								Description    string
 | 
				
			||||||
 | 
								ExpectedStatus int
 | 
				
			||||||
 | 
							}{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name:           "no_description",
 | 
				
			||||||
 | 
									Description:    "",
 | 
				
			||||||
 | 
									ExpectedStatus: http.StatusCreated,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name:           "description",
 | 
				
			||||||
 | 
									Description:    "some description",
 | 
				
			||||||
 | 
									ExpectedStatus: http.StatusCreated,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, c := range cases {
 | 
				
			||||||
 | 
								req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{
 | 
				
			||||||
 | 
									Data:        "data",
 | 
				
			||||||
 | 
									Description: c.Description,
 | 
				
			||||||
 | 
								}).AddTokenAuth(token)
 | 
				
			||||||
 | 
								MakeRequest(t, req, c.ExpectedStatus)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("Update", func(t *testing.T) {
 | 
						t.Run("Update", func(t *testing.T) {
 | 
				
			||||||
		name := "update_secret"
 | 
							name := "update_secret"
 | 
				
			||||||
		url := fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), name)
 | 
							url := fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), name)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user