Files
atlantis/server/events/vcs/github_client.go
Luke Kysow bf19968cec Split Bitbucket Server comments if over max length
- Refactor the comment split method into a common package
2018-12-04 09:58:37 -06:00

150 lines
4.8 KiB
Go

// Copyright 2017 HootSuite Media Inc.
//
// 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.
// Modified hereafter by contributors to runatlantis/atlantis.
package vcs
import (
"context"
"fmt"
"net/url"
"strings"
"github.com/runatlantis/atlantis/server/events/vcs/common"
"github.com/google/go-github/github"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/events/models"
)
// maxCommentLength is the maximum number of chars allowed in a single comment
// by GitHub.
const maxCommentLength = 65536
// GithubClient is used to perform GitHub actions.
type GithubClient struct {
client *github.Client
ctx context.Context
}
// NewGithubClient returns a valid GitHub client.
func NewGithubClient(hostname string, user string, pass string) (*GithubClient, error) {
tp := github.BasicAuthTransport{
Username: strings.TrimSpace(user),
Password: strings.TrimSpace(pass),
}
client := github.NewClient(tp.Client())
// 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.
if hostname != "github.com" {
baseURL := fmt.Sprintf("https://%s/api/v3/", hostname)
base, err := url.Parse(baseURL)
if err != nil {
return nil, errors.Wrapf(err, "Invalid github hostname trying to parse %s", baseURL)
}
client.BaseURL = base
}
return &GithubClient{
client: client,
ctx: context.Background(),
}, nil
}
// GetModifiedFiles returns the names of files that were modified in the pull request.
// The names include the path to the file from the repo root, ex. parent/child/file.txt.
func (g *GithubClient) GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error) {
var files []string
nextPage := 0
for {
opts := github.ListOptions{
PerPage: 300,
}
if nextPage != 0 {
opts.Page = nextPage
}
pageFiles, resp, err := g.client.PullRequests.ListFiles(g.ctx, repo.Owner, repo.Name, pull.Num, &opts)
if err != nil {
return files, err
}
for _, f := range pageFiles {
files = append(files, f.GetFilename())
}
if resp.NextPage == 0 {
break
}
nextPage = resp.NextPage
}
return files, nil
}
// CreateComment creates a comment on the pull request.
// If comment length is greater than the max comment length we split into
// multiple comments.
func (g *GithubClient) CreateComment(repo models.Repo, pullNum int, comment string) error {
sepEnd := "\n```\n</details>" +
"\n<br>\n\n**Warning**: Output length greater than max comment size. Continued in next comment."
sepStart := "Continued from previous comment.\n<details><summary>Show Output</summary>\n\n" +
"```diff\n"
comments := common.SplitComment(comment, maxCommentLength, sepEnd, sepStart)
for _, c := range comments {
_, _, err := g.client.Issues.CreateComment(g.ctx, repo.Owner, repo.Name, pullNum, &github.IssueComment{Body: &c})
if err != nil {
return err
}
}
return nil
}
// PullIsApproved returns true if the pull request was approved.
func (g *GithubClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
reviews, _, err := g.client.PullRequests.ListReviews(g.ctx, repo.Owner, repo.Name, pull.Num, nil)
if err != nil {
return false, errors.Wrap(err, "getting reviews")
}
for _, review := range reviews {
if review != nil && review.GetState() == "APPROVED" {
return true, nil
}
}
return false, nil
}
// GetPullRequest returns the pull request.
func (g *GithubClient) GetPullRequest(repo models.Repo, num int) (*github.PullRequest, error) {
pull, _, err := g.client.PullRequests.Get(g.ctx, repo.Owner, repo.Name, num)
return pull, err
}
// UpdateStatus updates the status badge on the pull request.
// See https://github.com/blog/1227-commit-status-api.
func (g *GithubClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
const statusContext = "Atlantis"
ghState := "error"
switch state {
case models.PendingCommitStatus:
ghState = "pending"
case models.SuccessCommitStatus:
ghState = "success"
case models.FailedCommitStatus:
ghState = "failure"
}
status := &github.RepoStatus{
State: github.String(ghState),
Description: github.String(description),
Context: github.String(statusContext)}
_, _, err := g.client.Repositories.CreateStatus(g.ctx, repo.Owner, repo.Name, pull.HeadCommit, status)
return err
}