1 package owners
2
3 import (
4 "context"
5 "flag"
6 "fmt"
7 "io/fs"
8 "os"
9 "path/filepath"
10 "sort"
11 "strings"
12
13 "github.com/peterbourgon/ff/v3"
14 "github.com/peterbourgon/ff/v3/ffcli"
15 "gopkg.in/yaml.v2"
16
17 repoowners "edge-infra.dev/pkg/f8n/devinfra/repo/owners"
18 "edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy"
19 "edge-infra.dev/pkg/tools/hack"
20 )
21
22 const (
23 policyBotFile = ".github/policy-bot.yaml"
24 rootRulesFile = ".github/repo-policy-bot-rules.yaml"
25 )
26
27 var ignoreList = [4]string{".git/", "tmp/", ".vscode/", "testdata"}
28
29 type owners struct {
30 *hack.Hack
31
32 policyBotFile string
33 rootRulesFile string
34 }
35
36 func New(root *hack.Hack) *ffcli.Command {
37 o := &owners{Hack: root}
38
39 fs := flag.NewFlagSet("hack owners", flag.ContinueOnError)
40 o.RegisterFlags(fs)
41
42 return &ffcli.Command{
43 Name: "owners",
44 FlagSet: fs,
45 Exec: o.Exec,
46 Subcommands: []*ffcli.Command{
47 newUpdate(o),
48 newCreate(o),
49 newVerify(o),
50 newList(o),
51 },
52 Options: []ff.Option{
53 ff.WithEnvVarNoPrefix(),
54 },
55 }
56 }
57
58 func (o *owners) RegisterFlags(fs *flag.FlagSet) {
59 fs.StringVar(&o.policyBotFile, "policy-bot-config", policyBotFile,
60 "path to policy-bot configuration, used for reading and updating")
61 fs.StringVar(&o.rootRulesFile, "root-rules", rootRulesFile,
62 "path to file containing root rules to OR with all generated rules")
63 }
64
65 func (o *owners) Exec(_ context.Context, _ []string) error {
66 flag.Usage()
67 return nil
68 }
69
70 func (o *owners) collectOwners() ([]string, map[string]repoowners.File, error) {
71 files := map[string]repoowners.File{}
72 fileKeys := []string{}
73 dir := o.Paths.RepoRoot
74
75 if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
76 if err != nil {
77 return err
78 }
79
80
81 for _, x := range ignoreList {
82 if strings.Contains(path, x) {
83 return filepath.SkipDir
84 }
85 }
86
87 if d.IsDir() || d.Name() != repoowners.FileName {
88 return nil
89 }
90
91 f, err := o.loadOwnersFile(path)
92 if err != nil {
93 return err
94 }
95
96
97 pathKey, err := filepath.Rel(dir, filepath.Dir(path))
98 if err != nil {
99 return err
100 }
101 fileKeys = append(fileKeys, pathKey)
102 files[pathKey] = *f
103
104 return nil
105 }); err != nil {
106 return nil, nil, err
107 }
108
109 sort.Slice(fileKeys, func(i, j int) bool {
110 return fileKeys[i] < fileKeys[j]
111 })
112
113 return fileKeys, files, nil
114 }
115
116 func (o *owners) loadOwnersFile(path string) (*repoowners.File, error) {
117 d, err := os.ReadFile(path)
118 if err != nil {
119 return nil, fmt.Errorf("failed to read %s: %w", path, err)
120 }
121
122 f := &repoowners.File{}
123 if err := yaml.UnmarshalStrict(d, f); err != nil {
124 return nil, fmt.Errorf("failed to parse %s: %w", path, err)
125 }
126
127 return f, nil
128 }
129
130 func (o *owners) getPolicyBotConfig() ([]byte, error) {
131 return os.ReadFile(filepath.Join(o.Paths.RepoRoot, o.policyBotFile))
132 }
133
134
135
136 func isValidLocalPolicy(requestPolicy []byte) (bool, error) {
137 var policyConfig policy.Config
138 if err := yaml.UnmarshalStrict(requestPolicy, &policyConfig); err != nil {
139 return false, err
140 }
141
142 if _, err := policy.ParsePolicy(&policyConfig); err != nil {
143 return false, err
144 }
145
146 return true, nil
147 }
148
View as plain text