Skip to content

Commit

Permalink
Add an extra flag to disable automerge with comment apply (#1533)
Browse files Browse the repository at this point in the history
```
atlantis apply --auto-merge-disabled
```

Issue: #1245
  • Loading branch information
spirosoik authored Jun 22, 2021
1 parent aef146f commit e48f803
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 53 deletions.
2 changes: 1 addition & 1 deletion server/events/apply_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func (a *ApplyCommandRunner) Run(ctx *CommandContext, cmd *CommentCommand) {

a.updateCommitStatus(ctx, pullStatus)

if a.autoMerger.automergeEnabled(projectCmds) {
if a.autoMerger.automergeEnabled(projectCmds) && !cmd.AutoMergeDisabled {
a.autoMerger.automerge(ctx, pullStatus, a.autoMerger.deleteSourceBranchOnMergeEnabled(projectCmds))
}
}
Expand Down
51 changes: 29 additions & 22 deletions server/events/comment_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ import (
)

const (
workspaceFlagLong = "workspace"
workspaceFlagShort = "w"
dirFlagLong = "dir"
dirFlagShort = "d"
projectFlagLong = "project"
projectFlagShort = "p"
verboseFlagLong = "verbose"
verboseFlagShort = ""
atlantisExecutable = "atlantis"
workspaceFlagLong = "workspace"
workspaceFlagShort = "w"
dirFlagLong = "dir"
dirFlagShort = "d"
projectFlagLong = "project"
projectFlagShort = "p"
autoMergeDisabledFlagLong = "auto-merge-disabled"
autoMergeDisabledFlagShort = ""
verboseFlagLong = "verbose"
verboseFlagShort = ""
atlantisExecutable = "atlantis"
)

// multiLineRegex is used to ignore multi-line comments since those aren't valid
Expand All @@ -64,7 +66,7 @@ type CommentBuilder interface {
// BuildPlanComment builds a plan comment for the specified args.
BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string
// BuildApplyComment builds an apply comment for the specified args.
BuildApplyComment(repoRelDir string, workspace string, project string) string
BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string
}

// CommentParser implements CommentParsing
Expand Down Expand Up @@ -170,7 +172,7 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
var workspace string
var dir string
var project string
var verbose bool
var verbose, autoMergeDisabled bool
var flagSet *pflag.FlagSet
var name models.CommandName

Expand All @@ -191,6 +193,7 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
flagSet.StringVarP(&workspace, workspaceFlagLong, workspaceFlagShort, "", "Apply the plan for this Terraform workspace.")
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Apply the plan for this directory, relative to root of repo, ex. 'child/dir'.")
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Apply the plan for this project. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", yaml.AtlantisYAMLFilename))
flagSet.BoolVarP(&autoMergeDisabled, autoMergeDisabledFlagLong, autoMergeDisabledFlagShort, false, "Disable automerge after apply.")
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
case models.ApprovePoliciesCommand.String():
name = models.ApprovePoliciesCommand
Expand Down Expand Up @@ -256,13 +259,13 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
}

return CommentParseResult{
Command: NewCommentCommand(dir, extraArgs, name, verbose, workspace, project),
Command: NewCommentCommand(dir, extraArgs, name, verbose, autoMergeDisabled, workspace, project),
}
}

