blob: ca4ef7b0485066d6ffae6b0a9b34a62a5cd6c9db [file] [log] [blame]
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package issues provides a general way to interact with issues,
// and a client for interacting with the GitHub issues API.
package issues
import (
"context"
"fmt"
"time"
"github.com/google/go-github/v41/github"
"golang.org/x/oauth2"
"golang.org/x/vulndb/internal/derrors"
)
// An Issue represents a GitHub issue or similar.
type Issue struct {
Title string
Body string
Labels []string
CreatedAt time.Time
}
// Client is a client that can create and retrieve issues.
type Client interface {
// Destination describes where issues will be created.
Destination() string
// Reference returns a string that refers to the issue with number.
Reference(number int) string
// IssueExists reports whether an issue with the given ID exists.
IssueExists(ctx context.Context, number int) (bool, error)
// CreateIssue creates a new issue.
CreateIssue(ctx context.Context, iss *Issue) (number int, err error)
// GetIssue returns an issue with the given issue number.
GetIssue(ctx context.Context, number int) (iss *Issue, err error)
}
type githubClient struct {
client *github.Client
owner string
repo string
}
// NewGitHubClient creates a Client that will create issues in
// the a GitHub repo.
// A GitHub access token is required to create issues.
func NewGitHubClient(owner, repo, accessToken string) *githubClient {
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})
tc := oauth2.NewClient(context.Background(), ts)
return &githubClient{
client: github.NewClient(tc),
owner: owner,
repo: repo,
}
}
// Destination implements Client.Destination.
func (c *githubClient) Destination() string {
return fmt.Sprintf("https://github.com/%s/%s", c.owner, c.repo)
}
// Reference implements Client.Reference.
func (c *githubClient) Reference(num int) string {
return fmt.Sprintf("%s/issues/%d", c.Destination(), num)
}
// IssueExists implements Client.IssueExists.
func (c *githubClient) IssueExists(ctx context.Context, number int) (_ bool, err error) {
defer derrors.Wrap(&err, "IssueExists(%d)", number)
iss, _, err := c.client.Issues.Get(ctx, c.owner, c.repo, number)
if err != nil {
return false, err
}
if iss != nil {
fmt.Printf("ID = %d, Number = %d\n", iss.GetID(), iss.GetNumber())
return true, nil
}
return false, nil
}
// GetIssue implements Client.GetIssue.
func (c *githubClient) GetIssue(ctx context.Context, number int) (_ *Issue, err error) {
defer derrors.Wrap(&err, "GetIssue(%d)", number)
iss, _, err := c.client.Issues.Get(ctx, c.owner, c.repo, number)
if err != nil {
return nil, err
}
r := &Issue{}
if iss.Title != nil {
r.Title = *iss.Title
}
if iss.Body != nil {
r.Body = *iss.Body
}
if iss.CreatedAt != nil {
r.CreatedAt = *iss.CreatedAt
}
return r, nil
}
// CreateIssue implements Client.CreateIssue.
func (c *githubClient) CreateIssue(ctx context.Context, iss *Issue) (number int, err error) {
defer derrors.Wrap(&err, "CreateIssue(%s)", iss.Title)
req := &github.IssueRequest{
Title: &iss.Title,
Body: &iss.Body,
}
if len(iss.Labels) > 0 {
req.Labels = &iss.Labels
}
giss, _, err := c.client.Issues.Create(ctx, c.owner, c.repo, req)
if err != nil {
return 0, err
}
return giss.GetNumber(), nil
}