Skip to content

Support configuration variables on Gitea Actions #24724

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 83 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
c88e2ad
implements varibales
sillyguodong Apr 23, 2023
427dfa2
typo
sillyguodong Apr 23, 2023
3c0aa03
Merge branch 'main' into feature/variables
sillyguodong Apr 23, 2023
5edf8ae
fix context package
sillyguodong Apr 23, 2023
9e860db
Merge branch 'main' into feature/variables
sillyguodong Apr 28, 2023
fbe232f
Merge branch 'main' into feature/variables
sillyguodong May 5, 2023
a438e97
update
sillyguodong May 8, 2023
b018fee
fix lint
sillyguodong May 8, 2023
fd6270c
complete
sillyguodong May 8, 2023
54d4918
lint
sillyguodong May 8, 2023
e19b2fe
lint
sillyguodong May 8, 2023
b1bf9a8
Merge branch 'main' into feature/variables
sillyguodong May 8, 2023
e1051a3
fmt check
sillyguodong May 8, 2023
89928e9
typo
sillyguodong May 8, 2023
4007170
typo
sillyguodong May 8, 2023
0e37460
Merge branch 'main' into feature/variables
sillyguodong May 10, 2023
bcaba8b
reanme table name
sillyguodong May 10, 2023
a580e4a
delete unnecessary where condition
sillyguodong May 15, 2023
de5cbbc
Merge branch 'main' into feature/variables
sillyguodong May 15, 2023
6c28158
delete
sillyguodong May 15, 2023
4081980
use button-ghost class
sillyguodong May 15, 2023
460ba32
add padding for list items
sillyguodong May 16, 2023
8867d20
user level variables
sillyguodong May 16, 2023
06eec8b
use fetch request
sillyguodong May 19, 2023
ae965db
await json
sillyguodong May 19, 2023
5e2bf96
Merge branch 'main' into feature/variables
sillyguodong May 22, 2023
e69f537
lint
sillyguodong May 22, 2023
e2a7327
make fmt
sillyguodong May 22, 2023
45165e4
rename filename and function name
sillyguodong May 24, 2023
fc9b3fa
delete comment
sillyguodong May 24, 2023
ba90273
Merge branch 'main' into feature/variables
sillyguodong May 24, 2023
3b9d874
getValidateContext
sillyguodong May 24, 2023
c0552dd
fix path param
sillyguodong May 24, 2023
7c184d5
fix jquery.are-you-sure interference
silverwind May 28, 2023
238418d
modal fixes
silverwind May 28, 2023
0d07e9c
rename field, use ignore-dirty
silverwind May 28, 2023
2029461
simplify form validation
silverwind May 28, 2023
2ba6a92
use flexbox, fix border-radius
silverwind May 28, 2023
57d9e70
Merge branch 'main' into feature/variables
sillyguodong Jun 6, 2023
795cd09
fix secrets list
sillyguodong Jun 8, 2023
bb7e455
action modal share js
sillyguodong Jun 8, 2023
83a593d
Update options/locale/locale_en-US.ini
sillyguodong Jun 8, 2023
945efc2
make check
sillyguodong Jun 8, 2023
b1f5d83
lint
sillyguodong Jun 9, 2023
e632c97
Merge branch 'main' into feature/variables
sillyguodong Jun 9, 2023
d849c9e
fix actions meun
sillyguodong Jun 12, 2023
a75d1f6
Merge branch 'main' into feature/variables
sillyguodong Jun 12, 2023
5da62b4
update
sillyguodong Jun 13, 2023
ad2b53f
fix
sillyguodong Jun 13, 2023
8928823
fix
sillyguodong Jun 13, 2023
2a4d05f
Merge branch 'main' into feature/variables
sillyguodong Jun 15, 2023
935a579
lint-templates
sillyguodong Jun 15, 2023
5811563
no index
sillyguodong Jun 16, 2023
cea22db
interact-bg
sillyguodong Jun 16, 2023
2506e95
update
sillyguodong Jun 16, 2023
791983f
init
sillyguodong Jun 16, 2023
cc2c282
delete space
sillyguodong Jun 16, 2023
27ded72
keep index of repo_id
sillyguodong Jun 16, 2023
fb0681f
modal onApprove
sillyguodong Jun 16, 2023
1d65fad
delete show-modal class
sillyguodong Jun 16, 2023
905e916
Merge branch 'main' into feature/variables
wxiaoguang Jun 16, 2023
058dcc8
fix fmt
wxiaoguang Jun 16, 2023
8e9a11a
use link-action
sillyguodong Jun 16, 2023
81d62c5
prepare to use general "show-modal"
wxiaoguang Jun 16, 2023
f766a4b
delete useless key class
sillyguodong Jun 16, 2023
1134fc6
use gt-rounded-bottom
sillyguodong Jun 16, 2023
e87c2c3
refactor
wxiaoguang Jun 16, 2023
14467cb
fine tune
wxiaoguang Jun 16, 2023
bc8f7ae
fix modal background for loading state
silverwind Jun 16, 2023
403090d
move comment
silverwind Jun 16, 2023
0339408
clean tooltip
sillyguodong Jun 16, 2023
0738c17
Update options/locale/locale_en-US.ini
sillyguodong Jun 16, 2023
9f618a3
delete unused trans
sillyguodong Jun 16, 2023
06b1f39
add top-level trans
sillyguodong Jun 16, 2023
48e9368
Merge branch 'main' into feature/variables
wxiaoguang Jun 18, 2023
9a43909
Merge branch 'main' into feature/variables
silverwind Jun 18, 2023
3c47977
rename column
sillyguodong Jun 19, 2023
d8abde9
forbid env name ci
sillyguodong Jun 19, 2023
2d80878
make fmt
sillyguodong Jun 19, 2023
aff6008
Merge branch 'main' into feature/variables
sillyguodong Jun 19, 2023
b22af4d
Merge branch 'main' into feature/variables
sillyguodong Jun 19, 2023
d0f6c54
fix max char count of name and data
sillyguodong Jun 20, 2023
58e9253
Merge branch 'main' into feature/variables
GiteaBot Jun 20, 2023
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
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,8 @@ var migrations = []Migration{
NewMigration("Add ArchivedUnix Column", v1_20.AddArchivedUnixToRepository),
// v256 -> v257
NewMigration("Add is_internal column to package", v1_20.AddIsInternalColumnToPackage),
// v257 -> v258
NewMigration("Add variable table", v1_20.CreateVariableTable),
}

