Skip to content

Commit

Permalink
feat(post-workflow-hook): pass the result status of executed command …
Browse files Browse the repository at this point in the history
…to the post hook envs

Signed-off-by: a1k0u <[email protected]>
  • Loading branch information
a1k0u committed Feb 10, 2025
1 parent 766dac9 commit e9665f6
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 7 deletions.
1 change: 1 addition & 0 deletions runatlantis.io/docs/post-workflow-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,6 @@ command](custom-workflows.md#custom-run-command).
* `COMMENT_ARGS` - Any additional flags passed in the comment on the pull request. Flags are separated by commas and
every character is escaped, ex. `atlantis plan -- arg1 arg2` will result in `COMMENT_ARGS=\a\r\g\1,\a\r\g\2`.
* `COMMAND_NAME` - The name of the command that is being executed, i.e. `plan`, `apply` etc.
* `COMMAND_HAS_ERRORS` - Indicates whether any errors occurred during the execution of the command (`plan`, `apply`). If set to `true`, at least one error was encountered; otherwise, it is `false`.
* `OUTPUT_STATUS_FILE` - An output file to customize the success or failure status. ex. `echo 'failure' > $OUTPUT_STATUS_FILE`.
:::
1 change: 1 addition & 0 deletions server/core/runtime/post_workflow_hook_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (wh DefaultPostWorkflowHookRunner) Run(ctx models.WorkflowHookCommandContex
"USER_NAME": ctx.User.Username,
"OUTPUT_STATUS_FILE": outputFilePath,
"COMMAND_NAME": ctx.CommandName,
"COMMAND_HAS_ERRORS": fmt.Sprintf("%t", ctx.CommandHasErrors),
}

finalEnvVars := baseEnvVars
Expand Down
19 changes: 14 additions & 5 deletions server/core/runtime/post_workflow_hook_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) {
Shell: defaultShell,
ShellArgs: defaultShellArgs,
ExpOut: defaultUnterminatedStringError,
ExpErr: "exit status 2: running \"sh -c echo 'a\" in",
ExpErr: "exit status 2: running 'sh -c echo 'a' in",
ExpDescription: "",
},
{
Expand All @@ -84,7 +84,7 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) {
Shell: defaultShell,
ShellArgs: defaultShellArgs,
ExpOut: fmt.Sprintf(defaultShellCommandNotFoundErrorFormat, "lkjlkj"),
ExpErr: "exit status 127: running \"sh -c lkjlkj\" in",
ExpErr: "exit status 127: running 'sh -c lkjlkj' in",
ExpDescription: "",
},
{
Expand All @@ -103,6 +103,14 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) {
ExpErr: "",
ExpDescription: "",
},
{
Command: "echo command_name=$COMMAND_NAME command_has_errors=$COMMAND_HAS_ERRORS",
Shell: defaultShell,
ShellArgs: defaultShellArgs,
ExpOut: "command_name=plan command_has_errors=false\r\n",
ExpErr: "",
ExpDescription: "",
},
{
Command: "echo something > $OUTPUT_STATUS_FILE",
Shell: defaultShell,
Expand Down Expand Up @@ -151,7 +159,7 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) {
tmpDir := t.TempDir()

projectCmdOutputHandler := jobmocks.NewMockProjectCommandOutputHandler()
r := runtime.DefaultPreWorkflowHookRunner{
r := runtime.DefaultPostWorkflowHookRunner{
OutputHandler: projectCmdOutputHandler,
}
t.Run(c.Command, func(t *testing.T) {
Expand All @@ -175,8 +183,9 @@ func TestPostWorkflowHookRunner_Run(t *testing.T) {
User: models.User{
Username: "acme-user",
},
Log: logger,
CommandName: "plan",
Log: logger,
CommandName: "plan",
CommandHasErrors: false,
}
_, desc, err := r.Run(ctx, c.Command, c.Shell, c.ShellArgs, tmpDir)
if c.ExpErr != "" {
Expand Down
1 change: 1 addition & 0 deletions server/events/apply_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func (a *ApplyCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) {
} else {
result = runProjectCmds(projectCmds, a.prjCmdRunner.Apply)
}
ctx.CommandHasErrors = result.HasErrors()

a.pullUpdater.updatePull(
ctx,
Expand Down
44 changes: 42 additions & 2 deletions server/events/apply_command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/runatlantis/atlantis/server/logging"
"github.com/runatlantis/atlantis/server/metrics"
. "github.com/runatlantis/atlantis/testing"
"github.com/stretchr/testify/require"
)

func TestApplyCommandRunner_IsLocked(t *testing.T) {
Expand Down Expand Up @@ -232,6 +233,7 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {
ProjectResults []command.ProjectResult
RunnerInvokeMatch []*EqMatcher
ExpComment string
ApplyFailed bool
}{
{
Description: "When first apply fails, the second don't run",
Expand Down Expand Up @@ -263,6 +265,7 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
ApplyFailed: true,
ExpComment: "Ran Apply for 2 projects:\n\n" +
"1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n---\n\n### 1. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " +
"2. dir: `` workspace: ``\n**Apply Error**\n```\nshabang\n```\n\n---\n### Apply Summary\n\n2 projects, 1 successful, 0 failed, 1 errored",
Expand Down Expand Up @@ -297,7 +300,8 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Never(),
},
ExpComment: "Ran Apply for dir: `` workspace: ``\n\n**Apply Error**\n```\nshabang\n```",
ApplyFailed: true,
ExpComment: "Ran Apply for dir: `` workspace: ``\n\n**Apply Error**\n```\nshabang\n```",
},
{
Description: "When both in a group of two succeeds, the following two will run",
Expand Down Expand Up @@ -348,6 +352,7 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {
Never(),
Never(),
},
ApplyFailed: true,
ExpComment: "Ran Apply for 2 projects:\n\n" +
"1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n---\n\n### 1. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " +
"2. dir: `` workspace: ``\n**Apply Error**\n```\nshabang\n```\n\n---\n### Apply Summary\n\n2 projects, 1 successful, 0 failed, 1 errored",
Expand Down Expand Up @@ -401,6 +406,7 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
ApplyFailed: true,
ExpComment: "Ran Apply for 4 projects:\n\n" +
"1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n---\n\n### 1. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " +
"2. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " +
Expand Down Expand Up @@ -435,6 +441,7 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
ApplyFailed: true,
ExpComment: "Ran Apply for 2 projects:\n\n" +
"1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n---\n\n### 1. dir: `` workspace: ``\n**Apply Error**\n```\nshabang\n```\n\n---\n### " +
"2. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### Apply Summary\n\n2 projects, 1 successful, 0 failed, 1 errored",
Expand Down Expand Up @@ -465,10 +472,42 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
ApplyFailed: true,
ExpComment: "Ran Apply for 2 projects:\n\n" +
"1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n---\n\n### 1. dir: `` workspace: ``\n**Apply Error**\n```\nshabang\n```\n\n---\n### " +
"2. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### Apply Summary\n\n2 projects, 1 successful, 0 failed, 1 errored",
},
{
Description: "All project finished successfully",
ProjectContexts: []command.ProjectContext{
{
ExecutionOrderGroup: 0,
ProjectName: "First",
},
{
ExecutionOrderGroup: 1,
ProjectName: "Second",
},
},
ProjectResults: []command.ProjectResult{
{
Command: command.Apply,
ApplySuccess: "Great success!",
},
{
Command: command.Apply,
ApplySuccess: "Great success!",
},
},
RunnerInvokeMatch: []*EqMatcher{
Once(),
Once(),
},
ApplyFailed: false,
ExpComment: "Ran Apply for 2 projects:\n\n" +
"1. dir: `` workspace: ``\n1. dir: `` workspace: ``\n---\n\n### 1. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### " +
"2. dir: `` workspace: ``\n```diff\nGreat success!\n```\n\n---\n### Apply Summary\n\n2 projects, 2 successful, 0 failed, 0 errored",
},
}

for _, c := range cases {
Expand Down Expand Up @@ -505,9 +544,10 @@ func TestApplyCommandRunner_ExecutionOrder(t *testing.T) {

for i := range c.ProjectContexts {
projectCommandRunner.VerifyWasCalled(c.RunnerInvokeMatch[i]).Apply(c.ProjectContexts[i])

}

require.Equal(t, c.ApplyFailed, ctx.CommandHasErrors)

vcsClient.VerifyWasCalledOnce().CreateComment(
Any[logging.SimpleLogging](), Eq(testdata.GithubRepo), Eq(modelPull.Num), Eq(c.ExpComment), Eq("apply"),
)
Expand Down
3 changes: 3 additions & 0 deletions server/events/command/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ type Context struct {

// TeamAllowlistChecker is used to check authorization on a project-level
TeamAllowlistChecker TeamAllowlistChecker

// Set true if there were any errors during the command execution
CommandHasErrors bool
}
2 changes: 2 additions & 0 deletions server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,8 @@ type WorkflowHookCommandContext struct {
BaseRepo Repo
// The name of the command that is being executed, i.e. 'plan', 'apply' etc.
CommandName string
// Set true if there were any errors during the command execution
CommandHasErrors bool
// EscapedCommentArgs are the extra arguments that were added to the atlantis
// command, ex. atlantis plan -- -target=resource. We then escape them
// by adding a \ before each character so that they can be used within
Expand Down
1 change: 1 addition & 0 deletions server/events/plan_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ func (p *PlanCommandRunner) run(ctx *command.Context, cmd *CommentCommand) {
} else {
result = runProjectCmds(projectCmds, p.prjCmdRunner.Plan)
}
ctx.CommandHasErrors = result.HasErrors()

if p.autoMerger.automergeEnabled(projectCmds) && result.HasErrors() {
ctx.Log.Info("deleting plans because there were errors and automerge requires all plans succeed")
Expand Down
45 changes: 45 additions & 0 deletions server/events/plan_command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/runatlantis/atlantis/server/logging"
"github.com/runatlantis/atlantis/server/metrics"
. "github.com/runatlantis/atlantis/testing"
"github.com/stretchr/testify/require"
)

func TestPlanCommandRunner_IsSilenced(t *testing.T) {
Expand Down Expand Up @@ -168,6 +169,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
ProjectResults []command.ProjectResult
RunnerInvokeMatch []*EqMatcher
PrevPlanStored bool
PlanFailed bool
}{
{
Description: "When first plan fails, the second don't run",
Expand Down Expand Up @@ -205,6 +207,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
PlanFailed: true,
},
{
Description: "When first fails, the second will not run",
Expand Down Expand Up @@ -238,6 +241,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Never(),
},
PlanFailed: true,
},
{
Description: "When first fails by autorun, the second will not run",
Expand Down Expand Up @@ -273,6 +277,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Never(),
},
PlanFailed: true,
},
{
Description: "When both in a group of two succeeds, the following two will run",
Expand Down Expand Up @@ -333,6 +338,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
Never(),
Never(),
},
PlanFailed: true,
},
{
Description: "When one out of two fails, the following two will not run",
Expand Down Expand Up @@ -393,6 +399,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
PlanFailed: true,
},
{
Description: "Don't block when parallel is not set",
Expand Down Expand Up @@ -426,6 +433,41 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
PlanFailed: true,
},
{
Description: "All project finished successfully",
ProjectContexts: []command.ProjectContext{
{
CommandName: command.Plan,
ExecutionOrderGroup: 0,
ProjectName: "First",
},
{
CommandName: command.Plan,
ExecutionOrderGroup: 1,
ProjectName: "Second",
},
},
ProjectResults: []command.ProjectResult{
{
Command: command.Plan,
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "true",
},
},
{
Command: command.Plan,
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "true",
},
},
},
RunnerInvokeMatch: []*EqMatcher{
Once(),
Once(),
},
PlanFailed: false,
},
{
Description: "Don't block when abortOnExecutionOrderFail is not set",
Expand Down Expand Up @@ -457,6 +499,7 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
Once(),
Once(),
},
PlanFailed: true,
},
}

Expand Down Expand Up @@ -510,6 +553,8 @@ func TestPlanCommandRunner_ExecutionOrder(t *testing.T) {
projectCommandRunner.VerifyWasCalled(c.RunnerInvokeMatch[i]).Plan(c.ProjectContexts[i])
}

require.Equal(t, c.PlanFailed, ctx.CommandHasErrors)

vcsClient.VerifyWasCalledOnce().CreateComment(
Any[logging.SimpleLogging](), Any[models.Repo](), Eq(modelPull.Num), Any[string](), Eq("plan"),
)
Expand Down
1 change: 1 addition & 0 deletions server/events/post_workflow_hooks_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (w *DefaultPostWorkflowHooksCommandRunner) RunPostHooks(ctx *command.Contex
Verbose: false,
EscapedCommentArgs: escapedArgs,
CommandName: cmd.Name.String(),
CommandHasErrors: ctx.CommandHasErrors,
API: ctx.API,
},
postWorkflowHooks, repoDir)
Expand Down
Loading

0 comments on commit e9665f6

Please sign in to comment.