From d8d4a4657dae2515754e75731bedbacca73e4bfd Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 29 Oct 2026 17:41:27 +0000 Subject: [PATCH] fix: scope the action identity to the step executing an action (#1359) `github.action_repository`/`github.action_ref` and their env vars now belong to the step executing an action, like on GitHub: set for remote actions, empty for `run`, `docker://` and local steps. Before, a local action nested in a remote composite resolved `$/` against the remote action instead of the workflow's repository. Updates actionslib to v1.5.1, which accepts `$/` as the repository root action. Co-authored-by: bircni Reviewed-on: https://gitea.com/gitea/runner/pulls/1359 Reviewed-by: bircni Co-authored-by: silverwind --- act/runner/action.go | 15 ++++++++------- act/runner/action_composite.go | 3 --- act/runner/action_composite_test.go | 27 ++++++++++++++++++++++++++- act/runner/expression.go | 10 +++++----- act/runner/job_executor.go | 2 +- act/runner/step.go | 20 +++++++++++++++++--- act/runner/step_action_builtin.go | 6 +----- act/runner/step_action_local.go | 4 ---- act/runner/step_action_local_test.go | 22 ++++++++++++++++++++-- act/runner/step_action_remote.go | 20 +++++++------------- act/runner/step_action_remote_test.go | 10 +++++----- act/runner/step_docker.go | 4 ---- act/runner/step_docker_test.go | 9 +++++++++ act/runner/step_run.go | 4 ---- act/runner/step_run_test.go | 12 ++++++++++-- go.mod | 2 +- go.sum | 4 ++-- 17 files changed, 112 insertions(+), 62 deletions(-) diff --git a/act/runner/action.go b/act/runner/action.go index b7d3e834..dc692a02 100644 --- a/act/runner/action.go +++ b/act/runner/action.go @@ -180,9 +180,9 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction action := step.getActionModel() - rc.withGithubEnv(ctx, step.getGithubContext(ctx), *step.getEnv()) + rc.withGithubEnv(ctx, getStepGithubContext(ctx, step), *step.getEnv()) populateEnvsFromSavedState(step.getEnv(), step, rc) - if err := populateEnvsFromInput(ctx, step.getEnv(), action, rc); err != nil { + if err := populateEnvsFromInput(ctx, step, action); err != nil { return err } warnUnexpectedInputs(ctx, action, stepModel.With) @@ -283,7 +283,7 @@ func (rc *RunContext) dockerActionImage(ctx context.Context, step actionStep, ac } image, ok := job.dockerActionImages[step.getStepModel()] if !ok { - image = dockerActionImageTag(step.getGithubContext(ctx).Repository, actionName, localAction) + "-" + strings.ToLower(rand.Text()) + image = dockerActionImageTag(getStepGithubContext(ctx, step).Repository, actionName, localAction) + "-" + strings.ToLower(rand.Text()) if !common.Dryrun(ctx) { // a dry run builds nothing to remove job.dockerActionImages[step.getStepModel()] = image } @@ -437,7 +437,7 @@ func evalDockerEnv(ctx context.Context, step step, action *model.Action) (*expre for key := range step.getStepModel().With { providedInputs[strings.ToLower(key)] = true } - eval := rc.NewExpressionEvaluator(ctx) + eval := rc.NewStepExpressionEvaluator(ctx, step) for k, input := range action.Inputs { if providedInputs[strings.ToLower(k)] { continue @@ -509,8 +509,9 @@ func inputEnvKey(inputID string) string { return "INPUT_" + inputEnvPattern.ReplaceAllString(strings.ToUpper(inputID), "_") } -func populateEnvsFromInput(ctx context.Context, env *map[string]string, action *model.Action, rc *RunContext) error { - eval := rc.NewExpressionEvaluator(ctx) +func populateEnvsFromInput(ctx context.Context, step step, action *model.Action) error { + env := step.getEnv() + eval := step.getRunContext().NewStepExpressionEvaluator(ctx, step) for inputID, input := range action.Inputs { envKey := inputEnvKey(inputID) if _, ok := (*env)[envKey]; !ok { @@ -652,7 +653,7 @@ func runPreStep(step actionStep) common.Executor { x := action.Runs.Using if !x.IsComposite() { // defaults in pre steps were missing, however provided inputs are available - if err := populateEnvsFromInput(ctx, step.getEnv(), action, rc); err != nil { + if err := populateEnvsFromInput(ctx, step, action); err != nil { return err } } diff --git a/act/runner/action_composite.go b/act/runner/action_composite.go index 2ec1385f..507950ee 100644 --- a/act/runner/action_composite.go +++ b/act/runner/action_composite.go @@ -57,9 +57,6 @@ func evaluateCompositeInputAndEnv(ctx context.Context, parent *RunContext, step env[envKey] = value } } - gh := step.getGithubContext(ctx) - env["GITHUB_ACTION_REPOSITORY"] = gh.ActionRepository - env["GITHUB_ACTION_REF"] = gh.ActionRef return env, nil } diff --git a/act/runner/action_composite_test.go b/act/runner/action_composite_test.go index 9792564f..66e451c5 100644 --- a/act/runner/action_composite_test.go +++ b/act/runner/action_composite_test.go @@ -19,6 +19,31 @@ import ( ) func TestCompositeActionParity(t *testing.T) { + t.Run("input defaults use the step identity", func(t *testing.T) { + for _, uses := range []string{"./action", "own/action@v2"} { + t.Run(uses, func(t *testing.T) { + rc := &RunContext{ + Config: &Config{PresetGitHubContext: &model.GithubContext{}}, + Run: &model.Run{JobID: "job", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"job": {}}}}, + Env: map[string]string{"GITHUB_ACTION_REPOSITORY": "other/composite", "GITHUB_ACTION_REF": "v1"}, + } + var currentStep step = &stepActionLocal{RunContext: rc, Step: &model.Step{Uses: uses}, env: map[string]string{}} + want := "|" + if uses == "own/action@v2" { + 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 }}"}}} + 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"]) + }) + } + }) + t.Run("inherits contexts without leaking inputs", func(t *testing.T) { hook := logtest.NewWithLevel(logging.LevelWarn) ctx := common.WithLogger(t.Context(), logging.New(hook)) @@ -54,7 +79,7 @@ func TestCompositeActionParity(t *testing.T) { assert.NotContains(t, composite.Env, "INPUT_EXTRA") nestedEnv := composite.GetEnv() - require.NoError(t, populateEnvsFromInput(ctx, &nestedEnv, &model.Action{Inputs: map[string]model.Input{"shared": {Default: "inner-default"}}}, composite)) + require.NoError(t, populateEnvsFromInput(ctx, &stepActionLocal{RunContext: composite, env: nestedEnv}, &model.Action{Inputs: map[string]model.Input{"shared": {Default: "inner-default"}}})) assert.Equal(t, "inner-default", nestedEnv["INPUT_SHARED"]) }) diff --git a/act/runner/expression.go b/act/runner/expression.go index 1115cf46..c07e65c8 100644 --- a/act/runner/expression.go +++ b/act/runner/expression.go @@ -90,18 +90,18 @@ var hashfiles string // NewStepExpressionEvaluator creates a new evaluator with the `inputs` of the enclosing workflow or composite action func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) *ExpressionEvaluator { - return rc.newStepExpressionEvaluator(ctx, step, rc.actionInputs, rc.getJobContext()) + return rc.newStepExpressionEvaluator(ctx, step, rc.actionInputs, rc.getJobContext(), getStepGithubContext(ctx, step)) } // NewActionInputsExpressionEvaluator creates a new evaluator with the step's own with: values as `inputs` func (rc *RunContext) NewActionInputsExpressionEvaluator(ctx context.Context, step step) *ExpressionEvaluator { - return rc.newStepExpressionEvaluator(ctx, step, inputsFromEnv(*step.getEnv()), rc.getJobContext()) + return rc.newStepExpressionEvaluator(ctx, step, inputsFromEnv(*step.getEnv()), rc.getJobContext(), getStepGithubContext(ctx, step)) } -func (rc *RunContext) newStepExpressionEvaluator(ctx context.Context, step step, stepInputs map[string]any, jobContext *model.JobContext) *ExpressionEvaluator { +func (rc *RunContext) newStepExpressionEvaluator(ctx context.Context, step step, stepInputs map[string]any, jobContext *model.JobContext, github *model.GithubContext) *ExpressionEvaluator { // todo: cleanup EvaluationEnvironment creation ee := &exprparser.EvaluationEnvironment{ - Github: step.getGithubContext(ctx), + Github: github, Env: *step.getEnv(), Job: jobContext, Steps: rc.StepResults, @@ -112,7 +112,7 @@ func (rc *RunContext) newStepExpressionEvaluator(ctx context.Context, step step, Needs: exprparser.NeedsContext(rc.Run), // todo: should be unavailable // but required to interpolate/evaluate the inputs in actions/composite - Inputs: getEvaluatorInputs(rc, stepInputs, rc.getGithubContext(ctx)), + Inputs: getEvaluatorInputs(rc, stepInputs, github), HashFiles: getHashFilesFunction(ctx, rc), } ee.Runner = rc.getRunnerContext(ctx) diff --git a/act/runner/job_executor.go b/act/runner/job_executor.go index 3a345424..513a7244 100644 --- a/act/runner/job_executor.go +++ b/act/runner/job_executor.go @@ -111,7 +111,7 @@ func printPrepareActions(rc *RunContext, preparers []actionPreparer) common.Exec if !ok || strings.Contains(action.Step.Uses, "${{") || action.action != nil { continue } - github := action.getGithubContext(common.WithLogger(ctx, logging.New(logging.DiscardHandler))) + github := action.RunContext.getGithubContext(common.WithLogger(ctx, logging.New(logging.DiscardHandler))) remote, err := newRemoteAction(action.Step.Uses, github, action.RunContext.Config.GitHubInstance) if err != nil || action.skipsLocalCheckout(remote, github) { continue diff --git a/act/runner/step.go b/act/runner/step.go index 31f4a9ba..10cccf12 100644 --- a/act/runner/step.go +++ b/act/runner/step.go @@ -27,12 +27,24 @@ type step interface { post() common.Executor getRunContext() *RunContext - getGithubContext(ctx context.Context) *model.GithubContext getStepModel() *model.Step getEnv() *map[string]string getIfExpression(context context.Context, stage stepStage) string } +func stepActionIdentity(step step) (repository, ref string) { + if action, ok := step.(interface{ actionIdentity() (string, string) }); ok { + return action.actionIdentity() + } + return "", "" +} + +func getStepGithubContext(ctx context.Context, step step) *model.GithubContext { + github := step.getRunContext().getGithubContext(ctx) + github.ActionRepository, github.ActionRef = stepActionIdentity(step) + return github +} + type stepStage int const ( @@ -92,6 +104,8 @@ func runStepExecutor(step step, stage stepStage, executor common.Executor) commo runStep, err = isStepEnabled(ctx, ifExpression, step, stage) } if err == nil && runStep { + env := *step.getEnv() + env["GITHUB_ACTION_REPOSITORY"], env["GITHUB_ACTION_REF"] = stepActionIdentity(step) err = setupInputs(ctx, step) } if err != nil { @@ -253,7 +267,7 @@ func setupEnv(ctx context.Context, step step) error { env := step.getEnv() // container env is the image's baseline, which job env and $GITHUB_ENV override mergeIntoMap(step, env, rc.containerSpec.Env, rc.GetEnv()) - rc.withGithubEnv(ctx, step.getGithubContext(ctx), *env) + rc.withGithubEnv(ctx, getStepGithubContext(ctx, step), *env) var err error exprEval := rc.NewExpressionEvaluator(ctx) @@ -302,7 +316,7 @@ func isStepEnabled(ctx context.Context, expr string, step step, stage stepStage) if rc.Parent != nil && stage == stepStageMain && jobContext.Status != "cancelled" { jobContext.Status = rc.ownStatus() } - runStep, err := EvalBool(ctx, rc.newStepExpressionEvaluator(ctx, step, rc.actionInputs, jobContext), expr, defaultStatusCheck) + runStep, err := EvalBool(ctx, rc.newStepExpressionEvaluator(ctx, step, rc.actionInputs, jobContext, rc.getGithubContext(ctx)), expr, defaultStatusCheck) if err != nil { return false, fmt.Errorf("if-expression %q evaluation failed: %s", expr, err) } diff --git a/act/runner/step_action_builtin.go b/act/runner/step_action_builtin.go index c3442125..7c6d4e51 100644 --- a/act/runner/step_action_builtin.go +++ b/act/runner/step_action_builtin.go @@ -39,7 +39,7 @@ func (sab *stepActionBuiltin) main() common.Executor { rc.ApplyExtraPath(ctx, &sab.env) return sab.run(ctx, &action.Context{ Container: rc.JobContainer, - Github: rc.getGithubContext(ctx), + Github: getStepGithubContext(ctx, sab), Inputs: sab.Step.With, HostWorkdir: rc.Config.Workdir, Workspace: rc.JobContainer.ToContainerPath(rc.Config.Workdir), @@ -58,10 +58,6 @@ func (sab *stepActionBuiltin) post() common.Executor { return common.NewPipeline func (sab *stepActionBuiltin) getRunContext() *RunContext { return sab.RunContext } -func (sab *stepActionBuiltin) getGithubContext(ctx context.Context) *model.GithubContext { - return sab.RunContext.getGithubContext(ctx) -} - func (sab *stepActionBuiltin) getStepModel() *model.Step { return sab.Step } func (sab *stepActionBuiltin) getEnv() *map[string]string { return &sab.env } diff --git a/act/runner/step_action_local.go b/act/runner/step_action_local.go index 0fde48c3..c444d9ba 100644 --- a/act/runner/step_action_local.go +++ b/act/runner/step_action_local.go @@ -95,10 +95,6 @@ func (sal *stepActionLocal) getRunContext() *RunContext { return sal.RunContext } -func (sal *stepActionLocal) getGithubContext(ctx context.Context) *model.GithubContext { - return sal.getRunContext().getGithubContext(ctx) -} - func (sal *stepActionLocal) getStepModel() *model.Step { return sal.Step } diff --git a/act/runner/step_action_local_test.go b/act/runner/step_action_local_test.go index 98fc841a..6754fc7c 100644 --- a/act/runner/step_action_local_test.go +++ b/act/runner/step_action_local_test.go @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v4" ) type stepActionMocks struct { @@ -53,7 +54,7 @@ func TestStepActionLocalTest(t *testing.T) { readAction: salm.readAction, runAction: salm.runAction, RunContext: &RunContext{ - StepResults: map[string]*model.StepResult{}, + StepResults: map[string]*model.StepResult{"previous": {Outputs: map[string]string{"value": "previous"}}}, ExprEval: &expressionEvaluator{}, Config: &Config{ Workdir: "/tmp", @@ -68,25 +69,42 @@ func TestStepActionLocalTest(t *testing.T) { }, }, JobContainer: cm, + Env: map[string]string{"GITHUB_ACTION_REPOSITORY": "other/action", "GITHUB_ACTION_REF": "v1"}, + Matrix: map[string]any{"value": "matrix"}, + actionInputs: map[string]any{"shared": "input"}, }, Step: &model.Step{ ID: "1", Uses: "./path/to/action", + If: yaml.Node{Value: "github.action_repository == 'other/action' && github.action_ref == 'v1' && " + + "env.STEP_VALUE == 'step' && env.GITHUB_ACTION_REPOSITORY == 'forged/repo' && env.GITHUB_ACTION_REF == 'forged-ref' && " + + "steps.previous.outputs.value == 'previous' && inputs.shared == 'input' && matrix.value == 'matrix'"}, }, } + require.NoError(t, sal.Step.Env.Encode(map[string]string{"STEP_VALUE": "step", "GITHUB_ACTION_REPOSITORY": "forged/repo", "GITHUB_ACTION_REF": "forged-ref"})) + require.NoError(t, sal.Step.RawWith.Encode(map[string]string{"identity": "${{ github.action_repository }}|${{ github.action_ref }}"})) salm.On("readAction", sal.Step, filepath.Clean("/tmp/path/to/action"), "", mock.Anything, mock.Anything). Return(&model.Action{}, nil) expectStepFileCommands(t, cm) - salm.On("runAction", sal, filepath.Clean("/tmp/path/to/action"), (*remoteAction)(nil)).Return(noopExecutor) + salm.On("runAction", sal, filepath.Clean("/tmp/path/to/action"), (*remoteAction)(nil)).Return(func(ctx context.Context) error { + sal.RunContext.setOutput(ctx, map[string]string{"name": "value"}, "ran") + return nil + }) err := sal.pre()(ctx) require.NoError(t, err) err = sal.main()(ctx) require.NoError(t, err) + assert.Empty(t, sal.env["GITHUB_ACTION_REPOSITORY"]) + assert.Empty(t, sal.env["GITHUB_ACTION_REF"]) + assert.Equal(t, "|", sal.Step.With["identity"]) + assert.Equal(t, "ran", sal.RunContext.StepResults["1"].Outputs["value"]) + assert.Equal(t, "other/action", sal.RunContext.Env["GITHUB_ACTION_REPOSITORY"]) + assert.Equal(t, "v1", sal.RunContext.Env["GITHUB_ACTION_REF"]) cm.AssertExpectations(t) salm.AssertExpectations(t) diff --git a/act/runner/step_action_remote.go b/act/runner/step_action_remote.go index 31ded4ca..4f523304 100644 --- a/act/runner/step_action_remote.go +++ b/act/runner/step_action_remote.go @@ -75,7 +75,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor { } sar.Step.Uses = uses - github := sar.getGithubContext(ctx) // read before remoteAction is set, so `$/` resolves against the enclosing action + github := sar.RunContext.getGithubContext(ctx) if sar.remoteAction, err = newRemoteAction(sar.Step.Uses, github, sar.RunContext.Config.GitHubInstance); err != nil { return err } @@ -153,7 +153,7 @@ func (sar *stepActionRemote) main() common.Executor { return common.NewPipelineExecutor( sar.prepareActionExecutor(), runStepExecutor(sar, stepStageMain, func(ctx context.Context) error { - github := sar.getGithubContext(ctx) + github := getStepGithubContext(ctx, sar) if sar.skipsLocalCheckout(sar.remoteAction, github) { printRunActionHeader(ctx, sar.Step, nil, sar.env, sar.RunContext) if sar.RunContext.Config.BindWorkdir { @@ -181,17 +181,11 @@ func (sar *stepActionRemote) getRunContext() *RunContext { return sar.RunContext } -func (sar *stepActionRemote) getGithubContext(ctx context.Context) *model.GithubContext { - ghc := sar.getRunContext().getGithubContext(ctx) - - // extend github context if we already have an initialized remoteAction - remoteAction := sar.remoteAction - if remoteAction != nil { - ghc.ActionRepository = fmt.Sprintf("%s/%s", remoteAction.Org, remoteAction.Repo) - ghc.ActionRef = remoteAction.Ref +func (sar *stepActionRemote) actionIdentity() (string, string) { + if sar.remoteAction == nil { + return "", "" } - - return ghc + return sar.remoteAction.Org + "/" + sar.remoteAction.Repo, sar.remoteAction.Ref } func (sar *stepActionRemote) getStepModel() *model.Step { @@ -205,7 +199,7 @@ func (sar *stepActionRemote) getEnv() *map[string]string { func (sar *stepActionRemote) getIfExpression(ctx context.Context, stage stepStage) string { switch stage { case stepStagePre: - github := sar.getGithubContext(ctx) + github := getStepGithubContext(ctx, sar) if sar.skipsLocalCheckout(sar.remoteAction, github) { // skip local checkout pre step return "false" diff --git a/act/runner/step_action_remote_test.go b/act/runner/step_action_remote_test.go index ea0197c1..bafc6d0a 100644 --- a/act/runner/step_action_remote_test.go +++ b/act/runner/step_action_remote_test.go @@ -76,7 +76,7 @@ func TestStepActionRemote(t *testing.T) { status model.StepStatus runError error }{ - {name: "run-successful", status: model.StepStatusSuccess}, + {name: "run-successful", condition: "github.action_repository == 'other/composite' && github.action_ref == 'v2'", status: model.StepStatusSuccess}, {name: "run-skipped", condition: "false", status: model.StepStatusSkipped}, {name: "run-error", status: model.StepStatusFailure, runError: assert.AnError}, } { @@ -110,6 +110,7 @@ func TestStepActionRemote(t *testing.T) { }, StepResults: map[string]*model.StepResult{}, JobContainer: cm, + Env: map[string]string{"GITHUB_ACTION_REPOSITORY": "other/composite", "GITHUB_ACTION_REF": "v2"}, }, Step: &model.Step{ID: "step", Uses: "remote/action@v1", If: yaml.Node{Value: tt.condition}}, readAction: sarm.readAction, @@ -127,6 +128,8 @@ func TestStepActionRemote(t *testing.T) { require.NoError(t, sar.pre()(ctx)) assert.Equal(t, tt.runError, sar.main()(ctx)) assert.True(t, clonedAction) + assert.Equal(t, "remote/action", sar.env["GITHUB_ACTION_REPOSITORY"]) + assert.Equal(t, "v1", sar.env["GITHUB_ACTION_REF"]) assert.Equal(t, &model.StepResult{Conclusion: tt.status, Outcome: tt.status, Outputs: map[string]string{}}, sar.RunContext.StepResults["step"]) sarm.AssertExpectations(t) @@ -182,7 +185,7 @@ func TestStepActionRemotePrepare(t *testing.T) { require.NoError(t, err) root, err := prepare(job, "org/repo@v1") require.NoError(t, err) - nested, err := prepare(&RunContext{Config: job.Config, Run: job.Run, JobContainer: job.JobContainer, Parent: job}, "org/repo/save@v1") + nested, err := prepare(&RunContext{Config: job.Config, Run: job.Run, JobContainer: job.JobContainer, Parent: job, Env: map[string]string{"GITHUB_ACTION_REPOSITORY": "org/repo", "GITHUB_ACTION_REF": "v1"}}, "$/save") require.NoError(t, err) other, err := prepare(job, "org/repo@v2") require.NoError(t, err) @@ -346,9 +349,6 @@ func Test_newRemoteActionSelfRepo(t *testing.T) { }, }, {name: "unknown instance", action: "$/.gitea/actions/build", github: &model.GithubContext{Repository: "owner/workflow-repo", Sha: "abc123"}}, - {name: "empty path", action: "$/", github: workflow}, - {name: "ref suffix is not allowed", action: "$/.gitea/actions/build@v1", github: workflow}, - {name: "path traversal", action: "$/../escape", github: workflow}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/act/runner/step_docker.go b/act/runner/step_docker.go index 4b290bff..dd7533ae 100644 --- a/act/runner/step_docker.go +++ b/act/runner/step_docker.go @@ -35,10 +35,6 @@ func (sd *stepDocker) getRunContext() *RunContext { return sd.RunContext } -func (sd *stepDocker) getGithubContext(ctx context.Context) *model.GithubContext { - return sd.getRunContext().getGithubContext(ctx) -} - func (sd *stepDocker) getStepModel() *model.Step { return sd.Step } diff --git a/act/runner/step_docker_test.go b/act/runner/step_docker_test.go index 7c15330f..78b28a4f 100644 --- a/act/runner/step_docker_test.go +++ b/act/runner/step_docker_test.go @@ -14,6 +14,7 @@ import ( "gitea.dev/actionslib/pkg/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v4" ) func TestStepDockerMain(t *testing.T) { @@ -57,13 +58,16 @@ func TestStepDockerMain(t *testing.T) { }, }, JobContainer: cm, + Env: map[string]string{"GITHUB_ACTION_REPOSITORY": "other/composite", "GITHUB_ACTION_REF": "v1"}, }, Step: &model.Step{ ID: "1", Uses: "docker://node:14", WorkingDirectory: "workdir", + If: yaml.Node{Value: "github.action_repository == 'other/composite' && github.action_ref == 'v1'"}, }, } + require.NoError(t, sd.Step.RawWith.Encode(map[string]string{"args": "identity:${{ github.action_repository }}:${{ github.action_ref }}"})) sd.RunContext.ExprEval = sd.RunContext.NewExpressionEvaluator(ctx) cm.On("Pull", false).Return(noopExecutor) @@ -80,6 +84,11 @@ func TestStepDockerMain(t *testing.T) { require.NoError(t, sd.main()(ctx)) assert.Equal(t, "node:14", input.Image) + assert.Equal(t, []string{"identity::"}, input.Cmd) + assert.Contains(t, input.Env, "GITHUB_ACTION_REPOSITORY=") + assert.Contains(t, input.Env, "GITHUB_ACTION_REF=") + assert.Equal(t, "other/composite", sd.RunContext.Env["GITHUB_ACTION_REPOSITORY"]) + assert.Equal(t, "v1", sd.RunContext.Env["GITHUB_ACTION_REF"]) assert.Empty(t, input.Username) assert.Empty(t, input.Password) diff --git a/act/runner/step_run.go b/act/runner/step_run.go index 29e0565c..ec82e95e 100644 --- a/act/runner/step_run.go +++ b/act/runner/step_run.go @@ -222,10 +222,6 @@ func (sr *stepRun) getRunContext() *RunContext { return sr.RunContext } -func (sr *stepRun) getGithubContext(ctx context.Context) *model.GithubContext { - return sr.getRunContext().getGithubContext(ctx) -} - func (sr *stepRun) getStepModel() *model.Step { return sr.Step } diff --git a/act/runner/step_run_test.go b/act/runner/step_run_test.go index af6d328f..6f8a4e2d 100644 --- a/act/runner/step_run_test.go +++ b/act/runner/step_run_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v4" ) type shellContainerMock struct{ *containerMock } @@ -36,7 +37,6 @@ func TestStepRun(t *testing.T) { sr := &stepRun{ RunContext: &RunContext{ StepResults: map[string]*model.StepResult{}, - ExprEval: &expressionEvaluator{}, Config: &Config{Env: testCheckoutEnv()}, Run: &model.Run{ JobID: "1", @@ -48,19 +48,27 @@ func TestStepRun(t *testing.T) { }, JobContainer: cm, jobRunDefaults: model.RunDefaults{Shell: "bash"}, + Env: map[string]string{"GITHUB_ACTION_REPOSITORY": "other/composite", "GITHUB_ACTION_REF": "v1"}, }, Step: &model.Step{ ID: "1", - Run: "cmd", + Run: "cmd${{ github.action_repository }}${{ github.action_ref }}", WorkingDirectory: "workdir", + If: yaml.Node{Value: "github.action_repository == 'other/composite' && github.action_ref == 'v1'"}, }, } + 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"})) 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) expectStepFileCommands(t, cm) require.NoError(t, sr.main()(t.Context())) + assert.Empty(t, sr.env["GITHUB_ACTION_REPOSITORY"]) + assert.Empty(t, sr.env["GITHUB_ACTION_REF"]) + assert.Equal(t, "other/composite", sr.RunContext.Env["GITHUB_ACTION_REPOSITORY"]) + assert.Equal(t, "v1", sr.RunContext.Env["GITHUB_ACTION_REF"]) cm.AssertExpectations(t) } diff --git a/go.mod b/go.mod index c87f9927..3fe25997 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.27.3 require ( connectrpc.com/connect/v2 v2.0.1 - gitea.dev/actionslib v1.5.0 + gitea.dev/actionslib v1.5.1 github.com/bmatcuk/doublestar/v4 v4.10.3 github.com/coder/websocket v1.8.15 github.com/containerd/errdefs v1.0.0 diff --git a/go.sum b/go.sum index 2c2f3308..08a628b5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ connectrpc.com/connect/v2 v2.0.1 h1:WTAycxe87UqxSM5oxS30d1dalZ+C5xz8Ec8O6zVYkHQ= connectrpc.com/connect/v2 v2.0.1/go.mod h1:N32OLrstcPUzkykFynnefFro3+xzSdOrZlK2jHmS/xo= -gitea.dev/actionslib v1.5.0 h1:dC3v2HTWbbvyNBSdg2W3kd4WNC/p+FnZaKpl1MdzM00= -gitea.dev/actionslib v1.5.0/go.mod h1:C9wmOFgDBCXyRvg4A3KTX8Vszs8v9+elH4QLGVDolqU= +gitea.dev/actionslib v1.5.1 h1:QVOaENNWx4EFRlZvrWmMTKaKLbaqxdlhWLtsI4kf2v8= +gitea.dev/actionslib v1.5.1/go.mod h1:C9wmOFgDBCXyRvg4A3KTX8Vszs8v9+elH4QLGVDolqU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=