Skip to content

Commit d120726

Browse files
committed
build: Add kube-api-linter
Validates upstream API conventions are correctly applied.
1 parent f26818b commit d120726

File tree

10 files changed

+184
-30
lines changed

10 files changed

+184
-30
lines changed

.github/workflows/checks.yml

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,21 +132,37 @@ jobs:
132132
with:
133133
enable-cache: true
134134

135-
- name: Export golang and golangci-lint versions
136-
id: versions
135+
- name: golangci-lint
136+
env:
137+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
137138
run: |
138-
echo "golangci-lint=$(devbox run -- golangci-lint version --short)" >>"${GITHUB_OUTPUT}"
139-
echo "golang=$(devbox run -- go version | grep -o "[[:digit:]]\+.[[:digit:]]\+\(.[[:digit:]]\+\)\?")" >>"${GITHUB_OUTPUT}"
139+
devbox run golangci-lint run --config=${{ github.workspace }}/.golangci.yml --output.text.path=stdout ./... | \
140+
devbox run reviewdog -f=golangci-lint -reporter=github-pr-review --fail-level=any
141+
working-directory: ${{ matrix.module }}
140142

141-
- name: golangci-lint
142-
uses: reviewdog/action-golangci-lint@v2
143+
lint-api:
144+
runs-on: ubuntu-24.04
145+
permissions:
146+
pull-requests: write
147+
steps:
148+
- name: Check out code
149+
uses: actions/checkout@v4
150+
151+
- name: Install devbox
152+
uses: jetify-com/[email protected]
143153
with:
144-
fail_level: error
145-
reporter: github-pr-review
146-
golangci_lint_version: v${{ steps.versions.outputs.golangci-lint }}
147-
go_version: v${{ steps.versions.outputs.golang }}
148-
workdir: ${{ matrix.module }}
149-
golangci_lint_flags: "--config=${{ github.workspace }}/.golangci.yml"
154+
enable-cache: true
155+
156+
- name: Install kube-api-linter
157+
run: devbox run make hack/tools/golangci-lint-kube-api-linter
158+
159+
- name: golangci-lint
160+
env:
161+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
162+
run: |
163+
hack/tools/golangci-lint-kube-api-linter run --config=${{ github.workspace }}/.golangci.yml --output.text.path=stdout ./... | \
164+
devbox run reviewdog -name=kube=api-linter -f=golangci-lint -reporter=github-pr-review --fail-level=any
165+
working-directory: api
150166

