dev/testsreporter/github.go (145 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package testsreporter
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"sort"
"strconv"
"strings"
"time"
"github.com/cli/go-gh/v2"
)
type commandRunner interface {
Exec(ctx context.Context, args ...string) (stdout, stdErr bytes.Buffer, err error)
}
type ghRunner struct {
DryRun bool
}
type githubOptions struct {
DryRun bool
Runner commandRunner
}
func (g *ghRunner) Exec(ctx context.Context, args ...string) (stdout, stdErr bytes.Buffer, err error) {
log.Printf("Running command: %s", strings.Join(args[:4], " "))
if g.DryRun {
if args[0] != "issue" || args[1] != "list" {
log.Printf("DRY-RUN> not run command")
return bytes.Buffer{}, bytes.Buffer{}, nil
}
}
return gh.ExecContext(ctx, args...)
}
type ghCli struct {
runner commandRunner
}
func newGhCli(options githubOptions) *ghCli {
var runner commandRunner
runner = options.Runner
if runner == nil {
runner = &ghRunner{
DryRun: options.DryRun,
}
}
return &ghCli{
runner: runner,
}
}
func (g *ghCli) Exists(ctx context.Context, issue *githubIssue, open bool) (bool, *githubIssue, error) {
stateIssue := "open"
if !open {
stateIssue = "closed"
}
stdout, stderr, err := g.runner.Exec(ctx,
"issue",
"list",
"--json",
"title,body,number,labels,state,url,createdAt,closedAt",
"--repo",
issue.repository,
"--search",
fmt.Sprintf("%s in:title sort:created-desc", issue.title),
"--limit",
"1000",
"--jq",
"map(select((.labels | length) > 0))| map(.labels = (.labels | map(.name)))",
"--state",
stateIssue,
)
if err != nil {
return false, nil, fmt.Errorf("failed to list issues: %w\n%s", err, stderr.String())
}
type responseListIssue struct {
CreatedAt time.Time `json:"createdAt"`
ClosedAt time.Time `json:"closedAt"`
Title string `json:"title"`
Body string `json:"body"`
Number int `json:"number"`
Labels []string `json:"labels"`
State string `json:"state"`
URL string `json:"url"`
}
var list []responseListIssue
err = json.Unmarshal(stdout.Bytes(), &list)
if err != nil {
return false, nil, fmt.Errorf("failed to unmarshal list of issues: %w", err)
}
if !open {
// There is no query available to sort by closing time of issues
sort.Slice(list, func(i, j int) bool {
return list[i].ClosedAt.After(list[j].ClosedAt)
})
}
for _, i := range list {
if i.Title == issue.title {
issueGot := newGithubIssue(githubIssueOptions{
Number: i.Number,
Title: i.Title,
Description: i.Body,
Labels: i.Labels,
State: i.State,
Repository: issue.repository,
URL: i.URL,
})
return true, issueGot, nil
}
}
return false, nil, nil
}
func (g *ghCli) Create(ctx context.Context, issue *githubIssue) error {
params := []string{
"issue",
"create",
"--title",
issue.title,
"--body",
issue.description,
"--repo",
issue.repository,
}
for _, label := range issue.labels {
params = append(params, "--label", label)
}
stdout, stderr, err := g.runner.Exec(ctx, params...)
if err != nil {
return fmt.Errorf("failed to create issue: %w\n%s", err, stderr.String())
}
fmt.Println("Created issue:", stdout.String())
return nil
}
func (g *ghCli) Update(ctx context.Context, issue *githubIssue) error {
params := []string{
"issue",
"edit",
strconv.Itoa(issue.number),
"--body",
issue.description,
"--repo",
issue.repository,
}
_, stderr, err := g.runner.Exec(ctx, params...)
if err != nil {
return fmt.Errorf("failed to update issue: %w\n%s", err, stderr.String())
}
return nil
}