Skip to content

Commit 35a653d

Browse files
sillyguodongsilverwindwxiaoguangGiteaBot
authored
Support configuration variables on Gitea Actions (#24724)
Co-Author: @silverwind @wxiaoguang Replace: #24404 See: - [defining configuration variables for multiple workflows](https://docs.github.com/en/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows) - [vars context](https://docs.github.com/en/actions/learn-github-actions/contexts#vars-context) Related to: - [x] protocol: https://gitea.com/gitea/actions-proto-def/pulls/7 - [x] act_runner: https://gitea.com/gitea/act_runner/pulls/157 - [x] act: https://gitea.com/gitea/act/pulls/43 #### Screenshoot Create Variable: ![image](https://user-images.githubusercontent.com/33891828/236758288-032b7f64-44e7-48ea-b07d-de8b8b0e3729.png) ![image](https://user-images.githubusercontent.com/33891828/236758174-5203f64c-1d0e-4737-a5b0-62061dee86f8.png) Workflow: ```yaml test_vars: runs-on: ubuntu-latest steps: - name: Print Custom Variables run: echo "${{ vars.test_key }}" - name: Try to print a non-exist var run: echo "${{ vars.NON_EXIST_VAR }}" ``` Actions Log: ![image](https://user-images.githubusercontent.com/33891828/236759075-af0c5950-368d-4758-a8ac-47a96e43b6e2.png) --- This PR just implement the org / user (depends on the owner of the current repository) and repo level variables, The Environment level variables have not been implemented. Because [Environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#about-environments) is a module separate from `Actions`. Maybe it would be better to create a new PR to do it. --------- Co-authored-by: silverwind <[email protected]> Co-authored-by: wxiaoguang <[email protected]> Co-authored-by: Giteabot <[email protected]>
1 parent 8220e50 commit 35a653d

File tree

22 files changed

+680
-135
lines changed

22 files changed

+680
-135
lines changed

models/actions/variable.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package actions
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"strings"
11+
12+
"code.gitea.io/gitea/models/db"
13+
"code.gitea.io/gitea/modules/timeutil"
14+
"code.gitea.io/gitea/modules/util"
15+
16+
"xorm.io/builder"
17+
)
18+
19+
type ActionVariable struct {
20+
ID int64 `xorm:"pk autoincr"`
21+
OwnerID int64 `xorm:"UNIQUE(owner_repo_name)"`
22+
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name)"`
23+
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
24+
Data string `xorm:"LONGTEXT NOT NULL"`
25+
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
26+
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
27+
}
28+
29+
func init() {
30+
db.RegisterModel(new(ActionVariable))
31+
}
32+
33+
func (v *ActionVariable) Validate() error {
34+
if v.OwnerID == 0 && v.RepoID == 0 {
35+
return errors.New("the variable is not bound to any scope")
36+
}
37+
return nil
38+
}
39+
40+
func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*ActionVariable, error) {
41+
variable := &ActionVariable{
42+
OwnerID: ownerID,
43+
RepoID: repoID,
44+
Name: strings.ToUpper(name),
45+
Data: data,
46+
}
47+
if err := variable.Validate(); err != nil {
48+
return variable, err
49+
}
50+
return variable, db.Insert(ctx, variable)
51+
}
52+
53+
type FindVariablesOpts struct {
54+
db.ListOptions
55+
OwnerID int64
56+
RepoID int64
57+
}
58+
59+
func (opts *FindVariablesOpts) toConds() builder.Cond {
60+
cond := builder.NewCond()
61+
if opts.OwnerID > 0 {
62+
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
63+
}
64+
if opts.RepoID > 0 {
65+
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
66+
}
67+
return cond
68+
}
69+
70+
func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) {
71+
var variables []*ActionVariable
72+
sess := db.GetEngine(ctx)
73+
if opts.PageSize != 0 {
74+
sess = db.SetSessionPagination(sess, &opts.ListOptions)
75+
}
76+
return variables, sess.Where(opts.toConds()).Find(&variables)
77+
}
78+
79+
func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) {
80+
var variable ActionVariable
81+
has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable)
82+
if err != nil {
83+
return nil, err
84+
} else if !has {
85+
return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist)
86+
}
87+
return &variable, nil
88+
}
89+
90+
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
91+
count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data").
92+
Update(&ActionVariable{
93+
Name: variable.Name,
94+
Data: variable.Data,
95+
})
96+
return count != 0, err
97+
}

models/migrations/migrations.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,9 @@ var migrations = []Migration{
503503

504504
// v260 -> v261
505505
NewMigration("Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner),
506+
507+
// v261 -> v262
508+
NewMigration("Add variable table", v1_21.CreateVariableTable),
506509
}
507510

508511
// GetCurrentDBVersion returns the current db version

models/migrations/v1_21/v261.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_21 //nolint
5+
6+
import (
7+
"code.gitea.io/gitea/modules/timeutil"
8+
9+
"xorm.io/xorm"
10+
)
11+
12+
func CreateVariableTable(x *xorm.Engine) error {
13+
type ActionVariable struct {
14+
ID int64 `xorm:"pk autoincr"`
15+
OwnerID int64 `xorm:"UNIQUE(owner_repo_name)"`
16+
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name)"`
17+
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
18+
Data string `xorm:"LONGTEXT NOT NULL"`
19+
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
20+
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
21+
}
22+
23+
return x.Sync(new(ActionVariable))
24+
}

models/secret/secret.go

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,17 @@ package secret
55

