Skip to content

Commit f6b23b1

Browse files
authored
Merge pull request #2774 from alexandremahdhaoui/sync_go_mod_kubernetes_kubernetes
🌱 verify go modules are in sync with upstream k/k
2 parents 7743591 + 105349a commit f6b23b1

File tree

6 files changed

+231
-3
lines changed

6 files changed

+231
-3
lines changed

.gomodcheck.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
upstreamRefs:
2+
- k8s.io/api
3+
- k8s.io/apiextensions-apiserver
4+
- k8s.io/apimachinery
5+
- k8s.io/apiserver
6+
- k8s.io/client-go
7+
- k8s.io/component-base
8+
- k8s.io/klog/v2
9+
# k8s.io/utils -> conflicts with k/k deps
10+
11+
excludedModules:
12+
# --- test dependencies:
13+
- github.com/onsi/ginkgo/v2
14+
- github.com/onsi/gomega

Makefile

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ GOLANGCI_LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint
9292
$(GOLANGCI_LINT): # Build golangci-lint from tools folder.
9393
GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) $(GOLANGCI_LINT_PKG) $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER)
9494

95+
GO_MOD_CHECK_DIR := $(abspath ./hack/tools/cmd/gomodcheck)
96+
GO_MOD_CHECK := $(abspath $(TOOLS_BIN_DIR)/gomodcheck)
97+
GO_MOD_CHECK_IGNORE := $(abspath .gomodcheck.yaml)
98+
.PHONY: $(GO_MOD_CHECK)
99+
$(GO_MOD_CHECK): # Build gomodcheck
100+
go build -C $(GO_MOD_CHECK_DIR) -o $(GO_MOD_CHECK)
101+
95102
## --------------------------------------
96103
## Linting
97104
## --------------------------------------
@@ -130,16 +137,15 @@ clean-bin: ## Remove all generated binaries.
130137
rm -rf hack/tools/bin
131138

132139
.PHONY: verify-modules
133-
verify-modules: modules ## Verify go modules are up to date
140+
verify-modules: modules $(GO_MOD_CHECK) ## Verify go modules are up to date
134141
@if !(git diff --quiet HEAD -- go.sum go.mod $(TOOLS_DIR)/go.mod $(TOOLS_DIR)/go.sum $(ENVTEST_DIR)/go.mod $(ENVTEST_DIR)/go.sum $(SCRATCH_ENV_DIR)/go.sum); then \
135142
git diff; \
136143
echo "go module files are out of date, please run 'make modules'"; exit 1; \
137144
fi
145+
$(GO_MOD_CHECK) $(GO_MOD_CHECK_IGNORE)
138146

139147
APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main)
140148

141149
.PHONY: apidiff
142150
verify-apidiff: $(GO_APIDIFF) ## Check for API differences
143151
$(GO_APIDIFF) $(APIDIFF_OLD_COMMIT) --print-compatible
144-
145-

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ require (
3030
sigs.k8s.io/yaml v1.3.0
3131
)
3232

