This commit is contained in:
Benson Wong 2017-12-08 15:55:31 -08:00
Родитель 0609d06fec
Коммит eab5e182b1
7 изменённых файлов: 442 добавлений и 14 удалений

16
.gitignore поставляемый
Просмотреть файл

@ -1,14 +1,2 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
vendor/
cli

15
Gopkg.lock сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,15 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "gopkg.in/urfave/cli.v1"
packages = ["."]
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
version = "v1.20.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "31a875231f3ca87038f250aaf7575aad37b2ec5481e318e1071bff16b72d8182"
solver-name = "gps-cdcl"
solver-version = 1

26
Gopkg.toml Normal file
Просмотреть файл

@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "gopkg.in/urfave/cli.v1"
version = "1.20.0"

17
cli.go Normal file
Просмотреть файл

@ -0,0 +1,17 @@
package main
import (
"os"
"github.com/mozilla/pd-cli/command/repo"
"gopkg.in/urfave/cli.v1"
)
func main() {
app := cli.NewApp()
app.Commands = []cli.Command{
repo.NewCommand(),
}
app.Run(os.Args)
}

232
command/repo/checks.go Normal file
Просмотреть файл

@ -0,0 +1,232 @@
package repo
import (
"context"
"errors"
"fmt"
"github.com/google/go-github/github"
"gopkg.in/urfave/cli.v1"
)
// this file contains all the verification checking functions. Look in repo.go for
// where all these private functions are wired up. These functions depend on
// preflight() which sets ghClient, owner and repo
func checkAll(c *cli.Context) error {
funcs := []cli.ActionFunc{
checkTopic,
checkLabels,
checkUnassigned,
checkUnlabled,
checkMilestones,
}
for _, f := range funcs {
if err := f(c); err != nil {
return err
}
}
return nil
}
// checkTopic ensures "product-delivery" topic is assigned to the repo
func checkTopic(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "Checking for [product-delivery] topic\n")
ctx := context.Background()
topics, _, err := ghClient.Repositories.ListAllTopics(ctx, owner, repo)
if err != nil {
fmt.Fprintf(c.App.Writer, " - Error: %s\n", err.Error())
return err
}
for _, name := range topics.Names {
if name == "product-delivery" {
fmt.Fprintf(c.App.Writer, " - OK. Found product-delivery topic\n")
return nil
}
}
fmt.Fprintf(c.App.Writer, " - Error: product-delivery topic not set\n")
return errors.New("product-delivery topic not set")
}
func checkLabels(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "Checking Labels\n")
labels, _, err := ghClient.Issues.ListLabels(context.Background(), owner, repo, nil)
if err != nil {
fmt.Fprintf(c.App.Writer, " - Error: %s\n", err.Error())
return err
}
// expected labels and their color
standardLabels := map[string]string{
"bug": "b60205",
"security": "b60205",
"documentation": "0e8a16",
"fix": "0e8a16",
"new-feature": "0e8a16",
"P1": "ffa32c",
"P2": "ffa32c",
"P3": "ffa32c",
"P5": "ffa32c",
"proposal": "1d76db",
"question": "1d76db",
"support-request": "1d76db",
}
for _, label := range labels {
if label == nil || label.Name == nil || label.Color == nil {
continue
}
name := *label.Name
color := *label.Color
if expectedColor, ok := standardLabels[name]; !ok {
// not a standard label
if color != "5319e7" {
fmt.Fprintf(c.App.Writer, " - Error: [%s] should have color #5319e7\n", name)
} else {
fmt.Fprintf(c.App.Writer, " - OK. [%s] verified\n", name)
}
} else {
// check standard label has correct color
if color != expectedColor {
fmt.Fprintf(c.App.Writer, " - Error: standard label [%s] should have color #%s\n", name, expectedColor)
} else {
fmt.Fprintf(c.App.Writer, " - OK. [%s] verified\n", name)
}
// delete it so we know how many are missing
delete(standardLabels, name)
}
}
// check for missing standard labels
for missing, color := range standardLabels {
fmt.Fprintf(c.App.Writer, " - Error: missing %s (%s)\n", missing, color)
}
return nil
}
func checkUnassigned(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "Checking Unassigned Issues\n")
query := fmt.Sprintf("repo:%s/%s is:open no:assignee label:P1", owner, repo)
results, _, err := ghClient.Search.Issues(context.Background(), query, nil)
if err != nil {
fmt.Fprintf(c.App.Writer, " - Error: %s\n", err.Error())
return err
}
count := *results.Total
if count > 0 {
fmt.Fprintf(c.App.Writer, " - Error: %d unassigned P1 issues\n", count)
for _, issue := range results.Issues {
fmt.Fprintf(c.App.Writer, " #%-4d %s", *issue.Number, *issue.Title)
}
} else {
fmt.Fprintf(c.App.Writer, " - OK. All P1 issues assigned\n")
}
return nil
}
func checkUnlabled(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "Checking Unlabled\n")
query := fmt.Sprintf("repo:%s/%s is:open no:label is:issue", owner, repo)
results, _, err := ghClient.Search.Issues(context.Background(), query, nil)
if err != nil {
fmt.Fprintf(c.App.Writer, " - Error: %s\n", err.Error())
return err
}
unassigned := *results.Total
if unassigned > 0 {
fmt.Fprintf(c.App.Writer, " - Error: %d issues unlabeled\n", unassigned)
for _, issue := range results.Issues {
fmt.Fprintf(c.App.Writer, " #%-4d %s\n", *issue.Number, *issue.Title)
}
} else {
fmt.Fprintf(c.App.Writer, " - OK. All issues are labeled\n")
}
return nil
}
func checkMilestones(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "Checking Milestones\n")
ctx := context.Background()
milestones, _, err := ghClient.Issues.ListMilestones(ctx, owner, repo, nil)
if err != nil {
fmt.Fprintf(c.App.Writer, " - Error: Feteching milestones, %s\n", err.Error())
return err
}
projects, _, err := ghClient.Repositories.ListProjects(ctx, owner, repo, nil)
if err != nil {
fmt.Fprintf(c.App.Writer, " - Error: Fetching projects, %s\n", err.Error())
return err
}
pMap := make(map[string]*github.Project)
for _, p := range projects {
pMap[*p.Name] = p
}
errHappend := false
for _, milestone := range milestones {
if project, found := pMap[*milestone.Title]; !found {
fmt.Fprintf(c.App.Writer, " - Error: %s does not have a matching project", *milestone.Title)
} else {
// check the project's columns
pCols, _, err := ghClient.Projects.ListProjectColumns(ctx, *project.ID, nil)
if err != nil {
fmt.Fprintf(c.App.Writer, " - Error: Fetching project columns, %s\n", err.Error())
return err
}
flags := 0
for _, col := range pCols {
switch *col.Name {
case "Backlog":
flags |= 0x01
case "In Progress":
flags |= 0x02
case "Blocked":
flags |= 0x04
case "Completed":
flags |= 0x08
default:
fmt.Fprintf(c.App.Writer, ` - Error: Project "%s" has unexpected column %s\n`,
*project.Name, *col.Name)
errHappend = true
}
}
if flags&0x01 == 0 {
fmt.Fprintf(c.App.Writer, ` - Error: Project "%s" missing "Backlog" column\n`, *project.Name)
errHappend = true
}
if flags&0x02 == 0 {
fmt.Fprintf(c.App.Writer, ` - Error: Project "%s" missing "In Progress" column\n`, *project.Name)
errHappend = true
}
if flags&0x04 == 0 {
fmt.Fprintf(c.App.Writer, ` - Error: Project "%s" missing "Blocked" column\n`, *project.Name)
errHappend = true
}
if flags&0x08 == 0 {
fmt.Fprintf(c.App.Writer, ` - Error: Project "%s" missing "Completed" column\n`, *project.Name)
errHappend = true
}
}
}
if !errHappend {
fmt.Fprintf(c.App.Writer, " - OK. Milestones verified\n")
}
return nil
}

