From 86ad2dc266add10bf0a898c3b712e4e971499a4d Mon Sep 17 00:00:00 2001 From: Alexandre Mahdhaoui Date: Sun, 14 Apr 2024 01:21:28 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=B1=20verify=20go=20modules=20are=20in?= =?UTF-8?q?=20sync=20with=20upstream=20k/k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses issues were go modules aren't in sync with upstream k/k by adding these changes: - add `tools/cmd/gomodcheck/main.go` to: - Parse and compares k/k direct dependencies to controller-runtime's direct deps. - If any version diffs is found, returns a payload describing the diffs and exit 1. - The user may exclude packages by passing them as arguments. - extend the `verify-modules` make target with `gomodcheck`. Signed-off-by: Alexandre Mahdhaoui --- .gomodcheck.ignore | 9 +++ Makefile | 9 ++- tools/cmd/gomodcheck/main.go | 119 +++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 .gomodcheck.ignore create mode 100644 tools/cmd/gomodcheck/main.go diff --git a/.gomodcheck.ignore b/.gomodcheck.ignore new file mode 100644 index 0000000000..aa1479c9a2 --- /dev/null +++ b/.gomodcheck.ignore @@ -0,0 +1,9 @@ +github.com/onsi/gomega +github.com/onsi/ginkgo/v2 + +k8s.io/api +k8s.io/apimachinery +k8s.io/apiextensions-apiserver +k8s.io/apiserver +k8s.io/client-go +k8s.io/component-base diff --git a/Makefile b/Makefile index 2b5c6efb13..66b1b0bfb1 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,12 @@ GOLANGCI_LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint $(GOLANGCI_LINT): # Build golangci-lint from tools folder. GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) $(GOLANGCI_LINT_PKG) $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER) +GO_MOD_CHECK_DIR := $(abspath ./tools/cmd/gomodcheck) +GO_MOD_CHECK := $(abspath $(TOOLS_BIN_DIR)/gomodcheck) +GO_MOD_CHECK_IGNORE := $(abspath ./.gomodcheck.ignore) +$(GO_MOD_CHECK): # Build gomodcheck + go build -C $(GO_MOD_CHECK_DIR) -o $(GO_MOD_CHECK) + ## -------------------------------------- ## Linting ## -------------------------------------- @@ -134,11 +140,12 @@ clean-bin: ## Remove all generated binaries. rm -rf hack/tools/bin .PHONY: verify-modules -verify-modules: modules ## Verify go modules are up to date +verify-modules: modules $(GO_MOD_CHECK) ## Verify go modules are up to date @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 \ git diff; \ echo "go module files are out of date, please run 'make modules'"; exit 1; \ fi + cat $(GO_MOD_CHECK_IGNORE) | xargs $(GO_MOD_CHECK) APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main) diff --git a/tools/cmd/gomodcheck/main.go b/tools/cmd/gomodcheck/main.go new file mode 100644 index 0000000000..ae1ea0ce8a --- /dev/null +++ b/tools/cmd/gomodcheck/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "regexp" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +const ( + kkModUrl = "https://raw.githubusercontent.com/kubernetes/kubernetes/master/go.mod" + modFile = "./go.mod" +) + +var ( + cleanMods = regexp.MustCompile(`\t| *//.*`) + modDelimStart = regexp.MustCompile(`^require.*`) + modDelimEnd = ")" +) + +type oosMod struct { + Name string `json:"name"` + Version string `json:"version"` + UpstreamVersion string `json:"upstreamVersion"` +} + +func main() { + logger := zap.New() + excludedMods := getExcludedMods() + + // 1. kk + resp, err := http.Get(kkModUrl) + if err != nil { + panic(err.Error()) + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + panic(err.Error()) + } + + kkMods := parseMod(b) + + // 2. go.mod + b, err = os.ReadFile(modFile) + if err != nil { + return + } + + oosMods := make([]oosMod, 0) + + mods := parseMod(b) + for k, v := range mods { + if _, ok := excludedMods[k]; ok { + logger.Info(fmt.Sprintf("skipped module: %s", k)) + continue + } + + if upstreamVersion, ok := kkMods[k]; ok && v != upstreamVersion { + oosMods = append(oosMods, oosMod{ + Name: k, + Version: v, + UpstreamVersion: upstreamVersion, + }) + } + } + + if len(oosMods) == 0 { + fmt.Println("Success! 🎉") + os.Exit(0) + } + + b, err = json.MarshalIndent(map[string]any{"outOfSyncModules": oosMods}, "", " ") + if err != nil { + panic(err) + } + + fmt.Println(string(b)) + os.Exit(1) +} + +func parseMod(b []byte) map[string]string { + in := string(cleanMods.ReplaceAll(b, []byte(""))) + out := make(map[string]string) + + start := false + for _, s := range strings.Split(in, "\n") { + switch { + case modDelimStart.MatchString(s) && !start: + start = true + case s == modDelimEnd: + return out + case start: + kv := strings.SplitN(s, " ", 2) + if len(kv) < 2 { + panic(fmt.Sprintf("unexpected format for module: %q", s)) + } + + out[kv[0]] = kv[1] + } + } + + return out +} + +func getExcludedMods() map[string]any { + out := make(map[string]any) + + for _, mod := range os.Args[1:] { + out[mod] = nil + } + + return out +}