diff --git a/hack/golangci-hints.yaml b/hack/golangci-hints.yaml index 8306b7be5a1..4d5298cd57d 100644 --- a/hack/golangci-hints.yaml +++ b/hack/golangci-hints.yaml @@ -111,6 +111,13 @@ linters: linters: - ineffassign + # Kube-API-Linter should only be run on the API definitions + - linters: + - kubeapilinter + path-except: staging/src/k8s.io/api/.* + + # Exceptions for kube-api-linter. + # Exceptions are used for kube-api-linter to ignore existing issues that cannot be fixed without breaking changes. default: standard enable: # please keep this alphabetized @@ -121,6 +128,7 @@ linters: - govet - errorlint - ineffassign + - kubeapilinter - logcheck - revive - staticcheck @@ -202,6 +210,40 @@ linters: # this restriction. Whether we then do a global search/replace remains # to be decided. with-helpers .* + kubeapilinter: + path: _output/local/bin/kube-api-linter.so + 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: + linters: + disable: + - '*' + enable: + # - "commentstart" # Ensure comments start with the serialized version of the field name. + # - "conditions" # Ensure conditions have the correct json tags and markers. + # - "integers" # Ensure only int32 and int64 are used for integers. + # - "jsontags" # Ensure every field has a json tag. + # - "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items. ONLY for CRDs until declarative markers exist in core types. + # - "nobools" # Bools do not evolve over time, should use enums instead. + # - "nofloats" # Ensure floats are not used. + # - "nomaps" # Ensure maps are not used, unless they are `map[string]string` (for labels/annotations/etc). + # - "nophase" # Ensure field names do not have the word "phase" in them. + # - "optionalorrequired" # Every field should be marked as `+optional` xor `+required`. + # - "requiredfields" # Required fields should not be pointers, and should not have `omitempty`. + lintersConfig: + # conditions: + # isFirstField: Warn # Require conditions to be the first field in the status struct. + # usePatchStrategy: SuggestFix # Conditions should not use the patch strategy on CRDs. + # useProtobuf: SuggestFix # We don't use protobuf, so protobuf tags are not required. + # jsonTags: + # jsonTagRegex: "^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$" # The default regex is appropriate for our use case. + # nomaps: + # policy: AllowStringToStringMaps # Determines how the linter should handle maps of basic types. Maps of objects are always disallowed. + # optionalOrRequired: + # preferredOptionalMarker: optional # The preferred optional marker to use, fixes will suggest to use this marker. Defaults to `optional`. + # preferredRequiredMarker: required # The preferred required marker to use, fixes will suggest to use this marker. Defaults to `required`. + # requiredFields: + # pointerPolicy: SuggestFix # Defaults to `SuggestFix`. We want our required fields to not be pointers. depguard: rules: main: diff --git a/hack/golangci.yaml b/hack/golangci.yaml index bc9c4bf9e72..842ace600f2 100644 --- a/hack/golangci.yaml +++ b/hack/golangci.yaml @@ -127,6 +127,13 @@ linters: - gocritic text: "assignOp:" + # Kube-API-Linter should only be run on the API definitions + - linters: + - kubeapilinter + path-except: staging/src/k8s.io/api/.* + + # Exceptions for kube-api-linter. + # Exceptions are used for kube-api-linter to ignore existing issues that cannot be fixed without breaking changes. default: none enable: # please keep this alphabetized @@ -136,6 +143,7 @@ linters: - gocritic - govet - ineffassign + - kubeapilinter - logcheck - revive - staticcheck @@ -216,6 +224,40 @@ linters: # this restriction. Whether we then do a global search/replace remains # to be decided. with-helpers .* + kubeapilinter: + path: _output/local/bin/kube-api-linter.so + 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: + linters: + disable: + - '*' + enable: + # - "commentstart" # Ensure comments start with the serialized version of the field name. + # - "conditions" # Ensure conditions have the correct json tags and markers. + # - "integers" # Ensure only int32 and int64 are used for integers. + # - "jsontags" # Ensure every field has a json tag. + # - "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items. ONLY for CRDs until declarative markers exist in core types. + # - "nobools" # Bools do not evolve over time, should use enums instead. + # - "nofloats" # Ensure floats are not used. + # - "nomaps" # Ensure maps are not used, unless they are `map[string]string` (for labels/annotations/etc). + # - "nophase" # Ensure field names do not have the word "phase" in them. + # - "optionalorrequired" # Every field should be marked as `+optional` xor `+required`. + # - "requiredfields" # Required fields should not be pointers, and should not have `omitempty`. + lintersConfig: + # conditions: + # isFirstField: Warn # Require conditions to be the first field in the status struct. + # usePatchStrategy: SuggestFix # Conditions should not use the patch strategy on CRDs. + # useProtobuf: SuggestFix # We don't use protobuf, so protobuf tags are not required. + # jsonTags: + # jsonTagRegex: "^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$" # The default regex is appropriate for our use case. + # nomaps: + # policy: AllowStringToStringMaps # Determines how the linter should handle maps of basic types. Maps of objects are always disallowed. + # optionalOrRequired: + # preferredOptionalMarker: optional # The preferred optional marker to use, fixes will suggest to use this marker. Defaults to `optional`. + # preferredRequiredMarker: required # The preferred required marker to use, fixes will suggest to use this marker. Defaults to `required`. + # requiredFields: + # pointerPolicy: SuggestFix # Defaults to `SuggestFix`. We want our required fields to not be pointers. depguard: rules: main: diff --git a/hack/golangci.yaml.in b/hack/golangci.yaml.in index 570aafdc239..706bc36f77a 100644 --- a/hack/golangci.yaml.in +++ b/hack/golangci.yaml.in @@ -144,6 +144,12 @@ linters: {{- end}} + # Kube-API-Linter should only be run on the API definitions + - linters: + - kubeapilinter + path-except: staging/src/k8s.io/api/.* + + {{include "hack/kube-api-linter/exceptions.yaml" | indent 6 | trim}} default: {{if .Base -}} none {{- else -}} standard {{- end}} enable: # please keep this alphabetized @@ -156,6 +162,7 @@ linters: - errorlint {{- end}} - ineffassign + - kubeapilinter - logcheck - revive - staticcheck @@ -175,6 +182,12 @@ linters: settings: config: | {{include "hack/logcheck.conf" | indent 12 | trim}} + kubeapilinter: + path: _output/local/bin/kube-api-linter.so + 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: + {{include "hack/kube-api-linter/kube-api-linter.yaml" | indent 10 | trim}} depguard: rules: main: diff --git a/hack/kube-api-linter/OWNERS b/hack/kube-api-linter/OWNERS new file mode 100644 index 00000000000..78b8d6f8bb2 --- /dev/null +++ b/hack/kube-api-linter/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners +options: + no_parent_owners: true +reviewers: + - api-approvers + - JoelSpeed +approvers: + - api-approvers diff --git a/hack/kube-api-linter/exceptions.yaml b/hack/kube-api-linter/exceptions.yaml new file mode 100644 index 00000000000..301d5f67383 --- /dev/null +++ b/hack/kube-api-linter/exceptions.yaml @@ -0,0 +1,2 @@ +# Exceptions for kube-api-linter. +# Exceptions are used for kube-api-linter to ignore existing issues that cannot be fixed without breaking changes. diff --git a/hack/kube-api-linter/kube-api-linter.yaml b/hack/kube-api-linter/kube-api-linter.yaml new file mode 100644 index 00000000000..2ad2fbdcd12 --- /dev/null +++ b/hack/kube-api-linter/kube-api-linter.yaml @@ -0,0 +1,30 @@ + +linters: + disable: + - '*' + enable: + # - "commentstart" # Ensure comments start with the serialized version of the field name. + # - "conditions" # Ensure conditions have the correct json tags and markers. + # - "integers" # Ensure only int32 and int64 are used for integers. + # - "jsontags" # Ensure every field has a json tag. + # - "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items. ONLY for CRDs until declarative markers exist in core types. + # - "nobools" # Bools do not evolve over time, should use enums instead. + # - "nofloats" # Ensure floats are not used. + # - "nomaps" # Ensure maps are not used, unless they are `map[string]string` (for labels/annotations/etc). + # - "nophase" # Ensure field names do not have the word "phase" in them. + # - "optionalorrequired" # Every field should be marked as `+optional` xor `+required`. + # - "requiredfields" # Required fields should not be pointers, and should not have `omitempty`. +lintersConfig: + # conditions: + # isFirstField: Warn # Require conditions to be the first field in the status struct. + # usePatchStrategy: SuggestFix # Conditions should not use the patch strategy on CRDs. + # useProtobuf: SuggestFix # We don't use protobuf, so protobuf tags are not required. + # jsonTags: + # jsonTagRegex: "^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$" # The default regex is appropriate for our use case. + # nomaps: + # policy: AllowStringToStringMaps # Determines how the linter should handle maps of basic types. Maps of objects are always disallowed. + # optionalOrRequired: + # preferredOptionalMarker: optional # The preferred optional marker to use, fixes will suggest to use this marker. Defaults to `optional`. + # preferredRequiredMarker: required # The preferred required marker to use, fixes will suggest to use this marker. Defaults to `required`. + # requiredFields: + # pointerPolicy: SuggestFix # Defaults to `SuggestFix`. We want our required fields to not be pointers. diff --git a/hack/tools/golangci-lint/go.mod b/hack/tools/golangci-lint/go.mod index 4c43f48d344..d186b63580a 100644 --- a/hack/tools/golangci-lint/go.mod +++ b/hack/tools/golangci-lint/go.mod @@ -4,6 +4,7 @@ go 1.24.0 tool ( github.com/golangci/golangci-lint/v2/cmd/golangci-lint + sigs.k8s.io/kube-api-linter/pkg/plugin sigs.k8s.io/logtools/logcheck ) @@ -51,7 +52,7 @@ require ( github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.6 // indirect github.com/dave/dst v0.27.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/ettle/strcase v0.2.0 // indirect @@ -73,7 +74,7 @@ require ( github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect @@ -134,7 +135,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.8.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect @@ -205,7 +206,9 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.6.1 // indirect + k8s.io/apimachinery v0.32.3 // indirect mvdan.cc/gofumpt v0.8.0 // indirect mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect + sigs.k8s.io/kube-api-linter v0.0.0-20250516124914-6df69a12c92c // indirect sigs.k8s.io/logtools v0.9.0 // indirect ) diff --git a/hack/tools/golangci-lint/go.sum b/hack/tools/golangci-lint/go.sum index b78b28e3724..e285ce1ef5b 100644 --- a/hack/tools/golangci-lint/go.sum +++ b/hack/tools/golangci-lint/go.sum @@ -144,8 +144,9 @@ github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEy github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= @@ -243,8 +244,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= @@ -450,8 +451,9 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= @@ -994,6 +996,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k= mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg= mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8= @@ -1001,5 +1005,7 @@ mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5gin rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/kube-api-linter v0.0.0-20250516124914-6df69a12c92c h1:sybeA6NAACfxBiZLdY9iRNAT9jVs25TwBnLnqG+TyTs= +sigs.k8s.io/kube-api-linter v0.0.0-20250516124914-6df69a12c92c/go.mod h1:tnzp9Oj2Qi0qPg5t5tbDrUixTlZLjPpEjzk1kxO/2EE= sigs.k8s.io/logtools v0.9.0 h1:IMP/HTDkfM6rg6os/tcEjmQeIHyOyu/enduM/cOPGNQ= sigs.k8s.io/logtools v0.9.0/go.mod h1:74Z5BP7ehrMHi/Q31W1gSf8YgwT/4GPjVH5xPSPeZA0= diff --git a/hack/verify-golangci-lint.sh b/hack/verify-golangci-lint.sh index 8ae58eb8c00..6499a93baa5 100755 --- a/hack/verify-golangci-lint.sh +++ b/hack/verify-golangci-lint.sh @@ -129,13 +129,14 @@ kube::util::ensure-temp-dir # Installing 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. -echo "installing golangci-lint and logcheck plugin from hack/tools into ${GOBIN}" +echo "installing golangci-lint, logcheck and kube-api-linter 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 - # This cannot be used without a config. + # 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 fi # Verify that the given config is valid. "golangci-lint run" does not