feat: add support for job, step shell override

This commit is contained in:
Marc 2024-08-22 23:38:32 -04:00
parent 27715ecebb
commit 1aa62019cb
Signed by: marc
GPG key ID: 048E042F22B5DC79
7 changed files with 101 additions and 12 deletions

View file

@ -22,7 +22,7 @@ syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for
- [ ] jobs.<job_id>.outputs
- [x] jobs.<job_id>.env
- [x] jobs.<job_id>.defaults
- [ ] jobs.<job_id>.run.shell
- [x] jobs.<job_id>.run.shell
- [x] jobs.<job_id>.run.working-directory
- [ ] jobs.<job_id>.timeout-minutes
- [ ] jobs.<job_id>.strategy
@ -40,7 +40,7 @@ syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for
- [ ] jobs.<job_id>.steps[*].uses
- [x] jobs.<job_id>.steps[*].run
- [x] jobs.<job_id>.steps[*].working-directory
- [ ] jobs.<job_id>.steps[*].shell
- [x] jobs.<job_id>.steps[*].shell
- [ ] jobs.<job_id>.steps[*].with
- [x] jobs.<job_id>.steps[*].env
- [X] jobs.<job_id>.steps[*].continue-on-error

View file

@ -26,11 +26,14 @@ type CommandOptions struct {
// Note: In the case of container drivers, these mappings are applied within the
// container.
Env map[string]string
// Shell to use when running the command.
Shell string
}
func NewCommandOptions() CommandOptions {
return CommandOptions{
Cwd: ".",
Shell: "bash",
Env: map[string]string{},
}
}

View file

@ -95,7 +95,7 @@ func (d PodmanDriver) Exec(containerId string, command string, options CommandOp
commandArgs = append(commandArgs, envArgs...)
commandArgs = append(commandArgs, containerId,
"bash",
options.Shell,
"-c",
command,
)

View file

@ -104,8 +104,8 @@ func (r Runner) runJob(jobContext context.Context, jobTracker *TaskTracker, jobW
//
// If the command raises an error while in the container or fails to run
// the command at all, an error is returned, otherwise nil.
func (r *Runner) RunCommandInContainer(containerId string, command string, stepCwd string, stepEnv map[string]string) error {
result := r.Driver.Exec(containerId, command, driver.CommandOptions{Cwd: stepCwd, Env: stepEnv})
func (r *Runner) RunCommandInContainer(containerId string, command string, options driver.CommandOptions) error {
result := r.Driver.Exec(containerId, command, options)
if result.Error != nil {
return result.Error
@ -133,15 +133,22 @@ func (r *Runner) RunJobInContainer(imageUri string, containerId string, jobConte
logger.Info("Started %s", containerId)
for stepIndex, step := range job.Steps {
stepCwd := jobContext.Value("workflow").(workflow.Workflow).GetWorkingDirectory(job.Name, stepIndex)
stepEnv := jobContext.Value("workflow").(workflow.Workflow).GetEnv(job.Name, stepIndex)
workflow := jobContext.Value("workflow").(workflow.Workflow)
stepCwd := workflow.GetWorkingDirectory(job.Name, stepIndex)
stepEnv := workflow.GetEnv(job.Name, stepIndex)
stepShell := workflow.GetShell(job.Name, stepIndex)
logger.Info("Run: %s", step.Run)
logger.Info("Using working directory %s", stepCwd)
var stepError error
if step.Run != "" {
stepError = r.RunCommandInContainer(containerId, step.Run, stepCwd, stepEnv)
commandOptions := driver.CommandOptions{
Cwd: stepCwd,
Env: stepEnv,
Shell: stepShell,
}
stepError = r.RunCommandInContainer(containerId, step.Run, commandOptions)
}
if stepError != nil && !step.ContinueOnError {

View file

@ -22,7 +22,7 @@ func TestRunnerRunCommandInContainerReturnsErrorFromDriver(t *testing.T) {
Driver: &mockDriver,
}
err := runner.RunCommandInContainer("test-container", "test-command", ".", map[string]string{})
err := runner.RunCommandInContainer("test-container", "test-command", driver.NewCommandOptions())
if err == nil {
t.Errorf("Expected error, got nil.")
@ -37,7 +37,7 @@ func TestRunnerRunCommandInContainerReturnsErrorIfCommandExitCodeNonzero(t *test
Driver: &mockDriver,
}
err := runner.RunCommandInContainer("test-container", "test-command", ".", map[string]string{})
err := runner.RunCommandInContainer("test-container", "test-command", driver.NewCommandOptions())
if err == nil {
t.Errorf("Expected error, got nil.")

View file

@ -17,6 +17,7 @@ type Job struct {
Defaults struct {
Run struct {
WorkingDirectory string `yaml:"working-directory"`
Shell string `yaml:"shell"`
} `yaml:"run"`
} `yaml:"defaults"`
}
@ -44,6 +45,7 @@ type Step struct {
WorkingDirectory string `yaml:"working-directory"`
ContinueOnError bool `yaml:"continue-on-error"`
Env map[string]string `yaml:"env"`
Shell string `yaml:"shell"`
}
func (s Step) Validate() []error {
@ -99,6 +101,26 @@ func (w Workflow) GetEnv(jobName string, stepIndex int) map[string]string {
return finalEnv
}
// Returns the shell defined for the given job's step.
// Overriding values are considered in the order: step, job, global.
//
// If the shell is undefined in all cases, "bash" is used.
func (w Workflow) GetShell(jobName string, stepIndex int) string {
var shell string
if stepShell := w.Jobs[jobName].Steps[stepIndex].Shell; stepShell != "" {
shell = stepShell
} else if jobShell := w.Jobs[jobName].Defaults.Run.Shell; jobShell != "" {
shell = jobShell
}
if shell == "" {
return "bash"
}
return shell
}
func (w Workflow) Validate() []error {
validationErrors := []error{}

View file

@ -206,3 +206,60 @@ func TestStepEnvOverwritesJobEnv(t *testing.T) {
t.Errorf("Unexpected env: %#v", env)
}
}
func TestStepShellOverridesAllOtherShellValues(t *testing.T) {
type TestCase struct {
sample string
name string
}
testCases := []TestCase{
{sample: `
jobs:
jobA:
defaults:
run:
shell: "job"
runs-on: default
steps:
- run: echo "test"
shell: "step"
`, name: "With job default"}, {
sample: `
jobs:
jobA:
runs-on: default
steps:
- run: echo "test"
shell: "step"
`, name: "Without job default"},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
workflow, _ := FromYamlBytes([]byte(testCase.sample))
shell := workflow.GetShell("jobA", 0)
if shell != "step" {
t.Errorf("Unexpected shell: %#v", shell)
}
})
}
}
func TestStepShellDefaultsToBash(t *testing.T) {
sample := `
jobs:
jobA:
runs-on: default
steps:
- run: echo "test"
`
workflow, _ := FromYamlBytes([]byte(sample))
shell := workflow.GetShell("jobA", 0)
if shell != "bash" {
t.Errorf("Unexpected shell: %#v", shell)
}
}