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
|
- [ ] jobs.<job_id>.outputs
|
||||||
- [x] jobs.<job_id>.env
|
- [x] jobs.<job_id>.env
|
||||||
- [x] jobs.<job_id>.defaults
|
- [x] jobs.<job_id>.defaults
|
||||||
- [ ] jobs.<job_id>.run.shell
|
- [x] jobs.<job_id>.run.shell
|
||||||
- [x] jobs.<job_id>.run.working-directory
|
- [x] jobs.<job_id>.run.working-directory
|
||||||
- [ ] jobs.<job_id>.timeout-minutes
|
- [ ] jobs.<job_id>.timeout-minutes
|
||||||
- [ ] jobs.<job_id>.strategy
|
- [ ] 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
|
- [ ] jobs.<job_id>.steps[*].uses
|
||||||
- [x] jobs.<job_id>.steps[*].run
|
- [x] jobs.<job_id>.steps[*].run
|
||||||
- [x] jobs.<job_id>.steps[*].working-directory
|
- [x] jobs.<job_id>.steps[*].working-directory
|
||||||
- [ ] jobs.<job_id>.steps[*].shell
|
- [x] jobs.<job_id>.steps[*].shell
|
||||||
- [ ] jobs.<job_id>.steps[*].with
|
- [ ] jobs.<job_id>.steps[*].with
|
||||||
- [x] jobs.<job_id>.steps[*].env
|
- [x] jobs.<job_id>.steps[*].env
|
||||||
- [X] jobs.<job_id>.steps[*].continue-on-error
|
- [X] jobs.<job_id>.steps[*].continue-on-error
|
||||||
|
|
|
@ -26,12 +26,15 @@ type CommandOptions struct {
|
||||||
// Note: In the case of container drivers, these mappings are applied within the
|
// Note: In the case of container drivers, these mappings are applied within the
|
||||||
// container.
|
// container.
|
||||||
Env map[string]string
|
Env map[string]string
|
||||||
|
// Shell to use when running the command.
|
||||||
|
Shell string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandOptions() CommandOptions {
|
func NewCommandOptions() CommandOptions {
|
||||||
return CommandOptions{
|
return CommandOptions{
|
||||||
Cwd: ".",
|
Cwd: ".",
|
||||||
Env: map[string]string{},
|
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, envArgs...)
|
||||||
|
|
||||||
commandArgs = append(commandArgs, containerId,
|
commandArgs = append(commandArgs, containerId,
|
||||||
"bash",
|
options.Shell,
|
||||||
"-c",
|
"-c",
|
||||||
command,
|
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
|
// If the command raises an error while in the container or fails to run
|
||||||
// the command at all, an error is returned, otherwise nil.
|
// 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 {
|
func (r *Runner) RunCommandInContainer(containerId string, command string, options driver.CommandOptions) error {
|
||||||
result := r.Driver.Exec(containerId, command, driver.CommandOptions{Cwd: stepCwd, Env: stepEnv})
|
result := r.Driver.Exec(containerId, command, options)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
|
@ -133,15 +133,22 @@ func (r *Runner) RunJobInContainer(imageUri string, containerId string, jobConte
|
||||||
|
|
||||||
logger.Info("Started %s", containerId)
|
logger.Info("Started %s", containerId)
|
||||||
for stepIndex, step := range job.Steps {
|
for stepIndex, step := range job.Steps {
|
||||||
stepCwd := jobContext.Value("workflow").(workflow.Workflow).GetWorkingDirectory(job.Name, stepIndex)
|
workflow := jobContext.Value("workflow").(workflow.Workflow)
|
||||||
stepEnv := jobContext.Value("workflow").(workflow.Workflow).GetEnv(job.Name, stepIndex)
|
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("Run: %s", step.Run)
|
||||||
logger.Info("Using working directory %s", stepCwd)
|
logger.Info("Using working directory %s", stepCwd)
|
||||||
var stepError error
|
var stepError error
|
||||||
|
|
||||||
if step.Run != "" {
|
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 {
|
if stepError != nil && !step.ContinueOnError {
|
||||||
|
|
|
@ -22,7 +22,7 @@ func TestRunnerRunCommandInContainerReturnsErrorFromDriver(t *testing.T) {
|
||||||
Driver: &mockDriver,
|
Driver: &mockDriver,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := runner.RunCommandInContainer("test-container", "test-command", ".", map[string]string{})
|
err := runner.RunCommandInContainer("test-container", "test-command", driver.NewCommandOptions())
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected error, got nil.")
|
t.Errorf("Expected error, got nil.")
|
||||||
|
@ -37,7 +37,7 @@ func TestRunnerRunCommandInContainerReturnsErrorIfCommandExitCodeNonzero(t *test
|
||||||
Driver: &mockDriver,
|
Driver: &mockDriver,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := runner.RunCommandInContainer("test-container", "test-command", ".", map[string]string{})
|
err := runner.RunCommandInContainer("test-container", "test-command", driver.NewCommandOptions())
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected error, got nil.")
|
t.Errorf("Expected error, got nil.")
|
||||||
|
|
|
@ -17,6 +17,7 @@ type Job struct {
|
||||||
Defaults struct {
|
Defaults struct {
|
||||||
Run struct {
|
Run struct {
|
||||||
WorkingDirectory string `yaml:"working-directory"`
|
WorkingDirectory string `yaml:"working-directory"`
|
||||||
|
Shell string `yaml:"shell"`
|
||||||
} `yaml:"run"`
|
} `yaml:"run"`
|
||||||
} `yaml:"defaults"`
|
} `yaml:"defaults"`
|
||||||
}
|
}
|
||||||
|
@ -44,6 +45,7 @@ type Step struct {
|
||||||
WorkingDirectory string `yaml:"working-directory"`
|
WorkingDirectory string `yaml:"working-directory"`
|
||||||
ContinueOnError bool `yaml:"continue-on-error"`
|
ContinueOnError bool `yaml:"continue-on-error"`
|
||||||
Env map[string]string `yaml:"env"`
|
Env map[string]string `yaml:"env"`
|
||||||
|
Shell string `yaml:"shell"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Step) Validate() []error {
|
func (s Step) Validate() []error {
|
||||||
|
@ -99,6 +101,26 @@ func (w Workflow) GetEnv(jobName string, stepIndex int) map[string]string {
|
||||||
return finalEnv
|
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 {
|
func (w Workflow) Validate() []error {
|
||||||
validationErrors := []error{}
|
validationErrors := []error{}
|
||||||
|
|
||||||
|
|
|
@ -206,3 +206,60 @@ func TestStepEnvOverwritesJobEnv(t *testing.T) {
|
||||||
t.Errorf("Unexpected env: %#v", env)
|
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