33+
require golang.org/x/mod v0.15.0
34+
3335
require (
3436
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
3537
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6R
156156
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
157157
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
158158
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
159+
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
160+
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
159161
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
160162
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
161163
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

hack/tools/.keep

Whitespace-only changes.

hack/tools/cmd/gomodcheck/main.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
"go.uber.org/zap"
11+
"golang.org/x/mod/modfile"
12+
"sigs.k8s.io/yaml"
13+
)
14+
15+
const (
16+
modFile = "./go.mod"
17+
)
18+
19+
type config struct {
20+
UpstreamRefs []string `json:"upstreamRefs"`
21+
ExcludedModules []string `json:"excludedModules"`
22+
}
23+
24+
type upstream struct {
25+
Ref string `json:"ref"`
26+
Version string `json:"version"`
27+
}
28+
29+
// representation of an out of sync module
30+
type oosMod struct {
31+
Name string `json:"name"`
32+
Version string `json:"version"`
33+
Upstreams []upstream `json:"upstreams"`
34+
}
35+
36+
func main() {
37+
l, _ := zap.NewProduction()
38+
logger := l.Sugar()
39+
40+
if len(os.Args) < 2 {
41+
fmt.Printf("USAGE: %s [PATH_TO_CONFIG_FILE]\n", os.Args[0])
42+
os.Exit(1)
43+
}
44+
45+
// --- 1. parse config
46+
b, err := os.ReadFile(os.Args[1])
47+
if err != nil {
48+
fatal(err)
49+
}
50+
51+
cfg := new(config)
52+
if err := yaml.Unmarshal(b, cfg); err != nil {
53+
fatal(err)
54+
}
55+
56+
excludedMods := make(map[string]any)
57+
for _, mod := range cfg.ExcludedModules {
58+
excludedMods[mod] = nil
59+
}
60+
61+
// --- 2. project mods
62+
projectModules, err := modulesFromGoModFile()
63+
if err != nil {
64+
fatal(err)
65+
}
66+
67+
// --- 3. upstream mods
68+
upstreamModules, err := modulesFromUpstreamModGraph(cfg.UpstreamRefs)
69+
if err != nil {
70+
fatal(err)
71+
}
72+
73+
oosMods := make([]oosMod, 0)
74+
75+
// --- 4. validate
76+
// for each module in our project,
77+
// if it matches an upstream module,
78+
// then for each upstream module,
79+
// if project module version doesn't match upstream version,
80+
// then we add the version and the ref to the list of out of sync modules.
81+
for mod, version := range projectModules {
82+
if _, ok := excludedMods[mod]; ok {
83+
logger.Infof("skipped: %s", mod)
84+
continue
85+
}
86+
87+
if versionToRef, ok := upstreamModules[mod]; ok {
88+
outOfSyncUpstream := make([]upstream, 0)
89+
90+
for upstreamVersion, upstreamRef := range versionToRef {
91+
if version == upstreamVersion { // pass if version in sync.
92+
continue
93+
}
94+
95+
outOfSyncUpstream = append(outOfSyncUpstream, upstream{
96+
Ref: upstreamRef,
97+
Version: upstreamVersion,
98+
})
99+
}
100+
101+
if len(outOfSyncUpstream) == 0 { // pass if no out of sync upstreams.
102+
continue
103+
}
104+
105+
oosMods = append(oosMods, oosMod{
106+
Name: mod,
107+
Version: version,
108+
Upstreams: outOfSyncUpstream,
109+
})
110+
}
111+
}
112+
113+
if len(oosMods) == 0 {
114+
fmt.Println("🎉 Success!")
115+
os.Exit(0)
116+
}
117+
118+
b, err = json.MarshalIndent(map[string]any{"outOfSyncModules": oosMods}, "", " ")
119+
if err != nil {
120+
fatal(err)
121+
}
122+
123+
fmt.Println(string(b))
124+
os.Exit(1)
125+
}
126+
127+
func modulesFromGoModFile() (map[string]string, error) {
128+
b, err := os.ReadFile(modFile)
129+
if err != nil {
130+
return nil, err
131+
}
132+
133+
f, err := modfile.Parse(modFile, b, nil)
134+
if err != nil {
135+
return nil, err
136+
}
137+
138+
out := make(map[string]string)
139+
for _, mod := range f.Require {
140+
out[mod.Mod.Path] = mod.Mod.Version
141+
}
142+
143+
return out, nil
144+
}
145+
146+
func modulesFromUpstreamModGraph(upstreamRefList []string) (map[string]map[string]string, error) {
147+
b, err := exec.Command("go", "mod", "graph").Output()
148+
if err != nil {
149+
return nil, err
150+
}
151+
152+
graph := string(b)
153+
154+
// upstreamRefs is a set of user specified upstream modules.
155+
// The set has 2 functions:
156+
// 1. Check if `go mod graph` modules are one of the user specified upstream modules.
157+
// 2. Mark if a user specified upstream module was found in the module graph.
158+
// If a user specified upstream module is not found, gomodcheck will exit with an error.
159+
upstreamRefs := make(map[string]bool)
160+
for _, ref := range upstreamRefList {
161+
upstreamRefs[ref] = false
162+
}
163+
164+
modToVersionToUpstreamRef := make(map[string]map[string]string)
165+
for _, line := range strings.Split(graph, "\n") {
166+
ref := strings.SplitN(line, "@", 2)[0]
167+
168+
if _, ok := upstreamRefs[ref]; !ok {
169+
continue
170+
}
171+
172+
upstreamRefs[ref] = true // mark the ref as found
173+
174+
kv := strings.SplitN(strings.SplitN(line, " ", 2)[1], "@", 2)
175+
name := kv[0]
176+
version := kv[1]
177+
178+
if _, ok := modToVersionToUpstreamRef[name]; !ok {
179+
modToVersionToUpstreamRef[name] = make(map[string]string)
180+
}
181+
182+
modToVersionToUpstreamRef[name][version] = ref
183+
}
184+
185+
notFoundErr := ""
186+
for ref, found := range upstreamRefs {
187+
if !found {
188+
notFoundErr = fmt.Sprintf("%s%s, ", notFoundErr, ref)
189+
}
190+
}
191+
192+
if notFoundErr != "" {
193+
return nil, fmt.Errorf("cannot verify modules: "+
194+
"the following specified upstream module(s) cannot be found in go.mod: [ %s ]",
195+
strings.TrimSuffix(notFoundErr, ", "))
196+
}
197+
198+
return modToVersionToUpstreamRef, nil
199+
}
200+
201+
func fatal(err error) {
202+
fmt.Printf("❌ %s\n", err.Error())
203+
os.Exit(1)
204+
}

0 commit comments

Comments
 (0)