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) } }