зеркало из https://github.com/mozilla/pd-cli.git
first commit
This commit is contained in:
Родитель
0609d06fec
Коммит
eab5e182b1
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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"
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Загрузка…
Ссылка в новой задаче