| // 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 |
| } |