From 5cf8d530871d1b456c66d93e7160a37088412561 Mon Sep 17 00:00:00 2001 From: Slatian Date: Sat, 14 Jun 2025 15:31:32 +0000 Subject: [PATCH] Feature: Added an action type for the action.yaml that uses sh (#141) Currently the only way to get pre and post actions is to go through the nodejs mechanism, which is quite wasteful when all one wants to do is run a couple of shell commands, I'm trying to get around this with this patch. It works similar to the node* actions in that it supports `pre`, `main` and `post`. It is different in that these strings are passed to the system shell using `sh -c` and execute similar to the composite run action with the shell set to `sh`. Example action to make use of this patch: https://codeberg.org/slatian/test-action/src/branch/main/action.yaml Reviewed-on: https://code.forgejo.org/forgejo/act/pulls/141 Reviewed-by: earl-warren Co-authored-by: Slatian Co-committed-by: Slatian --- act/model/action.go | 5 +- act/runner/action.go | 52 ++++++++++++++++--- act/runner/runner_test.go | 2 + .../uses-sh-test-action-path/push.yaml | 10 ++++ .../sh_test_action_path/action.yaml | 8 +++ act/runner/testdata/uses-sh/push.yaml | 10 ++++ .../testdata/uses-sh/sh_action/action.yaml | 8 +++ 7 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 act/runner/testdata/uses-sh-test-action-path/push.yaml create mode 100644 act/runner/testdata/uses-sh-test-action-path/sh_test_action_path/action.yaml create mode 100644 act/runner/testdata/uses-sh/push.yaml create mode 100644 act/runner/testdata/uses-sh/sh_action/action.yaml diff --git a/act/model/action.go b/act/model/action.go index 88df6f1e..9dcfd33c 100644 --- a/act/model/action.go +++ b/act/model/action.go @@ -20,7 +20,7 @@ func (a *ActionRunsUsing) UnmarshalYAML(unmarshal func(interface{}) error) error // Force input to lowercase for case insensitive comparison format := ActionRunsUsing(strings.ToLower(using)) switch format { - case ActionRunsUsingNode20, ActionRunsUsingNode16, ActionRunsUsingNode12, ActionRunsUsingDocker, ActionRunsUsingComposite, ActionRunsUsingGo: + case ActionRunsUsingNode20, ActionRunsUsingNode16, ActionRunsUsingNode12, ActionRunsUsingDocker, ActionRunsUsingComposite, ActionRunsUsingGo, ActionRunsUsingSh: *a = format default: return fmt.Errorf("The runs.using key in action.yml must be one of: %v, got %s", []string{ @@ -30,6 +30,7 @@ func (a *ActionRunsUsing) UnmarshalYAML(unmarshal func(interface{}) error) error ActionRunsUsingNode16, ActionRunsUsingNode20, ActionRunsUsingGo, + ActionRunsUsingSh, }, format) } return nil @@ -48,6 +49,8 @@ const ( ActionRunsUsingComposite = "composite" // ActionRunsUsingGo for running with go ActionRunsUsingGo = "go" + // ActionRunsUsingSh for running with sh + ActionRunsUsingSh = "sh" ) // ActionRuns are a field in Action diff --git a/act/runner/action.go b/act/runner/action.go index c4174676..e205c0b7 100644 --- a/act/runner/action.go +++ b/act/runner/action.go @@ -164,14 +164,18 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction action := step.getActionModel() logger.Debugf("About to run action %v", action) + actionLocation := path.Join(actionDir, actionPath) + actionName, containerActionDir := getContainerActionPaths(stepModel, actionLocation, rc) + + if action.Runs.Using == model.ActionRunsUsingSh { + rc.ActionPath = containerActionDir + } + err := setupActionEnv(ctx, step, remoteAction) if err != nil { return err } - actionLocation := path.Join(actionDir, actionPath) - actionName, containerActionDir := getContainerActionPaths(stepModel, actionLocation, rc) - logger.Debugf("type=%v actionDir=%s actionPath=%s workdir=%s actionCacheDir=%s actionName=%s containerActionDir=%s", stepModel.Type(), actionDir, actionPath, rc.Config.Workdir, rc.ActionCacheDir(), actionName, containerActionDir) switch action.Runs.Using { @@ -179,11 +183,23 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil { return err } + containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Main)} logger.Debugf("executing remote job container: %s", containerArgs) rc.ApplyExtraPath(ctx, step.getEnv()) + return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx) + case model.ActionRunsUsingSh: + if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil { + return err + } + + containerArgs := []string{"sh", "-c", action.Runs.Main} + logger.Debugf("executing remote job container: %s", containerArgs) + + rc.ApplyExtraPath(ctx, step.getEnv()) + return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx) case model.ActionRunsUsingDocker: location := actionLocation @@ -220,6 +236,7 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction model.ActionRunsUsingNode20, model.ActionRunsUsingComposite, model.ActionRunsUsingGo, + model.ActionRunsUsingSh, }, action.Runs.Using) } } @@ -509,7 +526,8 @@ func hasPreStep(step actionStep) common.Conditional { ((action.Runs.Using == model.ActionRunsUsingNode12 || action.Runs.Using == model.ActionRunsUsingNode16 || action.Runs.Using == model.ActionRunsUsingNode20 || - action.Runs.Using == model.ActionRunsUsingGo) && + action.Runs.Using == model.ActionRunsUsingGo || + action.Runs.Using == model.ActionRunsUsingSh) && action.Runs.Pre != "") } } @@ -524,7 +542,7 @@ func runPreStep(step actionStep) common.Executor { action := step.getActionModel() switch action.Runs.Using { - case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16, model.ActionRunsUsingNode20: + case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16, model.ActionRunsUsingNode20, model.ActionRunsUsingSh: // defaults in pre steps were missing, however provided inputs are available populateEnvsFromInput(ctx, step.getEnv(), action, rc) // todo: refactor into step @@ -551,7 +569,14 @@ func runPreStep(step actionStep) common.Executor { return err } - containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Pre)} + var containerArgs []string + + if action.Runs.Using == model.ActionRunsUsingSh { + rc.ActionPath = containerActionDir + containerArgs = []string{"sh", "-c", action.Runs.Pre} + } else { + containerArgs = []string{"node", path.Join(containerActionDir, action.Runs.Pre)} + } logger.Debugf("executing remote job container: %s", containerArgs) rc.ApplyExtraPath(ctx, step.getEnv()) @@ -643,7 +668,8 @@ func hasPostStep(step actionStep) common.Conditional { ((action.Runs.Using == model.ActionRunsUsingNode12 || action.Runs.Using == model.ActionRunsUsingNode16 || action.Runs.Using == model.ActionRunsUsingNode20 || - action.Runs.Using == model.ActionRunsUsingGo) && + action.Runs.Using == model.ActionRunsUsingGo || + action.Runs.Using == model.ActionRunsUsingSh) && action.Runs.Post != "") } } @@ -689,6 +715,18 @@ func runPostStep(step actionStep) common.Executor { return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx) + case model.ActionRunsUsingSh: + + populateEnvsFromSavedState(step.getEnv(), step, rc) + + rc.ActionPath = containerActionDir + containerArgs := []string{"sh", "-c", action.Runs.Post} + logger.Debugf("executing remote job container: %s", containerArgs) + + rc.ApplyExtraPath(ctx, step.getEnv()) + + return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx) + case model.ActionRunsUsingComposite: if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil { return err diff --git a/act/runner/runner_test.go b/act/runner/runner_test.go index 9234d3b3..de88f017 100644 --- a/act/runner/runner_test.go +++ b/act/runner/runner_test.go @@ -382,6 +382,8 @@ func TestRunEventHostEnvironment(t *testing.T) { {workdir, "uses-composite-with-error", "push", "Job 'failing-composite-action' failed", platforms, secrets}, {workdir, "uses-nested-composite", "push", "", platforms, secrets}, {workdir, "act-composite-env-test", "push", "", platforms, secrets}, + {workdir, "uses-sh", "push", "", platforms, secrets}, + {workdir, "uses-sh-test-action-path", "push", "", platforms, secrets}, // Eval {workdir, "evalmatrix", "push", "", platforms, secrets}, diff --git a/act/runner/testdata/uses-sh-test-action-path/push.yaml b/act/runner/testdata/uses-sh-test-action-path/push.yaml new file mode 100644 index 00000000..3253589a --- /dev/null +++ b/act/runner/testdata/uses-sh-test-action-path/push.yaml @@ -0,0 +1,10 @@ +--- +name: uses-sh-test-action-path +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./uses-sh-test-action-path/sh_test_action_path diff --git a/act/runner/testdata/uses-sh-test-action-path/sh_test_action_path/action.yaml b/act/runner/testdata/uses-sh-test-action-path/sh_test_action_path/action.yaml new file mode 100644 index 00000000..0a6297df --- /dev/null +++ b/act/runner/testdata/uses-sh-test-action-path/sh_test_action_path/action.yaml @@ -0,0 +1,8 @@ +--- +name: Test for the action path being a valid directory using the sh action type + +runs: + using: sh + pre: 'echo "testing pre"; if [ -d "$GITHUB_ACTION_PATH" ] ; then echo "Everything okay" ; ls "$GITHUB_ACTION_PATH" ; else echo "No directory at action path: $GITHUB_ACTION_PATH" ; exit 1; fi' + main: 'echo "testing main"; if [ -d "$GITHUB_ACTION_PATH" ] ; then echo "Everything okay" ; ls "$GITHUB_ACTION_PATH" ; else echo "No directory at action path: $GITHUB_ACTION_PATH" ; exit 1; fi' + post: 'echo "testing post"; if [ -d "$GITHUB_ACTION_PATH" ] ; then echo "Everything okay" ; ls "$GITHUB_ACTION_PATH" ; else echo "No directory at action path: $GITHUB_ACTION_PATH" ; exit 1; fi' diff --git a/act/runner/testdata/uses-sh/push.yaml b/act/runner/testdata/uses-sh/push.yaml new file mode 100644 index 00000000..a137393b --- /dev/null +++ b/act/runner/testdata/uses-sh/push.yaml @@ -0,0 +1,10 @@ +--- +name: uses-sh +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./uses-sh/sh_action diff --git a/act/runner/testdata/uses-sh/sh_action/action.yaml b/act/runner/testdata/uses-sh/sh_action/action.yaml new file mode 100644 index 00000000..d490fa05 --- /dev/null +++ b/act/runner/testdata/uses-sh/sh_action/action.yaml @@ -0,0 +1,8 @@ +--- +name: Basic test using the sh type + +runs: + using: sh + pre: 'echo "testing pre"' + main: 'echo "testing main"' + post: 'echo "testing post"'