summaryrefslogtreecommitdiffstats
path: root/pkg/runner/step_run.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-20 23:07:42 +0200
committerDaniel Baumann <daniel@debian.org>2024-11-09 15:38:42 +0100
commit714c83b2736d7e308bc33c49057952490eb98be2 (patch)
tree1d9ba7035798368569cd49056f4d596efc908cd8 /pkg/runner/step_run.go
parentInitial commit. (diff)
downloadforgejo-act-6c54618279f415b0e89fbfa57306cbac5dc6c765.tar.xz
forgejo-act-6c54618279f415b0e89fbfa57306cbac5dc6c765.zip
Adding upstream version 1.21.4.HEADupstream/1.21.4upstreamdebian
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'pkg/runner/step_run.go')
-rw-r--r--pkg/runner/step_run.go221
1 files changed, 221 insertions, 0 deletions
diff --git a/pkg/runner/step_run.go b/pkg/runner/step_run.go
new file mode 100644
index 0000000..054ed70
--- /dev/null
+++ b/pkg/runner/step_run.go
@@ -0,0 +1,221 @@
+package runner
+
+import (
+ "context"
+ "fmt"
+ "runtime"
+ "strings"
+
+ "github.com/kballard/go-shellquote"
+
+ "github.com/nektos/act/pkg/common"
+ "github.com/nektos/act/pkg/container"
+ "github.com/nektos/act/pkg/lookpath"
+ "github.com/nektos/act/pkg/model"
+)
+
+type stepRun struct {
+ Step *model.Step
+ RunContext *RunContext
+ cmd []string
+ cmdline string
+ env map[string]string
+ WorkingDirectory string
+}
+
+func (sr *stepRun) pre() common.Executor {
+ return func(ctx context.Context) error {
+ return nil
+ }
+}
+
+func (sr *stepRun) main() common.Executor {
+ sr.env = map[string]string{}
+ return runStepExecutor(sr, stepStageMain, common.NewPipelineExecutor(
+ sr.setupShellCommandExecutor(),
+ func(ctx context.Context) error {
+ sr.getRunContext().ApplyExtraPath(ctx, &sr.env)
+ if he, ok := sr.getRunContext().JobContainer.(*container.HostEnvironment); ok && he != nil {
+ return he.ExecWithCmdLine(sr.cmd, sr.cmdline, sr.env, "", sr.WorkingDirectory)(ctx)
+ }
+ return sr.getRunContext().JobContainer.Exec(sr.cmd, sr.env, "", sr.WorkingDirectory)(ctx)
+ },
+ ))
+}
+
+func (sr *stepRun) post() common.Executor {
+ return func(ctx context.Context) error {
+ return nil
+ }
+}
+
+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
+}
+
+func (sr *stepRun) getEnv() *map[string]string {
+ return &sr.env
+}
+
+func (sr *stepRun) getIfExpression(_ context.Context, _ stepStage) string {
+ return sr.Step.If.Value
+}
+
+func (sr *stepRun) setupShellCommandExecutor() common.Executor {
+ return func(ctx context.Context) error {
+ scriptName, script, err := sr.setupShellCommand(ctx)
+ if err != nil {
+ return err
+ }
+
+ rc := sr.getRunContext()
+ return rc.JobContainer.Copy(rc.JobContainer.GetActPath(), &container.FileEntry{
+ Name: scriptName,
+ Mode: 0o755,
+ Body: script,
+ })(ctx)
+ }
+}
+
+func getScriptName(rc *RunContext, step *model.Step) string {
+ scriptName := step.ID
+ for rcs := rc; rcs.Parent != nil; rcs = rcs.Parent {
+ scriptName = fmt.Sprintf("%s-composite-%s", rcs.Parent.CurrentStep, scriptName)
+ }
+ return fmt.Sprintf("workflow/%s", scriptName)
+}
+
+// TODO: Currently we just ignore top level keys, BUT we should return proper error on them
+// BUTx2 I leave this for when we rewrite act to use actionlint for workflow validation
+// so we return proper errors before any execution or spawning containers
+// it will error anyway with:
+// OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "${{": executable file not found in $PATH: unknown
+func (sr *stepRun) setupShellCommand(ctx context.Context) (name, script string, err error) {
+ logger := common.Logger(ctx)
+ sr.setupShell(ctx)
+ sr.setupWorkingDirectory(ctx)
+
+ step := sr.Step
+
+ script = sr.RunContext.NewStepExpressionEvaluator(ctx, sr).Interpolate(ctx, step.Run)
+
+ scCmd := step.ShellCommand()
+
+ name = getScriptName(sr.RunContext, step)
+
+ // Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L47-L64
+ // Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L19-L27
+ runPrepend := ""
+ runAppend := ""
+ switch step.Shell {
+ case "bash", "sh":
+ name += ".sh"
+ case "pwsh", "powershell":
+ name += ".ps1"
+ runPrepend = "$ErrorActionPreference = 'stop'"
+ runAppend = "if ((Test-Path -LiteralPath variable:/LASTEXITCODE)) { exit $LASTEXITCODE }"
+ case "cmd":
+ name += ".cmd"
+ runPrepend = "@echo off"
+ case "python":
+ name += ".py"
+ }
+
+ script = fmt.Sprintf("%s\n%s\n%s", runPrepend, script, runAppend)
+
+ if !strings.Contains(script, "::add-mask::") && !sr.RunContext.Config.InsecureSecrets {
+ logger.Debugf("Wrote command \n%s\n to '%s'", script, name)
+ } else {
+ logger.Debugf("Wrote add-mask command to '%s'", name)
+ }
+
+ rc := sr.getRunContext()
+ scriptPath := fmt.Sprintf("%s/%s", rc.JobContainer.GetActPath(), name)
+ sr.cmdline = strings.Replace(scCmd, `{0}`, scriptPath, 1)
+ sr.cmd, err = shellquote.Split(sr.cmdline)
+
+ return name, script, err
+}
+
+type localEnv struct {
+ env map[string]string
+}
+
+func (l *localEnv) Getenv(name string) string {
+ if runtime.GOOS == "windows" {
+ for k, v := range l.env {
+ if strings.EqualFold(name, k) {
+ return v
+ }
+ }
+ return ""
+ }
+ return l.env[name]
+}
+
+func (sr *stepRun) setupShell(ctx context.Context) {
+ rc := sr.RunContext
+ step := sr.Step
+
+ if step.Shell == "" {
+ step.Shell = rc.Run.Job().Defaults.Run.Shell
+ }
+
+ step.Shell = rc.NewExpressionEvaluator(ctx).Interpolate(ctx, step.Shell)
+
+ if step.Shell == "" {
+ step.Shell = rc.Run.Workflow.Defaults.Run.Shell
+ }
+
+ if step.Shell == "" {
+ if _, ok := rc.JobContainer.(*container.HostEnvironment); ok {
+ shellWithFallback := []string{"bash", "sh"}
+ // Don't use bash on windows by default, if not using a docker container
+ if runtime.GOOS == "windows" {
+ shellWithFallback = []string{"pwsh", "powershell"}
+ }
+ step.Shell = shellWithFallback[0]
+ lenv := &localEnv{env: map[string]string{}}
+ for k, v := range sr.env {
+ lenv.env[k] = v
+ }
+ sr.getRunContext().ApplyExtraPath(ctx, &lenv.env)
+ _, err := lookpath.LookPath2(shellWithFallback[0], lenv)
+ if err != nil {
+ step.Shell = shellWithFallback[1]
+ }
+ } else if containerImage := rc.containerImage(ctx); containerImage != "" {
+ // Currently only linux containers are supported, use sh by default like actions/runner
+ step.Shell = "sh"
+ }
+ }
+}
+
+func (sr *stepRun) setupWorkingDirectory(ctx context.Context) {
+ rc := sr.RunContext
+ step := sr.Step
+ workingdirectory := ""
+
+ if step.WorkingDirectory == "" {
+ workingdirectory = rc.Run.Job().Defaults.Run.WorkingDirectory
+ } else {
+ workingdirectory = step.WorkingDirectory
+ }
+
+ // jobs can receive context values, so we interpolate
+ workingdirectory = rc.NewExpressionEvaluator(ctx).Interpolate(ctx, workingdirectory)
+
+ // but top level keys in workflow file like `defaults` or `env` can't
+ if workingdirectory == "" {
+ workingdirectory = rc.Run.Workflow.Defaults.Run.WorkingDirectory
+ }
+ sr.WorkingDirectory = workingdirectory
+}