Added instance-level variables (#28115)

This PR adds instance-level variables, and so closes #27726



![gitea_instance_variables_1](https://github.com/go-gitea/gitea/assets/8344487/ad409cd4-ce36-4c84-a764-34451b0fb63a)

![gitea_instance_variables_2](https://github.com/go-gitea/gitea/assets/8344487/426f0965-dec6-4560-948c-067cdeddd720)

![gitea_instance_variables_3](https://github.com/go-gitea/gitea/assets/8344487/cf1d7776-4938-4825-922e-cbbbf28a5f33)
pull/28587/head^2
Jean-Baptiste Gomond 11 months ago committed by GitHub
parent 0407a402bb
commit d0f24ff4ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      models/actions/variable.go
  2. 10
      routers/api/actions/runner/utils.go
  3. 21
      routers/web/repo/setting/variables.go
  4. 9
      routers/web/web.go
  5. 3
      templates/admin/actions.tmpl
  6. 5
      templates/admin/navbar.tmpl

@ -31,8 +31,8 @@ func init() {
} }
func (v *ActionVariable) Validate() error { func (v *ActionVariable) Validate() error {
if v.OwnerID == 0 && v.RepoID == 0 { if v.OwnerID != 0 && v.RepoID != 0 {
return errors.New("the variable is not bound to any scope") return errors.New("a variable should not be bound to an owner and a repository at the same time")
} }
return nil return nil
} }
@ -58,12 +58,8 @@ type FindVariablesOpts struct {
func (opts FindVariablesOpts) ToConds() builder.Cond { func (opts FindVariablesOpts) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.OwnerID > 0 { cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
return cond return cond
} }

@ -94,6 +94,12 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s
func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string { func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
variables := map[string]string{} variables := map[string]string{}
// Global
globalVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{})
if err != nil {
log.Error("find global variables: %v", err)
}
// Org / User level // Org / User level
ownerVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID}) ownerVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID})
if err != nil { if err != nil {
@ -106,8 +112,8 @@ func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map
log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err) log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err)
} }
// Level precedence: Repo > Org / User // Level precedence: Repo > Org / User > Global
for _, v := range append(ownerVariables, repoVariables...) { for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) {
variables[v.Name] = v.Data variables[v.Name] = v.Data
} }

@ -15,9 +15,10 @@ import (
) )
const ( const (
tplRepoVariables base.TplName = "repo/settings/actions" tplRepoVariables base.TplName = "repo/settings/actions"
tplOrgVariables base.TplName = "org/settings/actions" tplOrgVariables base.TplName = "org/settings/actions"
tplUserVariables base.TplName = "user/settings/actions" tplUserVariables base.TplName = "user/settings/actions"
tplAdminVariables base.TplName = "admin/actions"
) )
type variablesCtx struct { type variablesCtx struct {
@ -26,6 +27,7 @@ type variablesCtx struct {
IsRepo bool IsRepo bool
IsOrg bool IsOrg bool
IsUser bool IsUser bool
IsGlobal bool
VariablesTemplate base.TplName VariablesTemplate base.TplName
RedirectLink string RedirectLink string
} }
@ -33,6 +35,7 @@ type variablesCtx struct {
func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) { func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsRepoSettings"] == true { if ctx.Data["PageIsRepoSettings"] == true {
return &variablesCtx{ return &variablesCtx{
OwnerID: 0,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
IsRepo: true, IsRepo: true,
VariablesTemplate: tplRepoVariables, VariablesTemplate: tplRepoVariables,
@ -48,6 +51,7 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
} }
return &variablesCtx{ return &variablesCtx{
OwnerID: ctx.ContextUser.ID, OwnerID: ctx.ContextUser.ID,
RepoID: 0,
IsOrg: true, IsOrg: true,
VariablesTemplate: tplOrgVariables, VariablesTemplate: tplOrgVariables,
RedirectLink: ctx.Org.OrgLink + "/settings/actions/variables", RedirectLink: ctx.Org.OrgLink + "/settings/actions/variables",
@ -57,12 +61,23 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsUserSettings"] == true { if ctx.Data["PageIsUserSettings"] == true {
return &variablesCtx{ return &variablesCtx{
OwnerID: ctx.Doer.ID, OwnerID: ctx.Doer.ID,
RepoID: 0,
IsUser: true, IsUser: true,
VariablesTemplate: tplUserVariables, VariablesTemplate: tplUserVariables,
RedirectLink: setting.AppSubURL + "/user/settings/actions/variables", RedirectLink: setting.AppSubURL + "/user/settings/actions/variables",
}, nil }, nil
} }
if ctx.Data["PageIsAdmin"] == true {
return &variablesCtx{
OwnerID: 0,
RepoID: 0,
IsGlobal: true,
VariablesTemplate: tplAdminVariables,
RedirectLink: setting.AppSubURL + "/admin/actions/variables",
}, nil
}
return nil, errors.New("unable to set Variables context") return nil, errors.New("unable to set Variables context")
} }

