Skip to content

Commit

Permalink
Merge pull request #994 from runatlantis/hide-comments
Browse files Browse the repository at this point in the history
Hide "plan" comments
  • Loading branch information
lkysow authored Apr 16, 2020
2 parents 1d89710 + 80347f4 commit a6bfb17
Show file tree
Hide file tree
Showing 47 changed files with 6,554 additions and 29 deletions.
6 changes: 6 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (
GitlabTokenFlag = "gitlab-token"
GitlabUserFlag = "gitlab-user"
GitlabWebhookSecretFlag = "gitlab-webhook-secret" // nolint: gosec
HidePrevPlanComments = "hide-prev-plan-comments"
LogLevelFlag = "log-level"
PortFlag = "port"
RepoConfigFlag = "repo-config"
Expand Down Expand Up @@ -252,6 +253,11 @@ var boolFlags = map[string]boolFlag{
description: "Disable \"atlantis apply\" command so a specific project/workspace/directory has to be specified for applies.",
defaultValue: false,
},
HidePrevPlanComments: {
description: "Hide previous plan comments to reduce clutter in the PR. " +
"VCS support is limited to: GitHub.",
defaultValue: false,
},
RequireApprovalFlag: {
description: "Require pull requests to be \"Approved\" before allowing the apply command to be run.",
defaultValue: false,
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/runatlantis/atlantis
go 1.14

require (
github.com/Laisky/graphql v1.0.4
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Masterminds/sprig v2.15.0+incompatible
github.com/aokoli/goutils v1.0.1 // indirect
Expand Down Expand Up @@ -41,6 +42,8 @@ require (
github.com/pelletier/go-toml v1.0.0 // indirect
github.com/petergtz/pegomock v2.7.0+incompatible
github.com/pkg/errors v0.8.0
github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
github.com/sirupsen/logrus v1.2.0 // indirect
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1 // indirect
github.com/spf13/cast v1.1.0 // indirect
Expand Down
32 changes: 8 additions & 24 deletions go.sum

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions runatlantis.io/docs/server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@ Values are chosen in this order:
```
View help.

* ### `--hide-prev-plan-comments`
```bash
atlantis server --hide-prev-plan-comments
```
Hide previous plan comments to declutter PRs. This is only supported in
GitHub currently.

* ### `--log-level`
```bash
atlantis server --log-level="<debug|info|warn|error>"
Expand Down
11 changes: 11 additions & 0 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type DefaultCommandRunner struct {
// this in our error message back to the user on a forked PR so they know
// how to enable this functionality.
AllowForkPRsFlag string
// HidePrevPlanComments will hide previous plan comments to declutter PRs.
HidePrevPlanComments bool
// SilenceForkPRErrors controls whether to comment on Fork PRs when AllowForkPRs = False
SilenceForkPRErrors bool
// SilenceForkPRErrorsFlag is the name of the flag that controls fork PR's. We use
Expand Down Expand Up @@ -429,6 +431,15 @@ func (c *DefaultCommandRunner) updatePull(ctx *CommandContext, command PullComma
ctx.Log.Warn(res.Failure)
}

// HidePrevPlanComments will hide old comments left from previous plan runs to reduce
// clutter in a pull/merge request. This will not delete the comment, since the
// comment trail may be useful in auditing or backtracing problems.
if c.HidePrevPlanComments {
if err := c.VCSClient.HidePrevPlanComments(ctx.BaseRepo, ctx.Pull.Num); err != nil {
ctx.Log.Err("unable to hide old comments: %s", err)
}
}

comment := c.MarkdownRenderer.Render(res, command.CommandName(), ctx.Log.History.String(), command.IsVerbose(), ctx.BaseRepo.VCSHost.Type)
if err := c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull.Num, comment); err != nil {
ctx.Log.Err("unable to comment: %s", err)
Expand Down
5 changes: 5 additions & 0 deletions server/events/vcs/azuredevops_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,12 @@ func (g *AzureDevopsClient) CreateComment(repo models.Repo, pullNum int, comment
return nil
}

func (g *AzureDevopsClient) HidePrevPlanComments(repo models.Repo, pullNum int) error {
return nil
}

// PullIsApproved returns true if the merge request was approved by another reviewer.
// https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops#require-a-minimum-number-of-reviewers
func (g *AzureDevopsClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
owner, project, repoName := SplitAzureDevopsRepoFullName(repo.FullName)

Expand Down
4 changes: 4 additions & 0 deletions server/events/vcs/bitbucketcloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ func (b *Client) CreateComment(repo models.Repo, pullNum int, comment string) er
return err
}

func (b *Client) HidePrevPlanComments(repo models.Repo, pullNum int) error {
return nil
}

// PullIsApproved returns true if the merge request was approved.
func (b *Client) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
path := fmt.Sprintf("%s/2.0/repositories/%s/pullrequests/%d", b.BaseURL, repo.FullName, pull.Num)
Expand Down
4 changes: 4 additions & 0 deletions server/events/vcs/bitbucketserver/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ func (b *Client) CreateComment(repo models.Repo, pullNum int, comment string) er
return nil
}

func (b *Client) HidePrevPlanComments(repo models.Repo, pullNum int) error {
return nil
}

// postComment actually posts the comment. It's a helper for CreateComment().
func (b *Client) postComment(repo models.Repo, pullNum int, comment string) error {
bodyBytes, err := json.Marshal(map[string]string{"text": comment})
Expand Down
1 change: 1 addition & 0 deletions server/events/vcs/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Client interface {
// relative to the repo root, e.g. parent/child/file.txt.
GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error)
CreateComment(repo models.Repo, pullNum int, comment string) error
HidePrevPlanComments(repo models.Repo, pullNum int) error
PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error)
PullIsMergeable(repo models.Repo, pull models.PullRequest) (bool, error)
// UpdateStatus updates the commit status to state for pull. src is the
Expand Down
97 changes: 92 additions & 5 deletions server/events/vcs/github_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (
"net/url"
"strings"

"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs/common"

"github.com/Laisky/graphql"
"github.com/google/go-github/v28/github"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/shurcooL/githubv4"
)

// maxCommentLength is the maximum number of chars allowed in a single comment
Expand All @@ -32,8 +34,10 @@ const maxCommentLength = 65536

// GithubClient is used to perform GitHub actions.
type GithubClient struct {
client *github.Client
ctx context.Context
user string
client *github.Client
v4MutateClient *graphql.Client
ctx context.Context
}

// NewGithubClient returns a valid GitHub client.
Expand All @@ -43,6 +47,8 @@ func NewGithubClient(hostname string, user string, pass string) (*GithubClient,
Password: strings.TrimSpace(pass),
}
client := github.NewClient(tp.Client())
graphqlURL := "https://api.github.com/graphql"

// If we're using github.com then we don't need to do any additional configuration
// for the client. It we're using Github Enterprise, then we need to manually
// set the base url for the API.
Expand All @@ -53,11 +59,31 @@ func NewGithubClient(hostname string, user string, pass string) (*GithubClient,
return nil, errors.Wrapf(err, "Invalid github hostname trying to parse %s", baseURL)
}
client.BaseURL = base
graphqlURL = fmt.Sprintf("https://%s/graphql", hostname)
_, err = url.Parse(graphqlURL)
if err != nil {
return nil, errors.Wrapf(err, "Invalid GraphQL github hostname trying to parse %s", graphqlURL)
}
}

// shurcooL's githubv4 library has a client ctor, but it doesn't support schema
// previews, which need custom Accept headers (https://developer.github.com/v4/previews)
// So for now use the graphql client, since the githubv4 library was basically
// a simple wrapper around it. And instead of using shurcooL's graphql lib, use
// Laisky's, since shurcooL's doesn't support custom headers.
// Once the Minimize Comment schema is official, this can revert back to using
// shurcooL's libraries completely.
v4MutateClient := graphql.NewClient(
graphqlURL,
tp.Client(),
graphql.WithHeader("Accept", "application/vnd.github.queen-beryl-preview+json"),
)

return &GithubClient{
client: client,
ctx: context.Background(),
user: user,
client: client,
v4MutateClient: v4MutateClient,
ctx: context.Background(),
}, nil
}

Expand Down Expand Up @@ -113,6 +139,67 @@ func (g *GithubClient) CreateComment(repo models.Repo, pullNum int, comment stri
return nil
}

func (g *GithubClient) HidePrevPlanComments(repo models.Repo, pullNum int) error {
var allComments []*github.IssueComment
nextPage := 0
for {
comments, resp, err := g.client.Issues.ListComments(g.ctx, repo.Owner, repo.Name, pullNum, &github.IssueListCommentsOptions{
Sort: "created",
Direction: "asc",
ListOptions: github.ListOptions{Page: nextPage},
})
if err != nil {
return err
}
allComments = append(allComments, comments...)
if resp.NextPage == 0 {
break
}
nextPage = resp.NextPage
}

for _, comment := range allComments {
// Using a case insensitive compare here because usernames aren't case
// sensitive and users may enter their atlantis users with different
// cases.
if comment.User != nil && !strings.EqualFold(comment.User.GetLogin(), g.user) {
continue
}
// Crude filtering: The comment templates typically include the command name
// somewhere in the first line. It's a bit of an assumption, but seems like
// a reasonable one, given we've already filtered the comments by the
// configured Atlantis user.
body := strings.Split(comment.GetBody(), "\n")
if len(body) == 0 {
continue
}
firstLine := strings.ToLower(body[0])
if !strings.Contains(firstLine, models.PlanCommand.String()) {
continue
}
var m struct {
MinimizeComment struct {
MinimizedComment struct {
IsMinimized githubv4.Boolean
MinimizedReason githubv4.String
ViewerCanMinimize githubv4.Boolean
}
} `graphql:"minimizeComment(input:$input)"`
}
input := map[string]interface{}{
"input": githubv4.MinimizeCommentInput{
Classifier: githubv4.ReportedContentClassifiersOutdated,
SubjectID: comment.GetNodeID(),
},
}
if err := g.v4MutateClient.Mutate(g.ctx, &m, input); err != nil {
return errors.Wrapf(err, "minimize comment %s", comment.GetNodeID())
}
}

return nil
}

// PullIsApproved returns true if the pull request was approved.
func (g *GithubClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
nextPage := 0
Expand Down
Loading

0 comments on commit a6bfb17

Please sign in to comment.