2020-05-22 02:33:03 +00:00
|
|
|
/*
|
|
|
|
Copyright 2020 Qiniu Cloud (qiniu.com)
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package github
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/google/go-github/github"
|
2020-06-07 03:58:55 +00:00
|
|
|
"github.com/hashicorp/go-retryablehttp"
|
2020-05-22 02:33:03 +00:00
|
|
|
"github.com/olekukonko/tablewriter"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
|
|
|
|
"github.com/qiniu/goc/pkg/cover"
|
|
|
|
)
|
|
|
|
|
2020-07-26 09:03:47 +00:00
|
|
|
// CommentsPrefix is the prefix when commenting on Github Pull Requests
|
|
|
|
// It is also the flag when checking whether the target comment exists or not to avoid duplicate
|
2020-05-22 02:33:03 +00:00
|
|
|
const CommentsPrefix = "The following is the coverage report on the affected files."
|
|
|
|
|
2020-07-24 09:27:03 +00:00
|
|
|
type PrComment interface {
|
|
|
|
CreateGithubComment(commentPrefix string, diffCovList cover.DeltaCovList) (err error)
|
|
|
|
PostComment(content, commentPrefix string) error
|
|
|
|
EraseHistoryComment(commentPrefix string) error
|
|
|
|
GetPrChangedFiles() (files []string, err error)
|
|
|
|
GetCommentFlag() string
|
|
|
|
}
|
|
|
|
|
|
|
|
// GithubPrComment is the entry which is able to comment on Github Pull Requests
|
|
|
|
type GithubPrComment struct {
|
2020-05-22 02:33:03 +00:00
|
|
|
RobotUserName string
|
|
|
|
RepoOwner string
|
|
|
|
RepoName string
|
|
|
|
CommentFlag string
|
|
|
|
PrNumber int
|
|
|
|
Ctx context.Context
|
|
|
|
opt *github.ListOptions
|
|
|
|
GithubClient *github.Client
|
|
|
|
}
|
|
|
|
|
2020-06-07 03:58:55 +00:00
|
|
|
// NewPrClient creates an Client which be able to comment on Github Pull Request
|
2020-07-24 09:27:03 +00:00
|
|
|
func NewPrClient(githubTokenPath, repoOwner, repoName, prNumStr, botUserName, commentFlag string) *GithubPrComment {
|
2020-05-22 02:33:03 +00:00
|
|
|
var client *github.Client
|
2020-06-07 03:58:55 +00:00
|
|
|
|
|
|
|
// performs automatic retries when connection error occurs or a 500-range response code received (except 501)
|
|
|
|
retryClient := retryablehttp.NewClient()
|
|
|
|
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, retryClient.StandardClient())
|
2020-05-22 02:33:03 +00:00
|
|
|
|
|
|
|
prNum, err := strconv.Atoi(prNumStr)
|
|
|
|
if err != nil {
|
|
|
|
logrus.WithError(err).Fatalf("Failed to convert prNumStr(=%v) to int.\n", prNumStr)
|
|
|
|
}
|
|
|
|
token, err := ioutil.ReadFile(githubTokenPath)
|
|
|
|
if err != nil {
|
|
|
|
logrus.WithError(err).Fatalf("Failed to get github token.\n")
|
|
|
|
}
|
|
|
|
ts := oauth2.StaticTokenSource(
|
|
|
|
&oauth2.Token{AccessToken: strings.TrimSpace(string(token))},
|
|
|
|
)
|
|
|
|
tc := oauth2.NewClient(ctx, ts)
|
|
|
|
client = github.NewClient(tc)
|
|
|
|
|
2020-07-24 09:27:03 +00:00
|
|
|
return &GithubPrComment{
|
2020-05-22 02:33:03 +00:00
|
|
|
RobotUserName: botUserName,
|
|
|
|
RepoOwner: repoOwner,
|
|
|
|
RepoName: repoName,
|
|
|
|
PrNumber: prNum,
|
|
|
|
CommentFlag: commentFlag,
|
|
|
|
Ctx: ctx,
|
|
|
|
opt: &github.ListOptions{Page: 1},
|
|
|
|
GithubClient: client,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-26 09:03:47 +00:00
|
|
|
// CreateGithubComment post github comment of diff coverage
|
2020-07-24 09:27:03 +00:00
|
|
|
func (c *GithubPrComment) CreateGithubComment(commentPrefix string, diffCovList cover.DeltaCovList) (err error) {
|
2020-05-22 02:33:03 +00:00
|
|
|
if len(diffCovList) == 0 {
|
|
|
|
logrus.Printf("Detect 0 files coverage diff, will not comment to github.")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
content := GenCommentContent(commentPrefix, diffCovList)
|
|
|
|
|
|
|
|
err = c.PostComment(content, commentPrefix)
|
|
|
|
if err != nil {
|
|
|
|
logrus.WithError(err).Fatalf("Post comment to github failed.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-26 09:03:47 +00:00
|
|
|
// PostComment post comment on github. It erased the old one if existed to avoid duplicate
|
2020-07-24 09:27:03 +00:00
|
|
|
func (c *GithubPrComment) PostComment(content, commentPrefix string) error {
|
2020-05-22 02:33:03 +00:00
|
|
|
//step1: erase history similar comment to avoid too many comment for same job
|
|
|
|
err := c.EraseHistoryComment(commentPrefix)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
//step2: post comment with new result
|
|
|
|
comment := &github.IssueComment{
|
|
|
|
Body: &content,
|
|
|
|
}
|
|
|
|
_, _, err = c.GithubClient.Issues.CreateComment(c.Ctx, c.RepoOwner, c.RepoName, c.PrNumber, comment)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-26 09:03:47 +00:00
|
|
|
// EraseHistoryComment erase history similar comment before post again
|
2020-07-24 09:27:03 +00:00
|
|
|
func (c *GithubPrComment) EraseHistoryComment(commentPrefix string) error {
|
2020-05-22 02:33:03 +00:00
|
|
|
comments, _, err := c.GithubClient.Issues.ListComments(c.Ctx, c.RepoOwner, c.RepoName, c.PrNumber, nil)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("list PR comments failed.")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logrus.Infof("the count of history comments by %s is: %v", c.RobotUserName, len(comments))
|
|
|
|
|
|
|
|
for _, cm := range comments {
|
|
|
|
if *cm.GetUser().Login == c.RobotUserName && strings.HasPrefix(cm.GetBody(), commentPrefix) {
|
|
|
|
_, err = c.GithubClient.Issues.DeleteComment(c.Ctx, c.RepoOwner, c.RepoName, *cm.ID)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("delete PR comments %d failed.", *cm.ID)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-07 03:58:55 +00:00
|
|
|
//GetPrChangedFiles get github pull request changes file list
|
2020-07-24 09:27:03 +00:00
|
|
|
func (c *GithubPrComment) GetPrChangedFiles() (files []string, err error) {
|
2020-05-22 02:33:03 +00:00
|
|
|
var commitFiles []*github.CommitFile
|
|
|
|
for {
|
|
|
|
f, resp, err := c.GithubClient.PullRequests.ListFiles(c.Ctx, c.RepoOwner, c.RepoName, c.PrNumber, c.opt)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Get PR changed file failed. repoOwner is: %s, repoName is: %s, prNum is: %d", c.RepoOwner, c.RepoName, c.PrNumber)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
commitFiles = append(commitFiles, f...)
|
|
|
|
if resp.NextPage == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
c.opt.Page = resp.NextPage
|
|
|
|
}
|
|
|
|
logrus.Infof("get %d PR changed files:", len(commitFiles))
|
|
|
|
for _, file := range commitFiles {
|
|
|
|
files = append(files, *file.Filename)
|
|
|
|
logrus.Infof("%s", *file.Filename)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-24 09:27:03 +00:00
|
|
|
// GetCommentFlag get CommentFlag from the GithubPrComment
|
|
|
|
func (c *GithubPrComment) GetCommentFlag() string {
|
|
|
|
return c.CommentFlag
|
|
|
|
}
|
|
|
|
|
2020-07-26 09:03:47 +00:00
|
|
|
// GenCommentContent generate github comment content based on diff coverage and commentFlag
|
2020-05-22 02:33:03 +00:00
|
|
|
func GenCommentContent(commentPrefix string, delta cover.DeltaCovList) string {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
table := tablewriter.NewWriter(&buf)
|
2020-06-27 05:24:57 +00:00
|
|
|
table.SetHeader([]string{"File", "Base Coverage", "New Coverage", "Delta"})
|
2020-05-29 02:58:24 +00:00
|
|
|
table.SetAutoFormatHeaders(false)
|
2020-05-22 02:33:03 +00:00
|
|
|
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
|
|
|
|
table.SetCenterSeparator("|")
|
|
|
|
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER})
|
|
|
|
for _, d := range delta {
|
|
|
|
table.Append([]string{fmt.Sprintf("[%s](%s)", d.FileName, d.LineCovLink), d.BasePer, d.NewPer, d.DeltaPer})
|
|
|
|
}
|
|
|
|
table.Render()
|
|
|
|
|
|
|
|
content := []string{
|
|
|
|
commentPrefix,
|
|
|
|
fmt.Sprintf("Say `/test %s` to re-run this coverage report", os.Getenv("JOB_NAME")),
|
|
|
|
buf.String(),
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(content, "\n")
|
|
|
|
}
|