Skip to content

Commit

Permalink
feat: disable autoplan label (runatlantis#3649)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
2 people authored and ijames-gc committed Feb 13, 2024
1 parent 3d4a5a7 commit b54ec48
Show file tree
Hide file tree
Showing 18 changed files with 345 additions and 3 deletions.
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) {
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) {
mr, _, err := g.Client.MergeRequests.GetMergeRequest(repo.FullName, pull.Num, nil)

if err != nil {
return nil, err
}

return mr.Labels, nil
}
Loading

0 comments on commit b54ec48

Please sign in to comment.