// GetCurrentDBVersion returns the current db version
Expand Down
24 changes: 24 additions & 0 deletions models/migrations/v1_20/v257.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_20 //nolint

import (
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/xorm"
)

func CreateVariableTable(x *xorm.Engine) error {
type ActionVariable struct {
ID int64 `xorm:"pk autoincr"`
OwnerID 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"`
Data string `xorm:"LONGTEXT NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

return x.Sync(new(ActionVariable))
}
136 changes: 136 additions & 0 deletions models/variable/variable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package variable

import (
"context"
"fmt"
"regexp"
"strings"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
)

type ActionVariable struct {
ID int64 `xorm:"pk autoincr"`
OwnerID 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"`
Data string `xorm:"LONGTEXT NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

func init() {
db.RegisterModel(new(ActionVariable))
}

type ErrVariableUnbound struct{}

func (err ErrVariableUnbound) Error() string {
return "variable is not bound to the repo or org"
}

type ErrVariableInvalidValue struct {
Name *string
Data *string
}

func (err ErrVariableInvalidValue) Error() string {
if err.Name != nil {
return fmt.Sprintf("variable name %s is invalid", *err.Name)
}
if err.Data != nil {
return fmt.Sprintf("variable data %s is invalid", *err.Data)
}
return util.ErrInvalidArgument.Error()
}

// some regular expression of `variables`
// reference to: https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
var (
variableNameReg = regexp.MustCompile("(?i)^[A-Z_][A-Z0-9_]*$")
variableForbiddenPrefixReg = regexp.MustCompile("(?i)^GIT(EA|HUB)_")
)

func (v *ActionVariable) Validate() error {
switch {
case v.OwnerID == 0 && v.RepoID == 0:
return ErrVariableUnbound{}
case len(v.Name) == 0 || len(v.Name) > 50:
return ErrVariableInvalidValue{Name: &v.Name}
case len(v.Data) == 0:
return ErrVariableInvalidValue{Data: &v.Data}
case !variableNameReg.MatchString(v.Name) || variableForbiddenPrefixReg.MatchString(v.Name):
return ErrVariableInvalidValue{Name: &v.Name}
default:
return nil
}
}

func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*ActionVariable, error) {
variable := &ActionVariable{
OwnerID: ownerID,
RepoID: repoID,
Name: strings.ToUpper(name),
Data: data,
}
if err := variable.Validate(); err != nil {
return variable, err
}
return variable, db.Insert(ctx, variable)
}

type FindVariablesOpts struct {
db.ListOptions
OwnerID int64
RepoID int64
}

func (opts *FindVariablesOpts) toConds() builder.Cond {
cond := builder.NewCond()
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
return cond
}

func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) {
var variables []*ActionVariable
sess := db.GetEngine(ctx)
if opts.PageSize != 0 {
sess = db.SetSessionPagination(sess, &opts.ListOptions)
}
return variables, sess.Where(opts.toConds()).Find(&variables)
}

func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) {
var variable ActionVariable
has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable)
if err != nil {
return nil, err
} else if !has {
return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist)
}
return &variable, nil
}

func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
if err := variable.Validate(); err != nil {
return false, err
}
count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data").
Update(&ActionVariable{
Name: variable.Name,
Data: variable.Data,
})
return count != 0, err
}
20 changes: 20 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3428,6 +3428,26 @@ runs.no_matching_runner_helper = No matching runner: %s

need_approval_desc = Need approval to run workflows for fork pull request.

variables = Variables
variables.management = Variables Management
variables.creation = Add Variable
variables.none = There are no variables yet.
variables.delete = Delete Variable "%s"
variables.edit_tooltip = Edit Variable "%s"
variables.deletion = Remove variable
variables.deletion.description = Removing a variable is permanent and cannot be undone. Continue?
variables.description = Variables will be passed to certain actions and cannot be read otherwise.
variables.name = Name
variables.value = Value
variables.id_not_exist = Variable with id %d not exists.
variables.edit = Edit Variable
variables.deletion.failed = Failed to remove variable.
variables.deletion.success = The variable has been removed.
variables.creation.failed = Failed to add variable.
variables.creation.success = The variable "%s" has been added.
variables.update.failed = Failed to edit variable.
variables.update.success = The variable has been edited.

[projects]
type-1.display_name = Individual Project
type-2.display_name = Repository Project
Expand Down
27 changes: 27 additions & 0 deletions routers/api/actions/runner/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

actions_model "code.gitea.io/gitea/models/actions"
secret_model "code.gitea.io/gitea/models/secret"
variable_model "code.gitea.io/gitea/models/variable"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
Expand Down Expand Up @@ -36,6 +37,7 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
WorkflowPayload: t.Job.WorkflowPayload,
Context: generateTaskContext(t),
Secrets: getSecretsOfTask(ctx, t),
Vars: getVariablesOfTask(ctx, t),
}

if needs, err := findTaskNeeds(ctx, t); err != nil {
Expand Down Expand Up @@ -88,6 +90,31 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s
return secrets
}

func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
variables := map[string]string{}

// Org level
ownerVariables, err := variable_model.FindVariables(ctx, variable_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID})
if err != nil {
log.Error("find variables of org: %d, error: %v", task.Job.Run.Repo.OwnerID, err)
}

// Repo level
repoVariables, err := variable_model.FindVariables(ctx, variable_model.FindVariablesOpts{RepoID: task.Job.Run.RepoID})
if err != nil {
log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err)
}

// TODO: Env level

// Level precedence: ENV > REPO > ORG
for _, v := range append(ownerVariables, repoVariables...) {
variables[v.Name] = v.Data
}

return variables
}

func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
event := map[string]interface{}{}
_ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event)
Expand Down
100 changes: 100 additions & 0 deletions routers/web/repo/setting/variables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package setting

import (
"errors"
"net/http"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
shared "code.gitea.io/gitea/routers/web/shared/variables"
)

const (
// TODO: Separate from runners when layout is ready
tplRepoVariables base.TplName = "repo/settings/actions"
tplOrgVariables base.TplName = "org/settings/actions"
)

type variablesCtx struct {
OwnerID int64
RepoID int64
IsRepo bool
IsOrg bool
VariablesTemplate base.TplName
RedirectLink string
}

func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsRepoSettings"] == true {
return &variablesCtx{
RepoID: ctx.Repo.Repository.ID,
IsRepo: true,
VariablesTemplate: tplRepoVariables,
RedirectLink: ctx.Repo.RepoLink + "/settings/actions/variables",
}, nil
}

if ctx.Data["PageIsOrgSettings"] == true {
return &variablesCtx{
OwnerID: ctx.ContextUser.ID,
IsOrg: true,
VariablesTemplate: tplOrgVariables,
RedirectLink: ctx.Org.OrgLink + "/settings/actions/variables",
}, nil
}

return nil, errors.New("unable to set Variables context")
}

func Variables(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("actions.variables")
ctx.Data["PageType"] = "variables"
ctx.Data["PageIsSharedSettingsVariables"] = true

vCtx, err := getVariablesCtx(ctx)
if err != nil {
ctx.ServerError("getVariablesCtx", err)
return
}

shared.SetVariablesContext(ctx, vCtx.OwnerID, vCtx.RepoID)
if ctx.Written() {
return
}

ctx.HTML(http.StatusOK, vCtx.VariablesTemplate)
}

func VariableDelete(ctx *context.Context) {
vCtx, err := getVariablesCtx(ctx)
if err != nil {
ctx.ServerError("getVariablesCtx", err)
return
}
shared.DeleteVariable(ctx, vCtx.OwnerID, vCtx.RepoID, vCtx.RedirectLink)
}

func VariableCreate(ctx *context.Context) {
vCtx, err := getVariablesCtx(ctx)
if err != nil {
ctx.ServerError("getVariablesCtx", err)
return
}
shared.CreateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, vCtx.RedirectLink)
}

func VariableUpdate(ctx *context.Context) {
vCtx, err := getVariablesCtx(ctx)
if err != nil {
ctx.ServerError("getVariablesCtx", err)
return
}
shared.UpdateVariable(ctx, vCtx.OwnerID, vCtx.RepoID, vCtx.RedirectLink)
}

func VariableByID(ctx *context.Context) {
shared.GetVariable(ctx)
}
Loading