Skip to content

Commit b899419

Browse files
authored
feat: Add Support for Customising the Shell for Pre and Post Workflow Hooks (runatlantis#3451)
1 parent 76c482b commit b899419

15 files changed

+523
-70
lines changed

runatlantis.io/docs/post-workflow-hooks.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ workflows](custom-workflows.html#custom-run-command) in that they are run
66
outside of Atlantis commands. Which means they do not surface their output
77
back to the PR as a comment.
88

9-
Post workflow hooks also only allow `run` and `description` commands.
10-
119
[[toc]]
1210

1311
## Usage
1412

1513
Post workflow hooks can only be specified in the Server-Side Repo Config under
16-
`repos` key.
14+
the `repos` key.
1715

1816
## Use Cases
1917

@@ -45,6 +43,25 @@ repos:
4543
# ...
4644
```
4745

46+
## Customizing the Shell
47+
48+
By default, the commands will be run using the 'sh' shell with an argument of '-c'. This
49+
can be customized using the `shell` and `shellArgs` keys.
50+
51+
Example:
52+
53+
```yaml
54+
repos:
55+
- id: /.*/
56+
post_workflow_hooks:
57+
- run: |
58+
echo 'atlantis.yaml config:'
59+
cat atlantis.yaml
60+
description: atlantis.yaml report
61+
shell: bash
62+
shellArgs: -cv
63+
```
64+
4865
## Reference
4966
5067
### Custom `run` Command
@@ -60,6 +77,8 @@ command](custom-workflows.html#custom-run-command).
6077
| ----------- | ------ | ------- | -------- | --------------------- |
6178
| run | string | none | no | Run a custom command |
6279
| description | string | none | no | Post hook description |
80+
| shell | string | 'sh' | no | The shell to use for running the command |
81+
| shellArgs | string | '-c' | no | The shell arguments to use for running the command |
6382

6483
::: tip Notes
6584
* `run` commands are executed with the following environment variables:

runatlantis.io/docs/pre-workflow-hooks.md

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,29 @@ Pre workflow hooks can be defined to run scripts before default or custom
44
workflows are executed. Pre workflow hooks differ from [custom
55
workflows](custom-workflows.html#custom-run-command) in several ways.
66

7-
1. Pre workflow hooks do not require for repository configuration to be
7+
1. Pre workflow hooks do not require the repository configuration to be
88
present. This can be utilized to [dynamically generate repo configs](pre-workflow-hooks.html#dynamic-repo-config-generation).
99
2. Pre workflow hooks are run outside of Atlantis commands. Which means
1010
they do not surface their output back to the PR as a comment.
11-
3. Pre workflow hooks only allow `run` and `description` commands.
1211

1312
[[toc]]
1413

1514
## Usage
16-
Pre workflow hooks can only be specified in the Server-Side Repo Config under
17-
`repos` key.
15+
16+
Pre workflow hooks can only be specified in the Server-Side Repo Config under the
17+
`repos` key.
1818
::: tip Note
1919
`pre-workflow-hooks` do not prevent Atlantis from executing its
2020
workflows(`plan`, `apply`) even if a `run` command exits with an error.
2121
:::
2222

2323
## Use Cases
24+
2425
### Dynamic Repo Config Generation
25-
If you want generate your `atlantis.yaml` before Atlantis can parse it. You
26-
can add a `run` command to `pre_workflow_hooks`. Your Repo config will be generated
27-
right before Atlantis can parse it.
26+
27+
To generate the repo `atlantis.yaml` before Atlantis can parse it,
28+
add a `run` command to `pre_workflow_hooks`. Your Repo config will be generated
29+
right before Atlantis parses it.
2830

2931
```yaml
3032
repos:
@@ -33,17 +35,43 @@ repos:
3335
- run: ./repo-config-generator.sh
3436
description: Generating configs
3537
```
38+
39+
## Customizing the Shell
40+
41+
By default, the command will be run using the 'sh' shell with an argument of '-c'. This
42+
can be customized using the `shell` and `shellArgs` keys.
43+
44+
Example:
45+
46+
```yaml
47+
repos:
48+
- id: /.*/
49+
pre_workflow_hooks:
50+
- run: |
51+
echo "generating atlantis.yaml"
52+
terragrunt-atlantis-config generate --output atlantis.yaml --autoplan --parallel
53+
description: Generating atlantis.yaml
54+
shell: bash
55+
shellArgs: -cv
56+
```
57+
3658
## Reference
59+
3760
### Custom `run` Command
38-
This is very similar to [custom workflow run
39-
command](custom-workflows.html#custom-run-command).
61+
62+
This is very similar to the [custom workflow run
63+
command](custom-workflows.html#custom-run-command).
64+
4065
```yaml
4166
- run: custom-command
4267
```
68+
4369
| Key | Type | Default | Required | Description |
4470
| ----------- | ------ | ------- | -------- | -------------------- |
4571
| run | string | none | no | Run a custom command |
4672
| description | string | none | no | Pre hook description |
73+
| shell | string | 'sh' | no | The shell to use for running the command |
74+
| shellArgs | string | '-c' | no | The shell arguments to use for running the command |
4775

4876
::: tip Notes
4977
* `run` commands are executed with the following environment variables:
@@ -64,4 +92,3 @@ command](custom-workflows.html#custom-run-command).
6492
* `COMMAND_NAME` - The name of the command that is being executed, i.e. `plan`, `apply` etc.
6593
* `OUTPUT_STATUS_FILE` - An output file to customize the success or failure status. ex. `echo 'failure' > $OUTPUT_STATUS_FILE`.
6694
:::
67-

server/controllers/events/events_controller_e2e_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -610,9 +610,11 @@ func TestGitHubWorkflow(t *testing.T) {
610610

611611
expNumHooks := len(c.Comments) + 1 - c.ExpParseFailedCount
612612
// Let's verify the pre-workflow hook was called for each comment including the pull request opened event
613-
mockPreWorkflowHookRunner.VerifyWasCalled(Times(expNumHooks)).Run(Any[models.WorkflowHookCommandContext](), Eq("some dummy command"), Any[string]())
613+
mockPreWorkflowHookRunner.VerifyWasCalled(Times(expNumHooks)).Run(Any[models.WorkflowHookCommandContext](),
614+
Eq("some dummy command"), Any[string](), Any[string](), Any[string]())
614615
// Let's verify the post-workflow hook was called for each comment including the pull request opened event
615-
mockPostWorkflowHookRunner.VerifyWasCalled(Times(expNumHooks)).Run(Any[models.WorkflowHookCommandContext](), Eq("some post dummy command"), Any[string]())
616+
mockPostWorkflowHookRunner.VerifyWasCalled(Times(expNumHooks)).Run(Any[models.WorkflowHookCommandContext](),
617+
Eq("some post dummy command"), Any[string](), Any[string](), Any[string]())
616618

617619
// Now we're ready to verify Atlantis made all the comments back (or
618620
// replies) that we expect. We expect each plan to have 1 comment,
@@ -794,7 +796,8 @@ func TestSimpleWorkflow_terraformLockFile(t *testing.T) {
794796
}
795797

796798
// Let's verify the pre-workflow hook was called for each comment including the pull request opened event
797-
mockPreWorkflowHookRunner.VerifyWasCalled(Times(2)).Run(Any[models.WorkflowHookCommandContext](), Eq("some dummy command"), Any[string]())
799+
mockPreWorkflowHookRunner.VerifyWasCalled(Times(2)).Run(Any[models.WorkflowHookCommandContext](),
800+
Eq("some dummy command"), Any[string](), Any[string](), Any[string]())
798801

799802
// Now we're ready to verify Atlantis made all the comments back (or
800803
// replies) that we expect. We expect each plan to have 1 comment,

server/core/config/raw/workflow_step.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ func (s WorkflowHook) ToValid() *valid.WorkflowHook {
7676
StepName: RunStepName,
7777
RunCommand: s.StringVal["run"],
7878
StepDescription: s.StringVal["description"],
79+
Shell: s.StringVal["shell"],
80+
ShellArgs: s.StringVal["shellArgs"],
7981
}
8082
}
8183

server/core/config/valid/global_cfg.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ type WorkflowHook struct {
109109
StepName string
110110
RunCommand string
111111
StepDescription string
112+
Shell string
113+
ShellArgs string
112114
}
113115

114116
// DefaultApplyStage is the Atlantis default apply stage.

server/core/runtime/mocks/mock_post_workflows_hook_runner.go

Lines changed: 16 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/core/runtime/mocks/mock_pre_workflows_hook_runner.go

Lines changed: 16 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/core/runtime/post_workflow_hook_runner.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,18 @@ import (
1313

1414
//go:generate pegomock generate --package mocks -o mocks/mock_post_workflows_hook_runner.go PostWorkflowHookRunner
1515
type PostWorkflowHookRunner interface {
16-
Run(ctx models.WorkflowHookCommandContext, command string, path string) (string, string, error)
16+
Run(ctx models.WorkflowHookCommandContext, command string, shell string, shellArgs string, path string) (string, string, error)
1717
}
1818

1919
type DefaultPostWorkflowHookRunner struct {
2020
OutputHandler jobs.ProjectCommandOutputHandler
2121
}
2222

23-
func (wh DefaultPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, path string) (string, string, error) {
23+
func (wh DefaultPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, shell string, shellArgs string, path string) (string, string, error) {
2424
outputFilePath := filepath.Join(path, "OUTPUT_STATUS_FILE")
2525

26-
cmd := exec.Command("sh", "-c", command) // #nosec
26+
shellArgsSlice := append(strings.Split(shellArgs, " "), command)
27+
cmd := exec.Command(shell, shellArgsSlice...) // #nosec
2728
cmd.Dir = path
2829

2930
baseEnvVars := os.Environ()
@@ -58,7 +59,7 @@ func (wh DefaultPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContex
5859
wh.OutputHandler.SendWorkflowHook(ctx, "\n", true)
5960

6061
if err != nil {
61-
err = fmt.Errorf("%s: running %q in %q: \n%s", err, command, path, out)
62+
err = fmt.Errorf("%s: running %q in %q: \n%s", err, shell+" "+shellArgs+" "+command, path, out)
6263
ctx.Log.Debug("error: %s", err)
6364
return string(out), "", err
6465
}
@@ -70,12 +71,12 @@ func (wh DefaultPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContex
7071
var customStatusErr error
7172
customStatusOut, customStatusErr = os.ReadFile(outputFilePath)
7273
if customStatusErr != nil {
73-
err = fmt.Errorf("%s: running %q in %q: \n%s", err, command, path, out)
74+
err = fmt.Errorf("%s: running %q in %q: \n%s", err, shell+" "+shellArgs+" "+command, path, out)
7475
ctx.Log.Debug("error: %s", err)
7576
return string(out), "", err
7677
}
7778
}
7879

79-
ctx.Log.Info("successfully ran %q in %q", command, path)
80-
return outString, strings.Trim(string(customStatusOut), "\n"), nil
80+
ctx.Log.Info("successfully ran %q in %q", shell+" "+shellArgs+" "+command, path)
81+
return string(out), strings.Trim(string(customStatusOut), "\n"), nil
8182
}

0 commit comments

Comments
 (0)