diff --git a/runatlantis.io/docs/repo-level-atlantis-yaml.md b/runatlantis.io/docs/repo-level-atlantis-yaml.md
index 6c3ad7b44d..4b41068e82 100644
--- a/runatlantis.io/docs/repo-level-atlantis-yaml.md
+++ b/runatlantis.io/docs/repo-level-atlantis-yaml.md
@@ -46,11 +46,13 @@ need to be defined.
```yaml
version: 3
automerge: true
+delete_source_branch_on_merge: true
projects:
- name: my-project-name
dir: .
workspace: default
terraform_version: v0.11.0
+ delete_source_branch_on_merge: true
autoplan:
when_modified: ["*.tf", "../modules/**.tf"]
enabled: true
@@ -189,13 +191,15 @@ See [Custom Workflow Use Cases: Custom Backend Config](custom-workflows.html#cus
```yaml
version:
automerge:
+delete_source_branch_on_merge:
projects:
workflows:
```
| Key | Type | Default | Required | Description |
|-------------------------------|----------------------------------------------------------|---------|----------|-------------------------------------------------------------|
| version | int | none | **yes** | This key is required and must be set to `3` |
-| automerge | bool | `false` | no | Automatically merge pull request when all plans are applied |
+| automerge | bool | `false` | no | Automatically merges pull request when all plans are applied|
+| delete_source_branch_on_merge | bool | `false` | no | Automatically deletes the source branch on merge |
| projects | array[[Project](repo-level-atlantis-yaml.html#project)] | `[]` | no | Lists the projects in this repo |
| workflows
*(restricted)* | map[string: [Workflow](custom-workflows.html#reference)] | `{}` | no | Custom workflows |
@@ -204,6 +208,7 @@ workflows:
name: myname
dir: mydir
workspace: myworkspace
+delete_source_branch_on_merge:
autoplan:
terraform_version: 0.11.0
apply_requirements: ["approved"]
@@ -216,6 +221,7 @@ workflow: myworkflow
| dir | string | none | **yes** | The directory of this project relative to the repo root. For example if the project was under `./project1` then use `project1`. Use `.` to indicate the repo root. |
| workspace | string | `"default"` | no | The [Terraform workspace](https://www.terraform.io/docs/state/workspaces.html) for this project. Atlantis will switch to this workplace when planning/applying and will create it if it doesn't exist. |
| autoplan | [Autoplan](#autoplan) | none | no | A custom autoplan configuration. If not specified, will use the autoplan config. See [Autoplanning](autoplanning.html). |
+| delete_source_branch_on_merge | bool | `false` | no | Automatically deletes the source branch on merge |
| terraform_version | string | none | no | A specific Terraform version to use when running commands for this project. Must be [Semver compatible](https://semver.org/), ex. `v0.11.0`, `0.12.0-beta1`. |
| apply_requirements
*(restricted)* | array[string] | none | no | Requirements that must be satisfied before `atlantis apply` can be run. Currently the only supported requirements are `approved` and `mergeable`. See [Apply Requirements](apply-requirements.html) for more details. |
| workflow
*(restricted)* | string | none | no | A custom workflow. If not specified, Atlantis will use its default workflow. |
diff --git a/runatlantis.io/docs/server-side-repo-config.md b/runatlantis.io/docs/server-side-repo-config.md
index 9dbb4e93ac..06b644cf1e 100644
--- a/runatlantis.io/docs/server-side-repo-config.md
+++ b/runatlantis.io/docs/server-side-repo-config.md
@@ -42,7 +42,7 @@ repos:
# allowed_overrides specifies which keys can be overridden by this repo in
# its atlantis.yaml file.
- allowed_overrides: [apply_requirements, workflow]
+ allowed_overrides: [apply_requirements, workflow, delete_source_branch_on_merge]
# allowed_workflows specifies which workflows the repos that match
# are allowed to select.
@@ -52,6 +52,10 @@ repos:
# workflows. If false (default), the repo can only use server-side defined
# workflows.
allow_custom_workflows: true
+
+ # delete_source_branch_on_merge defines whether the source branch would be deleted on merge
+ # If false (default), the source branch won't be deleted on merge
+ delete_source_branch_on_merge: true
# pre_workflow_hooks defines arbitrary list of scripts to execute before workflow execution.
pre_workflow_hooks:
@@ -366,14 +370,15 @@ If you set a workflow with the key `default`, it will override this.
:::
### Repo
-| Key | Type | Default | Required | Description |
-|------------------------|----------|---------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| id | string | none | yes | Value can be a regular expression when specified as /<regex>/ or an exact string match. Repo IDs are of the form `{vcs hostname}/{org}/{name}`, ex. `github.com/owner/repo`. Hostname is specified without scheme or port. For Bitbucket Server, {org} is the **name** of the project, not the key. |
-| workflow | string | none | no | A custom workflow. |
-| apply_requirements | []string | none | no | Requirements that must be satisfied before `atlantis apply` can be run. Currently the only supported requirements are `approved` and `mergeable`. See [Apply Requirements](apply-requirements.html) for more details. |
-| allowed_overrides | []string | none | no | A list of restricted keys that `atlantis.yaml` files can override. The only supported keys are `apply_requirements` and `workflow` |
-| allowed_workflows | []string | none | no | A list of workflows that `atlantis.yaml` files can select from. |
-| allow_custom_workflows | bool | false | no | Whether or not to allow [Custom Workflows](custom-workflows.html). |
+| Key | Type | Default | Required | Description |
+|-------------------------------|----------|---------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| id | string | none | yes | Value can be a regular expression when specified as /<regex>/ or an exact string match. Repo IDs are of the form `{vcs hostname}/{org}/{name}`, ex. `github.com/owner/repo`. Hostname is specified without scheme or port. For Bitbucket Server, {org} is the **name** of the project, not the key. |
+| workflow | string | none | no | A custom workflow. |
+| apply_requirements | []string | none | no | Requirements that must be satisfied before `atlantis apply` can be run. Currently the only supported requirements are `approved` and `mergeable`. See [Apply Requirements](apply-requirements.html) for more details. |
+| allowed_overrides | []string | none | no | A list of restricted keys that `atlantis.yaml` files can override. The only supported keys are `apply_requirements`, `workflow` and `delete_source_branch_on_merge` |
+| allowed_workflows | []string | none | no | A list of workflows that `atlantis.yaml` files can select from. |
+| allow_custom_workflows | bool | false | no | Whether or not to allow [Custom Workflows](custom-workflows.html). |
+| delete_source_branch_on_merge | bool | false | no | Whether or not to delete the source branch on merge (only AzureDevOps and GitLab support) |
:::tip Notes
diff --git a/server/events/apply_command_runner.go b/server/events/apply_command_runner.go
index b54ea99a4e..318bc43ba5 100644
--- a/server/events/apply_command_runner.go
+++ b/server/events/apply_command_runner.go
@@ -158,7 +158,7 @@ func (a *ApplyCommandRunner) Run(ctx *CommandContext, cmd *CommentCommand) {
a.updateCommitStatus(ctx, pullStatus)
if a.autoMerger.automergeEnabled(projectCmds) {
- a.autoMerger.automerge(ctx, pullStatus)
+ a.autoMerger.automerge(ctx, pullStatus, a.autoMerger.deleteSourceBranchOnMergeEnabled(projectCmds))
}
}
diff --git a/server/events/automerger.go b/server/events/automerger.go
index decd35b8fe..4ce7e3d2d8 100644
--- a/server/events/automerger.go
+++ b/server/events/automerger.go
@@ -12,7 +12,7 @@ type AutoMerger struct {
GlobalAutomerge bool
}
-func (c *AutoMerger) automerge(ctx *CommandContext, pullStatus models.PullStatus) {
+func (c *AutoMerger) automerge(ctx *CommandContext, pullStatus models.PullStatus, deleteSourceBranchOnMerge bool) {
// We only automerge if all projects have been successfully applied.
for _, p := range pullStatus.Projects {
if p.Status != models.AppliedPlanStatus {
@@ -29,7 +29,9 @@ func (c *AutoMerger) automerge(ctx *CommandContext, pullStatus models.PullStatus
// Make the API call to perform the merge.
ctx.Log.Info("automerging pull request")
- err := c.VCSClient.MergePull(ctx.Pull)
+ var pullOptions models.PullRequestOptions
+ pullOptions.DeleteSourceBranchOnMerge = deleteSourceBranchOnMerge
+ err := c.VCSClient.MergePull(ctx.Pull, pullOptions)
if err != nil {
ctx.Log.Err("automerging failed: %s", err)
@@ -48,3 +50,9 @@ func (c *AutoMerger) automergeEnabled(projectCmds []models.ProjectCommandContext
// Otherwise we check if this repo is configured for automerging.
(len(projectCmds) > 0 && projectCmds[0].AutomergeEnabled)
}
+
+// deleteSourceBranchOnMergeEnabled returns true if we should delete the source branch on merge in this context.
+func (c *AutoMerger) deleteSourceBranchOnMergeEnabled(projectCmds []models.ProjectCommandContext) bool {
+ //check if this repo is configured for automerging.
+ return (len(projectCmds) > 0 && projectCmds[0].DeleteSourceBranchOnMerge)
+}
diff --git a/server/events/command_runner.go b/server/events/command_runner.go
index 1167b85c71..24c717a01d 100644
--- a/server/events/command_runner.go
+++ b/server/events/command_runner.go
@@ -339,6 +339,4 @@ func (c *DefaultCommandRunner) logPanics(baseRepo models.Repo, pullNum int, logg
}
}
-// automergeComment is the comment that gets posted when Atlantis automatically
-// merges the PR.
var automergeComment = `Automatically merging because all plans have been successfully applied.`
diff --git a/server/events/command_runner_test.go b/server/events/command_runner_test.go
index 7ef503e968..1254e96d03 100644
--- a/server/events/command_runner_test.go
+++ b/server/events/command_runner_test.go
@@ -649,8 +649,12 @@ func TestApplyWithAutoMerge_VSCMerge(t *testing.T) {
autoMerger.GlobalAutomerge = true
defer func() { autoMerger.GlobalAutomerge = false }()
+ pullOptions := models.PullRequestOptions{
+ DeleteSourceBranchOnMerge: false,
+ }
+
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, &events.CommentCommand{Name: models.ApplyCommand})
- vcsClient.VerifyWasCalledOnce().MergePull(modelPull)
+ vcsClient.VerifyWasCalledOnce().MergePull(modelPull, pullOptions)
}
func TestRunApply_DiscardedProjects(t *testing.T) {
@@ -688,7 +692,8 @@ func TestRunApply_DiscardedProjects(t *testing.T) {
When(workingDir.GetPullDir(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).
ThenReturn(tmp, nil)
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, &pull, fixtures.User, fixtures.Pull.Num, &events.CommentCommand{Name: models.ApplyCommand})
- vcsClient.VerifyWasCalled(Never()).MergePull(matchers.AnyModelsPullRequest())
+
+ vcsClient.VerifyWasCalled(Never()).MergePull(matchers.AnyModelsPullRequest(), matchers.AnyModelsPullRequestOptions())
}
func TestRunCommentCommand_DrainOngoing(t *testing.T) {
diff --git a/server/events/mocks/matchers/models_pullrequestoptions.go b/server/events/mocks/matchers/models_pullrequestoptions.go
new file mode 100644
index 0000000000..b9d5e471a2
--- /dev/null
+++ b/server/events/mocks/matchers/models_pullrequestoptions.go
@@ -0,0 +1,21 @@
+// Code generated by pegomock. DO NOT EDIT.
+package matchers
+
+import (
+ "reflect"
+
+ "github.com/petergtz/pegomock"
+ models "github.com/runatlantis/atlantis/server/events/models"
+)
+
+func AnyModelsPullRequestOptions() models.PullRequestOptions {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(models.PullRequestOptions))(nil)).Elem()))
+ var nullValue models.PullRequestOptions
+ return nullValue
+}
+
+func EqModelsPullRequestOptions(value models.PullRequestOptions) models.PullRequestOptions {
+ pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
+ var nullValue models.PullRequestOptions
+ return nullValue
+}
diff --git a/server/events/models/models.go b/server/events/models/models.go
index bb3236d67d..817d4d1f90 100644
--- a/server/events/models/models.go
+++ b/server/events/models/models.go
@@ -171,6 +171,13 @@ type PullRequest struct {
BaseRepo Repo
}
+// PullRequestOptions is used to set optional paralmeters for PullRequest
+type PullRequestOptions struct {
+ // When DeleteSourceBranchOnMerge flag is set to true VCS deletes the source branch after the PR is merged
+ // Applied by GitLab & AzureDevops
+ DeleteSourceBranchOnMerge bool
+}
+
type PullRequestState int
const (
@@ -391,6 +398,8 @@ type ProjectCommandContext struct {
// PolicySets represent the policies that are run on the plan as part of the
// policy check stage
PolicySets valid.PolicySets
+ // DeleteSourceBranchOnMerge will attempt to allow a branch to be deleted when merged (AzureDevOps & GitLab Support Only)
+ DeleteSourceBranchOnMerge bool
}
// GetShowResultFileName returns the filename (not the path) to store the tf show result
diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go
index 4da9ccc17c..06dcf4c3c0 100644
--- a/server/events/project_command_builder.go
+++ b/server/events/project_command_builder.go
@@ -25,6 +25,8 @@ const (
DefaultParallelApplyEnabled = false
// DefaultParallelPlanEnabled is the default for the parallel plan setting.
DefaultParallelPlanEnabled = false
+ // DefaultDeleteSourceBranchOnMerge being false is the default setting whether or not to remove a source branch on merge
+ DefaultDeleteSourceBranchOnMerge = false
)
func NewProjectCommandBuilder(
@@ -235,6 +237,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
commentFlags,
repoDir,
repoCfg.Automerge,
+ mergedCfg.DeleteSourceBranchOnMerge,
repoCfg.ParallelApply,
repoCfg.ParallelPlan,
verbose,
@@ -261,6 +264,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
commentFlags,
repoDir,
DefaultAutomergeEnabled,
+ pCfg.DeleteSourceBranchOnMerge,
DefaultParallelApplyEnabled,
DefaultParallelPlanEnabled,
verbose,
@@ -451,10 +455,12 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex
var projCtxs []models.ProjectCommandContext
var projCfg valid.MergedProjectCfg
automerge := DefaultAutomergeEnabled
+ deleteBranchOnMerge := DefaultDeleteSourceBranchOnMerge
parallelApply := DefaultParallelApplyEnabled
parallelPlan := DefaultParallelPlanEnabled
if repoCfgPtr != nil {
automerge = repoCfgPtr.Automerge
+ deleteBranchOnMerge = projCfg.DeleteSourceBranchOnMerge
parallelApply = repoCfgPtr.ParallelApply
parallelPlan = repoCfgPtr.ParallelPlan
}
@@ -477,8 +483,9 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex
commentFlags,
repoDir,
automerge,
- parallelApply,
+ deleteBranchOnMerge,
parallelPlan,
+ parallelApply,
verbose,
)...)
}
@@ -492,8 +499,9 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex
commentFlags,
repoDir,
automerge,
- parallelApply,
+ deleteBranchOnMerge,
parallelPlan,
+ parallelApply,
verbose,
)...)
}
@@ -503,7 +511,6 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex
}
return projCtxs, nil
-
}
// validateWorkspaceAllowed returns an error if repoCfg defines projects in
diff --git a/server/events/project_command_context_builder.go b/server/events/project_command_context_builder.go
index 13b43c9a19..f681ec4df7 100644
--- a/server/events/project_command_context_builder.go
+++ b/server/events/project_command_context_builder.go
@@ -33,7 +33,7 @@ type ProjectCommandContextBuilder interface {
prjCfg valid.MergedProjectCfg,
commentFlags []string,
repoDir string,
- automerge, parallelPlan, parallelApply, verbose bool,
+ automerge, deleteSourceBranchOnMerge, parallelPlan, parallelApply, verbose bool,
) []models.ProjectCommandContext
}
@@ -47,7 +47,7 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
prjCfg valid.MergedProjectCfg,
commentFlags []string,
repoDir string,
- automerge, parallelApply, parallelPlan, verbose bool,
+ automerge, deleteSourceBranchOnMerge, parallelApply, parallelPlan, verbose bool,
) (projectCmds []models.ProjectCommandContext) {
ctx.Log.Debug("Building project command context for %s", cmdName)
@@ -75,6 +75,7 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
prjCfg.PolicySets,
escapeArgs(commentFlags),
automerge,
+ deleteSourceBranchOnMerge,
parallelApply,
parallelPlan,
verbose,
@@ -94,7 +95,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
prjCfg valid.MergedProjectCfg,
commentFlags []string,
repoDir string,
- automerge, parallelApply, parallelPlan, verbose bool,
+ automerge, deleteSourceBranchOnMerge, parallelApply, parallelPlan, verbose bool,
) (projectCmds []models.ProjectCommandContext) {
ctx.Log.Debug("PolicyChecks are enabled")
projectCmds = cb.ProjectCommandContextBuilder.BuildProjectContext(
@@ -104,6 +105,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
commentFlags,
repoDir,
automerge,
+ deleteSourceBranchOnMerge,
parallelApply,
parallelPlan,
verbose,
@@ -123,6 +125,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
prjCfg.PolicySets,
escapeArgs(commentFlags),
automerge,
+ deleteSourceBranchOnMerge,
parallelApply,
parallelPlan,
verbose,
@@ -143,6 +146,7 @@ func newProjectCommandContext(ctx *CommandContext,
policySets valid.PolicySets,
escapedCommentArgs []string,
automergeEnabled bool,
+ deleteSourceBranchOnMerge,
parallelApplyEnabled bool,
parallelPlanEnabled bool,
verbose bool,
@@ -167,30 +171,31 @@ func newProjectCommandContext(ctx *CommandContext,
}
return models.ProjectCommandContext{
- CommandName: cmd,
- ApplyCmd: applyCmd,
- BaseRepo: ctx.Pull.BaseRepo,
- EscapedCommentArgs: escapedCommentArgs,
- AutomergeEnabled: automergeEnabled,
- ParallelApplyEnabled: parallelApplyEnabled,
- ParallelPlanEnabled: parallelPlanEnabled,
- AutoplanEnabled: projCfg.AutoplanEnabled,
- Steps: steps,
- HeadRepo: ctx.HeadRepo,
- Log: ctx.Log,
- PullMergeable: ctx.PullMergeable,
- ProjectPlanStatus: projectPlanStatus,
- Pull: ctx.Pull,
- ProjectName: projCfg.Name,
- ApplyRequirements: projCfg.ApplyRequirements,
- RePlanCmd: planCmd,
- RepoRelDir: projCfg.RepoRelDir,
- RepoConfigVersion: projCfg.RepoCfgVersion,
- TerraformVersion: projCfg.TerraformVersion,
- User: ctx.User,
- Verbose: verbose,
- Workspace: projCfg.Workspace,
- PolicySets: policySets,
+ CommandName: cmd,
+ ApplyCmd: applyCmd,
+ BaseRepo: ctx.Pull.BaseRepo,
+ EscapedCommentArgs: escapedCommentArgs,
+ AutomergeEnabled: automergeEnabled,
+ DeleteSourceBranchOnMerge: deleteSourceBranchOnMerge,
+ ParallelApplyEnabled: parallelApplyEnabled,
+ ParallelPlanEnabled: parallelPlanEnabled,
+ AutoplanEnabled: projCfg.AutoplanEnabled,
+ Steps: steps,
+ HeadRepo: ctx.HeadRepo,
+ Log: ctx.Log,
+ PullMergeable: ctx.PullMergeable,
+ ProjectPlanStatus: projectPlanStatus,
+ Pull: ctx.Pull,
+ ProjectName: projCfg.Name,
+ ApplyRequirements: projCfg.ApplyRequirements,
+ RePlanCmd: planCmd,
+ RepoRelDir: projCfg.RepoRelDir,
+ RepoConfigVersion: projCfg.RepoCfgVersion,
+ TerraformVersion: projCfg.TerraformVersion,
+ User: ctx.User,
+ Verbose: verbose,
+ Workspace: projCfg.Workspace,
+ PolicySets: policySets,
}
}
diff --git a/server/events/project_command_context_builder_test.go b/server/events/project_command_context_builder_test.go
index c53408650d..d0a3de9bdd 100644
--- a/server/events/project_command_context_builder_test.go
+++ b/server/events/project_command_context_builder_test.go
@@ -57,7 +57,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
},
}
- result := subject.BuildProjectContext(commandCtx, models.PlanCommand, projCfg, []string{}, "some/dir", false, false, false, false)
+ result := subject.BuildProjectContext(commandCtx, models.PlanCommand, projCfg, []string{}, "some/dir", false, false, false, false, false)
assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus)
})
@@ -77,7 +77,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
},
}
- result := subject.BuildProjectContext(commandCtx, models.PlanCommand, projCfg, []string{}, "some/dir", false, false, false, false)
+ result := subject.BuildProjectContext(commandCtx, models.PlanCommand, projCfg, []string{}, "some/dir", false, false, false, false, false)
assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus)
})
diff --git a/server/events/vcs/azuredevops_client.go b/server/events/vcs/azuredevops_client.go
index ee96fdc046..beaf847921 100644
--- a/server/events/vcs/azuredevops_client.go
+++ b/server/events/vcs/azuredevops_client.go
@@ -288,7 +288,7 @@ func (g *AzureDevopsClient) UpdateStatus(repo models.Repo, pull models.PullReque
// If the user has set a branch policy that disallows no fast-forward, the merge will fail
// until we handle branch policies
// https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops
-func (g *AzureDevopsClient) MergePull(pull models.PullRequest) error {
+func (g *AzureDevopsClient) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
owner, project, repoName := SplitAzureDevopsRepoFullName(pull.BaseRepo.FullName)
descriptor := "Atlantis Terraform Pull Request Automation"
@@ -313,7 +313,7 @@ func (g *AzureDevopsClient) MergePull(pull models.PullRequest) error {
completionOpts := azuredevops.GitPullRequestCompletionOptions{
BypassPolicy: new(bool),
BypassReason: azuredevops.String(""),
- DeleteSourceBranch: new(bool),
+ DeleteSourceBranch: &pullOptions.DeleteSourceBranchOnMerge,
MergeCommitMessage: azuredevops.String(common.AutomergeCommitMsg),
MergeStrategy: &mcm,
SquashMerge: new(bool),
diff --git a/server/events/vcs/azuredevops_client_test.go b/server/events/vcs/azuredevops_client_test.go
index 5d4e4c4153..b127d71829 100644
--- a/server/events/vcs/azuredevops_client_test.go
+++ b/server/events/vcs/azuredevops_client_test.go
@@ -127,6 +127,8 @@ func TestAzureDevopsClient_MergePull(t *testing.T) {
Owner: "owner",
Name: "repo",
},
+ }, models.PullRequestOptions{
+ DeleteSourceBranchOnMerge: false,
})
if c.expErr == "" {
Ok(t, err)
diff --git a/server/events/vcs/bitbucketcloud/client.go b/server/events/vcs/bitbucketcloud/client.go
index ff60faf6c6..edf4786926 100644
--- a/server/events/vcs/bitbucketcloud/client.go
+++ b/server/events/vcs/bitbucketcloud/client.go
@@ -194,7 +194,7 @@ func (b *Client) UpdateStatus(repo models.Repo, pull models.PullRequest, status
}
// MergePull merges the pull request.
-func (b *Client) MergePull(pull models.PullRequest) error {
+func (b *Client) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
path := fmt.Sprintf("%s/2.0/repositories/%s/pullrequests/%d/merge", b.BaseURL, pull.BaseRepo.FullName, pull.Num)
_, err := b.makeRequest("POST", path, nil)
return err
diff --git a/server/events/vcs/bitbucketserver/client.go b/server/events/vcs/bitbucketserver/client.go
index adb6eba0d8..a30ccd6f70 100644
--- a/server/events/vcs/bitbucketserver/client.go
+++ b/server/events/vcs/bitbucketserver/client.go
@@ -244,7 +244,7 @@ func (b *Client) UpdateStatus(repo models.Repo, pull models.PullRequest, status
}
// MergePull merges the pull request.
-func (b *Client) MergePull(pull models.PullRequest) error {
+func (b *Client) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
projectKey, err := b.GetProjectKey(pull.BaseRepo.Name, pull.BaseRepo.SanitizedCloneURL)
if err != nil {
return err
diff --git a/server/events/vcs/bitbucketserver/client_test.go b/server/events/vcs/bitbucketserver/client_test.go
index 6ff44f83af..016db6a344 100644
--- a/server/events/vcs/bitbucketserver/client_test.go
+++ b/server/events/vcs/bitbucketserver/client_test.go
@@ -177,6 +177,8 @@ func TestClient_MergePull(t *testing.T) {
Hostname: "bitbucket.org",
},
},
+ }, models.PullRequestOptions{
+ DeleteSourceBranchOnMerge: false,
})
Ok(t, err)
}
diff --git a/server/events/vcs/client.go b/server/events/vcs/client.go
index dad6fa1901..e5902aafbd 100644
--- a/server/events/vcs/client.go
+++ b/server/events/vcs/client.go
@@ -36,7 +36,7 @@ type Client interface {
// url is an optional link that users should click on for more information
// about this status.
UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, src string, description string, url string) error
- MergePull(pull models.PullRequest) error
+ MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error
MarkdownPullLink(pull models.PullRequest) (string, error)
// DownloadRepoConfigFile return `atlantis.yaml` content from VCS (which support fetch a single file from repository)
diff --git a/server/events/vcs/github_client.go b/server/events/vcs/github_client.go
index 20d3ab1ae9..9dd8180b07 100644
--- a/server/events/vcs/github_client.go
+++ b/server/events/vcs/github_client.go
@@ -329,7 +329,7 @@ func (g *GithubClient) UpdateStatus(repo models.Repo, pull models.PullRequest, s
}
// MergePull merges the pull request.
-func (g *GithubClient) MergePull(pull models.PullRequest) error {
+func (g *GithubClient) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
// Users can set their repo to disallow certain types of merging.
// We detect which types aren't allowed and use the type that is.
g.logger.Debug("GET /repos/%v/%v", pull.BaseRepo.Owner, pull.BaseRepo.Name)
diff --git a/server/events/vcs/github_client_test.go b/server/events/vcs/github_client_test.go
index 70d719704e..0987a95cfd 100644
--- a/server/events/vcs/github_client_test.go
+++ b/server/events/vcs/github_client_test.go
@@ -640,6 +640,8 @@ func TestGithubClient_MergePullHandlesError(t *testing.T) {
},
},
Num: 1,
+ }, models.PullRequestOptions{
+ DeleteSourceBranchOnMerge: false,
})
if c.expErr == "" {
@@ -761,7 +763,10 @@ func TestGithubClient_MergePullCorrectMethod(t *testing.T) {
},
},
Num: 1,
+ }, models.PullRequestOptions{
+ DeleteSourceBranchOnMerge: false,
})
+
Ok(t, err)
})
}
diff --git a/server/events/vcs/gitlab_client.go b/server/events/vcs/gitlab_client.go
index 9cb320f4b7..9c3a2a4716 100644
--- a/server/events/vcs/gitlab_client.go
+++ b/server/events/vcs/gitlab_client.go
@@ -214,13 +214,14 @@ func (g *GitlabClient) GetMergeRequest(repoFullName string, pullNum int) (*gitla
}
// MergePull merges the merge request.
-func (g *GitlabClient) MergePull(pull models.PullRequest) error {
+func (g *GitlabClient) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
commitMsg := common.AutomergeCommitMsg
_, _, err := g.Client.MergeRequests.AcceptMergeRequest(
pull.BaseRepo.FullName,
pull.Num,
&gitlab.AcceptMergeRequestOptions{
- MergeCommitMessage: &commitMsg,
+ MergeCommitMessage: &commitMsg,
+ ShouldRemoveSourceBranch: &pullOptions.DeleteSourceBranchOnMerge,
})
return errors.Wrap(err, "unable to merge merge request, it may not be in a mergeable state")
}
diff --git a/server/events/vcs/gitlab_client_test.go b/server/events/vcs/gitlab_client_test.go
index bb0002381c..d4c0b47583 100644
--- a/server/events/vcs/gitlab_client_test.go
+++ b/server/events/vcs/gitlab_client_test.go
@@ -164,6 +164,8 @@ func TestGitlabClient_MergePull(t *testing.T) {
Owner: "runatlantis",
Name: "atlantis",
},
+ }, models.PullRequestOptions{
+ DeleteSourceBranchOnMerge: false,
})
if c.expErr == "" {
Ok(t, err)
diff --git a/server/events/vcs/mocks/mock_client.go b/server/events/vcs/mocks/mock_client.go
index 0b20b49b63..ad7eeef197 100644
--- a/server/events/vcs/mocks/mock_client.go
+++ b/server/events/vcs/mocks/mock_client.go
@@ -4,10 +4,11 @@
package mocks
import (
- pegomock "github.com/petergtz/pegomock"
- models "github.com/runatlantis/atlantis/server/events/models"
"reflect"
"time"
+
+ pegomock "github.com/petergtz/pegomock"
+ models "github.com/runatlantis/atlantis/server/events/models"
)
type MockClient struct {
@@ -127,11 +128,11 @@ func (mock *MockClient) UpdateStatus(repo models.Repo, pull models.PullRequest,
return ret0
}
-func (mock *MockClient) MergePull(pull models.PullRequest) error {
+func (mock *MockClient) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClient().")
}
- params := []pegomock.Param{pull}
+ params := []pegomock.Param{pull, pullOptions}
result := pegomock.GetGenericMockFrom(mock).Invoke("MergePull", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
if len(result) != 0 {
@@ -206,14 +207,14 @@ func (mock *MockClient) VerifyWasCalledOnce() *VerifierMockClient {
}
}
-func (mock *MockClient) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockClient {
+func (mock *MockClient) VerifyWasCalled(invocationCountMatcher pegomock.InvocationCountMatcher) *VerifierMockClient {
return &VerifierMockClient{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
}
}
-func (mock *MockClient) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockClient {
+func (mock *MockClient) VerifyWasCalledInOrder(invocationCountMatcher pegomock.InvocationCountMatcher, inOrderContext *pegomock.InOrderContext) *VerifierMockClient {
return &VerifierMockClient{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
@@ -221,7 +222,7 @@ func (mock *MockClient) VerifyWasCalledInOrder(invocationCountMatcher pegomock.M
}
}
-func (mock *MockClient) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockClient {
+func (mock *MockClient) VerifyWasCalledEventually(invocationCountMatcher pegomock.InvocationCountMatcher, timeout time.Duration) *VerifierMockClient {
return &VerifierMockClient{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
@@ -231,7 +232,7 @@ func (mock *MockClient) VerifyWasCalledEventually(invocationCountMatcher pegomoc
type VerifierMockClient struct {
mock *MockClient
- invocationCountMatcher pegomock.Matcher
+ invocationCountMatcher pegomock.InvocationCountMatcher
inOrderContext *pegomock.InOrderContext
timeout time.Duration
}
@@ -446,8 +447,8 @@ func (c *MockClient_UpdateStatus_OngoingVerification) GetAllCapturedArguments()
return
}
-func (verifier *VerifierMockClient) MergePull(pull models.PullRequest) *MockClient_MergePull_OngoingVerification {
- params := []pegomock.Param{pull}
+func (verifier *VerifierMockClient) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) *MockClient_MergePull_OngoingVerification {
+ params := []pegomock.Param{pull, pullOptions}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MergePull", params, verifier.timeout)
return &MockClient_MergePull_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
@@ -457,18 +458,22 @@ type MockClient_MergePull_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
-func (c *MockClient_MergePull_OngoingVerification) GetCapturedArguments() models.PullRequest {
- pull := c.GetAllCapturedArguments()
- return pull[len(pull)-1]
+func (c *MockClient_MergePull_OngoingVerification) GetCapturedArguments() (models.PullRequest, models.PullRequestOptions) {
+ pull, pullOptions := c.GetAllCapturedArguments()
+ return pull[len(pull)-1], pullOptions[len(pullOptions)-1]
}
-func (c *MockClient_MergePull_OngoingVerification) GetAllCapturedArguments() (_param0 []models.PullRequest) {
+func (c *MockClient_MergePull_OngoingVerification) GetAllCapturedArguments() (_param0 []models.PullRequest, _param1 []models.PullRequestOptions) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.PullRequest, len(c.methodInvocations))
for u, param := range params[0] {
_param0[u] = param.(models.PullRequest)
}
+ _param1 = make([]models.PullRequestOptions, len(c.methodInvocations))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.PullRequestOptions)
+ }
}
return
}
diff --git a/server/events/vcs/not_configured_vcs_client.go b/server/events/vcs/not_configured_vcs_client.go
index 7ce68a3a03..c73a6a22e2 100644
--- a/server/events/vcs/not_configured_vcs_client.go
+++ b/server/events/vcs/not_configured_vcs_client.go
@@ -44,7 +44,7 @@ func (a *NotConfiguredVCSClient) PullIsMergeable(repo models.Repo, pull models.P
func (a *NotConfiguredVCSClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, src string, description string, url string) error {
return a.err()
}
-func (a *NotConfiguredVCSClient) MergePull(pull models.PullRequest) error {
+func (a *NotConfiguredVCSClient) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
return a.err()
}
func (a *NotConfiguredVCSClient) MarkdownPullLink(pull models.PullRequest) (string, error) {
diff --git a/server/events/vcs/proxy.go b/server/events/vcs/proxy.go
index a583d70cc0..a49895c0e2 100644
--- a/server/events/vcs/proxy.go
+++ b/server/events/vcs/proxy.go
@@ -76,8 +76,8 @@ func (d *ClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, st
return d.clients[repo.VCSHost.Type].UpdateStatus(repo, pull, state, src, description, url)
}
-func (d *ClientProxy) MergePull(pull models.PullRequest) error {
- return d.clients[pull.BaseRepo.VCSHost.Type].MergePull(pull)
+func (d *ClientProxy) MergePull(pull models.PullRequest, pullOptions models.PullRequestOptions) error {
+ return d.clients[pull.BaseRepo.VCSHost.Type].MergePull(pull, pullOptions)
}
func (d *ClientProxy) MarkdownPullLink(pull models.PullRequest) (string, error) {
diff --git a/server/events/yaml/parser_validator_test.go b/server/events/yaml/parser_validator_test.go
index 21ffe793de..b8c73183f0 100644
--- a/server/events/yaml/parser_validator_test.go
+++ b/server/events/yaml/parser_validator_test.go
@@ -1035,7 +1035,7 @@ func TestParseGlobalCfg(t *testing.T) {
input: `repos:
- id: /.*/
allowed_overrides: [invalid]`,
- expErr: "repos: (0: (allowed_overrides: \"invalid\" is not a valid override, only \"apply_requirements\" and \"workflow\" are supported.).).",
+ expErr: "repos: (0: (allowed_overrides: \"invalid\" is not a valid override, only \"apply_requirements\", \"workflow\" and \"delete_source_branch_on_merge\" are supported.).).",
},
"invalid apply_requirement": {
input: `repos:
@@ -1118,7 +1118,7 @@ repos:
pre_workflow_hooks:
- run: custom workflow command
workflow: custom1
- allowed_overrides: [apply_requirements, workflow]
+ allowed_overrides: [apply_requirements, workflow, delete_source_branch_on_merge]
allow_custom_workflows: true
- id: /.*/
branch: /(master|main)/
@@ -1157,7 +1157,7 @@ policies:
ApplyRequirements: []string{"approved", "mergeable"},
PreWorkflowHooks: preWorkflowHooks,
Workflow: &customWorkflow1,
- AllowedOverrides: []string{"apply_requirements", "workflow"},
+ AllowedOverrides: []string{"apply_requirements", "workflow", "delete_source_branch_on_merge"},
AllowCustomWorkflows: Bool(true),
},
{
@@ -1253,9 +1253,10 @@ workflows:
},
},
},
- AllowedWorkflows: []string{},
- AllowedOverrides: []string{},
- AllowCustomWorkflows: Bool(false),
+ AllowedWorkflows: []string{},
+ AllowedOverrides: []string{},
+ AllowCustomWorkflows: Bool(false),
+ DeleteSourceBranchOnMerge: Bool(false),
},
},
Workflows: map[string]valid.Workflow{
diff --git a/server/events/yaml/raw/global_cfg.go b/server/events/yaml/raw/global_cfg.go
index f0fefe84e8..c64112e107 100644
--- a/server/events/yaml/raw/global_cfg.go
+++ b/server/events/yaml/raw/global_cfg.go
@@ -19,14 +19,15 @@ type GlobalCfg struct {
// Repo is the raw schema for repos in the server-side repo config.
type Repo struct {
- ID string `yaml:"id" json:"id"`
- Branch string `yaml:"branch" json:"branch"`
- ApplyRequirements []string `yaml:"apply_requirements" json:"apply_requirements"`
- PreWorkflowHooks []PreWorkflowHook `yaml:"pre_workflow_hooks" json:"pre_workflow_hooks"`
- Workflow *string `yaml:"workflow,omitempty" json:"workflow,omitempty"`
- AllowedWorkflows []string `yaml:"allowed_workflows,omitempty" json:"allowed_workflows,omitempty"`
- AllowedOverrides []string `yaml:"allowed_overrides" json:"allowed_overrides"`
- AllowCustomWorkflows *bool `yaml:"allow_custom_workflows,omitempty" json:"allow_custom_workflows,omitempty"`
+ ID string `yaml:"id" json:"id"`
+ Branch string `yaml:"branch" json:"branch"`
+ ApplyRequirements []string `yaml:"apply_requirements" json:"apply_requirements"`
+ PreWorkflowHooks []PreWorkflowHook `yaml:"pre_workflow_hooks" json:"pre_workflow_hooks"`
+ Workflow *string `yaml:"workflow,omitempty" json:"workflow,omitempty"`
+ AllowedWorkflows []string `yaml:"allowed_workflows,omitempty" json:"allowed_workflows,omitempty"`
+ AllowedOverrides []string `yaml:"allowed_overrides" json:"allowed_overrides"`
+ AllowCustomWorkflows *bool `yaml:"allow_custom_workflows,omitempty" json:"allow_custom_workflows,omitempty"`
+ DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty" json:"delete_source_branch_on_merge,omitempty"`
}
func (g GlobalCfg) Validate() error {
@@ -163,8 +164,8 @@ func (r Repo) Validate() error {
overridesValid := func(value interface{}) error {
overrides := value.([]string)
for _, o := range overrides {
- if o != valid.ApplyRequirementsKey && o != valid.WorkflowKey {
- return fmt.Errorf("%q is not a valid override, only %q and %q are supported", o, valid.ApplyRequirementsKey, valid.WorkflowKey)
+ if o != valid.ApplyRequirementsKey && o != valid.WorkflowKey && o != valid.DeleteSourceBranchOnMergeKey {
+ return fmt.Errorf("%q is not a valid override, only %q, %q and %q are supported", o, valid.ApplyRequirementsKey, valid.WorkflowKey, valid.DeleteSourceBranchOnMergeKey)
}
}
return nil
@@ -176,12 +177,18 @@ func (r Repo) Validate() error {
return nil
}
+ deleteSourceBranchOnMergeValid := func(value interface{}) error {
+ //TOBE IMPLEMENTED
+ return nil
+ }
+
return validation.ValidateStruct(&r,
validation.Field(&r.ID, validation.Required, validation.By(idValid)),
validation.Field(&r.Branch, validation.By(branchValid)),
validation.Field(&r.AllowedOverrides, validation.By(overridesValid)),
validation.Field(&r.ApplyRequirements, validation.By(validApplyReq)),
validation.Field(&r.Workflow, validation.By(workflowExists)),
+ validation.Field(&r.DeleteSourceBranchOnMerge, validation.By(deleteSourceBranchOnMergeValid)),
)
}
@@ -234,14 +241,15 @@ OUTER:
}
return valid.Repo{
- ID: id,
- IDRegex: idRegex,
- BranchRegex: branchRegex,
- ApplyRequirements: mergedApplyReqs,
- PreWorkflowHooks: preWorkflowHooks,
- Workflow: workflow,
- AllowedWorkflows: r.AllowedWorkflows,
- AllowedOverrides: r.AllowedOverrides,
- AllowCustomWorkflows: r.AllowCustomWorkflows,
+ ID: id,
+ IDRegex: idRegex,
+ BranchRegex: branchRegex,
+ ApplyRequirements: mergedApplyReqs,
+ PreWorkflowHooks: preWorkflowHooks,
+ Workflow: workflow,
+ AllowedWorkflows: r.AllowedWorkflows,
+ AllowedOverrides: r.AllowedOverrides,
+ AllowCustomWorkflows: r.AllowCustomWorkflows,
+ DeleteSourceBranchOnMerge: r.DeleteSourceBranchOnMerge,
}
}
diff --git a/server/events/yaml/raw/project.go b/server/events/yaml/raw/project.go
index 8573fcf7ac..14858f1117 100644
--- a/server/events/yaml/raw/project.go
+++ b/server/events/yaml/raw/project.go
@@ -19,13 +19,14 @@ const (
)
type Project struct {
- Name *string `yaml:"name,omitempty"`
- Dir *string `yaml:"dir,omitempty"`
- Workspace *string `yaml:"workspace,omitempty"`
- Workflow *string `yaml:"workflow,omitempty"`
- TerraformVersion *string `yaml:"terraform_version,omitempty"`
- Autoplan *Autoplan `yaml:"autoplan,omitempty"`
- ApplyRequirements []string `yaml:"apply_requirements,omitempty"`
+ Name *string `yaml:"name,omitempty"`
+ Dir *string `yaml:"dir,omitempty"`
+ Workspace *string `yaml:"workspace,omitempty"`
+ Workflow *string `yaml:"workflow,omitempty"`
+ TerraformVersion *string `yaml:"terraform_version,omitempty"`
+ Autoplan *Autoplan `yaml:"autoplan,omitempty"`
+ ApplyRequirements []string `yaml:"apply_requirements,omitempty"`
+ DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty"`
}
func (p Project) Validate() error {
@@ -86,6 +87,10 @@ func (p Project) ToValid() valid.Project {
v.Name = p.Name
+ if p.DeleteSourceBranchOnMerge != nil {
+ v.DeleteSourceBranchOnMerge = p.DeleteSourceBranchOnMerge
+ }
+
return v
}
diff --git a/server/events/yaml/raw/repo_cfg.go b/server/events/yaml/raw/repo_cfg.go
index c8851e853f..3f90803bd5 100644
--- a/server/events/yaml/raw/repo_cfg.go
+++ b/server/events/yaml/raw/repo_cfg.go
@@ -19,15 +19,19 @@ const DefaultParallelPlan = false
// DefaultParallelPolicyCheck is the default setting for parallel plan
const DefaultParallelPolicyCheck = false
+// DefaultDeleteSourceBranchOnMerge being false is the default setting whether or not to remove a source branch on merge
+const DefaultDeleteSourceBranchOnMerge = false
+
// RepoCfg is the raw schema for repo-level atlantis.yaml config.
type RepoCfg struct {
- Version *int `yaml:"version,omitempty"`
- Projects []Project `yaml:"projects,omitempty"`
- Workflows map[string]Workflow `yaml:"workflows,omitempty"`
- PolicySets PolicySets `yaml:"policies,omitempty"`
- Automerge *bool `yaml:"automerge,omitempty"`
- ParallelApply *bool `yaml:"parallel_apply,omitempty"`
- ParallelPlan *bool `yaml:"parallel_plan,omitempty"`
+ Version *int `yaml:"version,omitempty"`
+ Projects []Project `yaml:"projects,omitempty"`
+ Workflows map[string]Workflow `yaml:"workflows,omitempty"`
+ PolicySets PolicySets `yaml:"policies,omitempty"`
+ Automerge *bool `yaml:"automerge,omitempty"`
+ ParallelApply *bool `yaml:"parallel_apply,omitempty"`
+ ParallelPlan *bool `yaml:"parallel_plan,omitempty"`
+ DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty"`
}
func (r RepoCfg) Validate() error {
@@ -75,12 +79,13 @@ func (r RepoCfg) ToValid() valid.RepoCfg {
}
return valid.RepoCfg{
- Version: *r.Version,
- Projects: validProjects,
- Workflows: validWorkflows,
- Automerge: automerge,
- ParallelApply: parallelApply,
- ParallelPlan: parallelPlan,
- ParallelPolicyCheck: parallelPlan,
+ Version: *r.Version,
+ Projects: validProjects,
+ Workflows: validWorkflows,
+ Automerge: automerge,
+ ParallelApply: parallelApply,
+ ParallelPlan: parallelPlan,
+ ParallelPolicyCheck: parallelPlan,
+ DeleteSourceBranchOnMerge: r.DeleteSourceBranchOnMerge,
}
}
diff --git a/server/events/yaml/valid/global_cfg.go b/server/events/yaml/valid/global_cfg.go
index f91ac4671d..dc8f1dcc89 100644
--- a/server/events/yaml/valid/global_cfg.go
+++ b/server/events/yaml/valid/global_cfg.go
@@ -19,6 +19,7 @@ const AllowedWorkflowsKey = "allowed_workflows"
const AllowedOverridesKey = "allowed_overrides"
const AllowCustomWorkflowsKey = "allow_custom_workflows"
const DefaultWorkflowName = "default"
+const DeleteSourceBranchOnMergeKey = "delete_source_branch_on_merge"
// NonOverrideableApplyReqs will get applied across all "repos" in the server side config.
// If repo config is allowed overrides, they can override this.
@@ -41,27 +42,29 @@ type Repo struct {
ID string
// IDRegex is the regex match for this config.
// If ID is set then this will be nil.
- IDRegex *regexp.Regexp
- BranchRegex *regexp.Regexp
- ApplyRequirements []string
- PreWorkflowHooks []*PreWorkflowHook
- Workflow *Workflow
- AllowedWorkflows []string
- AllowedOverrides []string
- AllowCustomWorkflows *bool
+ IDRegex *regexp.Regexp
+ BranchRegex *regexp.Regexp
+ ApplyRequirements []string
+ PreWorkflowHooks []*PreWorkflowHook
+ Workflow *Workflow
+ AllowedWorkflows []string
+ AllowedOverrides []string
+ AllowCustomWorkflows *bool
+ DeleteSourceBranchOnMerge *bool
}
type MergedProjectCfg struct {
- ApplyRequirements []string
- Workflow Workflow
- AllowedWorkflows []string
- RepoRelDir string
- Workspace string
- Name string
- AutoplanEnabled bool
- TerraformVersion *version.Version
- RepoCfgVersion int
- PolicySets PolicySets
+ ApplyRequirements []string
+ Workflow Workflow
+ AllowedWorkflows []string
+ RepoRelDir string
+ Workspace string
+ Name string
+ AutoplanEnabled bool
+ TerraformVersion *version.Version
+ RepoCfgVersion int
+ PolicySets PolicySets
+ DeleteSourceBranchOnMerge bool
}
// PreWorkflowHook is a map of custom run commands to run before workflows.
@@ -160,22 +163,24 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg {
}
allowCustomWorkflows := false
+ deleteSourceBranchOnMerge := false
if args.AllowRepoCfg {
- allowedOverrides = []string{ApplyRequirementsKey, WorkflowKey}
+ allowedOverrides = []string{ApplyRequirementsKey, WorkflowKey, DeleteSourceBranchOnMergeKey}
allowCustomWorkflows = true
}
return GlobalCfg{
Repos: []Repo{
{
- IDRegex: regexp.MustCompile(".*"),
- BranchRegex: regexp.MustCompile(".*"),
- ApplyRequirements: applyReqs,
- PreWorkflowHooks: args.PreWorkflowHooks,
- Workflow: &defaultWorkflow,
- AllowedWorkflows: allowedWorkflows,
- AllowedOverrides: allowedOverrides,
- AllowCustomWorkflows: &allowCustomWorkflows,
+ IDRegex: regexp.MustCompile(".*"),
+ BranchRegex: regexp.MustCompile(".*"),
+ ApplyRequirements: applyReqs,
+ PreWorkflowHooks: args.PreWorkflowHooks,
+ Workflow: &defaultWorkflow,
+ AllowedWorkflows: allowedWorkflows,
+ AllowedOverrides: allowedOverrides,
+ AllowCustomWorkflows: &allowCustomWorkflows,
+ DeleteSourceBranchOnMerge: &deleteSourceBranchOnMerge,
},
},
Workflows: map[string]Workflow{
@@ -211,7 +216,8 @@ func (r Repo) IDString() string {
// MergeProjectCfg merges proj and rCfg with the global config to return a
// final config. It assumes that all configs have been validated.
func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, proj Project, rCfg RepoCfg) MergedProjectCfg {
- applyReqs, workflow, allowedOverrides, allowCustomWorkflows := g.getMatchingCfg(log, repoID)
+ log.Debug("MergeProjectCfg started")
+ applyReqs, workflow, allowedOverrides, allowCustomWorkflows, deleteSourceBranchOnMerge := g.getMatchingCfg(log, repoID)
// If repos are allowed to override certain keys then override them.
for _, key := range allowedOverrides {
@@ -243,22 +249,38 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro
}
log.Debug("overriding server-defined %s with repo-specified workflow: %q", WorkflowKey, workflow.Name)
}
+ case DeleteSourceBranchOnMergeKey:
+ //We check whether the server configured value and repo-root level
+ //config is different. If it is then we change to the more granular.
+ if rCfg.DeleteSourceBranchOnMerge != nil && deleteSourceBranchOnMerge != *rCfg.DeleteSourceBranchOnMerge {
+ log.Debug("overriding server-defined %s with repo settings: [%t]", DeleteSourceBranchOnMergeKey, rCfg.DeleteSourceBranchOnMerge)
+ deleteSourceBranchOnMerge = *rCfg.DeleteSourceBranchOnMerge
+ }
+ //Then we check whether the more granular project based config is
+ //different. If it is then we set it.
+ if proj.DeleteSourceBranchOnMerge != nil && deleteSourceBranchOnMerge != *proj.DeleteSourceBranchOnMerge {
+ log.Debug("overriding repo-root-defined %s with repo settings: [%t]", DeleteSourceBranchOnMergeKey, *proj.DeleteSourceBranchOnMerge)
+ deleteSourceBranchOnMerge = *proj.DeleteSourceBranchOnMerge
+ }
+ log.Debug("merged deleteSourceBranchOnMerge: [%t]", deleteSourceBranchOnMerge)
}
+ log.Debug("MergeProjectCfg completed")
}
log.Debug("final settings: %s: [%s], %s: %s",
ApplyRequirementsKey, strings.Join(applyReqs, ","), WorkflowKey, workflow.Name)
return MergedProjectCfg{
- ApplyRequirements: applyReqs,
- Workflow: workflow,
- RepoRelDir: proj.Dir,
- Workspace: proj.Workspace,
- Name: proj.GetName(),
- AutoplanEnabled: proj.Autoplan.Enabled,
- TerraformVersion: proj.TerraformVersion,
- RepoCfgVersion: rCfg.Version,
- PolicySets: g.PolicySets,
+ ApplyRequirements: applyReqs,
+ Workflow: workflow,
+ RepoRelDir: proj.Dir,
+ Workspace: proj.Workspace,
+ Name: proj.GetName(),
+ AutoplanEnabled: proj.Autoplan.Enabled,
+ TerraformVersion: proj.TerraformVersion,
+ RepoCfgVersion: rCfg.Version,
+ PolicySets: g.PolicySets,
+ DeleteSourceBranchOnMerge: deleteSourceBranchOnMerge,
}
}
@@ -266,16 +288,17 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro
// repo with id repoID. It is used when there is no repo config.
func (g GlobalCfg) DefaultProjCfg(log logging.SimpleLogging, repoID string, repoRelDir string, workspace string) MergedProjectCfg {
log.Debug("building config based on server-side config")
- applyReqs, workflow, _, _ := g.getMatchingCfg(log, repoID)
+ applyReqs, workflow, _, _, deleteSourceBranchOnMerge := g.getMatchingCfg(log, repoID)
return MergedProjectCfg{
- ApplyRequirements: applyReqs,
- Workflow: workflow,
- RepoRelDir: repoRelDir,
- Workspace: workspace,
- Name: "",
- AutoplanEnabled: DefaultAutoPlanEnabled,
- TerraformVersion: nil,
- PolicySets: g.PolicySets,
+ ApplyRequirements: applyReqs,
+ Workflow: workflow,
+ RepoRelDir: repoRelDir,
+ Workspace: workspace,
+ Name: "",
+ AutoplanEnabled: DefaultAutoPlanEnabled,
+ TerraformVersion: nil,
+ PolicySets: g.PolicySets,
+ DeleteSourceBranchOnMerge: deleteSourceBranchOnMerge,
}
}
@@ -316,6 +339,9 @@ func (g GlobalCfg) ValidateRepoCfg(rCfg RepoCfg, repoID string) error {
if p.ApplyRequirements != nil && !sliceContainsF(allowedOverrides, ApplyRequirementsKey) {
return fmt.Errorf("repo config not allowed to set '%s' key: server-side config needs '%s: [%s]'", ApplyRequirementsKey, AllowedOverridesKey, ApplyRequirementsKey)
}
+ if p.DeleteSourceBranchOnMerge != nil && !sliceContainsF(allowedOverrides, DeleteSourceBranchOnMergeKey) {
+ return fmt.Errorf("repo config not allowed to set '%s' key: server-side config needs '%s: [%s]'", DeleteSourceBranchOnMergeKey, AllowedOverridesKey, DeleteSourceBranchOnMergeKey)
+ }
}
// Check custom workflows.
@@ -374,7 +400,7 @@ func (g GlobalCfg) ValidateRepoCfg(rCfg RepoCfg, repoID string) error {
}
// getMatchingCfg returns the key settings for repoID.
-func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (applyReqs []string, workflow Workflow, allowedOverrides []string, allowCustomWorkflows bool) {
+func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (applyReqs []string, workflow Workflow, allowedOverrides []string, allowCustomWorkflows bool, deleteSourceBranchOnMerge bool) {
toLog := make(map[string]string)
traceF := func(repoIdx int, repoID string, key string, val interface{}) string {
from := "default server config"
@@ -396,7 +422,7 @@ func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (app
return fmt.Sprintf("setting %s: %s from %s", key, valStr, from)
}
- for _, key := range []string{ApplyRequirementsKey, WorkflowKey, AllowedOverridesKey, AllowCustomWorkflowsKey, PreWorkflowHooksKey} {
+ for _, key := range []string{ApplyRequirementsKey, WorkflowKey, AllowedOverridesKey, AllowCustomWorkflowsKey, DeleteSourceBranchOnMergeKey} {
for i, repo := range g.Repos {
if repo.IDMatches(repoID) {
switch key {
@@ -420,6 +446,11 @@ func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (app
toLog[AllowCustomWorkflowsKey] = traceF(i, repo.IDString(), AllowCustomWorkflowsKey, *repo.AllowCustomWorkflows)
allowCustomWorkflows = *repo.AllowCustomWorkflows
}
+ case DeleteSourceBranchOnMergeKey:
+ if repo.DeleteSourceBranchOnMerge != nil {
+ toLog[DeleteSourceBranchOnMergeKey] = traceF(i, repo.IDString(), DeleteSourceBranchOnMergeKey, *repo.DeleteSourceBranchOnMerge)
+ deleteSourceBranchOnMerge = *repo.DeleteSourceBranchOnMerge
+ }
}
}
}
diff --git a/server/events/yaml/valid/global_cfg_test.go b/server/events/yaml/valid/global_cfg_test.go
index 66628de9bb..64807e402f 100644
--- a/server/events/yaml/valid/global_cfg_test.go
+++ b/server/events/yaml/valid/global_cfg_test.go
@@ -49,13 +49,14 @@ func TestNewGlobalCfg(t *testing.T) {
baseCfg := valid.GlobalCfg{
Repos: []valid.Repo{
{
- IDRegex: regexp.MustCompile(".*"),
- BranchRegex: regexp.MustCompile(".*"),
- ApplyRequirements: []string{},
- Workflow: &expDefaultWorkflow,
- AllowedWorkflows: []string{},
- AllowedOverrides: []string{},
- AllowCustomWorkflows: Bool(false),
+ IDRegex: regexp.MustCompile(".*"),
+ BranchRegex: regexp.MustCompile(".*"),
+ ApplyRequirements: []string{},
+ Workflow: &expDefaultWorkflow,
+ AllowedWorkflows: []string{},
+ AllowedOverrides: []string{},
+ AllowCustomWorkflows: Bool(false),
+ DeleteSourceBranchOnMerge: Bool(false),
},
},
Workflows: map[string]valid.Workflow{
@@ -113,7 +114,7 @@ func TestNewGlobalCfg(t *testing.T) {
if c.allowRepoCfg {
exp.Repos[0].AllowCustomWorkflows = Bool(true)
- exp.Repos[0].AllowedOverrides = []string{"apply_requirements", "workflow"}
+ exp.Repos[0].AllowedOverrides = []string{"apply_requirements", "workflow", "delete_source_branch_on_merge"}
}
if c.mergeableReq {
exp.Repos[0].ApplyRequirements = append(exp.Repos[0].ApplyRequirements, "mergeable")
diff --git a/server/events/yaml/valid/repo_cfg.go b/server/events/yaml/valid/repo_cfg.go
index 6af60141f7..b107c06b41 100644
--- a/server/events/yaml/valid/repo_cfg.go
+++ b/server/events/yaml/valid/repo_cfg.go
@@ -13,14 +13,15 @@ import (
// RepoCfg is the atlantis.yaml config after it's been parsed and validated.
type RepoCfg struct {
// Version is the version of the atlantis YAML file.
- Version int
- Projects []Project
- Workflows map[string]Workflow
- PolicySets PolicySets
- Automerge bool
- ParallelApply bool
- ParallelPlan bool
- ParallelPolicyCheck bool
+ Version int
+ Projects []Project
+ Workflows map[string]Workflow
+ PolicySets PolicySets
+ Automerge bool
+ ParallelApply bool
+ ParallelPlan bool
+ ParallelPolicyCheck bool
+ DeleteSourceBranchOnMerge *bool
}
func (r RepoCfg) FindProjectsByDirWorkspace(repoRelDir string, workspace string) []Project {
@@ -98,13 +99,14 @@ func (r RepoCfg) ValidateWorkspaceAllowed(repoRelDir string, workspace string) e
}
type Project struct {
- Dir string
- Workspace string
- Name *string
- WorkflowName *string
- TerraformVersion *version.Version
- Autoplan Autoplan
- ApplyRequirements []string
+ Dir string
+ Workspace string
+ Name *string
+ WorkflowName *string
+ TerraformVersion *version.Version
+ Autoplan Autoplan
+ ApplyRequirements []string
+ DeleteSourceBranchOnMerge *bool
}
// GetName returns the name of the project or an empty string if there is no
diff --git a/server/events_controller_e2e_test.go b/server/events_controller_e2e_test.go
index e72829abd9..b6c262329b 100644
--- a/server/events_controller_e2e_test.go
+++ b/server/events_controller_e2e_test.go
@@ -445,9 +445,9 @@ func TestGitHubWorkflow(t *testing.T) {
if c.ExpAutomerge {
// Verify that the merge API call was made.
- vcsClient.VerifyWasCalledOnce().MergePull(matchers.AnyModelsPullRequest())
+ vcsClient.VerifyWasCalledOnce().MergePull(matchers.AnyModelsPullRequest(), matchers.AnyModelsPullRequestOptions())
} else {
- vcsClient.VerifyWasCalled(Never()).MergePull(matchers.AnyModelsPullRequest())
+ vcsClient.VerifyWasCalled(Never()).MergePull(matchers.AnyModelsPullRequest(), matchers.AnyModelsPullRequestOptions())
}
})
}
@@ -617,9 +617,9 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) {
if c.ExpAutomerge {
// Verify that the merge API call was made.
- vcsClient.VerifyWasCalledOnce().MergePull(matchers.AnyModelsPullRequest())
+ vcsClient.VerifyWasCalledOnce().MergePull(matchers.AnyModelsPullRequest(), matchers.AnyModelsPullRequestOptions())
} else {
- vcsClient.VerifyWasCalled(Never()).MergePull(matchers.AnyModelsPullRequest())
+ vcsClient.VerifyWasCalled(Never()).MergePull(matchers.AnyModelsPullRequest(), matchers.AnyModelsPullRequestOptions())
}
})
}