From ca8e9c3c64346c9530197780db7b901c1f16d100 Mon Sep 17 00:00:00 2001 From: KenanZhu Date: Fri, 30 Oct 2026 21:21:02 +0000 Subject: [PATCH] enhance: add GITEA_* aliases for built-in workflow environment variables (#1358) Closes https://gitea.com/gitea/runner/issues/1317, which makes https://github.com/go-gitea/gitea/issues/39813 redundant as the runner sets these variables. Every built-in `GITHUB_*` environment variable the runner sets is now also available as `GITEA_*` with the same value, so Gitea-native workflows don't have to rely on GitHub naming. The `GITEA_*` names mirror the `GITHUB_*` ones after their last write, so they stay equal and are unset together. Workflow, job and runner `envs` values for these `GITEA_*` names are now overwritten, the same as for `GITHUB_*`. --------- Co-authored-by: silverwind Reviewed-on: https://gitea.com/gitea/runner/pulls/1358 Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com> Co-authored-by: KenanZhu --- act/runner/action.go | 1 + act/runner/action_composite_test.go | 8 ++++++-- act/runner/job_hooks.go | 3 +-- act/runner/job_hooks_test.go | 1 + act/runner/run_context.go | 18 ++++++++++++++++++ act/runner/run_context_test.go | 28 ++++++++++++++-------------- act/runner/step.go | 10 +--------- act/runner/step_run_test.go | 8 +++++++- act/runner/step_test.go | 22 ++++++++++++---------- docs/job-hooks.md | 2 +- 10 files changed, 62 insertions(+), 39 deletions(-) diff --git a/act/runner/action.go b/act/runner/action.go index dc692a02..b2369124 100644 --- a/act/runner/action.go +++ b/act/runner/action.go @@ -456,6 +456,7 @@ func evalDockerEnv(ctx context.Context, step step, action *model.Action) (*expre } } mergeIntoMap(step, step.getEnv(), runsEnv, maps.Clone(*step.getEnv())) + aliasGiteaEnv(*step.getEnv()) return ee, nil } diff --git a/act/runner/action_composite_test.go b/act/runner/action_composite_test.go index 66e451c5..24f863a0 100644 --- a/act/runner/action_composite_test.go +++ b/act/runner/action_composite_test.go @@ -19,7 +19,7 @@ import ( ) func TestCompositeActionParity(t *testing.T) { - t.Run("input defaults use the step identity", func(t *testing.T) { + t.Run("input defaults use the step identity and docker env keeps the GITEA aliases", func(t *testing.T) { for _, uses := range []string{"./action", "own/action@v2"} { t.Run(uses, func(t *testing.T) { rc := &RunContext{ @@ -33,13 +33,17 @@ func TestCompositeActionParity(t *testing.T) { currentStep = &stepActionRemote{RunContext: rc, Step: &model.Step{Uses: uses}, env: map[string]string{}, remoteAction: mustNewRemoteAction(t, uses)} want = "own/action|v2" } - action := &model.Action{Inputs: map[string]model.Input{"identity": {Default: "${{ github.action_repository }}|${{ github.action_ref }}"}}} + action := &model.Action{ + Inputs: map[string]model.Input{"identity": {Default: "${{ github.action_repository }}|${{ github.action_ref }}"}}, + Runs: model.ActionRuns{Env: map[string]string{"GITHUB_WORKFLOW_SHA": "runs-sha"}}, + } require.NoError(t, setupEnv(t.Context(), currentStep)) require.NoError(t, populateEnvsFromInput(t.Context(), currentStep, action)) assert.Equal(t, want, (*currentStep.getEnv())["INPUT_IDENTITY"]) _, err := evalDockerEnv(t.Context(), currentStep, action) require.NoError(t, err) assert.Equal(t, want, (*currentStep.getEnv())["identity"]) + assert.Equal(t, "runs-sha", (*currentStep.getEnv())["GITEA_WORKFLOW_SHA"]) }) } }) diff --git a/act/runner/job_hooks.go b/act/runner/job_hooks.go index 246353b6..f32485b2 100644 --- a/act/runner/job_hooks.go +++ b/act/runner/job_hooks.go @@ -84,8 +84,7 @@ func (rc *RunContext) setupHookFileCommands(ctx context.Context, env map[string] actPath := rc.JobContainer.GetActPath() env["GITHUB_ENV"] = path.Join(actPath, hookEnvFileCommand) env["GITHUB_PATH"] = path.Join(actPath, hookPathFileCommand) - env["GITEA_ENV"] = env["GITHUB_ENV"] - env["GITEA_PATH"] = env["GITHUB_PATH"] + aliasGiteaEnv(env) return rc.JobContainer.Copy(actPath, &container.FileEntry{Name: hookEnvFileCommand, Mode: 0o666}, diff --git a/act/runner/job_hooks_test.go b/act/runner/job_hooks_test.go index dff4a4e6..e07b1081 100644 --- a/act/runner/job_hooks_test.go +++ b/act/runner/job_hooks_test.go @@ -121,6 +121,7 @@ func TestRunJobHook(t *testing.T) { assert.Equal(t, "job", jobContainer.env["GITHUB_JOB"]) assert.Equal(t, "/var/run/act/workflow/hook-envs.txt", jobContainer.env["GITHUB_ENV"]) assert.Equal(t, "/var/run/act/workflow/hook-path.txt", jobContainer.env["GITHUB_PATH"]) + assert.Equal(t, jobContainer.env["GITHUB_PATH"], jobContainer.env["GITEA_PATH"]) }) t.Run("each hook has its own command state", func(t *testing.T) { diff --git a/act/runner/run_context.go b/act/runner/run_context.go index 9b3ecb45..4573294d 100644 --- a/act/runner/run_context.go +++ b/act/runner/run_context.go @@ -1602,6 +1602,23 @@ func isLocalCheckout(ghc *model.GithubContext, step *model.Step) bool { return true } +// aliasGiteaEnv mirrors the runner's GITHUB_* variables as GITEA_*, call it after the last write to them. +func aliasGiteaEnv(env map[string]string) { + for _, suffix := range []string{ + "ACTION", "ACTION_PATH", "ACTION_REF", "ACTION_REPOSITORY", "ACTIONS", "ACTOR", "ACTOR_ID", "API_URL", "BASE_REF", + "ENV", "EVENT_NAME", "EVENT_PATH", "GRAPHQL_URL", "HEAD_REF", "JOB", "OUTPUT", "PATH", "REF", "REF_NAME", + "REF_PROTECTED", "REF_TYPE", "REPOSITORY", "REPOSITORY_ID", "REPOSITORY_OWNER", "REPOSITORY_OWNER_ID", + "RETENTION_DAYS", "RUN_ATTEMPT", "RUN_ID", "RUN_NUMBER", "SERVER_URL", "SHA", "STATE", "STEP_SUMMARY", + "TRIGGERING_ACTOR", "WORKFLOW", "WORKFLOW_REF", "WORKFLOW_SHA", "WORKSPACE", + } { + if value, ok := env["GITHUB_"+suffix]; ok { + env["GITEA_"+suffix] = value + } else { + delete(env, "GITEA_"+suffix) + } + } +} + func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubContext, env map[string]string) { if _, ok := env["CI"]; !ok { env["CI"] = "true" @@ -1654,6 +1671,7 @@ func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubCon env["GITHUB_GRAPHQL_URL"] = "" env["GITHUB_RUN_ATTEMPT"] = github.RunAttempt + aliasGiteaEnv(env) env["RUNNER_NAME"] = rc.Config.RunnerName env["RUNNER_ENVIRONMENT"] = "self-hosted" diff --git a/act/runner/run_context_test.go b/act/runner/run_context_test.go index 3f91357c..e84e3141 100644 --- a/act/runner/run_context_test.go +++ b/act/runner/run_context_test.go @@ -877,25 +877,25 @@ func TestGetGitHubContext(t *testing.T) { RepositoryID: "34", RepositoryOwnerID: "56", WorkflowRef: "owner/repo/.gitea/workflows/test.yml@refs/heads/main", - WorkflowSha: "workflow-sha", RefProtected: true, } ghc = rc.getGithubContext(t.Context()) assert.Equal(t, "preset-actor", ghc.Actor) assert.Equal(t, "triggerer", ghc.TriggeringActor) assert.Equal(t, "job1", ghc.Job) - env := map[string]string{} + env := map[string]string{"GITHUB_WORKFLOW_SHA": "workflow-sha"} rc.withGithubEnv(t.Context(), ghc, env) - for name, value := range map[string]string{ - "GITHUB_ACTOR_ID": "12", - "GITHUB_TRIGGERING_ACTOR": "triggerer", - "GITHUB_REPOSITORY_ID": "34", - "GITHUB_REPOSITORY_OWNER_ID": "56", - "GITHUB_WORKFLOW_REF": "owner/repo/.gitea/workflows/test.yml@refs/heads/main", - "GITHUB_WORKFLOW_SHA": "workflow-sha", - "GITHUB_REF_PROTECTED": "true", + for suffix, value := range map[string]string{ + "ACTOR_ID": "12", + "TRIGGERING_ACTOR": "triggerer", + "REPOSITORY_ID": "34", + "REPOSITORY_OWNER_ID": "56", + "WORKFLOW_REF": "owner/repo/.gitea/workflows/test.yml@refs/heads/main", + "WORKFLOW_SHA": "workflow-sha", + "REF_PROTECTED": "true", } { - assert.Equal(t, value, env[name], name) + assert.Equal(t, value, env["GITHUB_"+suffix], suffix) + assert.Equal(t, value, env["GITEA_"+suffix], suffix) } ghc.RefProtected = false rc.withGithubEnv(t.Context(), ghc, env) @@ -922,12 +922,12 @@ func TestGetGitHubContext(t *testing.T) { github := rc.getGithubContext(t.Context()) assert.Equal(t, testcase.serverURL, github.ServerURL) assert.Equal(t, testcase.apiURL, github.APIURL) - env := map[string]string{} + env := map[string]string{"GITEA_SERVER_URL": "stale", "GITEA_API_URL": "stale"} rc.withGithubEnv(t.Context(), github, env) _, exported := env["GITHUB_SERVER_URL"] assert.Equal(t, testcase.serverURL != "", exported) - assert.Equal(t, testcase.serverURL, env["GITHUB_SERVER_URL"]) - assert.Equal(t, testcase.apiURL, env["GITHUB_API_URL"]) + assert.Equal(t, testcase.serverURL, env["GITEA_SERVER_URL"]) + assert.Equal(t, testcase.apiURL, env["GITEA_API_URL"]) }) } } diff --git a/act/runner/step.go b/act/runner/step.go index 10cccf12..0b6f611b 100644 --- a/act/runner/step.go +++ b/act/runner/step.go @@ -157,15 +157,7 @@ func runStepExecutor(step step, stage stepStage, executor common.Executor) commo stepSummaryIndex := topRC.CurrentStepIndex summaryFileCommand := path.Join("workflow", "step-summary-"+strconv.Itoa(stepSummaryIndex)+".md") (*step.getEnv())["GITHUB_STEP_SUMMARY"] = path.Join(actPath, summaryFileCommand) - - { - // For Gitea - (*step.getEnv())["GITEA_OUTPUT"] = (*step.getEnv())["GITHUB_OUTPUT"] - (*step.getEnv())["GITEA_STATE"] = (*step.getEnv())["GITHUB_STATE"] - (*step.getEnv())["GITEA_PATH"] = (*step.getEnv())["GITHUB_PATH"] - (*step.getEnv())["GITEA_ENV"] = (*step.getEnv())["GITHUB_ENV"] - (*step.getEnv())["GITEA_STEP_SUMMARY"] = (*step.getEnv())["GITHUB_STEP_SUMMARY"] - } + aliasGiteaEnv(*step.getEnv()) // Reset the per-phase file-command files. GITHUB_STEP_SUMMARY is intentionally // excluded here and initialized below at most once per step so writes from later diff --git a/act/runner/step_run_test.go b/act/runner/step_run_test.go index 6f8a4e2d..d8e08c2f 100644 --- a/act/runner/step_run_test.go +++ b/act/runner/step_run_test.go @@ -58,7 +58,10 @@ func TestStepRun(t *testing.T) { }, } sr.RunContext.ExprEval = sr.RunContext.NewExpressionEvaluator(t.Context()) - require.NoError(t, sr.Step.Env.Encode(map[string]string{"GITHUB_ACTION_REPOSITORY": "forged/repo", "GITHUB_ACTION_REF": "forged-ref"})) + require.NoError(t, sr.Step.Env.Encode(map[string]string{ + "GITHUB_ACTION_REPOSITORY": "forged/repo", "GITHUB_ACTION_REF": "forged-ref", + "GITEA_ACTION_REPOSITORY": "forged/repo", "GITEA_ACTION_REF": "forged-ref", + })) cm.On("Copy", "/var/run/act", []*container.FileEntry{fileEntry}).Return(noopExecutor) cm.On("Exec", []string{"bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "/var/run/act/workflow/1.sh"}, mock.AnythingOfType("map[string]string"), "", "workdir").Return(noopExecutor) @@ -67,6 +70,9 @@ func TestStepRun(t *testing.T) { require.NoError(t, sr.main()(t.Context())) assert.Empty(t, sr.env["GITHUB_ACTION_REPOSITORY"]) assert.Empty(t, sr.env["GITHUB_ACTION_REF"]) + assert.Empty(t, sr.env["GITEA_ACTION_REPOSITORY"]) + assert.Empty(t, sr.env["GITEA_ACTION_REF"]) + assert.Equal(t, sr.env["GITHUB_STEP_SUMMARY"], sr.env["GITEA_STEP_SUMMARY"]) assert.Equal(t, "other/composite", sr.RunContext.Env["GITHUB_ACTION_REPOSITORY"]) assert.Equal(t, "v1", sr.RunContext.Env["GITHUB_ACTION_REF"]) diff --git a/act/runner/step_test.go b/act/runner/step_test.go index db524580..a369ad88 100644 --- a/act/runner/step_test.go +++ b/act/runner/step_test.go @@ -155,18 +155,14 @@ func TestSetupEnv(t *testing.T) { require.NoError(t, setupEnv(t.Context(), testStep)) require.NoError(t, setupInputs(t.Context(), testStep)) - delete(env, "GITHUB_REF") - delete(env, "GITHUB_REF_NAME") - delete(env, "GITHUB_REF_TYPE") - delete(env, "GITHUB_SHA") - delete(env, "GITHUB_WORKSPACE") - delete(env, "GITHUB_REPOSITORY") - delete(env, "GITHUB_REPOSITORY_OWNER") - delete(env, "GITHUB_ACTOR") + for _, suffix := range []string{"REF", "REF_NAME", "REF_TYPE", "SHA", "WORKSPACE", "REPOSITORY", "REPOSITORY_OWNER", "ACTOR"} { + delete(env, "GITHUB_"+suffix) + delete(env, "GITEA_"+suffix) + } delete(env, "RUNNER_NAME") delete(env, "RUNNER_WORKSPACE") - assert.Equal(t, map[string]string{ + expected := map[string]string{ "ACT": "true", "ACT_SKIP_CHECKOUT": "true", "CI": "true", @@ -193,7 +189,13 @@ func TestSetupEnv(t *testing.T) { "RUNNER_ENVIRONMENT": "self-hosted", "RUNNER_PERFLOG": "/dev/null", "RUNNER_TRACKING_ID": "", - }, env) + } + for name, value := range expected { + if suffix, ok := strings.CutPrefix(name, "GITHUB_"); ok { + expected["GITEA_"+suffix] = value + } + } + assert.Equal(t, expected, env) for _, expression := range []string{"inputs.args", "env.ARGS"} { t.Run("deferred inputs from "+expression, func(t *testing.T) { diff --git a/docs/job-hooks.md b/docs/job-hooks.md index 1cd43bff..09281072 100644 --- a/docs/job-hooks.md +++ b/docs/job-hooks.md @@ -45,7 +45,7 @@ A hook path that does not exist inside the job environment fails the job with `N ## Environment -A hook sees the job's environment: the workflow, job and `container:` `env:`, the runner's `envs`, and the `GITHUB_*` context variables, with the same masking applied to its output as to a step's. The step-specific ones (`GITHUB_ACTION`, `GITHUB_OUTPUT`, `GITHUB_STATE`) are not set — a hook is not a step, so `::save-state::` and `::set-output::` have nowhere to go. +A hook sees the job's environment: the workflow, job and `container:` `env:`, the runner's `envs`, and the `GITHUB_*`/`GITEA_*` context variables, with the same masking applied to its output as to a step's. The step-specific ones (`GITHUB_ACTION`, `GITHUB_OUTPUT`, `GITHUB_STATE`) are not set — a hook is not a step, so `::save-state::` and `::set-output::` have nowhere to go. Its stdout is part of the job log, after a collapsible group naming the hook and its shell, and is scanned for workflow commands. `::add-mask::` registers a value to be masked for the rest of the job. `::set-env::` and `::add-path::` apply to the steps that follow, but as in a step they need `ACTIONS_ALLOW_UNSECURE_COMMANDS: true`, and a refused one fails the hook.