Skip to content

Commit c9afce0

Browse files
msarvarGerald Barker
and
Gerald Barker
committed
Add Option to globally disable apply (#1230)
* Add Option to globally disable apply Co-authored-by: Gerald Barker <[email protected]>
1 parent dbebea6 commit c9afce0

13 files changed

+321
-18
lines changed

cmd/server.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const (
5454
DataDirFlag = "data-dir"
5555
DefaultTFVersionFlag = "default-tf-version"
5656
DisableApplyAllFlag = "disable-apply-all"
57+
DisableApplyFlag = "disable-apply"
5758
DisableAutoplanFlag = "disable-autoplan"
5859
DisableMarkdownFoldingFlag = "disable-markdown-folding"
5960
EnablePolicyChecksFlag = "enable-policy-checks"
@@ -286,7 +287,11 @@ var boolFlags = map[string]boolFlag{
286287
defaultValue: false,
287288
},
288289
DisableApplyAllFlag: {
289-
description: "Disable \"atlantis apply\" command so a specific project/workspace/directory has to be specified for applies.",
290+
description: "Disable \"atlantis apply\" command without any flags (i.e. apply all). A specific project/workspace/directory has to be specified for applies.",
291+
defaultValue: false,
292+
},
293+
DisableApplyFlag: {
294+
description: "Disable all \"atlantis apply\" command regardless of which flags are passed with it.",
290295
defaultValue: false,
291296
},
292297
DisableAutoplanFlag: {

cmd/server_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var testFlags = map[string]interface{}{
6565
DataDirFlag: "/path",
6666
DefaultTFVersionFlag: "v0.11.0",
6767
DisableApplyAllFlag: true,
68+
DisableApplyFlag: true,
6869
DisableMarkdownFoldingFlag: true,
6970
GHHostnameFlag: "ghhostname",
7071
GHTokenFlag: "token",

runatlantis.io/docs/server-configuration.md

+6
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ Values are chosen in this order:
201201
Terraform version to default to. Will download to `<data-dir>/bin/terraform<version>`
202202
if not in `PATH`. See [Terraform Versions](terraform-versions.html) for more details.
203203

204+
* ### `--disable-apply`
205+
```bash
206+
atlantis server --disable-apply
207+
```
208+
Disable all \"atlantis apply\" commands, regardless of which flags are passed with it.
209+
204210
* ### `--disable-apply-all`
205211
```bash
206212
atlantis server --disable-apply-all

server/events/apply_command_runner.go

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
func NewApplyCommandRunner(
1010
vcsClient vcs.Client,
1111
disableApplyAll bool,
12+
disableApply bool,
1213
commitStatusUpdater CommitStatusUpdater,
1314
prjCommandBuilder ProjectApplyCommandBuilder,
1415
prjCmdRunner ProjectApplyCommandRunner,
@@ -20,6 +21,7 @@ func NewApplyCommandRunner(
2021
return &ApplyCommandRunner{
2122
vcsClient: vcsClient,
2223
DisableApplyAll: disableApplyAll,
24+
DisableApply: disableApply,
2325
commitStatusUpdater: commitStatusUpdater,
2426
prjCmdBuilder: prjCommandBuilder,
2527
prjCmdRunner: prjCmdRunner,
@@ -32,6 +34,7 @@ func NewApplyCommandRunner(
3234

3335
type ApplyCommandRunner struct {
3436
DisableApplyAll bool
37+
DisableApply bool
3538
DB *db.BoltDB
3639
vcsClient vcs.Client
3740
commitStatusUpdater CommitStatusUpdater
@@ -47,6 +50,14 @@ func (a *ApplyCommandRunner) Run(ctx *CommandContext, cmd *CommentCommand) {
4750
baseRepo := ctx.Pull.BaseRepo
4851
pull := ctx.Pull
4952

53+
if a.DisableApply {
54+
ctx.Log.Info("ignoring apply command since apply disabled globally")
55+
if err := a.vcsClient.CreateComment(baseRepo, pull.Num, applyDisabledComment, models.ApplyCommand.String()); err != nil {
56+
ctx.Log.Err("unable to comment on pull request: %s", err)
57+
}
58+
return
59+
}
60+
5061
if a.DisableApplyAll && !cmd.IsForSpecificProject() {
5162
ctx.Log.Info("ignoring apply command without flags since apply all is disabled")
5263
if err := a.vcsClient.CreateComment(baseRepo, pull.Num, applyAllDisabledComment, models.ApplyCommand.String()); err != nil {
@@ -166,3 +177,6 @@ func (a *ApplyCommandRunner) anyFailedPolicyChecks(pull models.PullRequest) bool
166177
// are disabled and an apply all command is issued.
167178
var applyAllDisabledComment = "**Error:** Running `atlantis apply` without flags is disabled." +
168179
" You must specify which project to apply via the `-d <dir>`, `-w <workspace>` or `-p <project name>` flags."
180+
181+
// applyDisabledComment is posted when apply commands are disabled globally and an apply command is issued.
182+
var applyDisabledComment = "**Error:** Running `atlantis apply` is disabled."

server/events/command_runner_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,16 @@ func TestRunCommentCommand_DisableApplyAllDisabled(t *testing.T) {
290290
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "**Error:** Running `atlantis apply` without flags is disabled. You must specify which project to apply via the `-d <dir>`, `-w <workspace>` or `-p <project name>` flags.", "apply")
291291
}
292292

293+
func TestRunCommentCommand_ApplyDisabled(t *testing.T) {
294+
t.Log("if \"atlantis apply\" is run and this is disabled globally atlantis should" +
295+
" comment saying that this is not allowed")
296+
vcsClient := setup(t)
297+
ch.DisableApply = true
298+
modelPull := models.PullRequest{State: models.OpenPullState}
299+
ch.RunCommentCommand(fixtures.GithubRepo, nil, nil, fixtures.User, modelPull.Num, &events.CommentCommand{Name: models.ApplyCommand})
300+
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "**Error:** Running `atlantis apply` is disabled.", "apply")
301+
}
302+
293303
func TestRunCommentCommand_DisableDisableAutoplan(t *testing.T) {
294304
t.Log("if \"DisableAutoplan is true\" are disabled and we are silencing return and do not comment with error")
295305
setup(t)

server/events/comment_parser.go

+28-10
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@
1414
package events
1515

1616
import (
17+
"bytes"
1718
"fmt"
19+
"github.com/flynn-archive/go-shlex"
20+
"github.com/runatlantis/atlantis/server/events/models"
21+
"github.com/runatlantis/atlantis/server/events/yaml"
22+
"github.com/spf13/pflag"
1823
"io/ioutil"
1924
"net/url"
2025
"path/filepath"
2126
"regexp"
2227
"strings"
23-
24-
"github.com/flynn-archive/go-shlex"
25-
"github.com/runatlantis/atlantis/server/events/models"
26-
"github.com/runatlantis/atlantis/server/events/yaml"
27-
"github.com/spf13/pflag"
28+
"text/template"
2829
)
2930

3031
const (
@@ -69,6 +70,7 @@ type CommentParser struct {
6970
GitlabUser string
7071
BitbucketUser string
7172
AzureDevopsUser string
73+
ApplyDisabled bool
7274
}
7375

7476
// CommentParseResult describes the result of parsing a comment as a command.
@@ -148,13 +150,13 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
148150
// If they've just typed the name of the executable then give them the help
149151
// output.
150152
if len(args) == 1 {
151-
return CommentParseResult{CommentResponse: HelpComment}
153+
return CommentParseResult{CommentResponse: e.HelpComment(e.ApplyDisabled)}
152154
}
153155
command := args[1]
154156

155157
// Help output.
156158
if e.stringInSlice(command, []string{"help", "-h", "--help"}) {
157-
return CommentParseResult{CommentResponse: HelpComment}
159+
return CommentParseResult{CommentResponse: e.HelpComment(e.ApplyDisabled)}
158160
}
159161

160162
// Need to have a plan, apply, approve_policy or unlock at this point.
@@ -335,9 +337,21 @@ func (e *CommentParser) errMarkdown(errMsg string, command string, flagSet *pfla
335337
return fmt.Sprintf("```\nError: %s.\nUsage of %s:\n%s```", errMsg, command, flagSet.FlagUsagesWrapped(usagesCols))
336338
}
337339

338-
// HelpComment is the comment we add to the pull request when someone runs
339-
// `atlantis help`.
340-
var HelpComment = "```cmake\n" +
340+
func (e *CommentParser) HelpComment(applyDisabled bool) string {
341+
buf := &bytes.Buffer{}
342+
var tmpl = template.Must(template.New("").Parse(helpCommentTemplate))
343+
if err := tmpl.Execute(buf, struct {
344+
ApplyDisabled bool
345+
}{
346+
ApplyDisabled: applyDisabled,
347+
}); err != nil {
348+
return fmt.Sprintf("Failed to render template, this is a bug: %v", err)
349+
}
350+
return buf.String()
351+
352+
}
353+
354+
var helpCommentTemplate = "```cmake\n" +
341355
`atlantis
342356
Terraform Pull Request Automation
343357
@@ -347,18 +361,22 @@ Usage:
347361
Examples:
348362
# run plan in the root directory passing the -target flag to terraform
349363
atlantis plan -d . -- -target=resource
364+
{{- if not .ApplyDisabled }}
350365
351366
# apply all unapplied plans from this pull request
352367
atlantis apply
353368
354369
# apply the plan for the root directory and staging workspace
355370
atlantis apply -d . -w staging
371+
{{- end }}
356372
357373
Commands:
358374
plan Runs 'terraform plan' for the changes in this pull request.
359375
To plan a specific project, use the -d, -w and -p flags.
376+
{{- if not .ApplyDisabled }}
360377
apply Runs 'terraform apply' on all unapplied plans from this pull request.
361378
To only apply a specific plan, use the -d, -w and -p flags.
379+
{{- end }}
362380
unlock Removes all atlantis locks and discards all plans for this PR.
363381
To unlock a specific plan you can use the Atlantis UI.
364382
help View help.

server/events/comment_parser_test.go

+94-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,25 @@ func TestParse_HelpResponse(t *testing.T) {
5656
}
5757
for _, c := range helpComments {
5858
r := commentParser.Parse(c, models.Github)
59-
Equals(t, events.HelpComment, r.CommentResponse)
59+
Equals(t, commentParser.HelpComment(false), r.CommentResponse)
60+
}
61+
}
62+
63+
func TestParse_HelpResponseWithApplyDisabled(t *testing.T) {
64+
helpComments := []string{
65+
"run",
66+
"atlantis",
67+
"@github-user",
68+
"atlantis help",
69+
"atlantis --help",
70+
"atlantis -h",
71+
"atlantis help something else",
72+
"atlantis help plan",
73+
}
74+
for _, c := range helpComments {
75+
commentParser.ApplyDisabled = true
76+
r := commentParser.Parse(c, models.Github)
77+
Equals(t, commentParser.HelpComment(true), r.CommentResponse)
6078
}
6179
}
6280

@@ -656,6 +674,80 @@ func TestBuildPlanApplyComment(t *testing.T) {
656674
}
657675
}
658676

677+
func TestCommentParser_HelpComment(t *testing.T) {
678+
cases := []struct {
679+
applyDisabled bool
680+
expectResult string
681+
}{
682+
{
683+
applyDisabled: false,
684+
expectResult: "```cmake\n" +
685+
`atlantis
686+
Terraform Pull Request Automation
687+
688+
Usage:
689+
atlantis <command> [options] -- [terraform options]
690+
691+
Examples:
692+
# run plan in the root directory passing the -target flag to terraform
693+
atlantis plan -d . -- -target=resource
694+
695+
# apply all unapplied plans from this pull request
696+
atlantis apply
697+
698+
# apply the plan for the root directory and staging workspace
699+
atlantis apply -d . -w staging
700+
701+
Commands:
702+
plan Runs 'terraform plan' for the changes in this pull request.
703+
To plan a specific project, use the -d, -w and -p flags.
704+
apply Runs 'terraform apply' on all unapplied plans from this pull request.
705+
To only apply a specific plan, use the -d, -w and -p flags.
706+
unlock Removes all atlantis locks and discards all plans for this PR.
707+
To unlock a specific plan you can use the Atlantis UI.
708+
help View help.
709+
710+
Flags:
711+
-h, --help help for atlantis
712+
713+
Use "atlantis [command] --help" for more information about a command.` +
714+
"\n```",
715+
},
716+
{
717+
applyDisabled: true,
718+
expectResult: "```cmake\n" +
719+
`atlantis
720+
Terraform Pull Request Automation
721+
722+
Usage:
723+
atlantis <command> [options] -- [terraform options]
724+
725+
Examples:
726+
# run plan in the root directory passing the -target flag to terraform
727+
atlantis plan -d . -- -target=resource
728+
729+
Commands:
730+
plan Runs 'terraform plan' for the changes in this pull request.
731+
To plan a specific project, use the -d, -w and -p flags.
732+
unlock Removes all atlantis locks and discards all plans for this PR.
733+
To unlock a specific plan you can use the Atlantis UI.
734+
help View help.
735+
736+
Flags:
737+
-h, --help help for atlantis
738+
739+
Use "atlantis [command] --help" for more information about a command.` +
740+
"\n```",
741+
},
742+
}
743+
744+
for _, c := range cases {
745+
t.Run(fmt.Sprintf("ApplyDisabled: %v", c.applyDisabled), func(t *testing.T) {
746+
Equals(t, commentParser.HelpComment(c.applyDisabled), c.expectResult)
747+
})
748+
}
749+
}
750+
659751
func TestParse_VCSUsername(t *testing.T) {
660752
cp := events.CommentParser{
661753
GithubUser: "gh",
@@ -692,7 +784,7 @@ func TestParse_VCSUsername(t *testing.T) {
692784
for _, c := range cases {
693785
t.Run(c.vcs.String(), func(t *testing.T) {
694786
r := cp.Parse(fmt.Sprintf("@%s %s", c.user, "help"), c.vcs)
695-
Equals(t, events.HelpComment, r.CommentResponse)
787+
Equals(t, commentParser.HelpComment(false), r.CommentResponse)
696788
})
697789
}
698790
}

server/events/markdown_renderer.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type MarkdownRenderer struct {
4040
// If we're not configured with a GitLab client, this will be false.
4141
GitlabSupportsCommonMark bool
4242
DisableApplyAll bool
43+
DisableApply bool
4344
DisableMarkdownFolding bool
4445
}
4546

@@ -50,6 +51,7 @@ type commonData struct {
5051
Log string
5152
PlansDeleted bool
5253
DisableApplyAll bool
54+
DisableApply bool
5355
}
5456

5557
// errData is data about an error response.
@@ -73,6 +75,7 @@ type resultData struct {
7375
type planSuccessData struct {
7476
models.PlanSuccess
7577
PlanWasDeleted bool
78+
DisableApply bool
7679
}
7780

7881
type policyCheckSuccessData struct {
@@ -95,7 +98,8 @@ func (m *MarkdownRenderer) Render(res CommandResult, cmdName models.CommandName,
9598
Verbose: verbose,
9699
Log: log,
97100
PlansDeleted: res.PlansDeleted,
98-
DisableApplyAll: m.DisableApplyAll,
101+
DisableApplyAll: m.DisableApplyAll || m.DisableApply,
102+
DisableApply: m.DisableApply,
99103
}
100104
if res.Error != nil {
101105
return m.renderTemplate(unwrappedErrWithLogTmpl, errData{res.Error.Error(), common})
@@ -139,9 +143,9 @@ func (m *MarkdownRenderer) renderProjectResults(results []models.ProjectResult,
139143
})
140144
} else if result.PlanSuccess != nil {
141145
if m.shouldUseWrappedTmpl(vcsHost, result.PlanSuccess.TerraformOutput) {
142-
resultData.Rendered = m.renderTemplate(planSuccessWrappedTmpl, planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted})
146+
resultData.Rendered = m.renderTemplate(planSuccessWrappedTmpl, planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply})
143147
} else {
144-
resultData.Rendered = m.renderTemplate(planSuccessUnwrappedTmpl, planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted})
148+
resultData.Rendered = m.renderTemplate(planSuccessUnwrappedTmpl, planSuccessData{PlanSuccess: *result.PlanSuccess, PlanWasDeleted: common.PlansDeleted, DisableApply: common.DisableApply})
145149
}
146150
numPlanSuccesses++
147151
} else if result.PolicyCheckSuccess != nil {
@@ -300,8 +304,9 @@ var policyCheckNextSteps = "* :arrow_forward: To **apply** this plan, comment:\n
300304

301305
// planNextSteps are instructions appended after successful plans as to what
302306
// to do next.
303-
var planNextSteps = "{{ if .PlanWasDeleted }}This plan was not saved because one or more projects failed and automerge requires all plans pass.{{ else }}* :arrow_forward: To **apply** this plan, comment:\n" +
304-
" * `{{.ApplyCmd}}`\n" +
307+
var planNextSteps = "{{ if .PlanWasDeleted }}This plan was not saved because one or more projects failed and automerge requires all plans pass.{{ else }}" +
308+
"{{ if not .DisableApply }}* :arrow_forward: To **apply** this plan, comment:\n" +
309+
" * `{{.ApplyCmd}}`\n{{end}}" +
305310
"* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})\n" +
306311
"* :repeat: To **plan** this project again, comment:\n" +
307312
" * `{{.RePlanCmd}}`{{end}}"

0 commit comments

Comments
 (0)