github.com/autonomy/conform@v0.1.0-alpha.16/internal/summarizer/summarizer.go (about)

     1  /* This Source Code Form is subject to the terms of the Mozilla Public
     2   * License, v. 2.0. If a copy of the MPL was not distributed with this
     3   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     4  
     5  package summarizer
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"log"
    14  	"net/http"
    15  	"os"
    16  	"path"
    17  	"strings"
    18  
    19  	"github.com/autonomy/conform/internal/git"
    20  	"github.com/google/go-github/github"
    21  )
    22  
    23  // Summarizer describes a hook for send summarized results to a remote API.
    24  type Summarizer interface {
    25  	SetStatus(string, string, string, string) error
    26  }
    27  
    28  // GitHub is a summarizer that can be used with GitHub.
    29  type GitHub struct {
    30  	token string
    31  	owner string
    32  	repo  string
    33  	sha   string
    34  }
    35  
    36  // Noop is a summarizer that does nothing.
    37  type Noop struct {
    38  }
    39  
    40  // SetStatus is a noop func.
    41  func (n *Noop) SetStatus(state, policy, check, message string) error {
    42  	return nil
    43  }
    44  
    45  // NewGitHubSummarizer returns a summarizer that posts policy checks as status
    46  // checks on a pull request.
    47  func NewGitHubSummarizer(token string) (*GitHub, error) {
    48  	eventPath, ok := os.LookupEnv("GITHUB_EVENT_PATH")
    49  	if !ok {
    50  		return nil, errors.New("GITHUB_EVENT_PATH is not set")
    51  	}
    52  
    53  	data, err := ioutil.ReadFile(eventPath)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	pullRequestEvent := &github.PullRequestEvent{}
    59  	if err = json.Unmarshal(data, pullRequestEvent); err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	g, err := git.NewGit()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	if err = g.FetchPullRequest("origin", pullRequestEvent.GetNumber()); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	if err = g.CheckoutPullRequest(pullRequestEvent.GetNumber()); err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	sha, err := g.SHA()
    77  	if err != nil {
    78  		log.Fatal(err)
    79  	}
    80  
    81  	gh := &GitHub{
    82  		token: token,
    83  		owner: pullRequestEvent.GetRepo().GetOwner().GetLogin(),
    84  		repo:  pullRequestEvent.GetRepo().GetName(),
    85  		sha:   sha,
    86  	}
    87  
    88  	return gh, nil
    89  }
    90  
    91  // SetStatus sets the status of a GitHub check.
    92  // Valid statuses are "error", "failure", "pending", "success"
    93  func (gh *GitHub) SetStatus(state, policy, check, message string) error {
    94  	if gh.token == "" {
    95  		return errors.New("no token")
    96  	}
    97  	statusCheckContext := strings.ReplaceAll(strings.ToLower(path.Join("conform", policy, check)), " ", "-")
    98  	description := message
    99  	repoStatus := &github.RepoStatus{}
   100  	repoStatus.Context = &statusCheckContext
   101  	repoStatus.Description = &description
   102  	repoStatus.State = &state
   103  
   104  	http.DefaultClient.Transport = roundTripper{gh.token}
   105  	githubClient := github.NewClient(http.DefaultClient)
   106  
   107  	_, _, err := githubClient.Repositories.CreateStatus(context.Background(), gh.owner, gh.repo, gh.sha, repoStatus)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  type roundTripper struct {
   116  	accessToken string
   117  }
   118  
   119  // RoundTrip implements the net/http.RoundTripper interface.
   120  func (rt roundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
   121  	r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.accessToken))
   122  	return http.DefaultTransport.RoundTrip(r)
   123  }