Skip to content

Commit

Permalink
Merge pull request #67 from suzuki-shunsuke/feat/embed-github-comment…
Browse files Browse the repository at this point in the history
…-metadata

feat: embed github-comment metadata
  • Loading branch information
suzuki-shunsuke committed Feb 23, 2021
2 parents 84a2b67 + e5bb0c3 commit c5c5b75
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 259 deletions.
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
}

0 comments on commit c5c5b75

Please sign in to comment.