diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-20 23:07:42 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-11-09 15:38:42 +0100 |
commit | 714c83b2736d7e308bc33c49057952490eb98be2 (patch) | |
tree | 1d9ba7035798368569cd49056f4d596efc908cd8 /pkg/runner/step_run.go | |
parent | Initial commit. (diff) | |
download | forgejo-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.go | 221 |
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 +} |