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

Add an extra flag to disable automerge with comment apply #1533

Merged
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
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 @@ -61,6 +61,7 @@ type MergedProjectCfg struct {
Workspace string
Name string
AutoplanEnabled bool
AutoMergeDisabled bool
TerraformVersion *version.Version
RepoCfgVersion int
PolicySets PolicySets
Expand Down