12
command/repo/init.go Normal file
Просмотреть файл

@ -0,0 +1,12 @@
package repo
import (
"fmt"
"github.com/urfave/cli"
)
func initRepo(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "Initializing Repository %s/%s\n", owner, repo)
return nil
}

138
command/repo/repo.go Normal file
Просмотреть файл

@ -0,0 +1,138 @@
package repo
import (
"context"
"errors"
"fmt"
"golang.org/x/oauth2"
"github.com/google/go-github/github"
"gopkg.in/urfave/cli.v1"
)
var (
errMissingFlag = errors.New("Missing flag")
// these are set in preflight()
ghtoken, repo, owner string
ghClient *github.Client
)
// NewRepoCommand creates the `repo` command tree
func NewCommand() cli.Command {
return cli.Command{
Name: "repo",
Usage: "tools for managing our github repos",
Flags: []cli.Flag{
cli.StringFlag{
Name: "ghtoken, g",
Usage: "github access token",
EnvVar: "GH_ACCESS_TOKEN",
},
cli.StringFlag{
Name: "owner,o",
Usage: "Owner of the repo",
},
cli.StringFlag{
Name: "repo, r",
Usage: "Name of the repo",
},
},
Before: preflight,
Subcommands: cli.Commands{
cli.Command{
Name: "init",
Usage: "initializes a repo",
Action: initRepo,
},
cli.Command{
Name: "check",
Usage: "checks to verify standards conformity",
Subcommands: cli.Commands{
cli.Command{
Name: "all",
Usage: "Runs all checks on a repository",
Action: checkAll,
},
cli.Command{
Name: "topic",
Usage: "Checks `product-delivery` topic is set",
Action: checkTopic,
},
cli.Command{
Name: "labels",
Usage: "Checks `product-delivery` topic is set",
Action: checkLabels,
},
cli.Command{
Name: "unassigned",
Usage: "verify P1 issues are assigned to somebody",
Action: checkUnassigned,
},
cli.Command{
Name: "unlabled",
Usage: "finds issues that do not have a label",
Action: checkUnlabled,
},
cli.Command{
Name: "milestones",
Usage: "verify milestones have a project to track them",
Action: checkMilestones,
},
},
},
},
}
}
// preflight ensures necessary flags and sets the package vars: ghClient, owner and repo
func preflight(c *cli.Context) (err error) {
// set package vars
ghtoken = c.String("ghtoken")
owner = c.String("owner")
repo = c.String("repo")
if ghtoken == "" {
fmt.Fprintf(c.App.Writer, "Error: github access token required\n")
err = errMissingFlag
}
if owner == "" {
fmt.Fprintf(c.App.Writer, "Error: owner required\n")
err = errMissingFlag
}
if repo == "" {
fmt.Fprintf(c.App.Writer, "Error: repo name required\n")
err = errMissingFlag
}
if err != nil {
return
}
ctx := context.Background()
// init ghClient if it's not already there
// useful for checkAll where it calls all the sub-commands so they
// don't have to recreate the ghClient
if ghClient == nil {
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: ghtoken})
tc := oauth2.NewClient(ctx, ts)
ghClient = github.NewClient(tc)
}
r, _, err := ghClient.Repositories.Get(ctx, owner, repo)
if err != nil {
fmt.Fprintf(c.App.Writer, "Error: %s\n", err.Error())
return err
} else if r.FullName == nil {
fmt.Fprintf(c.App.Writer, "Error: [%s] does not have a FullName entry\n", repo)
return errors.New("No repo fullname")
}
return
}