mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
a6bc262093
* s/grafana-delivery/grafana-release-guild/g * Remove -squad suffix
180 lines
4.5 KiB
Go
Executable File
180 lines
4.5 KiB
Go
Executable File
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"log"
|
|
"strings"
|
|
|
|
"os"
|
|
|
|
"golang.org/x/mod/modfile"
|
|
)
|
|
|
|
type Module struct {
|
|
Name string
|
|
Owners []string
|
|
Indirect bool
|
|
}
|
|
|
|
func parseModule(mod *modfile.Require) Module {
|
|
m := Module{Name: mod.Mod.String()}
|
|
|
|
// For each require, access the comment.
|
|
for _, comment := range mod.Syntax.Comments.Suffix {
|
|
owners := strings.Fields(comment.Token)
|
|
// For each comment, determine if it contains owner(s).
|
|
for _, owner := range owners {
|
|
if strings.Contains(owner, "indirect") {
|
|
m.Indirect = true
|
|
}
|
|
// If there is an owner, add to owners list.
|
|
if strings.Contains(owner, "@") {
|
|
m.Owners = append(m.Owners, owner)
|
|
}
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func parseGoMod(fileSystem fs.FS, name string) ([]Module, error) {
|
|
file, err := fileSystem.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Turn modfile into array of bytes.
|
|
data, err := io.ReadAll(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse modfile.
|
|
modFile, err := modfile.Parse(name, data, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
modules := []Module{}
|
|
|
|
// Iterate through requires in modfile.
|
|
for _, mod := range modFile.Require {
|
|
m := parseModule(mod)
|
|
modules = append(modules, m)
|
|
}
|
|
return modules, nil
|
|
}
|
|
|
|
// Validate that each module has an owner.
|
|
// An example CLI command is `go run scripts/modowners/modowners.go check go.mod`
|
|
func check(fileSystem fs.FS, logger *log.Logger, args []string) error {
|
|
m, err := parseGoMod(fileSystem, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fail := false
|
|
for _, mod := range m {
|
|
if !mod.Indirect && len(mod.Owners) == 0 {
|
|
logger.Println(mod.Name)
|
|
fail = true
|
|
}
|
|
}
|
|
if fail {
|
|
return errors.New("one or more newly added dependencies do not have an assigned owner - please assign a team as an owner")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Print owner(s) for a given dependency.
|
|
// An example CLI command to get a list of all owners in go.mod with a count of the number of dependencies they own is `go run scripts/modowners/modowners.go owners -a -c go.mod`
|
|
// An example CLI command to get the owner for a specific dependency is `go run scripts/modowners/modowners.go owners -d cloud.google.com/go/storage@v1.30.1 go.mod`. You must use `dependency@version`, not `dependency version`.
|
|
func owners(fileSystem fs.FS, logger *log.Logger, args []string) error {
|
|
fs := flag.NewFlagSet("owners", flag.ExitOnError)
|
|
allOwners := fs.Bool("a", false, "print all owners in specified file")
|
|
count := fs.Bool("c", false, "print count of dependencies per owner")
|
|
dep := fs.String("d", "", "name of dependency")
|
|
fs.Parse(args)
|
|
m, err := parseGoMod(fileSystem, fs.Arg(0))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
owners := map[string]int{}
|
|
for _, mod := range m {
|
|
if len(*dep) > 0 && mod.Name == *dep {
|
|
for _, owner := range mod.Owners {
|
|
logger.Println(owner)
|
|
break
|
|
}
|
|
}
|
|
if mod.Indirect == false {
|
|
for _, owner := range mod.Owners {
|
|
owners[owner]++
|
|
}
|
|
}
|
|
}
|
|
if *allOwners {
|
|
for owner, n := range owners {
|
|
if *count {
|
|
logger.Println(owner, n)
|
|
} else {
|
|
logger.Println(owner)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Print dependencies for a given owner. Can specify one or more owners.
|
|
// An example CLI command to list all direct dependencies owned by Delivery and Authnz `go run scripts/modowners/modowners.go modules -o @grafana/grafana-release-guild,@grafana/identity-access-team go.mod`
|
|
func modules(fileSystem fs.FS, logger *log.Logger, args []string) error {
|
|
fs := flag.NewFlagSet("modules", flag.ExitOnError)
|
|
indirect := fs.Bool("i", false, "print indirect dependencies")
|
|
owner := fs.String("o", "", "one or more owners")
|
|
fs.Parse(args)
|
|
m, err := parseGoMod(fileSystem, fs.Arg(0))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ownerFlags := strings.Split(*owner, ",")
|
|
for _, mod := range m {
|
|
if len(*owner) == 0 || hasCommonElement(mod.Owners, ownerFlags) {
|
|
if *indirect || !mod.Indirect {
|
|
logger.Println(mod.Name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasCommonElement(a []string, b []string) bool {
|
|
for _, u := range a {
|
|
for _, v := range b {
|
|
if u == v {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func main() {
|
|
log.SetFlags(0)
|
|
log.SetOutput(os.Stdout)
|
|
if len(os.Args) < 2 {
|
|
fmt.Println("usage: modowners subcommand go.mod...")
|
|
os.Exit(1)
|
|
}
|
|
type CmdFunc func(fs.FS, *log.Logger, []string) error
|
|
cmds := map[string]CmdFunc{"check": check, "owners": owners, "modules": modules}
|
|
if f, ok := cmds[os.Args[1]]; !ok {
|
|
log.Fatal("invalid command")
|
|
} else if err := f(os.DirFS("."), log.Default(), os.Args[2:]); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|