refactor(actions): pass secret and variable scope as a struct (#39719)

Secret and variable functions took an adjacent `ownerID, repoID int64`
pair, so swapping the two compiled and only failed at runtime. They now
take a `BelongingScope` value instead.

`BelongingScope` also replaces the "drop OwnerID when RepoID is set"
step that `InsertEncryptedSecret` and `InsertVariable` each implemented
separately

No behaviour change. It also makes adding another scope dimension, such
as the environment ID in https://github.com/go-gitea/gitea/pull/38149, a
one-field change.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-10 18:21:28 +00:00
1 parent 23fb49fea7
commit 321d3ec104
13 files changed
+135 -163

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
// BelongingScope identifies the owner (user/org) or repository an Actions secret or variable belongs to.
// Both zero values mean "global scope".
// Always use the functions to create a BelongingScope, rather than creating one directly, to avoid accidentally creating an invalid scope.
type BelongingScope struct {
internalOwnerID int64
internalRepoID int64
}
func BelongingScopeOwner(ownerID int64) BelongingScope {
return BelongingScope{internalOwnerID: ownerID}
}
func BelongingScopeRepo(repoID int64) BelongingScope {
return BelongingScope{internalRepoID: repoID}
}
func BelongingScopeGlobal() BelongingScope {
return BelongingScope{}
}
func (scope BelongingScope) GetOwnerRepoIDs() (ownerID, repoID int64) {
return scope.internalOwnerID, scope.internalRepoID
}
+13 -18
View File
@@ -48,13 +48,7 @@ func init() {
db.RegisterModel(new(ActionVariable))
}
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
}
func InsertVariable(ctx context.Context, scope BelongingScope, name, data, description string) (*ActionVariable, error) {
if utf8.RuneCountInString(data) > VariableDataMaxLength {
return nil, util.NewInvalidArgumentErrorf("data too long")
}
@@ -62,8 +56,8 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, desc
description = util.TruncateRunes(description, VariableDescriptionMaxLength)
variable := &ActionVariable{
OwnerID: ownerID,
RepoID: repoID,
OwnerID: scope.internalOwnerID,
RepoID: scope.internalRepoID,
Name: strings.ToUpper(name),
Data: data,
Description: description,
@@ -73,10 +67,10 @@ func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data, desc
type FindVariablesOpts struct {
db.ListOptions
IDs []int64
RepoID int64
OwnerID int64 // it will be ignored if RepoID is set
Name string
IDs []int64
Name string
BelongingScope BelongingScope
}
func (opts FindVariablesOpts) ToOrders() string {
@@ -96,12 +90,13 @@ func (opts FindVariablesOpts) ToConds() builder.Cond {
// 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{"repo_id": opts.RepoID})
if opts.RepoID != 0 { // if RepoID is set
ownerID, repoID := opts.BelongingScope.GetOwnerRepoIDs()
cond = cond.And(builder.Eq{"repo_id": repoID})
if repoID != 0 { // if RepoID is set
// ignore OwnerID and treat it as 0
cond = cond.And(builder.Eq{"owner_id": 0})
} else {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
cond = cond.And(builder.Eq{"owner_id": ownerID})
}
if opts.Name != "" {
@@ -152,14 +147,14 @@ func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string,
}
// Org / User level
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID})
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeOwner(run.Repo.OwnerID)})
if err != nil {
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
return nil, err
}
// Repo level
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID})
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{BelongingScope: BelongingScopeRepo(run.RepoID)})
if err != nil {
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
return nil, err
+13 -18
View File
@@ -65,20 +65,15 @@ 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, 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.
ownerID = 0
}
if ownerID == 0 && repoID == 0 {
return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument)
}
func InsertEncryptedSecret(ctx context.Context, scope actions_model.BelongingScope, name, data, description string) (*Secret, error) {
if len(data) > SecretDataMaxLength {
return nil, util.NewInvalidArgumentErrorf("data too long")
}
ownerID, repoID := scope.GetOwnerRepoIDs()
if ownerID == 0 && repoID == 0 {
return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument)
}
description = util.TruncateRunes(description, SecretDescriptionMaxLength)
encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
@@ -102,10 +97,10 @@ func init() {
type FindSecretsOptions struct {
db.ListOptions
RepoID int64
OwnerID int64 // it will be ignored if RepoID is set
SecretID int64
Name string
BelongingScope actions_model.BelongingScope
}
func (opts FindSecretsOptions) ToOrders() string {
@@ -114,13 +109,13 @@ func (opts FindSecretsOptions) ToOrders() string {
func (opts FindSecretsOptions) ToConds() builder.Cond {
cond := builder.NewCond()
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
if opts.RepoID != 0 { // if RepoID is set
ownerID, repoID := opts.BelongingScope.GetOwnerRepoIDs()
cond = cond.And(builder.Eq{"repo_id": repoID})
if repoID != 0 { // if RepoID is set
// ignore OwnerID and treat it as 0
cond = cond.And(builder.Eq{"owner_id": 0})
} else {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
cond = cond.And(builder.Eq{"owner_id": ownerID})
}
if opts.SecretID != 0 {
@@ -170,12 +165,12 @@ func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[
return baseSecrets, nil
}
ownerSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID})
ownerSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{BelongingScope: actions_model.BelongingScopeOwner(task.Job.Run.Repo.OwnerID)})
if err != nil {
log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err)
return nil, err
}
repoSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{RepoID: task.Job.Run.RepoID})
repoSecrets, err := db.Find[Secret](ctx, FindSecretsOptions{BelongingScope: actions_model.BelongingScopeRepo(task.Job.Run.RepoID)})
if err != nil {
log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err)
return nil, err