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(automerge): implement --merge-method flag for apply command #4895

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
14 changes: 14 additions & 0 deletions runatlantis.io/docs/automerging.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ Automerging can be enabled either by:
If automerge is enabled, you can disable it for a single `atlantis apply`
command with the `--auto-merge-disabled` option.

## How to set merge method for automerge

If automerge is enabled, you can use `--merge-method` option
for `atlantis apply` command to specify which merge method use.

```shell
atlantis apply --merge-method squash
```

Implemented only for GitHub. You can choose one of them:
- merge
- rebase
- squash

## Requirements

### All Plans Must Succeed
Expand Down
1 change: 1 addition & 0 deletions runatlantis.io/docs/using-atlantis.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ atlantis apply -w staging
* `-p project` Apply the plan for this project. Refers to the name of the project configured in the repo's [`atlantis.yaml` file](repo-level-atlantis-yaml.md). Cannot be used at same time as `-d` or `-w`.
* `-w workspace` Apply the plan for this [Terraform workspace](https://developer.hashicorp.com/terraform/language/state/workspaces). Ignore this if Terraform workspaces are unused.
* `--auto-merge-disabled` Disable [automerge](automerging.md) for this apply command.
* `--merge-method method` Specify which [merge method](automerging.md#how-to-set-merge-method-for-automerge) use for apply command if [automerge](automerging.md) is enabled. Implemented only for GitHub.
* `--verbose` Append Atlantis log to comment.

### Additional Terraform flags
Expand Down
1 change: 1 addition & 0 deletions server/core/config/valid/global_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type MergedProjectCfg struct {
Name string
AutoplanEnabled bool
AutoMergeDisabled bool
MergeMethod string
TerraformVersion *version.Version
RepoCfgVersion int
PolicySets PolicySets
Expand Down
2 changes: 1 addition & 1 deletion server/events/apply_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (a *ApplyCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) {
a.updateCommitStatus(ctx, pullStatus)

if a.autoMerger.automergeEnabled(projectCmds) && !cmd.AutoMergeDisabled {
a.autoMerger.automerge(ctx, pullStatus, a.autoMerger.deleteSourceBranchOnMergeEnabled(projectCmds))
a.autoMerger.automerge(ctx, pullStatus, a.autoMerger.deleteSourceBranchOnMergeEnabled(projectCmds), cmd.MergeMethod)
}
}

Expand Down
3 changes: 2 additions & 1 deletion server/events/automerger.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type AutoMerger struct {
GlobalAutomerge bool
}

func (c *AutoMerger) automerge(ctx *command.Context, pullStatus models.PullStatus, deleteSourceBranchOnMerge bool) {
func (c *AutoMerger) automerge(ctx *command.Context, pullStatus models.PullStatus, deleteSourceBranchOnMerge bool, mergeMethod string) {
// We only automerge if all projects have been successfully applied.
for _, p := range pullStatus.Projects {
if p.Status != models.AppliedPlanStatus {
Expand All @@ -32,6 +32,7 @@ func (c *AutoMerger) automerge(ctx *command.Context, pullStatus models.PullStatu
ctx.Log.Info("automerging pull request")
var pullOptions models.PullRequestOptions
pullOptions.DeleteSourceBranchOnMerge = deleteSourceBranchOnMerge
pullOptions.MergeMethod = mergeMethod
err := c.VCSClient.MergePull(ctx.Log, ctx.Pull, pullOptions)

if err != nil {
Expand Down
36 changes: 28 additions & 8 deletions server/events/comment_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
policySetFlagShort = ""
autoMergeDisabledFlagLong = "auto-merge-disabled"
autoMergeDisabledFlagShort = ""
mergeMethodFlagLong = "merge-method"
mergeMethodFlagShort = ""
verboseFlagLong = "verbose"
verboseFlagShort = ""
clearPolicyApprovalFlagLong = "clear-policy-approval"
Expand Down Expand Up @@ -70,7 +72,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, autoMergeDisabled bool) string
BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool, mergeMethod string) string
// BuildApprovePoliciesComment builds an approve_policies comment for the specified args.
BuildApprovePoliciesComment(repoRelDir string, workspace string, project string) string
}
Expand Down Expand Up @@ -226,7 +228,9 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
var project string
var policySet string
var clearPolicyApproval bool
var verbose, autoMergeDisabled bool
var verbose bool
var autoMergeDisabled bool
var mergeMethod string
var flagSet *pflag.FlagSet
var name command.Name

Expand All @@ -248,6 +252,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Apply the plan for this directory, relative to root of repo, ex. 'child/dir'.")
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", "Apply the plan for this project. Refers to the name of the project configured in a repo config file. Cannot be used at same time as workspace or dir flags.")
flagSet.BoolVarP(&autoMergeDisabled, autoMergeDisabledFlagLong, autoMergeDisabledFlagShort, false, "Disable automerge after apply.")
flagSet.StringVarP(&mergeMethod, mergeMethodFlagLong, mergeMethodFlagShort, "", "Specifies merge method for the VCS if automerge is enabled.")
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
case command.ApprovePolicies.String():
name = command.ApprovePolicies
Expand Down Expand Up @@ -317,8 +322,20 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)}
}

if mergeMethod != "" {
if autoMergeDisabled {
err := fmt.Sprintf("cannot use --%s at same time with --%s", mergeMethodFlagLong, autoMergeDisabledFlagLong)
return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)}
}

if vcsHost != models.Github {
err := fmt.Sprintf("--%s not implemeted for %s", mergeMethodFlagLong, vcsHost.String())
return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)}
}
}

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

