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

+14 -14
View File
@@ -49,8 +49,8 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
opts := &secret_model.FindSecretsOptions{
OwnerID: ctx.Org.Organization.ID,
ListOptions: utils.GetListOptions(ctx),
BelongingScope: actions_model.BelongingScopeOwner(ctx.Org.Organization.ID),
ListOptions: utils.GetListOptions(ctx),
}
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
@@ -109,7 +109,7 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
opt := web.GetForm[*api.CreateOrUpdateSecretOption](ctx)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, actions_model.BelongingScopeOwner(ctx.Org.Organization.ID), ctx.PathParam("secretname"), opt.Data, opt.Description)
if err != nil {
ctx.APIErrorAuto(err)
return
@@ -156,7 +156,7 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
s, err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"))
s, err := secret_service.DeleteSecretByName(ctx, actions_model.BelongingScopeOwner(ctx.Org.Organization.ID), ctx.PathParam("secretname"))
if err != nil {
ctx.APIErrorAuto(err)
return
@@ -219,8 +219,8 @@ func (Action) ListVariables(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
OwnerID: ctx.Org.Organization.ID,
ListOptions: listOptions,
BelongingScope: actions_model.BelongingScopeOwner(ctx.Org.Organization.ID),
ListOptions: listOptions,
})
if err != nil {
ctx.APIErrorInternal(err)
@@ -269,8 +269,8 @@ func (Action) GetVariable(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
OwnerID: ctx.Org.Organization.ID,
Name: ctx.PathParam("variablename"),
BelongingScope: actions_model.BelongingScopeOwner(ctx.Org.Organization.ID),
Name: ctx.PathParam("variablename"),
})
if err != nil {
ctx.APIErrorAuto(err)
@@ -318,7 +318,7 @@ func (Action) DeleteVariable(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("variablename")); err != nil {
if err := actions_service.DeleteVariableByName(ctx, actions_model.BelongingScopeOwner(ctx.Org.Organization.ID), ctx.PathParam("variablename")); err != nil {
ctx.APIErrorAuto(err)
return
}
@@ -366,8 +366,8 @@ func (Action) CreateVariable(ctx *context.APIContext) {
variableName := ctx.PathParam("variablename")
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
OwnerID: ownerID,
Name: variableName,
BelongingScope: actions_model.BelongingScopeOwner(ownerID),
Name: variableName,
})
if err != nil && !errors.Is(err, util.ErrNotExist) {
ctx.APIErrorInternal(err)
@@ -378,7 +378,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
return
}
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
if _, err := actions_service.CreateVariable(ctx, actions_model.BelongingScopeOwner(ownerID), variableName, opt.Value, opt.Description); err != nil {
ctx.APIErrorAuto(err)
return
}
@@ -423,8 +423,8 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
opt := web.GetForm[*api.UpdateVariableOption](ctx)
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
OwnerID: ctx.Org.Organization.ID,
Name: ctx.PathParam("variablename"),
BelongingScope: actions_model.BelongingScopeOwner(ctx.Org.Organization.ID),
Name: ctx.PathParam("variablename"),
})
if err != nil {
ctx.APIErrorAuto(err)
+14 -14
View File
@@ -72,8 +72,8 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
opts := &secret_model.FindSecretsOptions{
RepoID: repo.ID,
ListOptions: listOptions,
BelongingScope: actions_model.BelongingScopeRepo(repo.ID),
ListOptions: listOptions,
}
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
@@ -138,7 +138,7 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
opt := web.GetForm[*api.CreateOrUpdateSecretOption](ctx)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data, opt.Description)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, actions_model.BelongingScopeRepo(repo.ID), ctx.PathParam("secretname"), opt.Data, opt.Description)
if err != nil {
ctx.APIErrorAuto(err)
return
@@ -192,7 +192,7 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
repo := ctx.Repo.Repository
s, err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.PathParam("secretname"))
s, err := secret_service.DeleteSecretByName(ctx, actions_model.BelongingScopeRepo(repo.ID), ctx.PathParam("secretname"))
if err != nil {
ctx.APIErrorAuto(err)
return
@@ -234,8 +234,8 @@ func (Action) GetVariable(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
RepoID: ctx.Repo.Repository.ID,
Name: ctx.PathParam("variablename"),
BelongingScope: actions_model.BelongingScopeRepo(ctx.Repo.Repository.ID),
Name: ctx.PathParam("variablename"),
})
if err != nil {
ctx.APIErrorAuto(err)
@@ -288,7 +288,7 @@ func (Action) DeleteVariable(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParam("variablename")); err != nil {
if err := actions_service.DeleteVariableByName(ctx, actions_model.BelongingScopeRepo(ctx.Repo.Repository.ID), ctx.PathParam("variablename")); err != nil {
ctx.APIErrorAuto(err)
return
}
@@ -339,8 +339,8 @@ func (Action) CreateVariable(ctx *context.APIContext) {
variableName := ctx.PathParam("variablename")
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
RepoID: repoID,
Name: variableName,
BelongingScope: actions_model.BelongingScopeRepo(repoID),
Name: variableName,
})
if err != nil && !errors.Is(err, util.ErrNotExist) {
ctx.APIErrorInternal(err)
@@ -351,7 +351,7 @@ func (Action) CreateVariable(ctx *context.APIContext) {
return
}
if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value, opt.Description); err != nil {
if _, err := actions_service.CreateVariable(ctx, actions_model.BelongingScopeRepo(repoID), variableName, opt.Value, opt.Description); err != nil {
ctx.APIErrorAuto(err)
return
}
@@ -399,8 +399,8 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
opt := web.GetForm[*api.UpdateVariableOption](ctx)
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
RepoID: ctx.Repo.Repository.ID,
Name: ctx.PathParam("variablename"),
BelongingScope: actions_model.BelongingScopeRepo(ctx.Repo.Repository.ID),
Name: ctx.PathParam("variablename"),
})
if err != nil {
ctx.APIErrorAuto(err)
@@ -460,8 +460,8 @@ func (Action) ListVariables(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
RepoID: ctx.Repo.Repository.ID,
ListOptions: listOptions,
BelongingScope: actions_model.BelongingScopeRepo(ctx.Repo.Repository.ID),
ListOptions: listOptions,
})
if err != nil {
ctx.APIErrorInternal(err)
+12 -12
View File
@@ -51,7 +51,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
opt := web.GetForm[*api.CreateOrUpdateSecretOption](ctx)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, actions_model.BelongingScopeOwner(ctx.Doer.ID), ctx.PathParam("secretname"), opt.Data, opt.Description)
if err != nil {
ctx.APIErrorAuto(err)
return
@@ -93,7 +93,7 @@ func DeleteSecret(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
s, err := secret_service.DeleteSecretByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"))
s, err := secret_service.DeleteSecretByName(ctx, actions_model.BelongingScopeOwner(ctx.Doer.ID), ctx.PathParam("secretname"))
if err != nil {
ctx.APIErrorAuto(err)
return
@@ -137,8 +137,8 @@ func CreateVariable(ctx *context.APIContext) {
variableName := ctx.PathParam("variablename")
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
OwnerID: ownerID,
Name: variableName,
BelongingScope: actions_model.BelongingScopeOwner(ownerID),
Name: variableName,
})
if err != nil && !errors.Is(err, util.ErrNotExist) {
ctx.APIErrorInternal(err)
@@ -149,7 +149,7 @@ func CreateVariable(ctx *context.APIContext) {
return
}
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
if _, err := actions_service.CreateVariable(ctx, actions_model.BelongingScopeOwner(ownerID), variableName, opt.Value, opt.Description); err != nil {
ctx.APIErrorAuto(err)
return
}
@@ -189,8 +189,8 @@ func UpdateVariable(ctx *context.APIContext) {
opt := web.GetForm[*api.UpdateVariableOption](ctx)
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
OwnerID: ctx.Doer.ID,
Name: ctx.PathParam("variablename"),
BelongingScope: actions_model.BelongingScopeOwner(ctx.Doer.ID),
Name: ctx.PathParam("variablename"),
})
if err != nil {
ctx.APIErrorAuto(err)
@@ -236,7 +236,7 @@ func DeleteVariable(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("variablename")); err != nil {
if err := actions_service.DeleteVariableByName(ctx, actions_model.BelongingScopeOwner(ctx.Doer.ID), ctx.PathParam("variablename")); err != nil {
ctx.APIErrorAuto(err)
return
}
@@ -266,8 +266,8 @@ func GetVariable(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
OwnerID: ctx.Doer.ID,
Name: ctx.PathParam("variablename"),
BelongingScope: actions_model.BelongingScopeOwner(ctx.Doer.ID),
Name: ctx.PathParam("variablename"),
})
if err != nil {
ctx.APIErrorAuto(err)
@@ -310,8 +310,8 @@ func ListVariables(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
OwnerID: ctx.Doer.ID,
ListOptions: listOptions,
BelongingScope: actions_model.BelongingScopeOwner(ctx.Doer.ID),
ListOptions: listOptions,
})
if err != nil {
ctx.APIErrorInternal(err)
+8 -6
View File
@@ -7,6 +7,7 @@ import (
"errors"
"net/http"
actions_model "gitea.dev/models/actions"
repo_model "gitea.dev/models/repo"
user_model "gitea.dev/models/user"
"gitea.dev/modules/setting"
@@ -26,8 +27,7 @@ const (
type secretsCtx struct {
Owner *user_model.User
Repo *repo_model.Repository
OwnerID int64
RepoID int64
BelongingScope actions_model.BelongingScope
IsRepo bool
IsOrg bool
IsUser bool
@@ -39,7 +39,7 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
if ctx.Data["PageIsRepoSettings"] == true {
return &secretsCtx{
Repo: ctx.Repo.Repository,
RepoID: ctx.Repo.Repository.ID,
BelongingScope: actions_model.BelongingScopeRepo(ctx.Repo.Repository.ID),
IsRepo: true,
SecretsTemplate: tplRepoSecrets,
RedirectLink: ctx.Repo.RepoLink + "/settings/actions/secrets",
@@ -53,7 +53,7 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
}
return &secretsCtx{
Owner: ctx.ContextUser,
OwnerID: ctx.ContextUser.ID,
BelongingScope: actions_model.BelongingScopeOwner(ctx.ContextUser.ID),
IsOrg: true,
SecretsTemplate: tplOrgSecrets,
RedirectLink: ctx.Org.OrgLink + "/settings/actions/secrets",
@@ -63,7 +63,7 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
if ctx.Data["PageIsUserSettings"] == true {
return &secretsCtx{
Owner: ctx.Doer,
OwnerID: ctx.Doer.ID,
BelongingScope: actions_model.BelongingScopeOwner(ctx.Doer.ID),
IsUser: true,
SecretsTemplate: tplUserSecrets,
RedirectLink: setting.AppSubURL + "/user/settings/actions/secrets",
@@ -88,7 +88,7 @@ func Secrets(ctx *context.Context) {
ctx.Data["DisableSSH"] = setting.SSH.Disabled
}
shared.SetSecretsContext(ctx, sCtx.OwnerID, sCtx.RepoID)
shared.SetSecretsContext(ctx, sCtx.BelongingScope)
if ctx.Written() {
return
}
@@ -109,6 +109,7 @@ func SecretsPost(ctx *context.Context) {
shared.PerformSecretsPost(
ctx,
sCtx.BelongingScope,
sCtx.Owner,
sCtx.Repo,
sCtx.RedirectLink,
@@ -123,6 +124,7 @@ func SecretsDelete(ctx *context.Context) {
}
shared.PerformSecretsDelete(
ctx,
sCtx.BelongingScope,
sCtx.Owner,
sCtx.Repo,
sCtx.RedirectLink,
+9 -31
View File
@@ -27,8 +27,7 @@ const (
)
type variablesCtx struct {
OwnerID int64
RepoID int64
BelongingScope actions_model.BelongingScope
IsRepo bool
IsOrg bool
IsUser bool
@@ -40,8 +39,7 @@ type variablesCtx struct {
func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsRepoSettings"] == true {
return &variablesCtx{
OwnerID: 0,
RepoID: ctx.Repo.Repository.ID,
BelongingScope: actions_model.BelongingScopeRepo(ctx.Repo.Repository.ID),
IsRepo: true,
VariablesTemplate: tplRepoVariables,
RedirectLink: ctx.Repo.RepoLink + "/settings/actions/variables",
@@ -54,8 +52,7 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
return nil, nil //nolint:nilnil // error is already handled by ctx.ServerError
}
return &variablesCtx{
OwnerID: ctx.ContextUser.ID,
RepoID: 0,
BelongingScope: actions_model.BelongingScopeOwner(ctx.Org.Organization.ID),
IsOrg: true,
VariablesTemplate: tplOrgVariables,
RedirectLink: ctx.Org.OrgLink + "/settings/actions/variables",
@@ -64,8 +61,7 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsUserSettings"] == true {
return &variablesCtx{
OwnerID: ctx.Doer.ID,
RepoID: 0,
BelongingScope: actions_model.BelongingScopeOwner(ctx.Doer.ID),
IsUser: true,
VariablesTemplate: tplUserVariables,
RedirectLink: setting.AppSubURL + "/user/settings/actions/variables",
@@ -74,8 +70,7 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsAdmin"] == true {
return &variablesCtx{
OwnerID: 0,
RepoID: 0,
BelongingScope: actions_model.BelongingScopeGlobal(),
IsGlobal: true,
VariablesTemplate: tplAdminVariables,
RedirectLink: setting.AppSubURL + "/-/admin/actions/variables",
@@ -97,8 +92,7 @@ func Variables(ctx *context.Context) {
}
variables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{
OwnerID: vCtx.OwnerID,
RepoID: vCtx.RepoID,
BelongingScope: vCtx.BelongingScope,
})
if err != nil {
ctx.ServerError("FindVariables", err)
@@ -124,7 +118,7 @@ func VariableCreate(ctx *context.Context) {
form := web.GetForm[*forms.EditVariableForm](ctx)
v, err := actions_service.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, form.Name, form.Data, form.Description)
v, err := actions_service.CreateVariable(ctx, vCtx.BelongingScope, form.Name, form.Data, form.Description)
if err != nil {
ctx.JSONErrorAuto(err)
return
@@ -168,25 +162,9 @@ func VariableUpdate(ctx *context.Context) {
func findActionsVariable(ctx *context.Context, id int64, vCtx *variablesCtx) *actions_model.ActionVariable {
opts := actions_model.FindVariablesOpts{
IDs: []int64{id},
IDs: []int64{id},
BelongingScope: vCtx.BelongingScope,
}
switch {
case vCtx.IsRepo:
opts.RepoID = vCtx.RepoID
if opts.RepoID == 0 {
panic("RepoID is 0")
}
case vCtx.IsOrg, vCtx.IsUser:
opts.OwnerID = vCtx.OwnerID
if opts.OwnerID == 0 {
panic("OwnerID is 0")
}
case vCtx.IsGlobal:
// do nothing
default:
panic("invalid actions variable")
}
got, err := actions_model.FindVariables(ctx, opts)
if err != nil {
ctx.ServerError("FindVariables", err)
+7 -18
View File
@@ -4,6 +4,7 @@
package secrets
import (
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
secret_model "gitea.dev/models/secret"
@@ -17,8 +18,8 @@ import (
secret_service "gitea.dev/services/secrets"
)
func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
secrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: ownerID, RepoID: repoID})
func SetSecretsContext(ctx *context.Context, scope actions_model.BelongingScope) {
secrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{BelongingScope: scope})
if err != nil {
ctx.ServerError("FindSecrets", err)
return
@@ -29,21 +30,10 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
ctx.Data["DescriptionMaxLength"] = secret_model.SecretDescriptionMaxLength
}
func secretOwnerRepoIDs(owner *user_model.User, repo *repo_model.Repository) (ownerID, repoID int64) {
if owner != nil {
ownerID = owner.ID
}
if repo != nil {
repoID = repo.ID
}
return ownerID, repoID
}
func PerformSecretsPost(ctx *context.Context, owner *user_model.User, repo *repo_model.Repository, redirectURL string) {
func PerformSecretsPost(ctx *context.Context, scope actions_model.BelongingScope, owner *user_model.User, repo *repo_model.Repository, redirectURL string) {
form := web.GetForm[*forms.AddSecretForm](ctx)
ownerID, repoID := secretOwnerRepoIDs(owner, repo)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.NormalizeStringEOL(form.Data), form.Description)
s, created, err := secret_service.CreateOrUpdateSecret(ctx, scope, form.Name, util.NormalizeStringEOL(form.Data), form.Description)
if err != nil {
ctx.JSONErrorAuto(err)
return
@@ -59,11 +49,10 @@ func PerformSecretsPost(ctx *context.Context, owner *user_model.User, repo *repo
ctx.JSONRedirect(redirectURL)
}
func PerformSecretsDelete(ctx *context.Context, owner *user_model.User, repo *repo_model.Repository, redirectURL string) {
func PerformSecretsDelete(ctx *context.Context, scope actions_model.BelongingScope, owner *user_model.User, repo *repo_model.Repository, redirectURL string) {
id := ctx.FormInt64("id")
ownerID, repoID := secretOwnerRepoIDs(owner, repo)
s, err := secret_service.DeleteSecretByID(ctx, ownerID, repoID, id)
s, err := secret_service.DeleteSecretByID(ctx, scope, id)
if err != nil {
log.Error("DeleteSecretByID(%d) failed: %v", id, err)
ctx.JSONError(ctx.Tr("secrets.deletion.failed"))