Skip to content

Commit

Permalink
feat: Add Support for Customising the Shell for Pre and Post Workflow…
Browse files Browse the repository at this point in the history
… Hooks (runatlantis#3451)
  • Loading branch information
X-Guardian authored and ijames-gc committed Feb 13, 2024

Unverified

This user has not yet uploaded their public signing key.
1 parent a5731c7 commit 736501f
Showing 15 changed files with 525 additions and 72 deletions.
25 changes: 22 additions & 3 deletions runatlantis.io/docs/post-workflow-hooks.md
Original file line number Diff line number Diff line change
@@ -6,14 +6,12 @@ workflows](custom-workflows.html#custom-run-command) in that they are run
outside of Atlantis commands. Which means they do not surface their output
back to the PR as a comment.

Post workflow hooks also only allow `run` and `description` commands.

[[toc]]

## Usage

Post workflow hooks can only be specified in the Server-Side Repo Config under
`repos` key.
the `repos` key.

## Use Cases

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

## Customizing the Shell

By default, the commands will be run using the 'sh' shell with an argument of '-c'. This
can be customized using the `shell` and `shellArgs` keys.

Example:

```yaml
repos:
- id: /.*/
post_workflow_hooks:
- run: |
echo 'atlantis.yaml config:'
cat atlantis.yaml
description: atlantis.yaml report
shell: bash
shellArgs: -cv
```
## Reference
### Custom `run` Command
@@ -60,6 +77,8 @@ command](custom-workflows.html#custom-run-command).
| ----------- | ------ | ------- | -------- | --------------------- |
| run | string | none | no | Run a custom command |
| description | string | none | no | Post hook description |
| shell | string | 'sh' | no | The shell to use for running the command |
| shellArgs | string | '-c' | no | The shell arguments to use for running the command |

::: tip Notes
* `run` commands are executed with the following environment variables:
47 changes: 37 additions & 10 deletions runatlantis.io/docs/pre-workflow-hooks.md
Original file line number Diff line number Diff line change
@@ -4,27 +4,29 @@ Pre workflow hooks can be defined to run scripts before default or custom
workflows are executed. Pre workflow hooks differ from [custom
workflows](custom-workflows.html#custom-run-command) in several ways.

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

[[toc]]

## Usage
Pre workflow hooks can only be specified in the Server-Side Repo Config under
`repos` key.

Pre workflow hooks can only be specified in the Server-Side Repo Config under the
`repos` key.
::: tip Note
`pre-workflow-hooks` do not prevent Atlantis from executing its
workflows(`plan`, `apply`) even if a `run` command exits with an error.
:::

## Use Cases

### Dynamic Repo Config Generation
If you want generate your `atlantis.yaml` before Atlantis can parse it. You
can add a `run` command to `pre_workflow_hooks`. Your Repo config will be generated
right before Atlantis can parse it.

To generate the repo `atlantis.yaml` before Atlantis can parse it,
add a `run` command to `pre_workflow_hooks`. Your Repo config will be generated
right before Atlantis parses it.

```yaml
repos:
@@ -33,17 +35,43 @@ repos:
- run: ./repo-config-generator.sh
description: Generating configs
```
## Customizing the Shell
By default, the command will be run using the 'sh' shell with an argument of '-c'. This
can be customized using the `shell` and `shellArgs` keys.

Example:

```yaml
repos:
- id: /.*/
pre_workflow_hooks:
- run: |
echo "generating atlantis.yaml"
terragrunt-atlantis-config generate --output atlantis.yaml --autoplan --parallel
description: Generating atlantis.yaml
shell: bash
shellArgs: -cv
```

## Reference

### Custom `run` Command
This is very similar to [custom workflow run
command](custom-workflows.html#custom-run-command).

This is very similar to the [custom workflow run
command](custom-workflows.html#custom-run-command).

```yaml
- run: custom-command
```

| Key | Type | Default | Required | Description |
| ----------- | ------ | ------- | -------- | -------------------- |
| run | string | none | no | Run a custom command |
| description | string | none | no | Pre hook description |
| shell | string | 'sh' | no | The shell to use for running the command |
| shellArgs | string | '-c' | no | The shell arguments to use for running the command |

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

9 changes: 6 additions & 3 deletions server/controllers/events/events_controller_e2e_test.go
Original file line number Diff line number Diff line change
@@ -610,9 +610,11 @@ func TestGitHubWorkflow(t *testing.T) {

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

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

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

// Now we're ready to verify Atlantis made all the comments back (or
// replies) that we expect. We expect each plan to have 1 comment,
2 changes: 2 additions & 0 deletions server/core/config/raw/workflow_step.go
Original file line number Diff line number Diff line change
@@ -76,6 +76,8 @@ func (s WorkflowHook) ToValid() *valid.WorkflowHook {
StepName: RunStepName,
RunCommand: s.StringVal["run"],
StepDescription: s.StringVal["description"],
Shell: s.StringVal["shell"],
ShellArgs: s.StringVal["shellArgs"],
}
}

2 changes: 2 additions & 0 deletions server/core/config/valid/global_cfg.go
Original file line number Diff line number Diff line change
@@ -109,6 +109,8 @@ type WorkflowHook struct {
StepName string
RunCommand string
StepDescription string
Shell string
ShellArgs string
}

// DefaultApplyStage is the Atlantis default apply stage.
24 changes: 16 additions & 8 deletions server/core/runtime/mocks/mock_post_workflows_hook_runner.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 16 additions & 8 deletions server/core/runtime/mocks/mock_pre_workflows_hook_runner.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions server/core/runtime/post_workflow_hook_runner.go
Original file line number Diff line number Diff line change
@@ -13,17 +13,18 @@ import (

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

type DefaultPostWorkflowHookRunner struct {
OutputHandler jobs.ProjectCommandOutputHandler
}

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

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

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

if err != nil {
err = fmt.Errorf("%s: running %q in %q: \n%s", err, command, path, out)
err = fmt.Errorf("%s: running %q in %q: \n%s", err, shell+" "+shellArgs+" "+command, path, out)
ctx.Log.Debug("error: %s", err)
return string(out), "", err
}
@@ -70,12 +71,12 @@ func (wh DefaultPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContex
var customStatusErr error
customStatusOut, customStatusErr = os.ReadFile(outputFilePath)
if customStatusErr != nil {
err = fmt.Errorf("%s: running %q in %q: \n%s", err, command, path, out)
err = fmt.Errorf("%s: running %q in %q: \n%s", err, shell+" "+shellArgs+" "+command, path, out)
ctx.Log.Debug("error: %s", err)
return string(out), "", err
}
}

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

0 comments on commit 736501f

Please sign in to comment.