Skip to content

Commit

Permalink
feat: Add A Server Config Flag to Enable Failure on Pre Workflow Hook…
Browse files Browse the repository at this point in the history
… Errors (#3729)

* FailOnPreWorkflowHookError

* Fix linting issues
  • Loading branch information
X-Guardian authored Sep 1, 2023
1 parent de44f6a commit dc64493
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 40 deletions.
5 changes: 5 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const (
EnableRegExpCmdFlag = "enable-regexp-cmd"
EnableDiffMarkdownFormat = "enable-diff-markdown-format"
ExecutableName = "executable-name"
FailOnPreWorkflowHookError = "fail-on-pre-workflow-hook-error"
HideUnchangedPlanComments = "hide-unchanged-plan-comments"
GHHostnameFlag = "gh-hostname"
GHTeamAllowlistFlag = "gh-team-allowlist"
Expand Down Expand Up @@ -457,6 +458,10 @@ var boolFlags = map[string]boolFlag{
description: "Enable Atlantis to format Terraform plan output into a markdown-diff friendly format for color-coding purposes.",
defaultValue: false,
},
FailOnPreWorkflowHookError: {
description: "Fail and do not run the requested Atlantis command if any of the pre workflow hooks error.",
defaultValue: false,
},
GHAllowMergeableBypassApply: {
description: "Feature flag to enable functionality to allow mergeable check to ignore apply required check",
defaultValue: false,
Expand Down
9 changes: 9 additions & 0 deletions runatlantis.io/docs/server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,15 @@ and set `--autoplan-modules` to `false`.

This is useful when running multiple Atlantis servers against a single repository.

### `--fail-on-pre-workflow-hook-error`
```bash
atlantis server --fail-on-pre-workflow-hook-error
# or
ATLANTIS_FAIL_ON_PRE_WORKFLOW_HOOK_ERROR=true
```

Fail and do not run the requested Atlantis command if any of the pre workflow hooks error.

### `--hide-unchanged-plan-comments`
```bash
atlantis server --hide-unchanged-plan-comments
Expand Down
35 changes: 26 additions & 9 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,15 @@ type DefaultCommandRunner struct {
GithubPullGetter GithubPullGetter
AzureDevopsPullGetter AzureDevopsPullGetter
GitlabMergeRequestGetter GitlabMergeRequestGetter
DisableAutoplan bool
EventParser EventParsing
Logger logging.SimpleLogging
GlobalCfg valid.GlobalCfg
StatsScope tally.Scope
// AllowForkPRs controls whether we operate on pull requests from forks.
// User config option: Disables autoplan when a pull request is opened or updated.
DisableAutoplan bool
EventParser EventParsing
// User config option: Fail and do not run the Atlantis command request if any of the pre workflow hooks error
FailOnPreWorkflowHookError bool
Logger logging.SimpleLogging
GlobalCfg valid.GlobalCfg
StatsScope tally.Scope
// User config option: controls whether to operate on pull requests from forks.
AllowForkPRs bool
// ParallelPoolSize controls the size of the wait group used to run
// parallel plans and applies (if enabled).
Expand All @@ -110,7 +113,7 @@ type DefaultCommandRunner struct {
// this in our error message back to the user on a forked PR so they know
// how to enable this functionality.
AllowForkPRsFlag string
// SilenceForkPRErrors controls whether to comment on Fork PRs when AllowForkPRs = False
// User config option: controls whether to comment on Fork PRs when AllowForkPRs = False
SilenceForkPRErrors bool
// SilenceForkPRErrorsFlag is the name of the flag that controls fork PR's. We use
// this in our error message back to the user on a forked PR so they know
Expand Down Expand Up @@ -169,7 +172,14 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
err = c.PreWorkflowHooksCommandRunner.RunPreHooks(ctx, cmd)

if err != nil {
ctx.Log.Err("Error running pre-workflow hooks %s. Proceeding with %s command.", err, command.Plan)
ctx.Log.Err("Error running pre-workflow hooks %s.", err)

if c.FailOnPreWorkflowHookError {
ctx.Log.Err("'fail-on-pre-workflow-hook-error' set, so not running %s command.", command.Plan)
return
}

ctx.Log.Err("'fail-on-pre-workflow-hook-error' not set so running %s command.", command.Plan)
}

autoPlanRunner := buildCommentCommandRunner(c, command.Plan)
Expand Down Expand Up @@ -293,7 +303,14 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
err = c.PreWorkflowHooksCommandRunner.RunPreHooks(ctx, cmd)

if err != nil {
ctx.Log.Err("Error running pre-workflow hooks %s. Proceeding with %s command.", err, cmd.Name.String())
ctx.Log.Err("Error running pre-workflow hooks %s.", err)

if c.FailOnPreWorkflowHookError {
ctx.Log.Err("'fail-on-pre-workflow-hook-error' set, so not running %s command.", cmd.Name.String())
return
}

ctx.Log.Err("'fail-on-pre-workflow-hook-error' not set so running %s command.", cmd.Name.String())
}

cmdRunner := buildCommentCommandRunner(c, cmd.CommandName())
Expand Down
87 changes: 87 additions & 0 deletions server/events/command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ func setup(t *testing.T, options ...func(testConfig *TestConfig)) *vcsmocks.Mock
VCSClient: vcsClient,
CommentCommandRunnerByCmd: commentCommandRunnerByCmd,
EventParser: eventParsing,
FailOnPreWorkflowHookError: false,
GithubPullGetter: githubGetter,
GitlabMergeRequestGetter: gitlabGetter,
AzureDevopsPullGetter: azuredevopsGetter,
Expand Down Expand Up @@ -646,6 +647,92 @@ func TestRunAutoplanCommand_DeletePlans(t *testing.T) {
lockingLocker.VerifyWasCalledOnce().UnlockByPull(testdata.Pull.BaseRepo.FullName, testdata.Pull.Num)
}

func TestRunAutoplanCommand_FailedPreWorkflowHook_FailOnPreWorkflowHookError_False(t *testing.T) {
setup(t)
tmp := t.TempDir()
boltDB, err := db.New(tmp)
Ok(t, err)
dbUpdater.Backend = boltDB
applyCommandRunner.Backend = boltDB

When(projectCommandBuilder.BuildAutoplanCommands(Any[*command.Context]())).
ThenReturn([]command.ProjectContext{
{
CommandName: command.Plan,
},
}, nil)
When(projectCommandRunner.Plan(Any[command.ProjectContext]())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}})
When(workingDir.GetPullDir(Any[models.Repo](), Any[models.PullRequest]())).ThenReturn(tmp, nil)
When(preWorkflowHooksCommandRunner.RunPreHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(errors.New("err"))
testdata.Pull.BaseRepo = testdata.GithubRepo
ch.FailOnPreWorkflowHookError = false
ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, testdata.Pull, testdata.User)
pendingPlanFinder.VerifyWasCalledOnce().DeletePlans(tmp)
lockingLocker.VerifyWasCalledOnce().UnlockByPull(testdata.Pull.BaseRepo.FullName, testdata.Pull.Num)
}

func TestRunAutoplanCommand_FailedPreWorkflowHook_FailOnPreWorkflowHookError_True(t *testing.T) {
setup(t)
tmp := t.TempDir()
boltDB, err := db.New(tmp)
Ok(t, err)
dbUpdater.Backend = boltDB
applyCommandRunner.Backend = boltDB

When(projectCommandBuilder.BuildAutoplanCommands(Any[*command.Context]())).
ThenReturn([]command.ProjectContext{
{
CommandName: command.Plan,
},
}, nil)
When(preWorkflowHooksCommandRunner.RunPreHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(errors.New("err"))
testdata.Pull.BaseRepo = testdata.GithubRepo
ch.FailOnPreWorkflowHookError = true
ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, testdata.Pull, testdata.User)
pendingPlanFinder.VerifyWasCalled(Never()).DeletePlans(Any[string]())
lockingLocker.VerifyWasCalled(Never()).UnlockByPull(Any[string](), Any[int]())
}

func TestRunCommentCommand_FailedPreWorkflowHook_FailOnPreWorkflowHookError_False(t *testing.T) {
setup(t)
tmp := t.TempDir()
boltDB, err := db.New(tmp)
Ok(t, err)
dbUpdater.Backend = boltDB
applyCommandRunner.Backend = boltDB

When(projectCommandRunner.Plan(Any[command.ProjectContext]())).ThenReturn(command.ProjectResult{PlanSuccess: &models.PlanSuccess{}})
When(workingDir.GetPullDir(Any[models.Repo](), Any[models.PullRequest]())).ThenReturn(tmp, nil)
pull := &github.PullRequest{State: github.String("open")}
modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, State: models.OpenPullState, Num: testdata.Pull.Num}
When(githubGetter.GetPullRequest(testdata.GithubRepo, testdata.Pull.Num)).ThenReturn(pull, nil)
When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, modelPull.BaseRepo, testdata.GithubRepo, nil)
When(preWorkflowHooksCommandRunner.RunPreHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(errors.New("err"))
testdata.Pull.BaseRepo = testdata.GithubRepo
ch.FailOnPreWorkflowHookError = false
ch.RunCommentCommand(testdata.GithubRepo, nil, nil, testdata.User, testdata.Pull.Num, &events.CommentCommand{Name: command.Plan})
pendingPlanFinder.VerifyWasCalledOnce().DeletePlans(tmp)
lockingLocker.VerifyWasCalledOnce().UnlockByPull(testdata.Pull.BaseRepo.FullName, testdata.Pull.Num)
}

func TestRunCommentCommand_FailedPreWorkflowHook_FailOnPreWorkflowHookError_True(t *testing.T) {
setup(t)
tmp := t.TempDir()
boltDB, err := db.New(tmp)
Ok(t, err)
dbUpdater.Backend = boltDB
applyCommandRunner.Backend = boltDB
autoMerger.GlobalAutomerge = true
defer func() { autoMerger.GlobalAutomerge = false }()

When(preWorkflowHooksCommandRunner.RunPreHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(errors.New("err"))
testdata.Pull.BaseRepo = testdata.GithubRepo
ch.FailOnPreWorkflowHookError = true
ch.RunCommentCommand(testdata.GithubRepo, nil, nil, testdata.User, testdata.Pull.Num, &events.CommentCommand{Name: command.Plan})
pendingPlanFinder.VerifyWasCalled(Never()).DeletePlans(Any[string]())
lockingLocker.VerifyWasCalled(Never()).UnlockByPull(Any[string](), Any[int]())
}

func TestRunGenericPlanCommand_DeletePlans(t *testing.T) {
setup(t)
tmp := t.TempDir()
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
AzureDevopsPullGetter: azuredevopsClient,
CommentCommandRunnerByCmd: commentCommandRunnerByCmd,
EventParser: eventParser,
FailOnPreWorkflowHookError: userConfig.FailOnPreWorkflowHookError,
Logger: logger,
GlobalCfg: globalCfg,
StatsScope: statsScope.SubScope("cmd"),
Expand Down
64 changes: 33 additions & 31 deletions server/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,39 @@ import (
// The mapstructure tags correspond to flags in cmd/server.go and are used when
// the config is parsed from a YAML file.
type UserConfig struct {
AllowForkPRs bool `mapstructure:"allow-fork-prs"`
AllowRepoConfig bool `mapstructure:"allow-repo-config"`
AllowCommands string `mapstructure:"allow-commands"`
AtlantisURL string `mapstructure:"atlantis-url"`
Automerge bool `mapstructure:"automerge"`
AutoplanFileList string `mapstructure:"autoplan-file-list"`
AutoplanModules bool `mapstructure:"autoplan-modules"`
AutoplanModulesFromProjects string `mapstructure:"autoplan-modules-from-projects"`
AzureDevopsToken string `mapstructure:"azuredevops-token"`
AzureDevopsUser string `mapstructure:"azuredevops-user"`
AzureDevopsWebhookPassword string `mapstructure:"azuredevops-webhook-password"`
AzureDevopsWebhookUser string `mapstructure:"azuredevops-webhook-user"`
AzureDevOpsHostname string `mapstructure:"azuredevops-hostname"`
BitbucketBaseURL string `mapstructure:"bitbucket-base-url"`
BitbucketToken string `mapstructure:"bitbucket-token"`
BitbucketUser string `mapstructure:"bitbucket-user"`
BitbucketWebhookSecret string `mapstructure:"bitbucket-webhook-secret"`
CheckoutDepth int `mapstructure:"checkout-depth"`
CheckoutStrategy string `mapstructure:"checkout-strategy"`
DataDir string `mapstructure:"data-dir"`
DisableApplyAll bool `mapstructure:"disable-apply-all"`
DisableApply bool `mapstructure:"disable-apply"`
DisableAutoplan bool `mapstructure:"disable-autoplan"`
DisableMarkdownFolding bool `mapstructure:"disable-markdown-folding"`
DisableRepoLocking bool `mapstructure:"disable-repo-locking"`
DiscardApprovalOnPlanFlag bool `mapstructure:"discard-approval-on-plan"`
EmojiReaction string `mapstructure:"emoji-reaction"`
EnablePolicyChecksFlag bool `mapstructure:"enable-policy-checks"`
EnableRegExpCmd bool `mapstructure:"enable-regexp-cmd"`
EnableDiffMarkdownFormat bool `mapstructure:"enable-diff-markdown-format"`
ExecutableName string `mapstructure:"executable-name"`
AllowForkPRs bool `mapstructure:"allow-fork-prs"`
AllowRepoConfig bool `mapstructure:"allow-repo-config"`
AllowCommands string `mapstructure:"allow-commands"`
AtlantisURL string `mapstructure:"atlantis-url"`
Automerge bool `mapstructure:"automerge"`
AutoplanFileList string `mapstructure:"autoplan-file-list"`
AutoplanModules bool `mapstructure:"autoplan-modules"`
AutoplanModulesFromProjects string `mapstructure:"autoplan-modules-from-projects"`
AzureDevopsToken string `mapstructure:"azuredevops-token"`
AzureDevopsUser string `mapstructure:"azuredevops-user"`
AzureDevopsWebhookPassword string `mapstructure:"azuredevops-webhook-password"`
AzureDevopsWebhookUser string `mapstructure:"azuredevops-webhook-user"`
AzureDevOpsHostname string `mapstructure:"azuredevops-hostname"`
BitbucketBaseURL string `mapstructure:"bitbucket-base-url"`
BitbucketToken string `mapstructure:"bitbucket-token"`
BitbucketUser string `mapstructure:"bitbucket-user"`
BitbucketWebhookSecret string `mapstructure:"bitbucket-webhook-secret"`
CheckoutDepth int `mapstructure:"checkout-depth"`
CheckoutStrategy string `mapstructure:"checkout-strategy"`
DataDir string `mapstructure:"data-dir"`
DisableApplyAll bool `mapstructure:"disable-apply-all"`
DisableApply bool `mapstructure:"disable-apply"`
DisableAutoplan bool `mapstructure:"disable-autoplan"`
DisableMarkdownFolding bool `mapstructure:"disable-markdown-folding"`
DisableRepoLocking bool `mapstructure:"disable-repo-locking"`
DiscardApprovalOnPlanFlag bool `mapstructure:"discard-approval-on-plan"`
EmojiReaction string `mapstructure:"emoji-reaction"`
EnablePolicyChecksFlag bool `mapstructure:"enable-policy-checks"`
EnableRegExpCmd bool `mapstructure:"enable-regexp-cmd"`
EnableDiffMarkdownFormat bool `mapstructure:"enable-diff-markdown-format"`
ExecutableName string `mapstructure:"executable-name"`
// Fail and do not run the Atlantis command request if any of the pre workflow hooks error.
FailOnPreWorkflowHookError bool `mapstructure:"fail-on-pre-workflow-hook-error"`
HideUnchangedPlanComments bool `mapstructure:"hide-unchanged-plan-comments"`
GithubAllowMergeableBypassApply bool `mapstructure:"gh-allow-mergeable-bypass-apply"`
GithubHostname string `mapstructure:"gh-hostname"`
Expand Down

0 comments on commit dc64493

Please sign in to comment.