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: embed github-comment metadata #67

Merged
merged 1 commit into from Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 5 additions & 27 deletions COMPARED_WITH_TFNOTIFY.md
Expand Up @@ -12,7 +12,7 @@ tfcmt isn't compatible with tfnotify.
* [Remove --message and --destroy-warning-message option and template variable .Message](#breaking-change-remove---message-and---destroy-warning-message-option-and-template-variable-message)
* [Remove --title and --destroy-warning-title options and template variable .Title](#breaking-change-remove---title-and---destroy-warning-title-options-and-template-variable-title)
* [Don't remove duplicate comments](#breaking-change-dont-remove-duplicate-comments)
* [Hide old comments by default](#breaking-change-hide-old-comments-by-default)
* [Embed metadata into comment](#breaking-change-embed-metadata-into-comment)
* [Change the behavior of deletion warning](#breaking-change-change-the-behavior-of-deletion-warning)
* [Update labels by default](#breaking-change-update-pull-request-labels-by-default)
* Features
Expand Down Expand Up @@ -130,34 +130,12 @@ The link to the comment would be broken when the comment would be removed.

So this feature is removed from tfcmt.

## Breaking Change: Hide old comments by default
## Breaking Change: Embed metadata into comment

[#60](https://github.com/suzuki-shunsuke/tfcmt/pull/14)
[#67](https://github.com/suzuki-shunsuke/tfcmt/pull/67)

Instead of removing duplicate comments, tfcmt hides old comments of `terraform plan` by default.
Comments of `terraform apply` aren't hidden.

When tfcmt posts a comment, tfcmt injects a HTML comment `\n<!-- tfcmt:plan{{if .Vars.target}}:{{.Vars.target}}{{end}} -->` to the suffix.
And before posting the comment, tfcmt gets a list of pull request comments and selects comments which will be hidden.

Comments which match all of the following conditions are hidden.

* comment author is same as tfcmt's authenticated user. When it failed to get the authenticated user login, this condition is ignored
* tfcmt's authenticated user has a permission to hide the comment
* comment includes `<!-- tfcmt:plan{{if .Vars.target}}:{{.Vars.target}}{{end}} -->`

After it succeeds to post a comment, tfcmt hides them.
If it failed to post a comment, comments aren't hidden.

We can disable this feature by setting `terraform.plan.hide_old_comment.disable: true`.

```yaml
---
terraform:
plan:
hide_old_comment:
disable: true
```
Instead of removing duplicate comments, tfcmt embeds metadata into comment with [githuub-comment-metadata](https://github.com/suzuki-shunsuke/github-comment-metadata).
tfcmt itself doesn't support to hide old comments, but we can hide comments with [github-comment's hide command](https://github.com/suzuki-shunsuke/github-comment#hide).

## Breaking Change: Change the behavior of deletion warning

Expand Down
10 changes: 1 addition & 9 deletions config/config.go
Expand Up @@ -67,16 +67,9 @@ type Plan struct {
WhenNoChanges WhenNoChanges `yaml:"when_no_changes,omitempty"`
WhenPlanError WhenPlanError `yaml:"when_plan_error,omitempty"`
WhenParseError WhenParseError `yaml:"when_parse_error,omitempty"`
HideOldComment HideOldComment `yaml:"hide_old_comment,omitempty"`
DisableLabel bool `yaml:"disable_label,omitempty"`
}

type HideOldComment struct {
// Condition string `yaml:"-"`
// InjectedComment string `yaml:"-"`
Disable bool
}

// WhenAddOrUpdateOnly is a configuration to notify the plan result contains new or updated in place resources
type WhenAddOrUpdateOnly struct {
Label string `yaml:"label,omitempty"`
Expand Down Expand Up @@ -116,8 +109,7 @@ type Apply struct {
// LoadFile binds the config file to Config structure
func (cfg *Config) LoadFile(path string) error {
cfg.path = path
_, err := os.Stat(cfg.path)
if err != nil {
if _, err := os.Stat(cfg.path); err != nil {
return fmt.Errorf("%s: no config file", cfg.path)
}
raw, _ := ioutil.ReadFile(cfg.path)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -12,6 +12,7 @@ require (
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect
github.com/sirupsen/logrus v1.8.0
github.com/suzuki-shunsuke/github-comment-metadata v0.1.0-0
github.com/suzuki-shunsuke/go-ci-env v1.1.0
github.com/suzuki-shunsuke/go-findconfig v1.0.0
github.com/urfave/cli/v2 v2.3.0
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Expand Up @@ -53,13 +53,16 @@ github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/suzuki-shunsuke/github-comment-metadata v0.1.0-0 h1:CYlVhFjRld+oYZWuTgajRwhV4LaTUMrTpJScr29EBMc=
github.com/suzuki-shunsuke/github-comment-metadata v0.1.0-0/go.mod h1:GNDhEmWAJ6Bbk9rIds0mAMF4noyPV3EqwqLetnEoNLg=
github.com/suzuki-shunsuke/go-ci-env v1.1.0 h1:eGpItM2bEDtHFXYYEm8zz+kDGxWlhOtwNK58cREa1QU=
github.com/suzuki-shunsuke/go-ci-env v1.1.0/go.mod h1:kO9UgcQIAH4Pu4ESkUJHPuQEtesxHPKkpQqeJHJzzdk=
github.com/suzuki-shunsuke/go-findconfig v1.0.0 h1:RSETNCdYurpepqz/z9iM8DzJKK6TbvtV63ZVCIXaxkI=
Expand Down
12 changes: 4 additions & 8 deletions main.go
Expand Up @@ -64,7 +64,7 @@ func (t *tfcmt) renderTemplate(tpl string) (string, error) {
return buf.String(), nil
}

func (t *tfcmt) renderGitHubLabels() (github.ResultLabels, error) {
func (t *tfcmt) renderGitHubLabels() (github.ResultLabels, error) { //nolint:cyclop
labels := github.ResultLabels{
AddOrUpdateLabelColor: t.config.Terraform.Plan.WhenAddOrUpdateOnly.Color,
DestroyLabelColor: t.config.Terraform.Plan.WhenDestroy.Color,
Expand Down Expand Up @@ -147,10 +147,6 @@ func (t *tfcmt) getNotifier(ctx context.Context, ci CI) (notifier.Notifier, erro
}
labels = a
}
hideOldComment := github.HideOldComment{
Disable: t.config.Terraform.Plan.HideOldComment.Disable,
}

client, err := github.NewClient(ctx, github.Config{
Token: t.config.Notifier.Github.Token,
BaseURL: t.config.Notifier.Github.BaseURL,
Expand All @@ -169,7 +165,6 @@ func (t *tfcmt) getNotifier(ctx context.Context, ci CI) (notifier.Notifier, erro
ResultLabels: labels,
Vars: t.config.Vars,
Templates: t.config.Templates,
HideOldComment: hideOldComment,
})
if err != nil {
return nil, err
Expand All @@ -178,7 +173,7 @@ func (t *tfcmt) getNotifier(ctx context.Context, ci CI) (notifier.Notifier, erro
}

// Run sends the notification with notifier
func (t *tfcmt) Run(ctx context.Context) error {
func (t *tfcmt) Run(ctx context.Context) error { //nolint:cyclop
ciname := t.config.CI
if t.context.String("ci") != "" {
ciname = t.context.String("ci")
Expand Down Expand Up @@ -239,6 +234,7 @@ func (t *tfcmt) Run(ctx context.Context) error {
CombinedOutput: combinedOutput.String(),
Cmd: cmd,
Args: args,
CIName: ciname,
ExitCode: cmd.ProcessState.ExitCode(),
}))
}
Expand Down Expand Up @@ -310,7 +306,7 @@ func parseVarOpts(vars []string, varsM map[string]string) error {
return nil
}

func newConfig(ctx *cli.Context) (config.Config, error) {
func newConfig(ctx *cli.Context) (config.Config, error) { //nolint:cyclop
cfg := config.Config{}
confPath, err := cfg.Find(ctx.String("config"))
if err != nil {
Expand Down
15 changes: 4 additions & 11 deletions notifier/github/client.go
Expand Up @@ -52,17 +52,10 @@ type Config struct {
DestroyWarningTemplate *terraform.Template
ParseErrorTemplate *terraform.Template
// ResultLabels is a set of labels to apply depending on the plan result
ResultLabels ResultLabels
Vars map[string]string
Templates map[string]string
HideOldComment HideOldComment
UseRawOutput bool
}

type HideOldComment struct {
// Condition string
// InjectedComment string
Disable bool
ResultLabels ResultLabels
Vars map[string]string
Templates map[string]string
UseRawOutput bool
}

// PullRequest represents GitHub Pull Request metadata
Expand Down
118 changes: 0 additions & 118 deletions notifier/github/comment.go
Expand Up @@ -3,10 +3,8 @@ package github
import (
"context"
"errors"
"fmt"

"github.com/google/go-github/v33/github"
"github.com/shurcooL/githubv4"
)

// CommentService handles communication with the comment related
Expand Down Expand Up @@ -45,119 +43,3 @@ type ListOptions struct {
Owner string
Repo string
}

type Comment struct {
ID string
Body string
Author struct {
Login string
}
CreatedAt string
// TODO remove
IsMinimized bool
ViewerCanMinimize bool
}

func (g *CommentService) listIssueComment(ctx context.Context, opt ListOptions) ([]Comment, error) {
// https://github.com/shurcooL/githubv4#pagination
var q struct {
Repository struct {
Issue struct {
Comments struct {
Nodes []Comment
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"comments(first: 100, after: $commentsCursor)"` // 100 per page.
} `graphql:"issue(number: $issueNumber)"`
} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
}
variables := map[string]interface{}{
"repositoryOwner": githubv4.String(opt.Owner),
"repositoryName": githubv4.String(opt.Repo),
"issueNumber": githubv4.Int(opt.PRNumber),
"commentsCursor": (*githubv4.String)(nil), // Null after argument to get first page.
}

var allComments []Comment
for {
if err := g.client.v4Client.Query(ctx, &q, variables); err != nil {
return nil, fmt.Errorf("list issue comments by GitHub API: %w", err)
}
allComments = append(allComments, q.Repository.Issue.Comments.Nodes...)
if !q.Repository.Issue.Comments.PageInfo.HasNextPage {
break
}
variables["commentsCursor"] = githubv4.NewString(q.Repository.Issue.Comments.PageInfo.EndCursor)
}
return allComments, nil
}

func (g *CommentService) listPRComment(ctx context.Context, opt ListOptions) ([]Comment, error) {
// https://github.com/shurcooL/githubv4#pagination
var q struct {
Repository struct {
PullRequest struct {
Comments struct {
Nodes []Comment
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"comments(first: 100, after: $commentsCursor)"` // 100 per page.
} `graphql:"pullRequest(number: $issueNumber)"`
} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
}
variables := map[string]interface{}{
"repositoryOwner": githubv4.String(opt.Owner),
"repositoryName": githubv4.String(opt.Repo),
"issueNumber": githubv4.Int(opt.PRNumber),
"commentsCursor": (*githubv4.String)(nil), // Null after argument to get first page.
}

var allComments []Comment
for {
if err := g.client.v4Client.Query(ctx, &q, variables); err != nil {
return nil, fmt.Errorf("list issue comments by GitHub API: %w", err)
}
allComments = append(allComments, q.Repository.PullRequest.Comments.Nodes...)
if !q.Repository.PullRequest.Comments.PageInfo.HasNextPage {
break
}
variables["commentsCursor"] = githubv4.NewString(q.Repository.PullRequest.Comments.PageInfo.EndCursor)
}
return allComments, nil
}

func (g *CommentService) list(ctx context.Context, opt ListOptions) ([]Comment, error) {
cmts, prErr := g.listPRComment(ctx, opt)
if prErr == nil {
return cmts, nil
}
cmts, err := g.listIssueComment(ctx, opt)
if err == nil {
return cmts, nil
}
return nil, fmt.Errorf("get pull request or issue comments: %w, %v", prErr, err)
}

func (g *CommentService) hide(ctx context.Context, nodeID string) error {
var m struct {
MinimizeComment struct {
MinimizedComment struct {
MinimizedReason githubv4.String
IsMinimized githubv4.Boolean
ViewerCanMinimize githubv4.Boolean
}
} `graphql:"minimizeComment(input:$input)"`
}
input := githubv4.MinimizeCommentInput{
Classifier: githubv4.ReportedContentClassifiersOutdated,
SubjectID: nodeID,
}
if err := g.client.v4Client.Mutate(ctx, &m, input, nil); err != nil {
return fmt.Errorf("hide an old comment: %w", err)
}
return nil
}