blob: baefa1468aa6e46a77fc9caec230cc44b571cdda [file] [log] [blame]
// Copyright 2023 The Fuchsia 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 issuetracker
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"github.com/pkg/errors"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/luciauth"
"go.skia.org/infra/go/util"
)
const (
issuesURL = "https://issuetracker.googleapis.com/v1/issues"
scope = "https://www.googleapis.com/auth/buganizer"
)
type IssueTracker struct {
client *http.Client
url string
}
// Issue represents the body of the request/response for creating new issues.
// The full schema can be found at https://go/issuetracker.proto.
type Issue struct {
IssueID string `json:"issueId,omitempty"`
IssueState IssueState `json:"issueState"`
IssueComment IssueComment `json:"issueComment"`
}
// IssueState represents the state of the issue.
type IssueState struct {
ComponentID string `json:"componentId"`
Type string `json:"type"`
Status string `json:"status"`
Priority string `json:"priority"`
Severity string `json:"severity"`
Title string `json:"title"`
AccessLimit AccessLimit `json:"accessLimit,omitempty"`
}
type AccessLimit struct {
AccessLevel string `json:"accessLevel"`
}
type IssueComment struct {
Comment string `json:"comment"`
}
// NewIssueTracker creates and returns an IssueTracker instance.
func NewIssueTracker(client *http.Client) *IssueTracker {
return &IssueTracker{
client: client,
url: issuesURL,
}
}
func NewIssueTrackerFromLUCIContext() (*IssueTracker, error) {
ts, err := luciauth.NewLUCIContextTokenSource(scope)
if err != nil {
return nil, err
}
client := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
return NewIssueTracker(client), nil
}
// AddIssue creates an issue with the passed in params.
func (m *IssueTracker) AddIssue(issue Issue) (Issue, error) {
newIssueResponse := Issue{}
callback := func(resp []byte) error {
if err := json.Unmarshal(resp, &newIssueResponse); err != nil {
return errors.Wrap(err, "unable to unmarshal new issue response")
}
issueURL := fmt.Sprintf("https://issuetracker.google.com/%s", newIssueResponse.IssueID)
log.Printf("Issue filed!\nIssue:\n%v\nReference: %s\n", newIssueResponse, issueURL)
return nil
}
err := post(m.client, m.url, issue, callback)
return newIssueResponse, err
}
func post(client *http.Client, dst string, request any, callback func(r []byte) error) error {
b := &bytes.Buffer{}
e := json.NewEncoder(b)
if err := e.Encode(request); err != nil {
return errors.Wrap(err, "failed to encode json for request")
}
resp, err := client.Post(dst, "application/json", b)
if err != nil || resp == nil {
return errors.Wrap(err, "failed to retrieve IssueTracker response")
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Unexpected IssueTracker response. Expected %d, received %d", http.StatusOK, resp.StatusCode)
}
defer util.Close(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "failed to read response body")
}
if err := callback(body); err != nil {
return errors.Wrap(err, "failed to log IssueTracker post request")
}
return nil
}