Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(post-workflow-hook): pass the result status of executed command to the post hook envs #5308

Merged
merged 2 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading