aboutsummaryrefslogtreecommitdiffstats
path: root/routers/web/shared
diff options
context:
space:
mode:
authorsillyguodong <33891828+sillyguodong@users.noreply.github.com>2023-06-21 06:54:15 +0800
committerGitHub <noreply@github.com>2023-06-20 22:54:15 +0000
commit35a653d7edbe0d693649604b8309bfc578dd988b (patch)
treed804f5341067234c2d286b5f07b5ad839f4ead52 /routers/web/shared
parent8220e50b56cf7bf9cdfff29a287c5721c3949464 (diff)
downloadgitea-35a653d7edbe0d693649604b8309bfc578dd988b.tar.gz
gitea-35a653d7edbe0d693649604b8309bfc578dd988b.zip
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 <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Giteabot <teabot@gitea.io>
Diffstat (limited to 'routers/web/shared')
-rw-r--r--routers/web/shared/actions/variables.go128
-rw-r--r--routers/web/shared/secrets/secrets.go36
2 files changed, 142 insertions, 22 deletions
diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go
new file mode 100644
index 0000000000..8d1516c91c
--- /dev/null
+++ b/routers/web/shared/actions/variables.go
@@ -0,0 +1,128 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "errors"
+ "regexp"
+ "strings"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/forms"
+)
+
+func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
+ variables, err := actions_model.FindVariables(ctx, actions_model.FindVariablesOpts{
+ OwnerID: ownerID,
+ RepoID: repoID,
+ })
+ if err != nil {
+ ctx.ServerError("FindVariables", err)
+ return
+ }
+ ctx.Data["Variables"] = variables
+}
+
+// some regular expression of `variables` and `secrets`
+// reference to:
+// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
+// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
+var (
+ nameRx = regexp.MustCompile("(?i)^[A-Z_][A-Z0-9_]*$")
+ forbiddenPrefixRx = regexp.MustCompile("(?i)^GIT(EA|HUB)_")
+
+ forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
+)
+
+func NameRegexMatch(name string) error {
+ if !nameRx.MatchString(name) || forbiddenPrefixRx.MatchString(name) {
+ log.Error("Name %s, regex match error", name)
+ return errors.New("name has invalid character")
+ }
+ return nil
+}
+
+func envNameCIRegexMatch(name string) error {
+ if forbiddenEnvNameCIRx.MatchString(name) {
+ log.Error("Env Name cannot be ci")
+ return errors.New("env name cannot be ci")
+ }
+ return nil
+}
+
+func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
+ form := web.GetForm(ctx).(*forms.EditVariableForm)
+
+ if err := NameRegexMatch(form.Name); err != nil {
+ ctx.JSONError(err.Error())
+ return
+ }
+
+ if err := envNameCIRegexMatch(form.Name); err != nil {
+ ctx.JSONError(err.Error())
+ return
+ }
+
+ v, err := actions_model.InsertVariable(ctx, ownerID, repoID, form.Name, ReserveLineBreakForTextarea(form.Data))
+ if err != nil {
+ log.Error("InsertVariable error: %v", err)
+ ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
+ ctx.JSONRedirect(redirectURL)
+}
+
+func UpdateVariable(ctx *context.Context, redirectURL string) {
+ id := ctx.ParamsInt64(":variable_id")
+ form := web.GetForm(ctx).(*forms.EditVariableForm)
+
+ if err := NameRegexMatch(form.Name); err != nil {
+ ctx.JSONError(err.Error())
+ return
+ }
+
+ if err := envNameCIRegexMatch(form.Name); err != nil {
+ ctx.JSONError(err.Error())
+ return
+ }
+
+ ok, err := actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{
+ ID: id,
+ Name: strings.ToUpper(form.Name),
+ Data: ReserveLineBreakForTextarea(form.Data),
+ })
+ if err != nil || !ok {
+ log.Error("UpdateVariable error: %v", err)
+ ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("actions.variables.update.success"))
+ ctx.JSONRedirect(redirectURL)
+}
+
+func DeleteVariable(ctx *context.Context, redirectURL string) {
+ id := ctx.ParamsInt64(":variable_id")
+
+ if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil {
+ log.Error("Delete variable [%d] failed: %v", id, err)
+ ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
+ ctx.JSONRedirect(redirectURL)
+}
+
+func ReserveLineBreakForTextarea(input string) string {
+ // Since the content is from a form which is a textarea, the line endings are \r\n.
+ // It's a standard behavior of HTML.
+ // But we want to store them as \n like what GitHub does.
+ // And users are unlikely to really need to keep the \r.
+ // Other than this, we should respect the original content, even leading or trailing spaces.
+ return strings.ReplaceAll(input, "\r\n", "\n")
+}
diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go
index a0d648f908..c09ce51499 100644
--- a/routers/web/shared/secrets/secrets.go
+++ b/routers/web/shared/secrets/secrets.go
@@ -4,14 +4,12 @@
package secrets
import (
- "net/http"
- "strings"
-
"code.gitea.io/gitea/models/db"
secret_model "code.gitea.io/gitea/models/secret"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/web/shared/actions"
"code.gitea.io/gitea/services/forms"
)
@@ -28,23 +26,20 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
form := web.GetForm(ctx).(*forms.AddSecretForm)
- content := form.Content
- // Since the content is from a form which is a textarea, the line endings are \r\n.
- // It's a standard behavior of HTML.
- // But we want to store them as \n like what GitHub does.
- // And users are unlikely to really need to keep the \r.
- // Other than this, we should respect the original content, even leading or trailing spaces.
- content = strings.ReplaceAll(content, "\r\n", "\n")
+ if err := actions.NameRegexMatch(form.Name); err != nil {
+ ctx.JSONError(ctx.Tr("secrets.creation.failed"))
+ return
+ }
- s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, form.Title, content)
+ s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, form.Name, actions.ReserveLineBreakForTextarea(form.Data))
if err != nil {
log.Error("InsertEncryptedSecret: %v", err)
- ctx.Flash.Error(ctx.Tr("secrets.creation.failed"))
- } else {
- ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name))
+ ctx.JSONError(ctx.Tr("secrets.creation.failed"))
+ return
}
- ctx.Redirect(redirectURL)
+ ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name))
+ ctx.JSONRedirect(redirectURL)
}
func PerformSecretsDelete(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
@@ -52,12 +47,9 @@ func PerformSecretsDelete(ctx *context.Context, ownerID, repoID int64, redirectU
if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id, OwnerID: ownerID, RepoID: repoID}); err != nil {
log.Error("Delete secret %d failed: %v", id, err)
- ctx.Flash.Error(ctx.Tr("secrets.deletion.failed"))
- } else {
- ctx.Flash.Success(ctx.Tr("secrets.deletion.success"))
+ ctx.JSONError(ctx.Tr("secrets.deletion.failed"))
+ return
}
-
- ctx.JSON(http.StatusOK, map[string]interface{}{
- "redirect": redirectURL,
- })
+ ctx.Flash.Success(ctx.Tr("secrets.deletion.success"))
+ ctx.JSONRedirect(redirectURL)
}