Skip to content

Commit 900cb40

Browse files
authored
Merge pull request kubernetes#125801 from akhilerm/rewrite-publishing-bot-verify-script
rewrite publishing-bot verify script in go
2 parents f248c24 + b0bf3fb commit 900cb40

File tree

6 files changed

+285
-137
lines changed

6 files changed

+285
-137
lines changed

hack/tools/go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ require (
1010
github.com/jcchavezs/porto v0.6.0
1111
github.com/vektra/mockery/v2 v2.40.3
1212
go.uber.org/automaxprocs v1.5.2
13+
golang.org/x/mod v0.20.0
1314
gotest.tools/gotestsum v1.12.0
1415
honnef.co/go/tools v0.5.1
16+
k8s.io/publishing-bot v0.5.0
1517
sigs.k8s.io/logtools v0.8.1
1618
)
1719

@@ -74,6 +76,7 @@ require (
7476
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
7577
github.com/gobwas/glob v0.2.3 // indirect
7678
github.com/gofrs/flock v0.8.1 // indirect
79+
github.com/golang/glog v1.2.2 // indirect
7780
github.com/golang/protobuf v1.5.3 // indirect
7881
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
7982
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
@@ -190,7 +193,6 @@ require (
190193
go.uber.org/zap v1.24.0 // indirect
191194
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
192195
golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect
193-
golang.org/x/mod v0.17.0 // indirect
194196
golang.org/x/sync v0.7.0 // indirect
195197
golang.org/x/sys v0.20.0 // indirect
196198
golang.org/x/term v0.18.0 // indirect

hack/tools/go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
207207
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
208208
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
209209
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
210+
github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=
211+
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
210212
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
211213
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
212214
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -678,8 +680,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
678680
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
679681
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
680682
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
681-
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
682-
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
683+
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
684+
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
683685
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
684686
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
685687
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1024,6 +1026,8 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
10241026
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
10251027
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
10261028
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
1029+
k8s.io/publishing-bot v0.5.0 h1:Hfnhltr+khEcqvoK4GBYrtaA8dHJ50Xjyi+0KGUfU3I=
1030+
k8s.io/publishing-bot v0.5.0/go.mod h1:S5+zQQhsVUEqdcaohbYf8O+2BeeWRtuYzp4tQLr5An8=
10271031
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
10281032
mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
10291033
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"path/filepath"
23+
"strings"
24+
25+
"golang.org/x/mod/modfile"
26+
"k8s.io/publishing-bot/cmd/publishing-bot/config"
27+
)
28+
29+
var (
30+
rulesFile string
31+
componentsDirectory string
32+
)
33+
34+
// getGoModDependencies gets all the staging dependencies for all the modules
35+
// in the given directory
36+
func getGoModDependencies(dir string) (map[string][]string, error) {
37+
allDependencies := make(map[string][]string)
38+
components, err := os.ReadDir(dir)
39+
if err != nil {
40+
return nil, err
41+
}
42+
for _, component := range components {
43+
componentName := component.Name()
44+
if !component.IsDir() {
45+
// currently there is no hard check that the staging directory should not contain
46+
// other files
47+
continue
48+
}
49+
gomodFilePath := filepath.Join(dir, componentName, "go.mod")
50+
gomodFileContent, err := os.ReadFile(gomodFilePath)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
fmt.Printf("%s dependencies", componentName)
56+
57+
allDependencies[componentName] = make([]string, 0)
58+
59+
gomodFile, err := modfile.Parse(gomodFilePath, gomodFileContent, nil)
60+
if err != nil {
61+
return nil, err
62+
}
63+
// get all the other dependencies from within staging, i.e all the modules in replace
64+
// section
65+
for _, module := range gomodFile.Replace {
66+
dep := strings.TrimPrefix(module.Old.Path, "k8s.io/")
67+
if dep == componentName {
68+
continue
69+
}
70+
allDependencies[componentName] = append(allDependencies[componentName], dep)
71+
}
72+
}
73+
return allDependencies, nil
74+
}
75+
76+
// diffSlice returns the difference of s1-s2
77+
func diffSlice(s1, s2 []string) []string {
78+
var diff []string
79+
set := make(map[string]struct{}, len(s2))
80+
for _, s := range s2 {
81+
set[s] = struct{}{}
82+
}
83+
for _, s := range s1 {
84+
if _, ok := set[s]; !ok {
85+
diff = append(diff, s)
86+
}
87+
}
88+
return diff
89+
}
90+
91+
// getKeys returns a slice with only the keys of the given map
92+
func getKeys[K comparable, V any](m map[K]V) []K {
93+
var keys []K
94+
for k := range m {
95+
keys = append(keys, k)
96+
}
97+
return keys
98+
}
99+
100+
// checkValidSourceDirectory checks if proper source directory fields are used in rules
101+
func checkValidSourceDirectory(rule config.RepositoryRule) error {
102+
for _, branch := range rule.Branches {
103+
if branch.Source.Dir != "" {
104+
return fmt.Errorf("use of deprecated `dir` field in rules for `%s`", rule.DestinationRepository)
105+
}
106+
if len(branch.Source.Dirs) > 1 {
107+
return fmt.Errorf("cannot have more than one directory (%s) per source branch `%s` of `%s`",
108+
branch.Source.Dirs,
109+
branch.Source.Branch,
110+
rule.DestinationRepository,
111+
)
112+
}
113+
if !strings.HasSuffix(branch.Source.Dirs[0], rule.DestinationRepository) {
114+
return fmt.Errorf("copy/paste error `%s` refers to `%s`", rule.DestinationRepository, branch.Source.Dirs[0])
115+
}
116+
}
117+
return nil
118+
}
119+
120+
// checkMasterBranch checks if the master branch of destination repository refers to the master
121+
// of the source
122+
func checkMasterBranch(rule config.RepositoryRule) error {
123+
branch := rule.Branches[0]
124+
if branch.Name != "master" {
125+
return fmt.Errorf("cannot find master branch for destination `%s`", rule.DestinationRepository)
126+
}
127+
128+
if branch.Source.Branch != "master" {
129+
return fmt.Errorf("cannot find master source branch for destination `%s`", rule.DestinationRepository)
130+
}
131+
return nil
132+
}
133+
134+
func checkDependencies(rule config.RepositoryRule, gomodDependencies map[string][]string) error {
135+
var processedDeps []string
136+
branch := rule.Branches[0]
137+
for _, dep := range gomodDependencies[rule.DestinationRepository] {
138+
found := false
139+
if len(branch.Dependencies) > 0 {
140+
for _, dep2 := range branch.Dependencies {
141+
processedDeps = append(processedDeps, dep2.Repository)
142+
if dep2.Branch != "master" {
143+
return fmt.Errorf("looking for master branch of %s and found : %s for destination", dep2.Repository, rule.DestinationRepository)
144+
}
145+
if dep2.Repository == dep {
146+
found = true
147+
}
148+
}
149+
} else {
150+
return fmt.Errorf("Please add %s as dependencies under destination %s", gomodDependencies[rule.DestinationRepository], rule.DestinationRepository)
151+
}
152+
if !found {
153+
return fmt.Errorf("Please add %s as a dependency under destination %s", dep, rule.DestinationRepository)
154+
} else {
155+
fmt.Printf("dependency %s found\n", dep)
156+
}
157+
}
158+
// check if all deps are processed.
159+
extraDeps := diffSlice(processedDeps, gomodDependencies[rule.DestinationRepository])
160+
if len(extraDeps) > 0 {
161+
return fmt.Errorf("extra dependencies in rules for %s: %s", rule.DestinationRepository, strings.Join(extraDeps, ","))
162+
}
163+
return nil
164+
}
165+
166+
func verifyPublishingBotRules() error {
167+
rules, err := config.LoadRules(rulesFile)
168+
if err != nil {
169+
return fmt.Errorf("error loading rules: %v", err)
170+
}
171+
172+
gomodDependencies, err := getGoModDependencies(componentsDirectory)
173+
174+
var processedRepos []string
175+
for _, rule := range rules.Rules {
176+
branch := rule.Branches[0]
177+
178+
// if this no longer exists in master
179+
if _, ok := gomodDependencies[rule.DestinationRepository]; !ok {
180+
// make sure we dont include a rule to publish it from master
181+
for _, branch := range rule.Branches {
182+
if branch.Name == "master" {
183+
err := fmt.Errorf("cannot find master branch for destination `%s`", rule.DestinationRepository)
184+
panic(err)
185+
}
186+
}
187+
// and skip the validation of publishing rules for it
188+
continue
189+
}
190+
191+
if err := checkValidSourceDirectory(rule); err != nil {
192+
return fmt.Errorf("error validating source directory: %v", err)
193+
}
194+
195+
if err := checkMasterBranch(rule); err != nil {
196+
return fmt.Errorf("error validating master branch: %v", err)
197+
}
198+
199+
// we specify the go version for all master branches through `default-go-version`
200+
// so ensure we don't specify explicit go version for master branch in rules
201+
if branch.GoVersion != "" {
202+
err := fmt.Errorf("go version must not be specified for master branch for destination `%s`", rule.DestinationRepository)
203+
panic(err)
204+
}
205+
206+
fmt.Printf("processing : %s", rule.DestinationRepository)
207+
if _, ok := gomodDependencies[rule.DestinationRepository]; !ok {
208+
err := fmt.Errorf("missing go.mod for `%s`", rule.DestinationRepository)
209+
panic(err)
210+
}
211+
processedRepos = append(processedRepos, rule.DestinationRepository)
212+
213+
if err := checkDependencies(rule, gomodDependencies); err != nil {
214+
return fmt.Errorf("error validating dependencies: %v", err)
215+
}
216+
}
217+
218+
// check if all repos are processed.
219+
items := diffSlice(getKeys(gomodDependencies), processedRepos)
220+
if len(items) > 0 {
221+
err := fmt.Errorf("missing rules for %s", strings.Join(items, ","))
222+
panic(err)
223+
}
224+
return nil
225+
}
226+
227+
func main() {
228+
if len(os.Args) != 2 {
229+
panic("invalid number of arguments")
230+
}
231+
232+
kubeRoot := os.Args[1]
233+
stagingDirectory := kubeRoot + "/staging/"
234+
rulesFile = stagingDirectory + "publishing/rules.yaml"
235+
componentsDirectory = stagingDirectory + "src/k8s.io/"
236+
237+
if err := verifyPublishingBotRules(); err != nil {
238+
panic(err)
239+
}
240+
}

hack/tools/tools.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ import (
3636

3737
// tools like cpu
3838
_ "go.uber.org/automaxprocs"
39+
40+
// for publishing bot
41+
_ "golang.org/x/mod/modfile"
42+
_ "k8s.io/publishing-bot/cmd/publishing-bot/config"
3943
)

0 commit comments

Comments
 (0)