Expand Down Expand Up @@ -387,7 +404,7 @@ func (e *CommentParser) parseArgs(name command.Name, args []string, flagSet *pfl

// 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, false)
flags := e.buildFlags(repoRelDir, workspace, project, false, "")
commentFlags := ""
if len(commentArgs) > 0 {
var flagsWithoutQuotes []string
Expand All @@ -402,18 +419,18 @@ 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, autoMergeDisabled bool) string {
flags := e.buildFlags(repoRelDir, workspace, project, autoMergeDisabled)
func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool, mergeMethod string) string {
flags := e.buildFlags(repoRelDir, workspace, project, autoMergeDisabled, mergeMethod)
return fmt.Sprintf("%s %s%s", e.ExecutableName, command.Apply.String(), flags)
}

// BuildApprovePoliciesComment builds an apply comment for the specified args.
func (e *CommentParser) BuildApprovePoliciesComment(repoRelDir string, workspace string, project string) string {
flags := e.buildFlags(repoRelDir, workspace, project, false)
flags := e.buildFlags(repoRelDir, workspace, project, false, "")
return fmt.Sprintf("%s %s%s", e.ExecutableName, command.ApprovePolicies.String(), flags)
}

func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string {
func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string, autoMergeDisabled bool, mergeMethod string) string {
// Add quotes if dir has spaces.
if strings.Contains(repoRelDir, " ") {
repoRelDir = fmt.Sprintf("%q", repoRelDir)
Expand Down Expand Up @@ -441,6 +458,9 @@ func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project
if autoMergeDisabled {
flags = fmt.Sprintf("%s --%s", flags, autoMergeDisabledFlagLong)
}
if mergeMethod != "" {
flags = fmt.Sprintf("%s --%s %s", flags, mergeMethodFlagLong, mergeMethod)
}
return flags
}

Expand Down
14 changes: 13 additions & 1 deletion server/events/comment_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ func TestBuildPlanApplyVersionComment(t *testing.T) {
workspace string
project string
autoMergeDisabled bool
mergeMethod string
commentArgs []string
expPlanFlags string
expApplyFlags string
Expand Down Expand Up @@ -824,6 +825,16 @@ func TestBuildPlanApplyVersionComment(t *testing.T) {
expApplyFlags: "-d dir -w workspace --auto-merge-disabled",
expVersionFlags: "-d dir -w workspace",
},
{
repoRelDir: "dir",
workspace: "workspace",
project: "",
mergeMethod: "squash",
commentArgs: []string{`"arg1"`, `"arg2"`, `arg3`},
expPlanFlags: "-d dir -w workspace -- arg1 arg2 arg3",
expApplyFlags: "-d dir -w workspace --merge-method squash",
expVersionFlags: "-d dir -w workspace",
},
}

for _, c := range cases {
Expand All @@ -834,7 +845,7 @@ func TestBuildPlanApplyVersionComment(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 command.Apply:
actComment := commentParser.BuildApplyComment(c.repoRelDir, c.workspace, c.project, c.autoMergeDisabled)
actComment := commentParser.BuildApplyComment(c.repoRelDir, c.workspace, c.project, c.autoMergeDisabled, c.mergeMethod)
Equals(t, fmt.Sprintf("atlantis apply %s", c.expApplyFlags), actComment)
}
}
Expand Down Expand Up @@ -1023,6 +1034,7 @@ var ApplyUsage = `Usage of apply:
--auto-merge-disabled Disable automerge after apply.
-d, --dir string Apply the plan for this directory, relative to root of
repo, ex. 'child/dir'.
--merge-method string Specifies merge method for the VCS if automerge is enabled.
-p, --project string Apply the plan for this project. Refers to the name of
the project configured in a repo config file. Cannot
be used at same time as workspace or dir flags.
Expand Down
7 changes: 5 additions & 2 deletions server/events/event_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ type CommentCommand struct {
SubName string
// AutoMergeDisabled is true if the command should not automerge after apply.
AutoMergeDisabled bool
// MergeMethod specified the merge method for the VCS if automerge enabled.
MergeMethod string
// 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 @@ -177,11 +179,11 @@ func (c CommentCommand) IsAutoplan() bool {

// String returns a string representation of the command.
func (c CommentCommand) String() string {
return fmt.Sprintf("command=%q verbose=%t dir=%q workspace=%q project=%q policyset=%q, clear-policy-approval=%t, flags=%q", c.Name.String(), c.Verbose, c.RepoRelDir, c.Workspace, c.ProjectName, c.PolicySet, c.ClearPolicyApproval, strings.Join(c.Flags, ","))
return fmt.Sprintf("command=%q, verbose=%t, dir=%q, workspace=%q, project=%q, policyset=%q, auto-merge-disabled=%t, merge-method=%s, clear-policy-approval=%t, flags=%q", c.Name.String(), c.Verbose, c.RepoRelDir, c.Workspace, c.ProjectName, c.PolicySet, c.AutoMergeDisabled, c.MergeMethod, c.ClearPolicyApproval, strings.Join(c.Flags, ","))
}

// NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults.
func NewCommentCommand(repoRelDir string, flags []string, name command.Name, subName string, verbose, autoMergeDisabled bool, workspace string, project string, policySet string, clearPolicyApproval bool) *CommentCommand {
func NewCommentCommand(repoRelDir string, flags []string, name command.Name, subName string, verbose, autoMergeDisabled bool, mergeMethod string, workspace string, project string, policySet string, clearPolicyApproval bool) *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 @@ -198,6 +200,7 @@ func NewCommentCommand(repoRelDir string, flags []string, name command.Name, sub
Verbose: verbose,
Workspace: workspace,
AutoMergeDisabled: autoMergeDisabled,
MergeMethod: mergeMethod,
ProjectName: project,
PolicySet: policySet,
ClearPolicyApproval: clearPolicyApproval,
Expand Down
8 changes: 4 additions & 4 deletions server/events/event_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -750,14 +750,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, command.Plan, "", false, false, "workspace", "", "", false)
cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, "", false, false, "", "workspace", "", "", false)
Equals(t, c.ExpDir, cmd.RepoRelDir)
})
}
}

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

func TestNewCommand_AllFieldsSet(t *testing.T) {
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, "", true, false, "workspace", "project", "policyset", false)
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, "", true, false, "", "workspace", "project", "policyset", false)
Equals(t, events.CommentCommand{
Workspace: "workspace",
RepoRelDir: "dir",
Expand Down Expand Up @@ -816,7 +816,7 @@ func TestCommentCommand_IsAutoplan(t *testing.T) {
}

func TestCommentCommand_String(t *testing.T) {
exp := `command="plan" verbose=true dir="mydir" workspace="myworkspace" project="myproject" policyset="", clear-policy-approval=false, flags="flag1,flag2"`
exp := `command="plan", verbose=true, dir="mydir", workspace="myworkspace", project="myproject", policyset="", auto-merge-disabled=false, merge-method=, clear-policy-approval=false, flags="flag1,flag2"`
Equals(t, exp, (events.CommentCommand{
RepoRelDir: "mydir",
Flags: []string{"flag1", "flag2"},
Expand Down
Loading