mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-28 22:28:25 +00:00
feat: Add Support for Customising the Shell for Pre and Post Workflow Hooks (#3451)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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`.
|
||||
:::
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,8 @@ type WorkflowHook struct {
|
||||
StepName string
|
||||
RunCommand string
|
||||
StepDescription string
|
||||
Shell string
|
||||
ShellArgs string
|
||||
}
|
||||
|
||||
// DefaultApplyStage is the Atlantis default apply stage.
|
||||
|
||||
@@ -25,11 +25,11 @@ func NewMockPostWorkflowHookRunner(options ...pegomock.Option) *MockPostWorkflow
|
||||
func (mock *MockPostWorkflowHookRunner) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
|
||||
func (mock *MockPostWorkflowHookRunner) FailHandler() pegomock.FailHandler { return mock.fail }
|
||||
|
||||
func (mock *MockPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, path string) (string, string, error) {
|
||||
func (mock *MockPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, shell string, shellArgs string, path string) (string, string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockPostWorkflowHookRunner().")
|
||||
}
|
||||
params := []pegomock.Param{ctx, command, path}
|
||||
params := []pegomock.Param{ctx, command, shell, shellArgs, path}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Run", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 string
|
||||
@@ -85,8 +85,8 @@ type VerifierMockPostWorkflowHookRunner struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, path string) *MockPostWorkflowHookRunner_Run_OngoingVerification {
|
||||
params := []pegomock.Param{ctx, command, path}
|
||||
func (verifier *VerifierMockPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, shell string, shellArgs string, path string) *MockPostWorkflowHookRunner_Run_OngoingVerification {
|
||||
params := []pegomock.Param{ctx, command, shell, shellArgs, path}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Run", params, verifier.timeout)
|
||||
return &MockPostWorkflowHookRunner_Run_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
@@ -96,12 +96,12 @@ type MockPostWorkflowHookRunner_Run_OngoingVerification struct {
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockPostWorkflowHookRunner_Run_OngoingVerification) GetCapturedArguments() (models.WorkflowHookCommandContext, string, string) {
|
||||
ctx, command, path := c.GetAllCapturedArguments()
|
||||
return ctx[len(ctx)-1], command[len(command)-1], path[len(path)-1]
|
||||
func (c *MockPostWorkflowHookRunner_Run_OngoingVerification) GetCapturedArguments() (models.WorkflowHookCommandContext, string, string, string, string) {
|
||||
ctx, command, shell, shellArgs, path := c.GetAllCapturedArguments()
|
||||
return ctx[len(ctx)-1], command[len(command)-1], shell[len(shell)-1], shellArgs[len(shellArgs)-1], path[len(path)-1]
|
||||
}
|
||||
|
||||
func (c *MockPostWorkflowHookRunner_Run_OngoingVerification) GetAllCapturedArguments() (_param0 []models.WorkflowHookCommandContext, _param1 []string, _param2 []string) {
|
||||
func (c *MockPostWorkflowHookRunner_Run_OngoingVerification) GetAllCapturedArguments() (_param0 []models.WorkflowHookCommandContext, _param1 []string, _param2 []string, _param3 []string, _param4 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]models.WorkflowHookCommandContext, len(c.methodInvocations))
|
||||
@@ -116,6 +116,14 @@ func (c *MockPostWorkflowHookRunner_Run_OngoingVerification) GetAllCapturedArgum
|
||||
for u, param := range params[2] {
|
||||
_param2[u] = param.(string)
|
||||
}
|
||||
_param3 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[3] {
|
||||
_param3[u] = param.(string)
|
||||
}
|
||||
_param4 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[4] {
|
||||
_param4[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ func NewMockPreWorkflowHookRunner(options ...pegomock.Option) *MockPreWorkflowHo
|
||||
func (mock *MockPreWorkflowHookRunner) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
|
||||
func (mock *MockPreWorkflowHookRunner) FailHandler() pegomock.FailHandler { return mock.fail }
|
||||
|
||||
func (mock *MockPreWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, path string) (string, string, error) {
|
||||
func (mock *MockPreWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, shell string, shellArgs string, path string) (string, string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockPreWorkflowHookRunner().")
|
||||
}
|
||||
params := []pegomock.Param{ctx, command, path}
|
||||
params := []pegomock.Param{ctx, command, shell, shellArgs, path}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Run", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 string
|
||||
@@ -85,8 +85,8 @@ type VerifierMockPreWorkflowHookRunner struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockPreWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, path string) *MockPreWorkflowHookRunner_Run_OngoingVerification {
|
||||
params := []pegomock.Param{ctx, command, path}
|
||||
func (verifier *VerifierMockPreWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, shell string, shellArgs string, path string) *MockPreWorkflowHookRunner_Run_OngoingVerification {
|
||||
params := []pegomock.Param{ctx, command, shell, shellArgs, path}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Run", params, verifier.timeout)
|
||||
return &MockPreWorkflowHookRunner_Run_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
@@ -96,12 +96,12 @@ type MockPreWorkflowHookRunner_Run_OngoingVerification struct {
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockPreWorkflowHookRunner_Run_OngoingVerification) GetCapturedArguments() (models.WorkflowHookCommandContext, string, string) {
|
||||
ctx, command, path := c.GetAllCapturedArguments()
|
||||
return ctx[len(ctx)-1], command[len(command)-1], path[len(path)-1]
|
||||
func (c *MockPreWorkflowHookRunner_Run_OngoingVerification) GetCapturedArguments() (models.WorkflowHookCommandContext, string, string, string, string) {
|
||||
ctx, command, shell, shellArgs, path := c.GetAllCapturedArguments()
|
||||
return ctx[len(ctx)-1], command[len(command)-1], shell[len(shell)-1], shellArgs[len(shellArgs)-1], path[len(path)-1]
|
||||
}
|
||||
|
||||
func (c *MockPreWorkflowHookRunner_Run_OngoingVerification) GetAllCapturedArguments() (_param0 []models.WorkflowHookCommandContext, _param1 []string, _param2 []string) {
|
||||
func (c *MockPreWorkflowHookRunner_Run_OngoingVerification) GetAllCapturedArguments() (_param0 []models.WorkflowHookCommandContext, _param1 []string, _param2 []string, _param3 []string, _param4 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]models.WorkflowHookCommandContext, len(c.methodInvocations))
|
||||
@@ -116,6 +116,14 @@ func (c *MockPreWorkflowHookRunner_Run_OngoingVerification) GetAllCapturedArgume
|
||||
for u, param := range params[2] {
|
||||
_param2[u] = param.(string)
|
||||
}
|
||||
_param3 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[3] {
|
||||
_param3[u] = param.(string)
|
||||
}
|
||||
_param4 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[4] {
|
||||
_param4[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -15,72 +15,122 @@ import (
|
||||
)
|
||||
|
||||
func TestPostWorkflowHookRunner_Run(t *testing.T) {
|
||||
|
||||
defaultShell := "sh"
|
||||
defaultShellArgs := "-c"
|
||||
|
||||
cases := []struct {
|
||||
Command string
|
||||
Shell string
|
||||
ShellArgs string
|
||||
ExpOut string
|
||||
ExpErr string
|
||||
ExpDescription string
|
||||
}{
|
||||
{
|
||||
Command: "",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo hi",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "hi\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: `printf \'your main.tf file does not provide default region.\\ncheck\'`,
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: `'your`,
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: `printf 'your main.tf file does not provide default region.\ncheck'`,
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "your main.tf file does not provide default region.\r\ncheck",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo 'a",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "sh: 1: Syntax error: Unterminated quoted string\r\n",
|
||||
ExpErr: "exit status 2: running \"echo 'a\" in",
|
||||
ExpErr: "exit status 2: running \"sh -c echo 'a\" in",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo hi >> file && cat file",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "hi\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "lkjlkj",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "sh: 1: lkjlkj: not found\r\n",
|
||||
ExpErr: "exit status 127: running \"lkjlkj\" in",
|
||||
ExpErr: "exit status 127: running \"sh -c lkjlkj\" in",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo base_repo_name=$BASE_REPO_NAME base_repo_owner=$BASE_REPO_OWNER head_repo_name=$HEAD_REPO_NAME head_repo_owner=$HEAD_REPO_OWNER head_branch_name=$HEAD_BRANCH_NAME head_commit=$HEAD_COMMIT base_branch_name=$BASE_BRANCH_NAME pull_num=$PULL_NUM pull_url=$PULL_URL pull_author=$PULL_AUTHOR",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "base_repo_name=basename base_repo_owner=baseowner head_repo_name=headname head_repo_owner=headowner head_branch_name=add-feat head_commit=12345abcdef base_branch_name=main pull_num=2 pull_url=https://github.com/runatlantis/atlantis/pull/2 pull_author=acme\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo user_name=$USER_NAME",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "user_name=acme-user\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo something > $OUTPUT_STATUS_FILE",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "",
|
||||
ExpErr: "",
|
||||
ExpDescription: "something",
|
||||
},
|
||||
{
|
||||
Command: "echo shell test 1",
|
||||
Shell: "bash",
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "shell test 1\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo shell test 2",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: "-cx",
|
||||
ExpOut: "+ echo shell test 2\r\nshell test 2\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo shell test 3",
|
||||
Shell: "bash",
|
||||
ShellArgs: "-cv",
|
||||
ExpOut: "echo shell test 3\r\nshell test 3\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -124,7 +174,7 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) {
|
||||
Log: logger,
|
||||
CommandName: "plan",
|
||||
}
|
||||
_, desc, err := r.Run(ctx, c.Command, tmpDir)
|
||||
_, desc, err := r.Run(ctx, c.Command, c.Shell, c.ShellArgs, tmpDir)
|
||||
if c.ExpErr != "" {
|
||||
ErrContains(t, c.ExpErr, err)
|
||||
} else {
|
||||
|
||||
@@ -13,17 +13,18 @@ import (
|
||||
|
||||
//go:generate pegomock generate --package mocks -o mocks/mock_pre_workflows_hook_runner.go PreWorkflowHookRunner
|
||||
type PreWorkflowHookRunner 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 DefaultPreWorkflowHookRunner struct {
|
||||
OutputHandler jobs.ProjectCommandOutputHandler
|
||||
}
|
||||
|
||||
func (wh DefaultPreWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext, command string, path string) (string, string, error) {
|
||||
func (wh DefaultPreWorkflowHookRunner) 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 DefaultPreWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext
|
||||
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 DefaultPreWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContext
|
||||
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
|
||||
}
|
||||
|
||||
@@ -15,72 +15,122 @@ import (
|
||||
)
|
||||
|
||||
func TestPreWorkflowHookRunner_Run(t *testing.T) {
|
||||
|
||||
defaultShell := "sh"
|
||||
defaultShellArgs := "-c"
|
||||
|
||||
cases := []struct {
|
||||
Command string
|
||||
Shell string
|
||||
ShellArgs string
|
||||
ExpOut string
|
||||
ExpErr string
|
||||
ExpDescription string
|
||||
}{
|
||||
{
|
||||
Command: "",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo hi",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "hi\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: `printf \'your main.tf file does not provide default region.\\ncheck\'`,
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: `'your`,
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: `printf 'your main.tf file does not provide default region.\ncheck'`,
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "your main.tf file does not provide default region.\r\ncheck",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo 'a",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "sh: 1: Syntax error: Unterminated quoted string\r\n",
|
||||
ExpErr: "exit status 2: running \"echo 'a\" in",
|
||||
ExpErr: "exit status 2: running \"sh -c echo 'a\" in",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo hi >> file && cat file",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "hi\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "lkjlkj",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "sh: 1: lkjlkj: not found\r\n",
|
||||
ExpErr: "exit status 127: running \"lkjlkj\" in",
|
||||
ExpErr: "exit status 127: running \"sh -c lkjlkj\" in",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo base_repo_name=$BASE_REPO_NAME base_repo_owner=$BASE_REPO_OWNER head_repo_name=$HEAD_REPO_NAME head_repo_owner=$HEAD_REPO_OWNER head_branch_name=$HEAD_BRANCH_NAME head_commit=$HEAD_COMMIT base_branch_name=$BASE_BRANCH_NAME pull_num=$PULL_NUM pull_url=$PULL_URL pull_author=$PULL_AUTHOR",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "base_repo_name=basename base_repo_owner=baseowner head_repo_name=headname head_repo_owner=headowner head_branch_name=add-feat head_commit=12345abcdef base_branch_name=main pull_num=2 pull_url=https://github.com/runatlantis/atlantis/pull/2 pull_author=acme\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo user_name=$USER_NAME",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "user_name=acme-user\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo something > $OUTPUT_STATUS_FILE",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "",
|
||||
ExpErr: "",
|
||||
ExpDescription: "something",
|
||||
},
|
||||
{
|
||||
Command: "echo shell test 1",
|
||||
Shell: "bash",
|
||||
ShellArgs: defaultShellArgs,
|
||||
ExpOut: "shell test 1\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo shell test 2",
|
||||
Shell: defaultShell,
|
||||
ShellArgs: "-cx",
|
||||
ExpOut: "+ echo shell test 2\r\nshell test 2\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
{
|
||||
Command: "echo shell test 3",
|
||||
Shell: "bash",
|
||||
ShellArgs: "-cv",
|
||||
ExpOut: "echo shell test 3\r\nshell test 3\r\n",
|
||||
ExpErr: "",
|
||||
ExpDescription: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@@ -124,7 +174,7 @@ func TestPreWorkflowHookRunner_Run(t *testing.T) {
|
||||
Log: logger,
|
||||
CommandName: "plan",
|
||||
}
|
||||
_, desc, err := r.Run(ctx, c.Command, tmpDir)
|
||||
_, desc, err := r.Run(ctx, c.Command, c.Shell, c.ShellArgs, tmpDir)
|
||||
if c.ExpErr != "" {
|
||||
ErrContains(t, c.ExpErr, err)
|
||||
} else {
|
||||
|
||||
@@ -109,6 +109,16 @@ func (w *DefaultPostWorkflowHooksCommandRunner) runHooks(
|
||||
}
|
||||
|
||||
ctx.HookID = uuid.NewString()
|
||||
shell := hook.Shell
|
||||
if shell == "" {
|
||||
ctx.Log.Debug("Setting shell to default: %q", shell)
|
||||
shell = "sh"
|
||||
}
|
||||
shellArgs := hook.ShellArgs
|
||||
if shellArgs == "" {
|
||||
ctx.Log.Debug("Setting shellArgs to default: %q", shellArgs)
|
||||
shellArgs = "-c"
|
||||
}
|
||||
url, err := w.Router.GenerateProjectWorkflowHookURL(ctx.HookID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -118,7 +128,7 @@ func (w *DefaultPostWorkflowHooksCommandRunner) runHooks(
|
||||
ctx.Log.Warn("unable to update post workflow hook status: %s", err)
|
||||
}
|
||||
|
||||
_, runtimeDesc, err := w.PostWorkflowHookRunner.Run(ctx, hook.RunCommand, repoDir)
|
||||
_, runtimeDesc, err := w.PostWorkflowHookRunner.Run(ctx, hook.RunCommand, shell, shellArgs, repoDir)
|
||||
|
||||
if err != nil {
|
||||
if err := w.CommitStatusUpdater.UpdatePostWorkflowHook(ctx.Pull, models.FailedCommitStatus, hookDescription, runtimeDesc, url); err != nil {
|
||||
|
||||
@@ -57,11 +57,33 @@ func TestRunPostHooks_Clone(t *testing.T) {
|
||||
Log: log,
|
||||
}
|
||||
|
||||
defaultShell := "sh"
|
||||
defaultShellArgs := "-c"
|
||||
|
||||
testHook := valid.WorkflowHook{
|
||||
StepName: "test",
|
||||
RunCommand: "some command",
|
||||
}
|
||||
|
||||
testHookWithShell := valid.WorkflowHook{
|
||||
StepName: "test1",
|
||||
RunCommand: "echo test1",
|
||||
Shell: "bash",
|
||||
}
|
||||
|
||||
testHookWithShellArgs := valid.WorkflowHook{
|
||||
StepName: "test2",
|
||||
RunCommand: "echo test2",
|
||||
ShellArgs: "-ce",
|
||||
}
|
||||
|
||||
testHookWithShellandShellArgs := valid.WorkflowHook{
|
||||
StepName: "test3",
|
||||
RunCommand: "echo test3",
|
||||
Shell: "bash",
|
||||
ShellArgs: "-ce",
|
||||
}
|
||||
|
||||
repoDir := "path/to/repo"
|
||||
result := "some result"
|
||||
runtimeDesc := ""
|
||||
@@ -104,11 +126,14 @@ func TestRunPostHooks_Clone(t *testing.T) {
|
||||
|
||||
When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(postWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPostWorkflowHookRunner.Run(pCtx, testHook.RunCommand, repoDir)).ThenReturn(result, runtimeDesc, nil)
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](),
|
||||
Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := postWh.RunPostHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))
|
||||
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
t.Run("success hooks not in cfg", func(t *testing.T) {
|
||||
@@ -136,7 +161,8 @@ func TestRunPostHooks_Clone(t *testing.T) {
|
||||
|
||||
Ok(t, err)
|
||||
|
||||
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(pCtx, testHook.RunCommand, repoDir)
|
||||
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
postWhWorkingDirLocker.VerifyWasCalled(Never()).TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, "path")
|
||||
postWhWorkingDir.VerifyWasCalled(Never()).Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)
|
||||
})
|
||||
@@ -162,7 +188,8 @@ func TestRunPostHooks_Clone(t *testing.T) {
|
||||
|
||||
Assert(t, err != nil, "error not nil")
|
||||
postWhWorkingDir.VerifyWasCalled(Never()).Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)
|
||||
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(pCtx, testHook.RunCommand, repoDir)
|
||||
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
})
|
||||
|
||||
t.Run("error cloning", func(t *testing.T) {
|
||||
@@ -193,7 +220,8 @@ func TestRunPostHooks_Clone(t *testing.T) {
|
||||
|
||||
Assert(t, err != nil, "error not nil")
|
||||
|
||||
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(pCtx, testHook.RunCommand, repoDir)
|
||||
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
@@ -220,7 +248,8 @@ func TestRunPostHooks_Clone(t *testing.T) {
|
||||
|
||||
When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(postWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))).ThenReturn(result, runtimeDesc, errors.New("some error"))
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, errors.New("some error"))
|
||||
|
||||
err := postWh.RunPostHooks(ctx, cmd)
|
||||
|
||||
@@ -259,12 +288,117 @@ func TestRunPostHooks_Clone(t *testing.T) {
|
||||
|
||||
When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(postWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := postWh.RunPostHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))
|
||||
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
t.Run("shell passed to webhooks", func(t *testing.T) {
|
||||
postWorkflowHooksSetup(t)
|
||||
|
||||
var unlockCalled = newBool(false)
|
||||
unlockFn := func() {
|
||||
unlockCalled = newBool(true)
|
||||
}
|
||||
|
||||
globalCfg := valid.GlobalCfg{
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
ID: testdata.GithubRepo.ID(),
|
||||
PostWorkflowHooks: []*valid.WorkflowHook{
|
||||
&testHookWithShell,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
postWh.GlobalCfg = globalCfg
|
||||
|
||||
When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(postWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHookWithShell.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := postWh.RunPostHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShell.RunCommand), Eq(testHookWithShell.Shell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
t.Run("shellArgs passed to webhooks", func(t *testing.T) {
|
||||
postWorkflowHooksSetup(t)
|
||||
|
||||
var unlockCalled = newBool(false)
|
||||
unlockFn := func() {
|
||||
unlockCalled = newBool(true)
|
||||
}
|
||||
|
||||
globalCfg := valid.GlobalCfg{
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
ID: testdata.GithubRepo.ID(),
|
||||
PostWorkflowHooks: []*valid.WorkflowHook{
|
||||
&testHookWithShellArgs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
postWh.GlobalCfg = globalCfg
|
||||
|
||||
When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(postWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := postWh.RunPostHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShellArgs.RunCommand), Eq(defaultShell), Eq(testHookWithShellArgs.ShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
t.Run("Shell and ShellArgs passed to webhooks", func(t *testing.T) {
|
||||
postWorkflowHooksSetup(t)
|
||||
|
||||
var unlockCalled = newBool(false)
|
||||
unlockFn := func() {
|
||||
unlockCalled = newBool(true)
|
||||
}
|
||||
|
||||
globalCfg := valid.GlobalCfg{
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
ID: testdata.GithubRepo.ID(),
|
||||
PostWorkflowHooks: []*valid.WorkflowHook{
|
||||
&testHookWithShellandShellArgs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
postWh.GlobalCfg = globalCfg
|
||||
|
||||
When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(postWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShellandShellArgs.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := postWh.RunPostHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShellandShellArgs.RunCommand), Eq(testHookWithShellandShellArgs.Shell), Eq(testHookWithShellandShellArgs.ShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -106,6 +106,16 @@ func (w *DefaultPreWorkflowHooksCommandRunner) runHooks(
|
||||
}
|
||||
|
||||
ctx.HookID = uuid.NewString()
|
||||
shell := hook.Shell
|
||||
if shell == "" {
|
||||
ctx.Log.Debug("Setting shell to default: %q", shell)
|
||||
shell = "sh"
|
||||
}
|
||||
shellArgs := hook.ShellArgs
|
||||
if shellArgs == "" {
|
||||
ctx.Log.Debug("Setting shellArgs to default: %q", shellArgs)
|
||||
shellArgs = "-c"
|
||||
}
|
||||
url, err := w.Router.GenerateProjectWorkflowHookURL(ctx.HookID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -116,7 +126,7 @@ func (w *DefaultPreWorkflowHooksCommandRunner) runHooks(
|
||||
return err
|
||||
}
|
||||
|
||||
_, runtimeDesc, err := w.PreWorkflowHookRunner.Run(ctx, hook.RunCommand, repoDir)
|
||||
_, runtimeDesc, err := w.PreWorkflowHookRunner.Run(ctx, hook.RunCommand, shell, shellArgs, repoDir)
|
||||
|
||||
if err != nil {
|
||||
if err := w.CommitStatusUpdater.UpdatePreWorkflowHook(ctx.Pull, models.FailedCommitStatus, hookDescription, runtimeDesc, url); err != nil {
|
||||
|
||||
@@ -60,11 +60,33 @@ func TestRunPreHooks_Clone(t *testing.T) {
|
||||
Log: log,
|
||||
}
|
||||
|
||||
defaultShell := "sh"
|
||||
defaultShellArgs := "-c"
|
||||
|
||||
testHook := valid.WorkflowHook{
|
||||
StepName: "test",
|
||||
RunCommand: "some command",
|
||||
}
|
||||
|
||||
testHookWithShell := valid.WorkflowHook{
|
||||
StepName: "test1",
|
||||
RunCommand: "echo test1",
|
||||
Shell: "bash",
|
||||
}
|
||||
|
||||
testHookWithShellArgs := valid.WorkflowHook{
|
||||
StepName: "test2",
|
||||
RunCommand: "echo test2",
|
||||
ShellArgs: "-ce",
|
||||
}
|
||||
|
||||
testHookWithShellandShellArgs := valid.WorkflowHook{
|
||||
StepName: "test3",
|
||||
RunCommand: "echo test3",
|
||||
Shell: "bash",
|
||||
ShellArgs: "-ce",
|
||||
}
|
||||
|
||||
repoDir := "path/to/repo"
|
||||
result := "some result"
|
||||
runtimeDesc := ""
|
||||
@@ -106,12 +128,14 @@ func TestRunPreHooks_Clone(t *testing.T) {
|
||||
|
||||
When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(preWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := preWh.RunPreHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))
|
||||
whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
@@ -140,7 +164,7 @@ func TestRunPreHooks_Clone(t *testing.T) {
|
||||
|
||||
Ok(t, err)
|
||||
|
||||
whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))
|
||||
whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
preWhWorkingDirLocker.VerifyWasCalled(Never()).TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, "")
|
||||
preWhWorkingDir.VerifyWasCalled(Never()).Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)
|
||||
})
|
||||
@@ -167,7 +191,7 @@ func TestRunPreHooks_Clone(t *testing.T) {
|
||||
|
||||
Assert(t, err != nil, "error not nil")
|
||||
preWhWorkingDir.VerifyWasCalled(Never()).Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)
|
||||
whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))
|
||||
whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
})
|
||||
|
||||
t.Run("error cloning", func(t *testing.T) {
|
||||
@@ -198,7 +222,7 @@ func TestRunPreHooks_Clone(t *testing.T) {
|
||||
|
||||
Assert(t, err != nil, "error not nil")
|
||||
|
||||
whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))
|
||||
whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
@@ -225,7 +249,8 @@ func TestRunPreHooks_Clone(t *testing.T) {
|
||||
|
||||
When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(preWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))).ThenReturn(result, runtimeDesc, errors.New("some error"))
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, errors.New("some error"))
|
||||
|
||||
err := preWh.RunPreHooks(ctx, cmd)
|
||||
|
||||
@@ -264,12 +289,115 @@ func TestRunPreHooks_Clone(t *testing.T) {
|
||||
|
||||
When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(preWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := preWh.RunPreHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(repoDir))
|
||||
whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
t.Run("shell passed to webhooks", func(t *testing.T) {
|
||||
preWorkflowHooksSetup(t)
|
||||
|
||||
var unlockCalled = newBool(false)
|
||||
unlockFn := func() {
|
||||
unlockCalled = newBool(true)
|
||||
}
|
||||
|
||||
globalCfg := valid.GlobalCfg{
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
ID: testdata.GithubRepo.ID(),
|
||||
PreWorkflowHooks: []*valid.WorkflowHook{
|
||||
&testHookWithShell,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
preWh.GlobalCfg = globalCfg
|
||||
|
||||
When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(preWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHookWithShell.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := preWh.RunPreHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShell.RunCommand), Eq(testHookWithShell.Shell), Eq(defaultShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
t.Run("shellArgs passed to webhooks", func(t *testing.T) {
|
||||
preWorkflowHooksSetup(t)
|
||||
|
||||
var unlockCalled = newBool(false)
|
||||
unlockFn := func() {
|
||||
unlockCalled = newBool(true)
|
||||
}
|
||||
|
||||
globalCfg := valid.GlobalCfg{
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
ID: testdata.GithubRepo.ID(),
|
||||
PreWorkflowHooks: []*valid.WorkflowHook{
|
||||
&testHookWithShellArgs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
preWh.GlobalCfg = globalCfg
|
||||
|
||||
When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(preWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand),
|
||||
Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := preWh.RunPreHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShellArgs.RunCommand), Eq(defaultShell), Eq(testHookWithShellArgs.ShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
|
||||
t.Run("Shell and ShellArgs passed to webhooks", func(t *testing.T) {
|
||||
preWorkflowHooksSetup(t)
|
||||
|
||||
var unlockCalled = newBool(false)
|
||||
unlockFn := func() {
|
||||
unlockCalled = newBool(true)
|
||||
}
|
||||
|
||||
globalCfg := valid.GlobalCfg{
|
||||
Repos: []valid.Repo{
|
||||
{
|
||||
ID: testdata.GithubRepo.ID(),
|
||||
PreWorkflowHooks: []*valid.WorkflowHook{
|
||||
&testHookWithShellandShellArgs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
preWh.GlobalCfg = globalCfg
|
||||
|
||||
When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
|
||||
When(preWhWorkingDir.Clone(log, testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
|
||||
When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShellandShellArgs.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil)
|
||||
|
||||
err := preWh.RunPreHooks(ctx, cmd)
|
||||
|
||||
Ok(t, err)
|
||||
whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](),
|
||||
Eq(testHookWithShellandShellArgs.RunCommand), Eq(testHookWithShellandShellArgs.Shell),
|
||||
Eq(testHookWithShellandShellArgs.ShellArgs), Eq(repoDir))
|
||||
Assert(t, *unlockCalled == true, "unlock function called")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user