@ -417,7 +417,7 @@ func registerRoutes(m *web.Route) {
m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksEditPost) m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksEditPost)
} }
addSettingVariablesRoutes := func() { addSettingsVariablesRoutes := func() {
m.Group("/variables", func() { m.Group("/variables", func() {
m.Get("", repo_setting.Variables) m.Get("", repo_setting.Variables)
m.Post("/new", web.Bind(forms.EditVariableForm{}), repo_setting.VariableCreate) m.Post("/new", web.Bind(forms.EditVariableForm{}), repo_setting.VariableCreate)
@ -618,7 +618,7 @@ func registerRoutes(m *web.Route) {
m.Get("", user_setting.RedirectToDefaultSetting) m.Get("", user_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes() addSettingsRunnersRoutes()
addSettingsSecretsRoutes() addSettingsSecretsRoutes()
addSettingVariablesRoutes() addSettingsVariablesRoutes()
}, actions.MustEnableActions) }, actions.MustEnableActions)
m.Get("/organization", user_setting.Organization) m.Get("/organization", user_setting.Organization)
@ -763,6 +763,7 @@ func registerRoutes(m *web.Route) {
m.Group("/actions", func() { m.Group("/actions", func() {
m.Get("", admin.RedirectToDefaultSetting) m.Get("", admin.RedirectToDefaultSetting)
addSettingsRunnersRoutes() addSettingsRunnersRoutes()
addSettingsVariablesRoutes()
}) })
}, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enable, "EnablePackages", setting.Packages.Enabled)) }, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enable, "EnablePackages", setting.Packages.Enabled))
// ***** END: Admin ***** // ***** END: Admin *****
@ -905,7 +906,7 @@ func registerRoutes(m *web.Route) {
m.Get("", org_setting.RedirectToDefaultSetting) m.Get("", org_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes() addSettingsRunnersRoutes()
addSettingsSecretsRoutes() addSettingsSecretsRoutes()
addSettingVariablesRoutes() addSettingsVariablesRoutes()
}, actions.MustEnableActions) }, actions.MustEnableActions)
m.Methods("GET,POST", "/delete", org.SettingsDelete) m.Methods("GET,POST", "/delete", org.SettingsDelete)
@ -1084,7 +1085,7 @@ func registerRoutes(m *web.Route) {
m.Get("", repo_setting.RedirectToDefaultSetting) m.Get("", repo_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes() addSettingsRunnersRoutes()
addSettingsSecretsRoutes() addSettingsSecretsRoutes()
addSettingVariablesRoutes() addSettingsVariablesRoutes()
}, actions.MustEnableActions) }, actions.MustEnableActions)
// the follow handler must be under "settings", otherwise this incomplete repo can't be accessed // the follow handler must be under "settings", otherwise this incomplete repo can't be accessed
m.Group("/migrate", func() { m.Group("/migrate", func() {

@ -3,5 +3,8 @@
{{if eq .PageType "runners"}} {{if eq .PageType "runners"}}
{{template "shared/actions/runner_list" .}} {{template "shared/actions/runner_list" .}}
{{end}} {{end}}
{{if eq .PageType "variables"}}
{{template "shared/variables/variable_list" .}}
{{end}}
</div> </div>
{{template "admin/layout_footer" .}} {{template "admin/layout_footer" .}}

@ -60,12 +60,15 @@
{{end}} {{end}}
{{end}} {{end}}
{{if .EnableActions}} {{if .EnableActions}}
<details class="item toggleable-item" {{if .PageIsSharedSettingsRunners}}open{{end}}> <details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsVariables}}open{{end}}>
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary> <summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
<div class="menu"> <div class="menu">
<a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{AppSubUrl}}/admin/actions/runners"> <a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{AppSubUrl}}/admin/actions/runners">
{{ctx.Locale.Tr "actions.runners"}} {{ctx.Locale.Tr "actions.runners"}}
</a> </a>
<a class="{{if .PageIsSharedSettingsVariables}}active {{end}}item" href="{{AppSubUrl}}/admin/actions/variables">
{{ctx.Locale.Tr "actions.variables"}}
</a>
</div> </div>
</details> </details>
{{end}} {{end}}

Loading…
Cancel
Save