diff --git a/hack/golangci-hints.yaml b/hack/golangci-hints.yaml index 2bd0383cec6..3df7e6711ad 100644 --- a/hack/golangci-hints.yaml +++ b/hack/golangci-hints.yaml @@ -316,8 +316,8 @@ linters: settings: # please keep this alphabetized custom: logcheck: - # Installed there by hack/verify-golangci-lint.sh. - path: _output/local/bin/logcheck.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: structured logging checker original-url: k8s.io/logtools/logcheck settings: @@ -391,8 +391,8 @@ linters: # API design needed). -contextual k8s.io/client-go/plugin/pkg/client/auth/.* sorted: - # Installed there by hack/verify-golangci-lint.sh. - path: _output/local/bin/sorted.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: check if feature gates are sorted original-url: k8s.io/kubernetes/hack/tools/golangci-lint/sorted settings: @@ -406,7 +406,8 @@ linters: - test/e2e/feature/feature.go - test/e2e/environment/environment.go kubeapilinter: - path: _output/local/bin/kube-api-linter.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: kube-api-linter and lints Kube like APIs based on API conventions and best practices. original-url: sigs.k8s.io/kube-api-linter settings: diff --git a/hack/golangci.yaml b/hack/golangci.yaml index 12003649288..23d0f9080ec 100644 --- a/hack/golangci.yaml +++ b/hack/golangci.yaml @@ -329,8 +329,8 @@ linters: settings: # please keep this alphabetized custom: logcheck: - # Installed there by hack/verify-golangci-lint.sh. - path: _output/local/bin/logcheck.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: structured logging checker original-url: k8s.io/logtools/logcheck settings: @@ -404,8 +404,8 @@ linters: # API design needed). -contextual k8s.io/client-go/plugin/pkg/client/auth/.* sorted: - # Installed there by hack/verify-golangci-lint.sh. - path: _output/local/bin/sorted.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: check if feature gates are sorted original-url: k8s.io/kubernetes/hack/tools/golangci-lint/sorted settings: @@ -419,7 +419,8 @@ linters: - test/e2e/feature/feature.go - test/e2e/environment/environment.go kubeapilinter: - path: _output/local/bin/kube-api-linter.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: kube-api-linter and lints Kube like APIs based on API conventions and best practices. original-url: sigs.k8s.io/kube-api-linter settings: diff --git a/hack/golangci.yaml.in b/hack/golangci.yaml.in index 1dd5e127278..2ec8b8d0952 100644 --- a/hack/golangci.yaml.in +++ b/hack/golangci.yaml.in @@ -186,22 +186,23 @@ linters: settings: # please keep this alphabetized custom: logcheck: - # Installed there by hack/verify-golangci-lint.sh. - path: _output/local/bin/logcheck.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: structured logging checker original-url: k8s.io/logtools/logcheck settings: config: | {{include "hack/logcheck.conf" | indent 12 | trim}} sorted: - # Installed there by hack/verify-golangci-lint.sh. - path: _output/local/bin/sorted.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: check if feature gates are sorted original-url: k8s.io/kubernetes/hack/tools/golangci-lint/sorted settings: {{include "hack/tools/golangci-lint/sorted/config.yaml" | indent 10 | trim}} kubeapilinter: - path: _output/local/bin/kube-api-linter.so + # hack/tools/golangci-lint/main.go compiles this linter into golangci-lint. + type: module description: kube-api-linter and lints Kube like APIs based on API conventions and best practices. original-url: sigs.k8s.io/kube-api-linter settings: diff --git a/hack/tools/golangci-lint/go.mod b/hack/tools/golangci-lint/go.mod index f8a88df972a..fac1545399c 100644 --- a/hack/tools/golangci-lint/go.mod +++ b/hack/tools/golangci-lint/go.mod @@ -2,15 +2,13 @@ module k8s.io/kubernetes/hack/tools/golangci-lint go 1.26.0 -tool ( - github.com/golangci/golangci-lint/v2/cmd/golangci-lint - sigs.k8s.io/kube-api-linter/pkg/plugin - sigs.k8s.io/logtools/logcheck -) - require ( + github.com/golangci/golangci-lint/v2 v2.11.2 + github.com/golangci/plugin-module-register v0.1.2 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 golang.org/x/tools v0.42.0 + sigs.k8s.io/kube-api-linter v0.0.0-20260716143926-092fe0c72997 + sigs.k8s.io/logtools v0.10.1 ) require ( @@ -93,10 +91,8 @@ require ( github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect - github.com/golangci/golangci-lint/v2 v2.11.2 // indirect github.com/golangci/golines v0.15.0 // indirect github.com/golangci/misspell v0.8.0 // indirect - github.com/golangci/plugin-module-register v0.1.2 // indirect github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect @@ -219,7 +215,5 @@ require ( k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect mvdan.cc/gofumpt v0.9.2 // indirect mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect - sigs.k8s.io/kube-api-linter v0.0.0-20260716143926-092fe0c72997 // indirect - sigs.k8s.io/logtools v0.10.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/hack/tools/golangci-lint/main.go b/hack/tools/golangci-lint/main.go new file mode 100644 index 00000000000..335d2e054dc --- /dev/null +++ b/hack/tools/golangci-lint/main.go @@ -0,0 +1,51 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "os" + "runtime" + "runtime/debug" + + "github.com/golangci/golangci-lint/v2/pkg/commands" + "github.com/golangci/golangci-lint/v2/pkg/exitcodes" + + // These imports register the custom linters. + _ "sigs.k8s.io/kube-api-linter" + _ "sigs.k8s.io/logtools/logcheck/gclplugin" + + _ "k8s.io/kubernetes/hack/tools/golangci-lint/sorted/plugin" +) + +func main() { + // populate the golangci-lint version so the verify config command works + golangCIVersion := "" + if info, ok := debug.ReadBuildInfo(); ok { + for _, dep := range info.Deps { + if dep.Path == "github.com/golangci/golangci-lint/v2" { + golangCIVersion = dep.Version + break + } + } + } + + if err := commands.Execute(commands.BuildInfo{GoVersion: runtime.Version(), Version: golangCIVersion}); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "The command is terminated due to an error: %v\n", err) + os.Exit(exitcodes.Failure) + } +} diff --git a/hack/tools/golangci-lint/sorted/README.md b/hack/tools/golangci-lint/sorted/README.md index 01eb40ad75a..f9f2727677a 100644 --- a/hack/tools/golangci-lint/sorted/README.md +++ b/hack/tools/golangci-lint/sorted/README.md @@ -221,7 +221,7 @@ codebase. Run it locally before submitting pull requests that modify feature gat ## Troubleshooting 1. **Enable debug mode**: Set `debug: true` in your configuration to see processing details -2. **Check plugin build**: Ensure the plugin is correctly built with `go build -buildmode=plugin` +2. **Check the build**: Ensure `hack/tools/golangci-lint/main.go` imports the `plugin` package 3. **Verify file paths**: Confirm target files are in the default list or explicitly configured 4. **Test with standalone tool**: Run `go run main.go path/to/file.go` to test specific files diff --git a/hack/tools/golangci-lint/sorted/example.golangci.yml b/hack/tools/golangci-lint/sorted/example.golangci.yml index 4a9d23e959a..a52d3dab049 100644 --- a/hack/tools/golangci-lint/sorted/example.golangci.yml +++ b/hack/tools/golangci-lint/sorted/example.golangci.yml @@ -2,7 +2,7 @@ linters: settings: custom: sorted: - path: /path/to/sorted.so + type: module description: Checks if feature gates are sorted alphabetically original-url: k8s.io/kubernetes/hack/tools/golangci-lint/sorted settings: diff --git a/hack/tools/golangci-lint/sorted/plugin/plugin.go b/hack/tools/golangci-lint/sorted/plugin/plugin.go index 83fecb0acfa..36e8729376b 100644 --- a/hack/tools/golangci-lint/sorted/plugin/plugin.go +++ b/hack/tools/golangci-lint/sorted/plugin/plugin.go @@ -14,27 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -// This must be package main -package main +// Package plugin registers the sorted linter as a golangci-lint module plugin. +package plugin import ( "bytes" "encoding/json" "fmt" + + "github.com/golangci/plugin-module-register/register" "golang.org/x/tools/go/analysis" "k8s.io/kubernetes/hack/tools/golangci-lint/sorted/pkg" ) -type analyzerPlugin struct{} - -func (*analyzerPlugin) GetAnalyzers() []*analysis.Analyzer { - return []*analysis.Analyzer{pkg.NewAnalyzer()} -} - -// AnalyzerPlugin is the entry point for golangci-lint. -var AnalyzerPlugin analyzerPlugin - // settings defines the configuration options for the sorted linter type settings struct { // Debug enables debug logging @@ -54,8 +47,7 @@ var defaultTargetFiles = []string{ "test/e2e/environment/environment.go", } -// New is the entry point for golangci-lint plugin system -func New(pluginSettings interface{}) ([]*analysis.Analyzer, error) { +func New(pluginSettings interface{}) (register.LinterPlugin, error) { // Create default config config := pkg.Config{} @@ -86,11 +78,24 @@ func New(pluginSettings interface{}) ([]*analysis.Analyzer, error) { fmt.Printf("sorted settings: %+v\n", s) fmt.Printf("final config: %+v\n", config) } + } else { + config.Files = defaultTargetFiles } - // Get the analyzer with config - analyzer := pkg.NewAnalyzerWithConfig(config) + return linterPlugin{config: config}, nil +} - // Return the analyzer - return []*analysis.Analyzer{analyzer}, nil +type linterPlugin struct { + config pkg.Config +} + +func (l linterPlugin) BuildAnalyzers() ([]*analysis.Analyzer, error) { + return []*analysis.Analyzer{pkg.NewAnalyzerWithConfig(l.config)}, nil +} +func (l linterPlugin) GetLoadMode() string { + return register.LoadModeSyntax +} + +func init() { + register.Plugin("sorted", New) } diff --git a/hack/verify-golangci-lint.sh b/hack/verify-golangci-lint.sh index b2e00b9e8e5..5ee68e480d6 100755 --- a/hack/verify-golangci-lint.sh +++ b/hack/verify-golangci-lint.sh @@ -123,22 +123,11 @@ kube::util::ensure-temp-dir # Install golangci-lint. # -# hack/tools/golangci-lint uses the "tool" directive in a stand-alone -# go.mod. -# -# Installing from source (https://golangci-lint.run/welcome/install/#install-from-sources) +# Building from source (https://golangci-lint.run/welcome/install/#install-from-sources) # is not recommended, but for Kubernetes we prefer it because it avoids the need for -# pre-built binaries for different platforms and gives more insights on dependencies. +# pre-built binaries for different platforms, gives more insights on dependencies, and allows custom plugins. echo "installing golangci-lint, logcheck kube-api-linter and sorted plugins from hack/tools/golangci-lint into ${GOBIN}" -GOTOOLCHAIN="$(kube::golang::hack_tools_gotoolchain)" go -C "${KUBE_ROOT}/hack/tools/golangci-lint" install github.com/golangci/golangci-lint/v2/cmd/golangci-lint -if [ "${golangci_config}" ]; then - # Plugins cannot be used without a config. - # This uses `go build` because `go install -buildmode=plugin` doesn't work - # (on purpose: https://github.com/golang/go/issues/64964). - GOTOOLCHAIN="$(kube::golang::hack_tools_gotoolchain)" go -C "${KUBE_ROOT}/hack/tools/golangci-lint" build -o "${GOBIN}/logcheck.so" -buildmode=plugin sigs.k8s.io/logtools/logcheck/plugin - GOTOOLCHAIN="$(kube::golang::hack_tools_gotoolchain)" go -C "${KUBE_ROOT}/hack/tools/golangci-lint" build -o "${GOBIN}/kube-api-linter.so" -buildmode=plugin sigs.k8s.io/kube-api-linter/pkg/plugin - GOTOOLCHAIN="$(kube::golang::hack_tools_gotoolchain)" go -C "${KUBE_ROOT}/hack/tools/golangci-lint" build -o "${GOBIN}/sorted.so" -buildmode=plugin k8s.io/kubernetes/hack/tools/golangci-lint/sorted/plugin -fi +GOTOOLCHAIN="$(kube::golang::hack_tools_gotoolchain)" go -C "${KUBE_ROOT}/hack/tools/golangci-lint" build -o "${GOBIN}/golangci-lint" . # Verify that the given config is valid (if one is provided). "golangci-lint run" does not # do that, which makes it easy to miss mistakes while editing the configuration. @@ -159,17 +148,6 @@ EOF fi if [ "${golangci_config}" ]; then - # The relative path to _output/local/bin only works if that actually is the - # GOBIN. If not, then we have to make a temporary copy of the config and - # replace the path with an absolute one. This could be done also - # unconditionally, but the invocation that is printed below is nicer if we - # don't to do it when not required. - if grep -q 'path: _output/local/bin/' "${golangci_config}" && - [ "${GOBIN}" != "${KUBE_ROOT}/_output/local/bin" ]; then - patched_golangci_config="${KUBE_TEMP}/$(basename "${golangci_config}")" - sed -e "s;path: _output/local/bin/;path: ${GOBIN}/;" "${golangci_config}" >"${patched_golangci_config}" - golangci_config="${patched_golangci_config}" - fi golangci+=(--config="${golangci_config}") fi