package owners import ( "context" "flag" "fmt" "io/fs" "os" "path/filepath" "sort" "strings" "github.com/peterbourgon/ff/v3" "github.com/peterbourgon/ff/v3/ffcli" "gopkg.in/yaml.v2" repoowners "edge-infra.dev/pkg/f8n/devinfra/repo/owners" "edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy" "edge-infra.dev/pkg/tools/hack" ) const ( policyBotFile = ".github/policy-bot.yaml" rootRulesFile = ".github/repo-policy-bot-rules.yaml" ) var ignoreList = [4]string{".git/", "tmp/", ".vscode/", "testdata"} type owners struct { *hack.Hack policyBotFile string rootRulesFile string } func New(root *hack.Hack) *ffcli.Command { o := &owners{Hack: root} fs := flag.NewFlagSet("hack owners", flag.ContinueOnError) o.RegisterFlags(fs) return &ffcli.Command{ Name: "owners", FlagSet: fs, Exec: o.Exec, Subcommands: []*ffcli.Command{ newUpdate(o), newCreate(o), newVerify(o), newList(o), }, Options: []ff.Option{ ff.WithEnvVarNoPrefix(), }, } } func (o *owners) RegisterFlags(fs *flag.FlagSet) { fs.StringVar(&o.policyBotFile, "policy-bot-config", policyBotFile, "path to policy-bot configuration, used for reading and updating") fs.StringVar(&o.rootRulesFile, "root-rules", rootRulesFile, "path to file containing root rules to OR with all generated rules") } func (o *owners) Exec(_ context.Context, _ []string) error { flag.Usage() return nil } func (o *owners) collectOwners() ([]string, map[string]repoowners.File, error) { files := map[string]repoowners.File{} fileKeys := []string{} dir := o.Paths.RepoRoot if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } // check the path against the list of ignored directories for _, x := range ignoreList { if strings.Contains(path, x) { return filepath.SkipDir } } if d.IsDir() || d.Name() != repoowners.FileName { return nil } f, err := o.loadOwnersFile(path) if err != nil { return err } // make path relative to repo root pathKey, err := filepath.Rel(dir, filepath.Dir(path)) if err != nil { return err } fileKeys = append(fileKeys, pathKey) files[pathKey] = *f return nil }); err != nil { return nil, nil, err } sort.Slice(fileKeys, func(i, j int) bool { return fileKeys[i] < fileKeys[j] }) return fileKeys, files, nil } func (o *owners) loadOwnersFile(path string) (*repoowners.File, error) { d, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read %s: %w", path, err) } f := &repoowners.File{} if err := yaml.UnmarshalStrict(d, f); err != nil { return nil, fmt.Errorf("failed to parse %s: %w", path, err) } return f, nil } func (o *owners) getPolicyBotConfig() ([]byte, error) { return os.ReadFile(filepath.Join(o.Paths.RepoRoot, o.policyBotFile)) } // stolen from pbot // https://github.com/palantir/policy-bot/blob/da581a398aa00701bd481bc9c4bd5f4e2fc831ec/server/handler/validate.go#L70 func isValidLocalPolicy(requestPolicy []byte) (bool, error) { var policyConfig policy.Config if err := yaml.UnmarshalStrict(requestPolicy, &policyConfig); err != nil { return false, err } if _, err := policy.ParsePolicy(&policyConfig); err != nil { return false, err } return true, nil }