66
import (
77
"context"
8-
"fmt"
9-
"regexp"
8+
"errors"
109
"strings"
1110

1211
"code.gitea.io/gitea/models/db"
1312
secret_module "code.gitea.io/gitea/modules/secret"
1413
"code.gitea.io/gitea/modules/setting"
1514
"code.gitea.io/gitea/modules/timeutil"
16-
"code.gitea.io/gitea/modules/util"
1715

1816
"xorm.io/builder"
1917
)
2018

21-
type ErrSecretInvalidValue struct {
22-
Name *string
23-
Data *string
24-
}
25-
26-
func (err ErrSecretInvalidValue) Error() string {
27-
if err.Name != nil {
28-
return fmt.Sprintf("secret name %q is invalid", *err.Name)
29-
}
30-
if err.Data != nil {
31-
return fmt.Sprintf("secret data %q is invalid", *err.Data)
32-
}
33-
return util.ErrInvalidArgument.Error()
34-
}
35-
36-
func (err ErrSecretInvalidValue) Unwrap() error {
37-
return util.ErrInvalidArgument
38-
}
39-
4019
// Secret represents a secret
4120
type Secret struct {
4221
ID int64
@@ -74,24 +53,11 @@ func init() {
7453
db.RegisterModel(new(Secret))
7554
}
7655

77-
var (
78-
secretNameReg = regexp.MustCompile("^[A-Z_][A-Z0-9_]*$")
79-
forbiddenSecretPrefixReg = regexp.MustCompile("^GIT(EA|HUB)_")
80-
)
81-
82-
// Validate validates the required fields and formats.
8356
func (s *Secret) Validate() error {
84-
switch {
85-
case len(s.Name) == 0 || len(s.Name) > 50:
86-
return ErrSecretInvalidValue{Name: &s.Name}
87-
case len(s.Data) == 0:
88-
return ErrSecretInvalidValue{Data: &s.Data}
89-
case !secretNameReg.MatchString(s.Name) ||
90-
forbiddenSecretPrefixReg.MatchString(s.Name):
91-
return ErrSecretInvalidValue{Name: &s.Name}
92-
default:
93-
return nil
57+
if s.OwnerID == 0 && s.RepoID == 0 {
58+
return errors.New("the secret is not bound to any scope")
9459
}
60+
return nil
9561
}
9662

9763
type FindSecretsOptions struct {

options/locale/locale_en-US.ini

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ show_full_screen = Show full screen
132132

133133
confirm_delete_selected = Confirm to delete all selected items?
134134

135+
name = Name
136+
value = Value
137+
135138
[aria]
136139
navbar = Navigation Bar
137140
footer = Footer
@@ -3391,8 +3394,6 @@ owner.settings.chef.keypair.description = Generate a key pair used to authentica
33913394
secrets = Secrets
33923395
description = Secrets will be passed to certain actions and cannot be read otherwise.
33933396
none = There are no secrets yet.
3394-
value = Value
3395-
name = Name
33963397
creation = Add Secret
33973398
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
33983399
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
@@ -3462,6 +3463,22 @@ runs.no_matching_runner_helper = No matching runner: %s
34623463
34633464
need_approval_desc = Need approval to run workflows for fork pull request.
34643465
3466+
variables = Variables
3467+
variables.management = Variables Management
3468+
variables.creation = Add Variable
3469+
variables.none = There are no variables yet.
3470+
variables.deletion = Remove variable
3471+
variables.deletion.description = Removing a variable is permanent and cannot be undone. Continue?
3472+
variables.description = Variables will be passed to certain actions and cannot be read otherwise.
3473+
variables.id_not_exist = Variable with id %d not exists.
3474+
variables.edit = Edit Variable
3475+
variables.deletion.failed = Failed to remove variable.
3476+
variables.deletion.success = The variable has been removed.
3477+
variables.creation.failed = Failed to add variable.
3478+
variables.creation.success = The variable "%s" has been added.
3479+
variables.update.failed = Failed to edit variable.
3480+
variables.update.success = The variable has been edited.
3481+
34653482
[projects]
34663483
type-1.display_name = Individual Project
34673484
type-2.display_name = Repository Project

routers/api/actions/runner/utils.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
3636
WorkflowPayload: t.Job.WorkflowPayload,
3737
Context: generateTaskContext(t),
3838
Secrets: getSecretsOfTask(ctx, t),
39+
Vars: getVariablesOfTask(ctx, t),
3940
}
4041

4142
if needs, err := findTaskNeeds(ctx, t); err != nil {
@@ -88,6 +89,29 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s
8889
return secrets
8990
}
9091

92+
func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
93+
variables := map[string]string{}
94+
95+
// Org / User level
96+
ownerVariables, err := actions_model.FindVariables(ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID})
97+
if err != nil {
98+
log.Error("find variables of org: %d, error: %v", task.Job.Run.Repo.OwnerID, err)
99+
}
100+
101+
// Repo level
102+
repoVariables, err := actions_model.FindVariables(ctx, actions_model.FindVariablesOpts{RepoID: task.Job.Run.RepoID})
103+
if err != nil {
104+
log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err)
105+
}
106+
107+
// Level precedence: Repo > Org / User
108+
for _, v := range append(ownerVariables, repoVariables...) {
109+
variables[v.Name] = v.Data
110+
}
111+
112+
return variables
113+
}
114+
91115
func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
92116
event := map[string]interface{}{}
93117
_ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event)

routers/web/repo/setting/secrets.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ func SecretsPost(ctx *context.Context) {
9292
ctx.ServerError("getSecretsCtx", err)
9393
return
9494
}
95+
96+
if ctx.HasError() {
97+
ctx.JSONError(ctx.GetErrMsg())
98+
return
99+
}
100+
95101
shared.PerformSecretsPost(
96102
ctx,
97103
sCtx.OwnerID,

0 commit comments

Comments
 (0)