// BuildPlanComment builds a plan comment for the specified args.
func (e *CommentParser) BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string {
flags := e.buildFlags(repoRelDir, workspace, project)
flags := e.buildFlags(repoRelDir, workspace, project, false)
commentFlags := ""
if len(commentArgs) > 0 {
var flagsWithoutQuotes []string
Expand All @@ -277,36 +280,40 @@ func (e *CommentParser) BuildPlanComment(repoRelDir string, workspace string, pr
}

// BuildApplyComment builds an apply comment for the specified args.
func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, project string) string {
flags := e.buildFlags(repoRelDir, workspace, project)
func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string {
flags := e.buildFlags(repoRelDir, workspace, project, autoMergeDisabled)
return fmt.Sprintf("%s %s%s", atlantisExecutable, models.ApplyCommand.String(), flags)
}

func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string) string {
func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string {
// Add quotes if dir has spaces.
if strings.Contains(repoRelDir, " ") {
repoRelDir = fmt.Sprintf("%q", repoRelDir)
}

var flags string
switch {
// If project is specified we can just use its name.
case project != "":
return fmt.Sprintf(" -%s %s", projectFlagShort, project)
flags = fmt.Sprintf(" -%s %s", projectFlagShort, project)
case repoRelDir == DefaultRepoRelDir && workspace == DefaultWorkspace:
// If it's the root and default workspace then we just need to specify one
// of the flags and the other will get defaulted.
return fmt.Sprintf(" -%s %s", dirFlagShort, DefaultRepoRelDir)
flags = fmt.Sprintf(" -%s %s", dirFlagShort, DefaultRepoRelDir)
case repoRelDir == DefaultRepoRelDir:
// If dir is the default then we just need to specify workspace.
return fmt.Sprintf(" -%s %s", workspaceFlagShort, workspace)
flags = fmt.Sprintf(" -%s %s", workspaceFlagShort, workspace)
case workspace == DefaultWorkspace:
// If workspace is the default then we just need to specify the dir.

return fmt.Sprintf(" -%s %s", dirFlagShort, repoRelDir)
flags = fmt.Sprintf(" -%s %s", dirFlagShort, repoRelDir)
default:
// Otherwise we have to specify both flags.
return fmt.Sprintf(" -%s %s -%s %s", dirFlagShort, repoRelDir, workspaceFlagShort, workspace)
flags = fmt.Sprintf(" -%s %s -%s %s", dirFlagShort, repoRelDir, workspaceFlagShort, workspace)
}
if autoMergeDisabled {
flags = fmt.Sprintf("%s --%s", flags, autoMergeDisabledFlagLong)
}
return flags
}

func (e *CommentParser) validateDir(dir string) (string, error) {
Expand Down
39 changes: 25 additions & 14 deletions server/events/comment_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,12 +578,13 @@ func TestParse_Parsing(t *testing.T) {

func TestBuildPlanApplyComment(t *testing.T) {
cases := []struct {
repoRelDir string
workspace string
project string
commentArgs []string
expPlanFlags string
expApplyFlags string
repoRelDir string
workspace string
project string
autoMergeDisabled bool
commentArgs []string
expPlanFlags string
expApplyFlags string
}{
{
repoRelDir: ".",
Expand Down Expand Up @@ -656,6 +657,15 @@ func TestBuildPlanApplyComment(t *testing.T) {
expPlanFlags: "-d \"dir with spaces\"",
expApplyFlags: "-d \"dir with spaces\"",
},
{
repoRelDir: "dir",
workspace: "workspace",
project: "",
autoMergeDisabled: true,
commentArgs: []string{`"arg1"`, `"arg2"`, `arg3`},
expPlanFlags: "-d dir -w workspace -- arg1 arg2 arg3",
expApplyFlags: "-d dir -w workspace --auto-merge-disabled",
},
}

for _, c := range cases {
Expand All @@ -666,7 +676,7 @@ func TestBuildPlanApplyComment(t *testing.T) {
actComment := commentParser.BuildPlanComment(c.repoRelDir, c.workspace, c.project, c.commentArgs)
Equals(t, fmt.Sprintf("atlantis plan %s", c.expPlanFlags), actComment)
case models.ApplyCommand:
actComment := commentParser.BuildApplyComment(c.repoRelDir, c.workspace, c.project)
actComment := commentParser.BuildApplyComment(c.repoRelDir, c.workspace, c.project, c.autoMergeDisabled)
Equals(t, fmt.Sprintf("atlantis apply %s", c.expApplyFlags), actComment)
}
}
Expand Down Expand Up @@ -800,13 +810,14 @@ var PlanUsage = `Usage of plan:
`

var ApplyUsage = `Usage of apply:
-d, --dir string Apply the plan for this directory, relative to root of
repo, ex. 'child/dir'.
-p, --project string Apply the plan for this project. Refers to the name of
the project configured in atlantis.yaml. Cannot be used
at same time as workspace or dir flags.
--verbose Append Atlantis log to comment.
-w, --workspace string Apply the plan for this Terraform workspace.
--auto-merge-disabled Disable automerge after apply.
-d, --dir string Apply the plan for this directory, relative to root of
repo, ex. 'child/dir'.
-p, --project string Apply the plan for this project. Refers to the name of
the project configured in atlantis.yaml. Cannot be
used at same time as workspace or dir flags.
--verbose Append Atlantis log to comment.
-w, --workspace string Apply the plan for this Terraform workspace.
`

var ApprovePolicyUsage = `Usage of approve_policies:
Expand Down
17 changes: 10 additions & 7 deletions server/events/event_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ type CommentCommand struct {
Flags []string
// Name is the name of the command the comment specified.
Name models.CommandName
// AutoMergeDisabled is true if the command should not automerge after apply.
AutoMergeDisabled bool
// Verbose is true if the command should output verbosely.
Verbose bool
// Workspace is the name of the Terraform workspace to run the command in.
Expand Down Expand Up @@ -130,7 +132,7 @@ func (c CommentCommand) String() string {
}

// NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults.
func NewCommentCommand(repoRelDir string, flags []string, name models.CommandName, verbose bool, workspace string, project string) *CommentCommand {
func NewCommentCommand(repoRelDir string, flags []string, name models.CommandName, verbose, autoMergeDisabled bool, workspace string, project string) *CommentCommand {
// If repoRelDir was empty we want to keep it that way to indicate that it
// wasn't specified in the comment.
if repoRelDir != "" {
Expand All @@ -140,12 +142,13 @@ func NewCommentCommand(repoRelDir string, flags []string, name models.CommandNam
}
}
return &CommentCommand{
RepoRelDir: repoRelDir,
Flags: flags,
Name: name,
Verbose: verbose,
Workspace: workspace,
ProjectName: project,
RepoRelDir: repoRelDir,
Flags: flags,
Name: name,
Verbose: verbose,
Workspace: workspace,
AutoMergeDisabled: autoMergeDisabled,
ProjectName: project,
}
}

Expand Down
6 changes: 3 additions & 3 deletions server/events/event_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,14 +650,14 @@ func TestNewCommand_CleansDir(t *testing.T) {

for _, c := range cases {
t.Run(c.RepoRelDir, func(t *testing.T) {
cmd := events.NewCommentCommand(c.RepoRelDir, nil, models.PlanCommand, false, "workspace", "")
cmd := events.NewCommentCommand(c.RepoRelDir, nil, models.PlanCommand, false, false, "workspace", "")
Equals(t, c.ExpDir, cmd.RepoRelDir)
})
}
}

func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) {
cmd := events.NewCommentCommand("", nil, models.PlanCommand, false, "", "")
cmd := events.NewCommentCommand("", nil, models.PlanCommand, false, false, "", "")
Equals(t, events.CommentCommand{
RepoRelDir: "",
Flags: nil,
Expand All @@ -669,7 +669,7 @@ func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) {
}

func TestNewCommand_AllFieldsSet(t *testing.T) {
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, models.PlanCommand, true, "workspace", "project")
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, models.PlanCommand, true, false, "workspace", "project")
Equals(t, events.CommentCommand{
Workspace: "workspace",
RepoRelDir: "dir",
Expand Down
4 changes: 2 additions & 2 deletions server/events/mocks/mock_comment_building.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions server/events/project_command_context_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
projectCmds = append(projectCmds, newProjectCommandContext(
ctx,
cmdName,
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name),
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled),
cb.CommentBuilder.BuildPlanComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, commentFlags),
prjCfg,
steps,
Expand Down Expand Up @@ -118,7 +118,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
projectCmds = append(projectCmds, newProjectCommandContext(
ctx,
models.PolicyCheckCommand,
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name),
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled),
cb.CommentBuilder.BuildPlanComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, commentFlags),
prjCfg,
steps,
Expand Down
4 changes: 2 additions & 2 deletions server/events/project_command_context_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {

t.Run("with project name defined", func(t *testing.T) {
When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, projName, []string{})).ThenReturn(expectedPlanCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, projName)).ThenReturn(expectedApplyCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, projName, false)).ThenReturn(expectedApplyCmt)

pullStatus.Projects = []models.ProjectStatus{
{
Expand All @@ -65,7 +65,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
t.Run("with no project name defined", func(t *testing.T) {
projCfg.Name = ""
When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, "", []string{})).ThenReturn(expectedPlanCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "")).ThenReturn(expectedApplyCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false)).ThenReturn(expectedApplyCmt)
pullStatus.Projects = []models.ProjectStatus{
{
Status: models.ErroredPlanStatus,
Expand Down
1 change: 1 addition & 0 deletions server/events/yaml/valid/global_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type MergedProjectCfg struct {
Workspace string
Name string
AutoplanEnabled bool
AutoMergeDisabled bool
TerraformVersion *version.Version
RepoCfgVersion int
PolicySets PolicySets
Expand Down

0 comments on commit e48f803

Please sign in to comment.