From b54ec486accdeb38ab4aaa5db1f7c6e1fcabe146 Mon Sep 17 00:00:00 2001 From: Ghais Zaher Date: Mon, 25 Sep 2023 19:43:35 +0200 Subject: [PATCH] feat: disable autoplan label (#3649) * feat: disable autoplan label * documentation * revert unrelated change * fix property * gitlab and github * dd more test * small fixes * add tests for github and gitlab clients * fix: remove unrelated comments * fmt --------- Co-authored-by: PePe Amengual --- cmd/server.go | 5 + cmd/server_test.go | 1 + runatlantis.io/docs/server-configuration.md | 10 ++ server/events/command_runner.go | 10 ++ server/events/command_runner_test.go | 56 ++++++++- server/events/vcs/azuredevops_client.go | 4 + server/events/vcs/bitbucketcloud/client.go | 4 + server/events/vcs/bitbucketserver/client.go | 4 + server/events/vcs/client.go | 3 + server/events/vcs/github_client.go | 15 +++ server/events/vcs/github_client_test.go | 107 ++++++++++++++++++ server/events/vcs/gitlab_client.go | 10 ++ server/events/vcs/gitlab_client_test.go | 59 ++++++++++ server/events/vcs/mocks/mock_client.go | 50 ++++++++ .../events/vcs/not_configured_vcs_client.go | 4 + server/events/vcs/proxy.go | 4 + server/server.go | 1 + server/user_config.go | 1 + 18 files changed, 345 insertions(+), 3 deletions(-) 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"`