diff --git a/cmd/server.go b/cmd/server.go
index 19ed98763e..5aeecbb909 100644
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -72,6 +72,7 @@ const (
DisableApplyAllFlag = "disable-apply-all"
DisableApplyFlag = "disable-apply"
DisableAutoplanFlag = "disable-autoplan"
+ DisableAutoplanLabelFlag = "disable-autoplan-label"
DisableMarkdownFoldingFlag = "disable-markdown-folding"
DisableRepoLockingFlag = "disable-repo-locking"
DiscardApprovalOnPlanFlag = "discard-approval-on-plan"
@@ -257,6 +258,10 @@ var stringFlags = map[string]stringFlag{
description: "Path to directory to store Atlantis data.",
defaultValue: DefaultDataDir,
},
+ DisableAutoplanLabelFlag: {
+ description: "Pull request label to disable atlantis auto planning feature only if present.",
+ defaultValue: "",
+ },
EmojiReaction: {
description: "Emoji Reaction to use to react to comments",
defaultValue: DefaultEmojiReaction,
diff --git a/cmd/server_test.go b/cmd/server_test.go
index a597fced70..b5bfba2574 100644
--- a/cmd/server_test.go
+++ b/cmd/server_test.go
@@ -115,6 +115,7 @@ var testFlags = map[string]interface{}{
VCSStatusName: "my-status",
WriteGitCredsFlag: true,
DisableAutoplanFlag: true,
+ DisableAutoplanLabelFlag: "no-auto-plan",
EnablePolicyChecksFlag: false,
EnableRegExpCmdFlag: false,
EnableDiffMarkdownFormat: false,
diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md
index 2e9cfe52fd..e4370c7046 100644
--- a/runatlantis.io/docs/server-configuration.md
+++ b/runatlantis.io/docs/server-configuration.md
@@ -355,6 +355,16 @@ and set `--autoplan-modules` to `false`.
```
Disable atlantis auto planning.
+### `--disable-autoplan-label`
+ ```bash
+ atlantis server --disable-autoplan-label="no-autoplan"
+ # or
+ ATLANTIS_DISABLE_AUTOPLAN_LABEL="no-autoplan"
+ ```
+ Disable atlantis auto planning only on pull requests with the specified label.
+
+ If `disable-autoplan` property is `true`, this flag has no effect.
+
### `--disable-markdown-folding`
```bash
atlantis server --disable-markdown-folding
diff --git a/server/events/command_runner.go b/server/events/command_runner.go
index a61d63ee20..24d697392b 100644
--- a/server/events/command_runner.go
+++ b/server/events/command_runner.go
@@ -15,6 +15,7 @@ package events
import (
"fmt"
+ "github.com/runatlantis/atlantis/server/utils"
"strconv"
"github.com/google/go-github/v54/github"
@@ -98,6 +99,7 @@ type DefaultCommandRunner struct {
GitlabMergeRequestGetter GitlabMergeRequestGetter
// User config option: Disables autoplan when a pull request is opened or updated.
DisableAutoplan bool
+ DisableAutoplanLabel string
EventParser EventParsing
// User config option: Fail and do not run the Atlantis command request if any of the pre workflow hooks error
FailOnPreWorkflowHookError bool
@@ -165,6 +167,14 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
if c.DisableAutoplan {
return
}
+ if len(c.DisableAutoplanLabel) > 0 {
+ labels, err := c.VCSClient.GetPullLabels(baseRepo, pull)
+ if err != nil {
+ ctx.Log.Err("Unable to get pull labels. Proceeding with %s command.", err, command.Plan)
+ } else if utils.SlicesContains(labels, c.DisableAutoplanLabel) {
+ return
+ }
+ }
cmd := &CommentCommand{
Name: command.Autoplan,
diff --git a/server/events/command_runner_test.go b/server/events/command_runner_test.go
index 90995ba296..59b6a6b21d 100644
--- a/server/events/command_runner_test.go
+++ b/server/events/command_runner_test.go
@@ -496,9 +496,11 @@ func TestRunCommentCommand_DisableApplyAllDisabled(t *testing.T) {
vcsClient.VerifyWasCalledOnce().CreateComment(testdata.GithubRepo, modelPull.Num, "**Error:** Running `atlantis apply` without flags is disabled. You must specify which project to apply via the `-d
`, `-w ` or `-p ` flags.", "apply")
}
-func TestRunCommentCommand_DisableDisableAutoplan(t *testing.T) {
- t.Log("if \"DisableAutoplan is true\" are disabled and we are silencing return and do not comment with error")
+func TestRunCommentCommand_DisableAutoplan(t *testing.T) {
+ t.Log("if \"DisableAutoplan\" is true, auto plans are disabled and we are silencing return and do not comment with error")
setup(t)
+ modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, BaseBranch: "main"}
+
ch.DisableAutoplan = true
defer func() { ch.DisableAutoplan = false }()
@@ -512,8 +514,56 @@ func TestRunCommentCommand_DisableDisableAutoplan(t *testing.T) {
},
}, nil)
- ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, testdata.Pull, testdata.User)
+ ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, modelPull, testdata.User)
+ projectCommandBuilder.VerifyWasCalled(Never()).BuildAutoplanCommands(Any[*command.Context]())
+}
+
+func TestRunCommentCommand_DisableAutoplanLabel(t *testing.T) {
+ t.Log("if \"DisableAutoplanLabel\" is present and pull request has that label, auto plans are disabled and we are silencing return and do not comment with error")
+ vcsClient := setup(t)
+ modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, BaseBranch: "main"}
+
+ ch.DisableAutoplanLabel = "disable-auto-plan"
+ defer func() { ch.DisableAutoplanLabel = "" }()
+
+ When(projectCommandBuilder.BuildAutoplanCommands(Any[*command.Context]())).
+ ThenReturn([]command.ProjectContext{
+ {
+ CommandName: command.Plan,
+ },
+ {
+ CommandName: command.Plan,
+ },
+ }, nil)
+ When(ch.VCSClient.GetPullLabels(testdata.GithubRepo, modelPull)).ThenReturn([]string{"disable-auto-plan", "need-help"}, nil)
+
+ ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, modelPull, testdata.User)
projectCommandBuilder.VerifyWasCalled(Never()).BuildAutoplanCommands(Any[*command.Context]())
+ vcsClient.VerifyWasCalledOnce().GetPullLabels(testdata.GithubRepo, modelPull)
+}
+
+func TestRunCommentCommand_DisableAutoplanLabel_PullNotLabeled(t *testing.T) {
+ t.Log("if \"DisableAutoplanLabel\" is present but pull request doesn't have that label, auto plans run")
+ vcsClient := setup(t)
+ modelPull := models.PullRequest{BaseRepo: testdata.GithubRepo, BaseBranch: "main"}
+
+ ch.DisableAutoplanLabel = "disable-auto-plan"
+ defer func() { ch.DisableAutoplanLabel = "" }()
+
+ When(projectCommandBuilder.BuildAutoplanCommands(Any[*command.Context]())).
+ ThenReturn([]command.ProjectContext{
+ {
+ CommandName: command.Plan,
+ },
+ {
+ CommandName: command.Plan,
+ },
+ }, nil)
+ When(ch.VCSClient.GetPullLabels(testdata.GithubRepo, modelPull)).ThenReturn(nil, nil)
+
+ ch.RunAutoplanCommand(testdata.GithubRepo, testdata.GithubRepo, modelPull, testdata.User)
+ projectCommandBuilder.VerifyWasCalled(Once()).BuildAutoplanCommands(Any[*command.Context]())
+ vcsClient.VerifyWasCalledOnce().GetPullLabels(testdata.GithubRepo, modelPull)
}
func TestRunCommentCommand_ClosedPull(t *testing.T) {
diff --git a/server/events/vcs/azuredevops_client.go b/server/events/vcs/azuredevops_client.go
index 2d48e5aba2..5ff3f3ff38 100644
--- a/server/events/vcs/azuredevops_client.go
+++ b/server/events/vcs/azuredevops_client.go
@@ -424,3 +424,7 @@ func GitStatusContextFromSrc(src string) *azuredevops.GitStatusContext {
func (g *AzureDevopsClient) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) {
return "", fmt.Errorf("not yet implemented")
}
+
+func (g *AzureDevopsClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ return nil, fmt.Errorf("not yet implemented")
+}
diff --git a/server/events/vcs/bitbucketcloud/client.go b/server/events/vcs/bitbucketcloud/client.go
index 38179590fa..fa1751db19 100644
--- a/server/events/vcs/bitbucketcloud/client.go
+++ b/server/events/vcs/bitbucketcloud/client.go
@@ -281,3 +281,7 @@ func (b *Client) GetFileContent(pull models.PullRequest, fileName string) (bool,
func (b *Client) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) {
return "", fmt.Errorf("not yet implemented")
}
+
+func (b *Client) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ return nil, fmt.Errorf("not yet implemented")
+}
diff --git a/server/events/vcs/bitbucketserver/client.go b/server/events/vcs/bitbucketserver/client.go
index 6df5c7c9a2..943fd3b6c8 100644
--- a/server/events/vcs/bitbucketserver/client.go
+++ b/server/events/vcs/bitbucketserver/client.go
@@ -365,3 +365,7 @@ func (b *Client) GetFileContent(pull models.PullRequest, fileName string) (bool,
func (b *Client) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) {
return "", fmt.Errorf("not yet implemented")
}
+
+func (b *Client) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ return nil, fmt.Errorf("not yet implemented")
+}
diff --git a/server/events/vcs/client.go b/server/events/vcs/client.go
index 6ab283c8a7..dd2e489f6f 100644
--- a/server/events/vcs/client.go
+++ b/server/events/vcs/client.go
@@ -49,4 +49,7 @@ type Client interface {
GetFileContent(pull models.PullRequest, fileName string) (bool, []byte, error)
SupportsSingleFileDownload(repo models.Repo) bool
GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error)
+
+ // GetPullLabels returns the labels of a pull request
+ GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error)
}
diff --git a/server/events/vcs/github_client.go b/server/events/vcs/github_client.go
index 608deda5d7..76ad3bf421 100644
--- a/server/events/vcs/github_client.go
+++ b/server/events/vcs/github_client.go
@@ -724,3 +724,18 @@ func (g *GithubClient) GetCloneURL(VCSHostType models.VCSHostType, repo string)
}
return repository.GetCloneURL(), nil
}
+
+func (g *GithubClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ pullDetails, _, err := g.client.PullRequests.Get(g.ctx, repo.Owner, repo.Name, pull.Num)
+ if err != nil {
+ return nil, err
+ }
+
+ var labels []string
+
+ for _, label := range pullDetails.Labels {
+ labels = append(labels, *label.Name)
+ }
+
+ return labels, nil
+}
diff --git a/server/events/vcs/github_client_test.go b/server/events/vcs/github_client_test.go
index e42e353027..4f2f8fd3e7 100644
--- a/server/events/vcs/github_client_test.go
+++ b/server/events/vcs/github_client_test.go
@@ -1474,3 +1474,110 @@ func TestGithubClient_DiscardReviews(t *testing.T) {
})
}
}
+
+func TestGithubClient_GetPullLabels(t *testing.T) {
+ logger := logging.NewNoopLogger(t)
+ resp := `{
+ "url": "https://api.github.com/repos/runatlantis/atlantis/pulls/1",
+ "id": 167530667,
+ "merge_commit_sha": "3fe6aa34bc25ac3720e639fcad41b428e83bdb37",
+ "labels": [
+ {
+ "id": 1303230720,
+ "node_id": "MDU6TGFiZWwxMzAzMjMwNzIw",
+ "url": "https://api.github.com/repos/runatlantis/atlantis/labels/docs",
+ "name": "docs",
+ "color": "d87165",
+ "default": false,
+ "description": "Documentation"
+ },
+ {
+ "id": 2552271640,
+ "node_id": "MDU6TGFiZWwyNTUyMjcxNjQw",
+ "url": "https://api.github.com/repos/runatlantis/atlantis/labels/go",
+ "name": "go",
+ "color": "16e2e2",
+ "default": false,
+ "description": "Pull requests that update Go code"
+ },
+ {
+ "id": 2696098981,
+ "node_id": "MDU6TGFiZWwyNjk2MDk4OTgx",
+ "url": "https://api.github.com/repos/runatlantis/atlantis/labels/needs%20tests",
+ "name": "needs tests",
+ "color": "FBB1DE",
+ "default": false,
+ "description": "Change requires tests"
+ },
+ {
+ "id": 4439792681,
+ "node_id": "LA_kwDOBy76Zc8AAAABCKHcKQ",
+ "url": "https://api.github.com/repos/runatlantis/atlantis/labels/work-in-progress",
+ "name": "work-in-progress",
+ "color": "B1E20A",
+ "default": false,
+ "description": ""
+ }
+ ]
+ }`
+ testServer := httptest.NewTLSServer(
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.RequestURI {
+ case "/api/v3/repos/runatlantis/atlantis/pulls/1":
+ w.Write([]byte(resp)) // nolint: errcheck
+ default:
+ t.Errorf("got unexpected request at %q", r.RequestURI)
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ }))
+ testServerURL, err := url.Parse(testServer.URL)
+ Ok(t, err)
+ client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logger)
+ Ok(t, err)
+ defer disableSSLVerification()()
+
+ labels, err := client.GetPullLabels(models.Repo{
+ Owner: "runatlantis",
+ Name: "atlantis",
+ }, models.PullRequest{
+ Num: 1,
+ })
+ Ok(t, err)
+ Equals(t, []string{"docs", "go", "needs tests", "work-in-progress"}, labels)
+}
+
+func TestGithubClient_GetPullLabels_EmptyResponse(t *testing.T) {
+ logger := logging.NewNoopLogger(t)
+ resp := `{
+ "url": "https://api.github.com/repos/runatlantis/atlantis/pulls/1",
+ "id": 167530667,
+ "merge_commit_sha": "3fe6aa34bc25ac3720e639fcad41b428e83bdb37",
+ "labels": []
+ }`
+ testServer := httptest.NewTLSServer(
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.RequestURI {
+ case "/api/v3/repos/runatlantis/atlantis/pulls/1":
+ w.Write([]byte(resp)) // nolint: errcheck
+ default:
+ t.Errorf("got unexpected request at %q", r.RequestURI)
+ http.Error(w, "not found", http.StatusNotFound)
+ return
+ }
+ }))
+ testServerURL, err := url.Parse(testServer.URL)
+ Ok(t, err)
+ client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logger)
+ Ok(t, err)
+ defer disableSSLVerification()()
+
+ labels, err := client.GetPullLabels(models.Repo{
+ Owner: "runatlantis",
+ Name: "atlantis",
+ }, models.PullRequest{
+ Num: 1,
+ })
+ Ok(t, err)
+ Equals(t, 0, len(labels))
+}
diff --git a/server/events/vcs/gitlab_client.go b/server/events/vcs/gitlab_client.go
index 030629c8b8..b98c4513c8 100644
--- a/server/events/vcs/gitlab_client.go
+++ b/server/events/vcs/gitlab_client.go
@@ -515,3 +515,13 @@ func (g *GitlabClient) GetCloneURL(VCSHostType models.VCSHostType, repo string)
}
return project.HTTPURLToRepo, nil
}
+
+func (g *GitlabClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ mr, _, err := g.Client.MergeRequests.GetMergeRequest(repo.FullName, pull.Num, nil)
+
+ if err != nil {
+ return nil, err
+ }
+
+ return mr.Labels, nil
+}
diff --git a/server/events/vcs/gitlab_client_test.go b/server/events/vcs/gitlab_client_test.go
index a7f1a77be6..fdadf5da8b 100644
--- a/server/events/vcs/gitlab_client_test.go
+++ b/server/events/vcs/gitlab_client_test.go
@@ -604,6 +604,65 @@ func TestGitlabClient_HideOldComments(t *testing.T) {
Equals(t, summaryFooter, gotNotePutCalls[1].comment[2])
}
+func TestGithubClient_GetPullLabels(t *testing.T) {
+ var mergeSuccessWithLabel = strings.ReplaceAll(mergeSuccess, `"labels":[]`, `"labels":["work in progress"]`)
+ testServer := httptest.NewServer(
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.RequestURI {
+ case "/api/v4/projects/runatlantis%2Fatlantis/merge_requests/1":
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(mergeSuccessWithLabel)) // nolint: errcheck
+ default:
+ t.Errorf("got unexpected request at %q", r.RequestURI)
+ http.Error(w, "not found", http.StatusNotFound)
+ }
+ }))
+
+ internalClient, err := gitlab.NewClient("token", gitlab.WithBaseURL(testServer.URL))
+ Ok(t, err)
+ client := &GitlabClient{
+ Client: internalClient,
+ Version: nil,
+ }
+
+ labels, err := client.GetPullLabels(models.Repo{
+ FullName: "runatlantis/atlantis",
+ }, models.PullRequest{
+ Num: 1,
+ })
+ Ok(t, err)
+ Equals(t, []string{"work in progress"}, labels)
+}
+
+func TestGithubClient_GetPullLabels_EmptyResponse(t *testing.T) {
+ testServer := httptest.NewServer(
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.RequestURI {
+ case "/api/v4/projects/runatlantis%2Fatlantis/merge_requests/1":
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(pipelineSuccess)) // nolint: errcheck
+ default:
+ t.Errorf("got unexpected request at %q", r.RequestURI)
+ http.Error(w, "not found", http.StatusNotFound)
+ }
+ }))
+
+ internalClient, err := gitlab.NewClient("token", gitlab.WithBaseURL(testServer.URL))
+ Ok(t, err)
+ client := &GitlabClient{
+ Client: internalClient,
+ Version: nil,
+ }
+
+ labels, err := client.GetPullLabels(models.Repo{
+ FullName: "runatlantis/atlantis",
+ }, models.PullRequest{
+ Num: 1,
+ })
+ Ok(t, err)
+ Equals(t, 0, len(labels))
+}
+
var mergeSuccess = `{"id":22461274,"iid":13,"project_id":4580910,"title":"Update main.tf","description":"","state":"merged","created_at":"2019-01-15T18:27:29.375Z","updated_at":"2019-01-25T17:28:01.437Z","merged_by":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"merged_at":"2019-01-25T17:28:01.459Z","closed_by":null,"closed_at":null,"target_branch":"patch-1","source_branch":"patch-1-merger","upvotes":0,"downvotes":0,"author":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"assignee":null,"source_project_id":4580910,"target_project_id":4580910,"labels":[],"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","detailed_merge_status":"mergeable","sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha":"c9b336f1c71d3e64810b8cfa2abcfab232d6bff6","user_notes_count":0,"discussion_locked":null,"should_remove_source_branch":null,"force_remove_source_branch":false,"web_url":"https://gitlab.com/lkysow/atlantis-example/merge_requests/13","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":false,"subscribed":true,"changes_count":"1","latest_build_started_at":null,"latest_build_finished_at":null,"first_deployed_to_production_at":null,"pipeline":null,"diff_refs":{"base_sha":"67cb91d3f6198189f433c045154a885784ba6977","head_sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","start_sha":"67cb91d3f6198189f433c045154a885784ba6977"},"merge_error":null,"approvals_before_merge":null}`
var pipelineSuccess = `{"id": 22461274,"iid": 13,"project_id": 4580910,"title": "Update main.tf","description": "","state": "opened","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","merged_by": null,"merged_at": null,"closed_by": null,"closed_at": null,"target_branch": "patch-1","source_branch": "patch-1-merger","user_notes_count": 0,"upvotes": 0,"downvotes": 0,"author": {"id": 1755902,"name": "Luke Kysow","username": "lkysow","state": "active","avatar_url": "https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url": "https://gitlab.com/lkysow"},"assignee": null,"reviewers": [],"source_project_id": 4580910,"target_project_id": 4580910,"labels": [],"work_in_progress": false,"milestone": null,"merge_when_pipeline_succeeds": false,"merge_status": "can_be_merged","detailed_merge_status": "mergeable","sha": "cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha": null,"squash_commit_sha": null,"discussion_locked": null,"should_remove_source_branch": null,"force_remove_source_branch": true,"reference": "!13","references": {"short": "!13","relative": "!13","full": "lkysow/atlantis-example!13"},"web_url": "https://gitlab.com/lkysow/atlantis-example/merge_requests/13","time_stats": {"time_estimate": 0,"total_time_spent": 0,"human_time_estimate": null,"human_total_time_spent": null},"squash": true,"task_completion_status": {"count": 0,"completed_count": 0},"has_conflicts": false,"blocking_discussions_resolved": true,"approvals_before_merge": null,"subscribed": false,"changes_count": "1","latest_build_started_at": "2019-01-15T18:27:29.375Z","latest_build_finished_at": "2019-01-25T17:28:01.437Z","first_deployed_to_production_at": null,"pipeline": {"id": 488598,"sha": "67cb91d3f6198189f433c045154a885784ba6977","ref": "patch-1-merger","status": "success","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","web_url": "https://gitlab.com/lkysow/atlantis-example/-/pipelines/488598"},"head_pipeline": {"id": 488598,"sha": "67cb91d3f6198189f433c045154a885784ba6977","ref": "patch-1-merger","status": "success","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","web_url": "https://gitlab.com/lkysow/atlantis-example/-/pipelines/488598","before_sha": "0000000000000000000000000000000000000000","tag": false,"yaml_errors": null,"user": {"id": 1755902,"name": "Luke Kysow","username": "lkysow","state": "active","avatar_url": "https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url": "https://gitlab.com/lkysow"},"started_at": "2019-01-15T18:27:29.375Z","finished_at": "2019-01-25T17:28:01.437Z","committed_at": null,"duration": 31,"coverage": null,"detailed_status": {"icon": "status_success","text": "passed","label": "passed","group": "success","tooltip": "passed","has_details": true,"details_path": "/lkysow/atlantis-example/-/pipelines/488598","illustration": null,"favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"}},"diff_refs": {"base_sha": "67cb91d3f6198189f433c045154a885784ba6977","head_sha": "cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","start_sha": "67cb91d3f6198189f433c045154a885784ba6977"},"merge_error": null,"first_contribution": false,"user": {"can_merge": true}}`
var projectSuccess = `{"id": 4580910,"description": "","name": "atlantis-example","name_with_namespace": "lkysow / atlantis-example","path": "atlantis-example","path_with_namespace": "lkysow/atlantis-example","created_at": "2018-04-30T13:44:28.367Z","default_branch": "patch-1","tag_list": [],"ssh_url_to_repo": "git@gitlab.com:lkysow/atlantis-example.git","http_url_to_repo": "https://gitlab.com/lkysow/atlantis-example.git","web_url": "https://gitlab.com/lkysow/atlantis-example","readme_url": "https://gitlab.com/lkysow/atlantis-example/-/blob/main/README.md","avatar_url": "https://gitlab.com/uploads/-/system/project/avatar/4580910/avatar.png","forks_count": 0,"star_count": 7,"last_activity_at": "2021-06-29T21:10:43.968Z","namespace": {"id": 1,"name": "lkysow","path": "lkysow","kind": "group","full_path": "lkysow","parent_id": 1,"avatar_url": "/uploads/-/system/group/avatar/1651/platform.png","web_url": "https://gitlab.com/groups/lkysow"},"_links": {"self": "https://gitlab.com/api/v4/projects/4580910","issues": "https://gitlab.com/api/v4/projects/4580910/issues","merge_requests": "https://gitlab.com/api/v4/projects/4580910/merge_requests","repo_branches": "https://gitlab.com/api/v4/projects/4580910/repository/branches","labels": "https://gitlab.com/api/v4/projects/4580910/labels","events": "https://gitlab.com/api/v4/projects/4580910/events","members": "https://gitlab.com/api/v4/projects/4580910/members"},"packages_enabled": false,"empty_repo": false,"archived": false,"visibility": "private","resolve_outdated_diff_discussions": false,"container_registry_enabled": false,"container_expiration_policy": {"cadence": "1d","enabled": false,"keep_n": 10,"older_than": "90d","name_regex": ".*","name_regex_keep": null,"next_run_at": "2021-05-01T13:44:28.397Z"},"issues_enabled": true,"merge_requests_enabled": true,"wiki_enabled": false,"jobs_enabled": true,"snippets_enabled": true,"service_desk_enabled": false,"service_desk_address": null,"can_create_merge_request_in": true,"issues_access_level": "private","repository_access_level": "enabled","merge_requests_access_level": "enabled","forking_access_level": "enabled","wiki_access_level": "disabled","builds_access_level": "enabled","snippets_access_level": "enabled","pages_access_level": "private","operations_access_level": "disabled","analytics_access_level": "enabled","emails_disabled": null,"shared_runners_enabled": true,"lfs_enabled": false,"creator_id": 818,"import_status": "none","import_error": null,"open_issues_count": 0,"runners_token": "1234456","ci_default_git_depth": 50,"ci_forward_deployment_enabled": true,"public_jobs": true,"build_git_strategy": "fetch","build_timeout": 3600,"auto_cancel_pending_pipelines": "enabled","build_coverage_regex": null,"ci_config_path": "","shared_with_groups": [],"only_allow_merge_if_pipeline_succeeds": true,"allow_merge_on_skipped_pipeline": false,"restrict_user_defined_variables": false,"request_access_enabled": true,"only_allow_merge_if_all_discussions_are_resolved": true,"remove_source_branch_after_merge": true,"printing_merge_request_link_enabled": true,"merge_method": "merge","suggestion_commit_message": "","auto_devops_enabled": false,"auto_devops_deploy_strategy": "continuous","autoclose_referenced_issues": true,"repository_storage": "default","approvals_before_merge": 0,"mirror": false,"external_authorization_classification_label": null,"marked_for_deletion_at": null,"marked_for_deletion_on": null,"requirements_enabled": false,"compliance_frameworks": [],"permissions": {"project_access": null,"group_access": {"access_level": 50,"notification_level": 3}}}`
diff --git a/server/events/vcs/mocks/mock_client.go b/server/events/vcs/mocks/mock_client.go
index 9f80f46def..7583e22fac 100644
--- a/server/events/vcs/mocks/mock_client.go
+++ b/server/events/vcs/mocks/mock_client.go
@@ -116,6 +116,25 @@ func (mock *MockClient) GetModifiedFiles(repo models.Repo, pull models.PullReque
return ret0, ret1
}
+func (mock *MockClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ if mock == nil {
+ panic("mock must not be nil. Use myMock := NewMockClient().")
+ }
+ params := []pegomock.Param{repo, pull}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("GetPullLabels", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 []string
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].([]string)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
func (mock *MockClient) GetTeamNamesForUser(repo models.Repo, user models.User) ([]string, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClient().")
@@ -467,6 +486,37 @@ func (c *MockClient_GetModifiedFiles_OngoingVerification) GetAllCapturedArgument
return
}
+func (verifier *VerifierMockClient) GetPullLabels(repo models.Repo, pull models.PullRequest) *MockClient_GetPullLabels_OngoingVerification {
+ params := []pegomock.Param{repo, pull}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetPullLabels", params, verifier.timeout)
+ return &MockClient_GetPullLabels_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type MockClient_GetPullLabels_OngoingVerification struct {
+ mock *MockClient
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *MockClient_GetPullLabels_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) {
+ repo, pull := c.GetAllCapturedArguments()
+ return repo[len(repo)-1], pull[len(pull)-1]
+}
+
+func (c *MockClient_GetPullLabels_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(c.methodInvocations))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]models.PullRequest, len(c.methodInvocations))
+ for u, param := range params[1] {
+ _param1[u] = param.(models.PullRequest)
+ }
+ }
+ return
+}
+
func (verifier *VerifierMockClient) GetTeamNamesForUser(repo models.Repo, user models.User) *MockClient_GetTeamNamesForUser_OngoingVerification {
params := []pegomock.Param{repo, user}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetTeamNamesForUser", params, verifier.timeout)
diff --git a/server/events/vcs/not_configured_vcs_client.go b/server/events/vcs/not_configured_vcs_client.go
index 8ab7e03e89..6ce6f2da56 100644
--- a/server/events/vcs/not_configured_vcs_client.go
+++ b/server/events/vcs/not_configured_vcs_client.go
@@ -73,3 +73,7 @@ func (a *NotConfiguredVCSClient) GetFileContent(pull models.PullRequest, fileNam
func (a *NotConfiguredVCSClient) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) {
return "", a.err()
}
+
+func (a *NotConfiguredVCSClient) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ return nil, a.err()
+}
diff --git a/server/events/vcs/proxy.go b/server/events/vcs/proxy.go
index 64fb8fa8ef..25637bcd0f 100644
--- a/server/events/vcs/proxy.go
+++ b/server/events/vcs/proxy.go
@@ -107,3 +107,7 @@ func (d *ClientProxy) SupportsSingleFileDownload(repo models.Repo) bool {
func (d *ClientProxy) GetCloneURL(VCSHostType models.VCSHostType, repo string) (string, error) {
return d.clients[VCSHostType].GetCloneURL(VCSHostType, repo)
}
+
+func (d *ClientProxy) GetPullLabels(repo models.Repo, pull models.PullRequest) ([]string, error) {
+ return d.clients[repo.VCSHost.Type].GetPullLabels(repo, pull)
+}
diff --git a/server/server.go b/server/server.go
index a2410a1315..1fbbce19f3 100644
--- a/server/server.go
+++ b/server/server.go
@@ -805,6 +805,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
SilenceForkPRErrors: userConfig.SilenceForkPRErrors,
SilenceForkPRErrorsFlag: config.SilenceForkPRErrorsFlag,
DisableAutoplan: userConfig.DisableAutoplan,
+ DisableAutoplanLabel: userConfig.DisableAutoplanLabel,
Drainer: drainer,
PreWorkflowHooksCommandRunner: preWorkflowHooksCommandRunner,
PostWorkflowHooksCommandRunner: postWorkflowHooksCommandRunner,
diff --git a/server/user_config.go b/server/user_config.go
index edfc6dd1da..81fb7bef7a 100644
--- a/server/user_config.go
+++ b/server/user_config.go
@@ -34,6 +34,7 @@ type UserConfig struct {
DisableApplyAll bool `mapstructure:"disable-apply-all"`
DisableApply bool `mapstructure:"disable-apply"`
DisableAutoplan bool `mapstructure:"disable-autoplan"`
+ DisableAutoplanLabel string `mapstructure:"disable-autoplan-label"`
DisableMarkdownFolding bool `mapstructure:"disable-markdown-folding"`
DisableRepoLocking bool `mapstructure:"disable-repo-locking"`
DiscardApprovalOnPlanFlag bool `mapstructure:"discard-approval-on-plan"`