feat: add support for job, step shell override
This commit is contained in:
parent
27715ecebb
commit
1aa62019cb
7 changed files with 101 additions and 12 deletions
|
@ -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
|
||||
|
|
|
@ -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{},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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{}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue