Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: disable autoplan label #3649

Merged
merged 22 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions runatlantis.io/docs/server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package events

import (
"fmt"
"github.com/runatlantis/atlantis/server/utils"
"strconv"

"github.com/google/go-github/v54/github"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
56 changes: 53 additions & 3 deletions server/events/command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dir>`, `-w <workspace>` or `-p <project name>` 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 }()

Expand All @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions server/events/vcs/azuredevops_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
4 changes: 4 additions & 0 deletions server/events/vcs/bitbucketcloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
4 changes: 4 additions & 0 deletions server/events/vcs/bitbucketserver/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
3 changes: 3 additions & 0 deletions server/events/vcs/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
15 changes: 15 additions & 0 deletions server/events/vcs/github_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
ghaiszaher marked this conversation as resolved.
Show resolved Hide resolved
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
}
107 changes: 107 additions & 0 deletions server/events/vcs/github_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
10 changes: 10 additions & 0 deletions server/events/vcs/gitlab_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
ghaiszaher marked this conversation as resolved.
Show resolved Hide resolved
mr, _, err := g.Client.MergeRequests.GetMergeRequest(repo.FullName, pull.Num, nil)

if err != nil {
return nil, err
}

return mr.Labels, nil
}
Loading