151167
lint-gha:
152168
runs-on: ubuntu-24.04

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ test/e2e/config/caren-envsubst.yaml
5050
hack/tools/fetch-images/fetch-images
5151
caren-images.txt
5252
hack/examples/release/*-cluster-class.yaml
53+
golangci-lint-kube-api-linter

.golangci-kal.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2025 Nutanix. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
version: "2"
5+
linters:
6+
default: none
7+
enable:
8+
- kubeapilinter # linter for Kube API conventions
9+
settings:
10+
custom:
11+
kubeapilinter:
12+
type: module
13+
description: KAL is the Kube-API-Linter and lints Kube like APIs based on API conventions and best practices.
14+
settings:
15+
linters:
16+
enable:
17+
- "commentstart" # Ensure comments start with the serialized version of the field name.
18+
# - "conditions" # Ensure conditions have the correct json tags and markers.
19+
- "duplicatemarkers" # Ensure there are no exact duplicate markers. for types and fields.
20+
- "integers" # Ensure only int32 and int64 are used for integers.
21+
- "jsontags" # Ensure every field has a json tag.
22+
- "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items.
23+
- "nobools" # Bools do not evolve over time, should use enums instead.
24+
- "nofloats" # Ensure floats are not used.
25+
- "nomaps" # Ensure maps are not used.
26+
# "nophase" # Ensure phases are not used, as they are not extensible.
27+
- "optionalfields" # Ensure that all fields marked as optional adhere to being pointers and
28+
# having the `omitempty` value in their `json` tag where appropriate.
29+
- "optionalorrequired" # Every field should be marked as `+optional` or `+required`.
30+
- "requiredfields" # Required fields should not be pointers, and should not have `omitempty`.
31+
# - "ssatags" # Ensure array fields have the appropriate listType markers
32+
# - "statusoptional" # Ensure all first children within status should be optional.
33+
# - "statussubresource" # All root objects that have a `status` field should have a status subresource.
34+
- "uniquemarkers" # Ensure that types and fields do not contain more than a single definition of a marker that should only be present once.
35+
36+
# Linters below this line are disabled, pending conversation on how and when to enable them.
37+
disable:
38+
- "*" # We will manually enable new linters after understanding the impact. Disable all by default.
39+
lintersConfig:
40+
optionalfields:
41+
pointers:
42+
preference: WhenRequired # Always | WhenRequired # Whether to always require pointers, or only when required. Defaults to `Always`.
43+
# jsontags:
44+
# jsonTagRegex: "^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$" # The default regex is appropriate for our use case.
45+
optionalorrequired:
46+
preferredOptionalMarker: kubebuilder:validation:Optional # The preferred optional marker to use, fixes will suggest to use this marker. Defaults to `optional`.
47+
preferredRequiredMarker: kubebuilder:validation:Required # The preferred required marker to use, fixes will suggest to use this marker. Defaults to `required`.
48+
49+
exclusions:
50+
generated: strict
51+
paths:
52+
# Ignore generated files.
53+
- zz_generated.*\.go$
54+
# Ignore external API packages.
55+
- external/
56+
# Ignore test files.
57+
- '.+_test\.go$'
58+
59+
issues:
60+
max-same-issues: 0
61+
max-issues-per-linter: 0

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ repos:
2121
language: system
2222
files: "(.*\\.go|go.mod|go.sum|go.mk)$"
2323
pass_filenames: false
24+
- id: kube-api-linter
25+
name: kube-api-linter
26+
entry: make lint-kube-api
27+
language: system
28+
files: "((^api/(.*\\.go|go.mod|go.sum))|go.mk)$"
29+
pass_filenames: false
2430
- id: chart-docs
2531
name: chart-docs
2632
entry: make chart-docs

common/pkg/capi/utils/utils.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ func ManagementOrFutureManagementCluster(ctx context.Context, c client.Reader) (
6464
return cluster, nil
6565
}
6666

67-
func clusterAnnotationsFromNodes(ctx context.Context, c client.Reader) (string, string, error) {
67+
func clusterAnnotationsFromNodes(
68+
ctx context.Context, c client.Reader,
69+
) (clusterName, clusterNamespace string, err error) {
6870
allNodes := &corev1.NodeList{}
69-
err := c.List(ctx, allNodes)
71+
err = c.List(ctx, allNodes)
7072
if err != nil {
7173
return "", "", fmt.Errorf("error listing Nodes: %w", err)
7274
}

common/pkg/capi/utils/utils_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func TestManagementOrFutureManagementCluster(t *testing.T) {
138138
},
139139
},
140140
wantErr: fmt.Errorf(
141-
"error determining management cluster for the provided client: error getting Cluster object based on Node annotations: clusters.cluster.x-k8s.io \"management-cluster\" not found",
141+
"error determining management cluster for the provided client: error getting Cluster object based on Node annotations: clusters.cluster.x-k8s.io \"management-cluster\" not found", //nolint:lll // Long error message is expected here.
142142
),
143143
},
144144
{
@@ -162,7 +162,7 @@ func TestManagementOrFutureManagementCluster(t *testing.T) {
162162
},
163163
},
164164
wantErr: fmt.Errorf(
165-
"error determining management cluster for the provided client: missing \"cluster.x-k8s.io/cluster-name\" annotation",
165+
"error determining management cluster for the provided client: missing \"cluster.x-k8s.io/cluster-name\" annotation", //nolint:lll // Long error message is expected here.
166166
),
167167
},
168168
{
@@ -186,7 +186,7 @@ func TestManagementOrFutureManagementCluster(t *testing.T) {
186186
},
187187
},
188188
wantErr: fmt.Errorf(
189-
"error determining management cluster for the provided client: missing \"cluster.x-k8s.io/cluster-namespace\" annotation",
189+
"error determining management cluster for the provided client: missing \"cluster.x-k8s.io/cluster-namespace\" annotation", //nolint:lll // Long error message is expected here.
190190
),
191191
},
192192
{
@@ -213,7 +213,7 @@ func TestManagementOrFutureManagementCluster(t *testing.T) {
213213
},
214214
},
215215
wantErr: fmt.Errorf(
216-
"error determining management cluster for the provided client: multiple Cluster objects found, expected exactly one",
216+
"error determining management cluster for the provided client: multiple Cluster objects found, expected exactly one", //nolint:lll // Long error message is expected here.
217217
),
218218
},
219219
}
@@ -239,7 +239,7 @@ func TestManagementOrFutureManagementCluster(t *testing.T) {
239239

240240
func buildFakeClientForTest(t *testing.T, clusters []clusterv1.Cluster, nodes []corev1.Node) client.Client {
241241
t.Helper()
242-
var objs []client.Object
242+
objs := make([]client.Object, 0, len(clusters)+len(nodes))
243243
for i := range clusters {
244244
objs = append(objs, &clusters[i])
245245
}

devbox.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"kubernetes-controller-tools@latest",
2929
"kustomize@latest",
3030
"pre-commit@latest",
31+
"reviewdog@latest",
3132
"rsync@latest",
3233
"setup-envtest@latest",
3334
"shfmt@latest",

devbox.lock

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -854,50 +854,50 @@
854854
}
855855
},
856856
"golangci-lint@latest": {
857-
"last_modified": "2025-05-16T20:19:48Z",
858-
"resolved": "github:NixOS/nixpkgs/12a55407652e04dcf2309436eb06fef0d3713ef3#golangci-lint",
857+
"last_modified": "2025-07-04T02:32:11Z",
858+
"resolved": "github:NixOS/nixpkgs/472908faa934435cf781ae8fac77291af3d137d3#golangci-lint",
859859
"source": "devbox-search",
860-
"version": "2.1.6",
860+
"version": "2.2.1",
861861
"systems": {
862862
"aarch64-darwin": {
863863
"outputs": [
864864
{
865865
"name": "out",
866-
"path": "/nix/store/8d3n5ly5r6prqyx3h5nr2v778hzwp83r-golangci-lint-2.1.6",
866+
"path": "/nix/store/y34ymr5fc4nywh487v49djqzplqy8nih-golangci-lint-2.2.1",
867867
"default": true
868868
}
869869
],
870-
"store_path": "/nix/store/8d3n5ly5r6prqyx3h5nr2v778hzwp83r-golangci-lint-2.1.6"
870+
"store_path": "/nix/store/y34ymr5fc4nywh487v49djqzplqy8nih-golangci-lint-2.2.1"
871871
},
872872
"aarch64-linux": {
873873
"outputs": [
874874
{
875875
"name": "out",
876-
"path": "/nix/store/sazvkjsaxnpnplrsaj5wf1x9xkgv0s88-golangci-lint-2.1.6",
876+
"path": "/nix/store/yvlh8x7mmlg7j8vqbzjslngipvkgfgdc-golangci-lint-2.2.1",
877877
"default": true
878878
}
879879
],
880-
"store_path": "/nix/store/sazvkjsaxnpnplrsaj5wf1x9xkgv0s88-golangci-lint-2.1.6"
880+
"store_path": "/nix/store/yvlh8x7mmlg7j8vqbzjslngipvkgfgdc-golangci-lint-2.2.1"
881881
},
882882
"x86_64-darwin": {
883883
"outputs": [
884884
{
885885
"name": "out",
886-
"path": "/nix/store/cqfnavdyysfdm9cam8bsyscbsk8zg781-golangci-lint-2.1.6",
886+
"path": "/nix/store/zji7s77vyb0l2gpnz1nsa7al4w83rjw6-golangci-lint-2.2.1",
887887
"default": true
888888
}
889889
],
890-
"store_path": "/nix/store/cqfnavdyysfdm9cam8bsyscbsk8zg781-golangci-lint-2.1.6"
890+
"store_path": "/nix/store/zji7s77vyb0l2gpnz1nsa7al4w83rjw6-golangci-lint-2.2.1"
891891
},
892892
"x86_64-linux": {
893893
"outputs": [
894894
{
895895
"name": "out",
896-
"path": "/nix/store/snqg2xccy7fb6q6n2dw3bfql6mqbh6ar-golangci-lint-2.1.6",
896+
"path": "/nix/store/p1144qggxazwz6791avlf6cqzyvkh8cv-golangci-lint-2.2.1",
897897
"default": true
898898
}
899899
],
900-
"store_path": "/nix/store/snqg2xccy7fb6q6n2dw3bfql6mqbh6ar-golangci-lint-2.1.6"
900+
"store_path": "/nix/store/p1144qggxazwz6791avlf6cqzyvkh8cv-golangci-lint-2.2.1"
901901
}
902902
}
903903
},
@@ -1577,6 +1577,54 @@
15771577
}
15781578
}
15791579
},
1580+
"reviewdog@latest": {
1581+
"last_modified": "2025-06-20T02:24:11Z",
1582+
"resolved": "github:NixOS/nixpkgs/076e8c6678d8c54204abcb4b1b14c366835a58bb#reviewdog",
1583+
"source": "devbox-search",
1584+
"version": "0.20.3",
1585+
"systems": {
1586+
"aarch64-darwin": {
1587+
"outputs": [
1588+
{
1589+
"name": "out",
1590+
"path": "/nix/store/3yyxphpj15k63glrjhks2gkpj0ni9snf-reviewdog-0.20.3",
1591+
"default": true
1592+
}
1593+
],
1594+
"store_path": "/nix/store/3yyxphpj15k63glrjhks2gkpj0ni9snf-reviewdog-0.20.3"
1595+
},
1596+
"aarch64-linux": {
1597+
"outputs": [
1598+
{
1599+
"name": "out",
1600+
"path": "/nix/store/drhvxi0lcpa5drqvh3asqll3kak7g2ja-reviewdog-0.20.3",
1601+
"default": true
1602+
}
1603+
],
1604+
"store_path": "/nix/store/drhvxi0lcpa5drqvh3asqll3kak7g2ja-reviewdog-0.20.3"
1605+
},
1606+
"x86_64-darwin": {
1607+
"outputs": [
1608+
{
1609+
"name": "out",
1610+
"path": "/nix/store/i6cknyqzrvzzpzjh9n267y4b15zdxc6n-reviewdog-0.20.3",
1611+
"default": true
1612+
}
1613+
],
1614+
"store_path": "/nix/store/i6cknyqzrvzzpzjh9n267y4b15zdxc6n-reviewdog-0.20.3"
1615+
},
1616+
"x86_64-linux": {
1617+
"outputs": [
1618+
{
1619+
"name": "out",
1620+
"path": "/nix/store/15vfbrwqpi0z4d97790vs7cr5ysz6srd-reviewdog-0.20.3",
1621+
"default": true
1622+
}
1623+
],
1624+
"store_path": "/nix/store/15vfbrwqpi0z4d97790vs7cr5ysz6srd-reviewdog-0.20.3"
1625+
}
1626+
}
1627+
},
15801628
"rsync@latest": {
15811629
"last_modified": "2025-05-16T20:19:48Z",
15821630
"resolved": "github:NixOS/nixpkgs/12a55407652e04dcf2309436eb06fef0d3713ef3#rsync",

hack/tools/.custom-gcl.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright 2025 Nutanix. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
version: v2.2.1
5+
name: golangci-lint-kube-api-linter
6+
plugins:
7+
- module: 'sigs.k8s.io/kube-api-linter'
8+
version: v0.0.0-20250710131328-b566fe88b732

make/go.mk

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ endif
125125
endif
126126

127127
GOLANGCI_CONFIG_FILE ?= $(wildcard $(REPO_ROOT)/.golangci.y*ml)
128+
GOLANGCI_KAL_CONFIG_FILE ?= $(wildcard $(REPO_ROOT)/.golangci-kal.y*ml)
128129

129130
.PHONY:fmt
130131
fmt: ## Runs golangci-lint fmt for all modules in repository
@@ -155,6 +156,16 @@ lint.%: ## Runs golangci-lint run for a specific module
155156
lint.%: fmt.% ; $(info $(M) linting $* module)
156157
$(if $(filter-out root,$*),cd $* && )golangci-lint run --fix --config=$(GOLANGCI_CONFIG_FILE)
157158

159+
.PHONY: lint-kube-api
160+
lint-kube-api: ## Runs kube-api-linter via custom golangci-lint configuration
161+
lint-kube-api: go-generate hack/tools/golangci-lint-kube-api-linter
162+
lint-kube-api: ; $(info $(M) running kube-api-linter)
163+
cd api && $(PWD)/hack/tools/golangci-lint-kube-api-linter run --fix --config=$(PWD)/.golangci-kal.yml
164+
165+
hack/tools/golangci-lint-kube-api-linter: hack/tools/.custom-gcl.yml
166+
hack/tools/golangci-lint-kube-api-linter: ; $(info $(M) installing golangci-lint-kube-api-linter tool)
167+
cd hack/tools && golangci-lint custom
168+
158169
.PHONY: mod-tidy
159170
mod-tidy: ## Run go mod tidy for all modules
160171
ifneq ($(wildcard $(REPO_ROOT)/go.mod),)

0 commit comments

Comments
 (0)