Skip to content

Added Description Field for Secrets and Variables #33526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions models/actions/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ package actions
import (
"context"
"strings"
"unicode/utf8"

"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"
)
Expand All @@ -32,26 +34,39 @@ type ActionVariable struct {
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name)"`
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
Data string `xorm:"LONGTEXT NOT NULL"`
Description string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

const (
VariableDataMaxLength = 65536
VariableDescriptionMaxLength = 4096
)

func init() {
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 {
// 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.
ownerID = 0
}

if utf8.RuneCountInString(data) > VariableDataMaxLength {
return nil, util.NewInvalidArgumentErrorf("data too long")
}

description = util.TruncateRunes(description, VariableDescriptionMaxLength)

variable := &ActionVariable{
OwnerID: ownerID,
RepoID: repoID,
Name: strings.ToUpper(name),
Data: data,
OwnerID: ownerID,
RepoID: repoID,
Name: strings.ToUpper(name),
Data: data,
Description: description,
}
return variable, db.Insert(ctx, variable)
}
Expand Down Expand Up @@ -96,6 +111,12 @@ func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariab
}

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)
count, err := db.GetEngine(ctx).
ID(variable.ID).
Expand Down
1 change: 1 addition & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ func prepareMigrationTasks() []*migration {
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(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
}
return preparedMigrations
}
Expand Down
20 changes: 20 additions & 0 deletions models/migrations/v1_24/v316.go
Original file line number Diff line number Diff line change
@@ -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))
}
37 changes: 29 additions & 8 deletions models/secret/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ type Secret struct {
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"`
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
Data string `xorm:"LONGTEXT"` // encrypted data
Description string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
}

const (
SecretDataMaxLength = 65536
SecretDescriptionMaxLength = 4096
)

// ErrSecretNotFound represents a "secret not found" error.
type ErrSecretNotFound struct {
Name string
Expand All @@ -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
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 {
// 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.
Expand All @@ -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)
}

if len(data) > SecretDataMaxLength {
return nil, util.NewInvalidArgumentErrorf("data too long")
}

description = util.TruncateRunes(description, SecretDescriptionMaxLength)

encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
if err != nil {
return nil, err
}

secret := &Secret{
OwnerID: ownerID,
RepoID: repoID,
Name: strings.ToUpper(name),
Data: encrypted,
OwnerID: ownerID,
RepoID: repoID,
Name: strings.ToUpper(name),
Data: encrypted,
Description: description,
}
return secret, db.Insert(ctx, secret)
}
Expand Down Expand Up @@ -114,16 +128,23 @@ func (opts FindSecretsOptions) ToConds() builder.Cond {
}

// 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)
if err != nil {
return err
}

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 {
return ErrSecretNotFound{}
}
Expand Down
7 changes: 7 additions & 0 deletions modules/structs/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import "time"
type Secret struct {
// the secret's name
Name string `json:"name"`
// the secret's description
Description string `json:"description"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
}
Expand All @@ -21,4 +23,9 @@ type CreateOrUpdateSecretOption struct {
//
// required: true
Data string `json:"data" binding:"Required"`

// Description of the secret to update
//
// required: false
Description string `json:"description"`
}
12 changes: 12 additions & 0 deletions modules/structs/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ type CreateVariableOption struct {
//
// required: true
Value string `json:"value" binding:"Required"`

// Description of the variable to create
//
// required: false
Description string `json:"description"`
}

// UpdateVariableOption the option when updating variable
Expand All @@ -21,6 +26,11 @@ type UpdateVariableOption struct {
//
// required: true
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
Expand All @@ -34,4 +44,6 @@ type ActionVariable struct {
Name string `json:"name"`
// the value of the variable
Data string `json:"data"`
// the description of the variable
Description string `json:"description"`
}
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3712,8 +3712,10 @@ secrets = Secrets
description = Secrets will be passed to certain actions and cannot be read otherwise.
none = There are no secrets yet.
creation = Add Secret
creation.description = Description
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.description_placeholder = Enter short description (optional).
creation.success = The secret "%s" has been added.
creation.failed = Failed to add secret.
deletion = Remove secret
Expand Down
29 changes: 17 additions & 12 deletions routers/api/v1/org/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
apiSecrets := make([]*api.Secret, len(secrets))
for k, v := range secrets {
apiSecrets[k] = &api.Secret{
Name: v.Name,
Created: v.CreatedUnix.AsTime(),
Name: v.Name,
Description: v.Description,
Created: v.CreatedUnix.AsTime(),
}
}

Expand Down Expand Up @@ -106,7 +107,8 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {

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 errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err)
Expand Down Expand Up @@ -230,10 +232,11 @@ func (Action) ListVariables(ctx *context.APIContext) {
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,
OwnerID: v.OwnerID,
RepoID: v.RepoID,
Name: v.Name,
Data: v.Data,
Description: v.Description,
}
}

Expand Down Expand Up @@ -281,10 +284,11 @@ func (Action) GetVariable(ctx *context.APIContext) {
}

variable := &api.ActionVariable{
OwnerID: v.OwnerID,
RepoID: v.RepoID,
Name: v.Name,
Data: v.Data,
OwnerID: v.OwnerID,
RepoID: v.RepoID,
Name: v.Name,
Data: v.Data,
Description: v.Description,
}

ctx.JSON(http.StatusOK, variable)
Expand Down Expand Up @@ -386,7 +390,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
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) {
ctx.APIError(http.StatusBadRequest, err)
} else {
Expand Down Expand Up @@ -453,6 +457,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {

v.Name = opt.Name
v.Data = opt.Value
v.Description = opt.Description

if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
Expand Down
28 changes: 17 additions & 11 deletions routers/api/v1/repo/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
apiSecrets := make([]*api.Secret, len(secrets))
for k, v := range secrets {
apiSecrets[k] = &api.Secret{
Name: v.Name,
Created: v.CreatedUnix.AsTime(),
Name: v.Name,
Description: v.Description,
Created: v.CreatedUnix.AsTime(),
}
}

Expand Down Expand Up @@ -136,7 +137,8 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {

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 errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err)
Expand Down Expand Up @@ -249,10 +251,11 @@ func (Action) GetVariable(ctx *context.APIContext) {
}

variable := &api.ActionVariable{
OwnerID: v.OwnerID,
RepoID: v.RepoID,
Name: v.Name,
Data: v.Data,
OwnerID: v.OwnerID,
RepoID: v.RepoID,
Name: v.Name,
Data: v.Data,
Description: v.Description,
}

ctx.JSON(http.StatusOK, variable)
Expand Down Expand Up @@ -362,7 +365,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
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) {
ctx.APIError(http.StatusBadRequest, err)
} else {
Expand Down Expand Up @@ -432,6 +435,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) {

v.Name = opt.Name
v.Data = opt.Value
v.Description = opt.Description

if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
Expand Down Expand Up @@ -491,9 +495,11 @@ func (Action) ListVariables(ctx *context.APIContext) {
variables := make([]*api.ActionVariable, len(vars))
for i, v := range vars {
variables[i] = &api.ActionVariable{
OwnerID: v.OwnerID,
RepoID: v.RepoID,
Name: v.Name,
OwnerID: v.OwnerID,
RepoID: v.RepoID,
Name: v.Name,
Data: v.Data,
Description: v.Description,
}
}

Expand Down
Loading
Loading