Skip to content

Commit ef65eba

Browse files
authored
build: Add kube-api-linter (#1209)
Validates upstream API conventions are correctly applied.
1 parent 82fb7c2 commit ef65eba

File tree

58 files changed

+663
-235
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+663
-235
lines changed

.github/workflows/checks.yml

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

135-
- name: Export golang and golangci-lint versions
136-
id: versions
137-
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}"
135+
- name: Install custom golangci-lint
136+
run: devbox run -- make hack/tools/golangci-lint-kube-api-linter
140137

141138
- name: golangci-lint
142-
uses: reviewdog/action-golangci-lint@v2
143-
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"
139+
env:
140+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
141+
run: |
142+
${{ github.workspace }}/hack/tools/golangci-lint-kube-api-linter run --config=${{ github.workspace }}/.golangci.yml --output.text.path=stdout ./... | \
143+
devbox run -- reviewdog -f=golangci-lint -reporter=github-pr-review --fail-level=any
144+
working-directory: ${{ matrix.module }}
150145

151146
lint-gha:
152147
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.yml

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ linters:
2323
- govet
2424
- importas
2525
- ineffassign
26+
- kubeapilinter
2627
- lll
2728
- misspell
2829
- nolintlint
@@ -74,8 +75,55 @@ linters:
7475
- github.com/onsi/gomega
7576
testifylint:
7677
enable-all: true
78+
custom:
79+
kubeapilinter:
80+
type: module
81+
description: KAL is the Kube-API-Linter and lints Kube like APIs based on API conventions and best practices.
82+
settings:
83+
linters:
84+
enable:
85+
# - "commentstart" # Ensure comments start with the serialized version of the field name.
86+
# - "conditions" # Ensure conditions have the correct json tags and markers.
87+
- "duplicatemarkers" # Ensure there are no exact duplicate markers. for types and fields.
88+
- "integers" # Ensure only int32 and int64 are used for integers.
89+
- "jsontags" # Ensure every field has a json tag.
90+
- "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items.
91+
- "nobools" # Bools do not evolve over time, should use enums instead.
92+
- "nofloats" # Ensure floats are not used.
93+
# - "nomaps" # Ensure maps are not used.
94+
# - "nophase" # Ensure phases are not used, as they are not extensible.
95+
- "optionalfields" # Ensure that all fields marked as optional adhere to being pointers and
96+
# having the `omitempty` value in their `json` tag where appropriate.
97+
- "optionalorrequired" # Every field should be marked as `+optional` or `+required`.
98+
- "requiredfields" # Required fields should not be pointers, and should not have `omitempty`.
99+
# - "ssatags" # Ensure array fields have the appropriate listType markers
100+
# - "statusoptional" # Ensure all first children within status should be optional.
101+
# - "statussubresource" # All root objects that have a `status` field should have a status subresource.
102+
- "uniquemarkers" # Ensure that types and fields do not contain more than a single definition of a marker that should only be present once.
103+
104+
# Linters below this line are disabled, pending conversation on how and when to enable them.
105+
disable:
106+
- "*" # We will manually enable new linters after understanding the impact. Disable all by default.
107+
lintersConfig:
108+
optionalfields:
109+
pointers:
110+
preference: WhenRequired # Always | WhenRequired # Whether to always require pointers, or only when required. Defaults to `Always`.
111+
jsontags:
112+
jsonTagRegex: "^[a-z][a-z0-9-]*(?:[A-Z][a-z0-9]*)*$" # The default regex is appropriate for our use case.
113+
optionalorrequired:
114+
preferredOptionalMarker: kubebuilder:validation:Optional # The preferred optional marker to use, fixes will suggest to use this marker. Defaults to `optional`.
115+
preferredRequiredMarker: kubebuilder:validation:Required # The preferred required marker to use, fixes will suggest to use this marker. Defaults to `required`.
77116
exclusions:
78-
generated: lax
117+
generated: strict
118+
paths:
119+
# Ignore generated files.
120+
- zz_generated.*\.go$
121+
# Ignore external API packages.
122+
- external/
123+
# Ignore test files.
124+
- '.+_test\.go$'
125+
# Ignore aggregate types.
126+
- 'aggregate_types\.go$'
79127
presets:
80128
- comments
81129
- common-false-positives
@@ -103,11 +151,36 @@ linters:
103151
- gocritic
104152
path: internal/test/envtest
105153
text: 'hugeParam: input is heavy'
106-
paths:
107-
- external
108-
- third_party$
109-
- builtin$
110-
- examples$
154+
155+
# START kube-api-linter rules
156+
- path: ".*"
157+
text: "optionalorrequired: embedded field must be marked as kubebuilder:validation:Optional or kubebuilder:validation:Required"
158+
linters:
159+
- kubeapilinter
160+
161+
# kube-api-linter does not handle formats correctly yet.
162+
- path: '/addon_types\.go$'
163+
text: "maxlength: field (Start|End) must have a maximum length, add kubebuilder:validation:MaxLength marker"
164+
linters:
165+
- kubeapilinter
166+
- path: '/common_types\.go$'
167+
text: "maxlength: field Address must have a maximum length, add kubebuilder:validation:MaxLength marker"
168+
linters:
169+
- kubeapilinter
170+
171+
# kube-api-linter does not handle patterns correctly yet.
172+
- path: '/aws_(clusterconfig|node)_types\.go$'
173+
text: "maxlength: field (ID|IAMInstanceProfile|InstanceType|Org) must have a maximum length, add kubebuilder:validation:MaxLength marker"
174+
linters:
175+
- kubeapilinter
176+
- path: '/(nutanix_)?(clusterconfig)_types\.go$'
177+
text: "maxlength: field (URL|Tag) must have a maximum length, add kubebuilder:validation:MaxLength marker"
178+
linters:
179+
- kubeapilinter
180+
- path-except: "^/api/"
181+
linters:
182+
- kubeapilinter
183+
# END kube-api-linter rules
111184
formatters:
112185
enable:
113186
- gci

api/v1alpha1/addon_types.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ type CNI struct {
118118

119119
// Addon strategy used to deploy the CNI provider to the workload cluster.
120120
// +kubebuilder:default=HelmAddon
121+
// +kubebuilder:validation:Optional
121122
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
122-
Strategy *AddonStrategy `json:"strategy,omitempty"`
123+
Strategy AddonStrategy `json:"strategy,omitzero"`
123124

124125
// AddonConfig contains the configuration for the CNI provider.
125126
// +kubebuilder:validation:Optional
@@ -153,24 +154,27 @@ type ValuesReference struct {
153154
// Name is the name of resource being referenced.
154155
// +kubebuilder:validation:Required
155156
// +kubebuilder:validation:MinLength=1
157+
// +kubebuilder:validation:MaxLength=253
156158
Name string `json:"name"`
157159
}
158160

159161
// NFD tells us to enable or disable the node feature discovery addon.
160162
type NFD struct {
161163
// Addon strategy used to deploy Node Feature Discovery (NFD) to the workload cluster.
162164
// +kubebuilder:default=HelmAddon
165+
// +kubebuilder:validation:Optional
163166
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
164-
Strategy *AddonStrategy `json:"strategy,omitempty"`
167+
Strategy AddonStrategy `json:"strategy,omitzero"`
165168
}
166169

167170
// ClusterAutoscaler tells us to enable or disable the cluster-autoscaler addon.
168171
type ClusterAutoscaler struct {
169172
// Addon strategy used to deploy cluster-autoscaler to the management cluster
170173
// targeting the workload cluster.
171174
// +kubebuilder:default=HelmAddon
175+
// +kubebuilder:validation:Optional
172176
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
173-
Strategy *AddonStrategy `json:"strategy,omitempty"`
177+
Strategy AddonStrategy `json:"strategy,omitzero"`
174178
}
175179

176180
type GenericCSI struct {
@@ -185,15 +189,17 @@ type GenericCSI struct {
185189
type GenericCOSI struct {
186190
// Addon strategy used to deploy the COSI controller to the workload cluster.
187191
// +kubebuilder:default=HelmAddon
192+
// +kubebuilder:validation:Optional
188193
// +kubebuilder:validation:Enum=HelmAddon
189-
Strategy *AddonStrategy `json:"strategy,omitempty"`
194+
Strategy AddonStrategy `json:"strategy,omitzero"`
190195
}
191196

192197
type SnapshotController struct {
193198
// Addon strategy used to deploy the snapshot controller to the workload cluster.
194199
// +kubebuilder:default=HelmAddon
200+
// +kubebuilder:validation:Optional
195201
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
196-
Strategy *AddonStrategy `json:"strategy,omitempty"`
202+
Strategy AddonStrategy `json:"strategy,omitzero"`
197203
}
198204

199205
type DefaultStorage struct {
@@ -205,6 +211,7 @@ type DefaultStorage struct {
205211
// Name of the default storage class config the specified default provider.
206212
// +kubebuilder:validation:Required
207213
// +kubebuilder:validation:MinLength=1
214+
// +kubebuilder:validation:MaxLength=242
208215
StorageClassConfig string `json:"storageClassConfig"`
209216
}
210217

@@ -251,8 +258,9 @@ type CSIProvider struct {
251258

252259
// Addon strategy used to deploy the CSI provider to the workload cluster.
253260
// +kubebuilder:default=HelmAddon
261+
// +kubebuilder:validation:Optional
254262
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
255-
Strategy *AddonStrategy `json:"strategy,omitempty"`
263+
Strategy AddonStrategy `json:"strategy,omitzero"`
256264

257265
// The reference to any secret used by the CSI Provider.
258266
// +kubebuilder:validation:Optional
@@ -276,7 +284,6 @@ type StorageClassConfig struct {
276284

277285
// If the storage class should allow volume expanding
278286
// +kubebuilder:validation:Optional
279-
// +kubebuilder:default=false
280287
AllowExpansion bool `json:"allowExpansion,omitempty"`
281288
}
282289

@@ -302,8 +309,9 @@ type CCM struct {
302309

303310
// Addon strategy used to deploy the CCM to the workload cluster.
304311
// +kubebuilder:default=HelmAddon
312+
// +kubebuilder:validation:Optional
305313
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
306-
Strategy *AddonStrategy `json:"strategy,omitempty"`
314+
Strategy AddonStrategy `json:"strategy,omitzero"`
307315
}
308316

309317
type CCMCredentials struct {
@@ -329,21 +337,25 @@ type ServiceLoadBalancerConfiguration struct {
329337
// provider uses to choose an address for a load balancer.
330338
// +kubebuilder:validation:Required
331339
// +kubebuilder:validation:MinItems=1
340+
// +kubebuilder:validation:MaxItems=10
332341
AddressRanges []AddressRange `json:"addressRanges"`
333342
}
334343

335344
// AddressRange defines an IPv4 range.
336345
type AddressRange struct {
337346
// +kubebuilder:validation:Format=ipv4
347+
// +kubebuilder:validation:Required
338348
Start string `json:"start"`
339349

340350
// +kubebuilder:validation:Format=ipv4
351+
// +kubebuilder:validation:Required
341352
End string `json:"end"`
342353
}
343354

344355
type RegistryAddon struct {
345356
// The OCI registry provider to deploy.
346357
// +kubebuilder:default="CNCF Distribution"
358+
// +kubebuilder:validation:Required
347359
// +kubebuilder:validation:Enum="CNCF Distribution"
348360
Provider string `json:"provider"`
349361
}

api/v1alpha1/aws_clusterconfig_types.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type AWSSpec struct {
2020
ControlPlaneLoadBalancer *AWSLoadBalancerSpec `json:"controlPlaneLoadBalancer,omitempty"`
2121
}
2222

23+
// +kubebuilder:validation:MinLength=4
24+
// +kubebuilder:validation:MaxLength=16
2325
type Region string
2426

2527
type AWSNetwork struct {
@@ -34,17 +36,18 @@ type AWSNetwork struct {
3436
type VPC struct {
3537
// Existing VPC ID to use for the cluster.
3638
// +kubebuilder:validation:Required
37-
// +kubebuilder:validation:MinLength=1
39+
// +kubebuilder:validation:Format=`^vpc-[0-9a-f]{8}(?:[0-9a-f]{9})?$`
3840
ID string `json:"id"`
3941
}
4042

43+
// +kubebuilder:validation:MaxItems=10
4144
type Subnets []SubnetSpec
4245

4346
// SubnetSpec configures an AWS Subnet.
4447
type SubnetSpec struct {
4548
// Existing Subnet ID to use for the cluster.
4649
// +kubebuilder:validation:Required
47-
// +kubebuilder:validation:MinLength=1
50+
// +kubebuilder:validation:Format=`^subnet-[0-9a-f]{8}(?:[0-9a-f]{9})?$`
4851
ID string `json:"id"`
4952
}
5053

api/v1alpha1/aws_node_types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ type AWSControlPlaneNodeSpec struct {
77
// The IAM instance profile to use for the cluster Machines.
88
// +kubebuilder:validation:Optional
99
// +kubebuilder:default=control-plane.cluster-api-provider-aws.sigs.k8s.io
10+
// +kubebuilder:validation:MinLength=1
11+
// +kubebuilder:validation:MaxLength=128
1012
IAMInstanceProfile string `json:"iamInstanceProfile,omitempty"`
1113

1214
// +kubebuilder:validation:Optional
1315
// +kubebuilder:default=m5.xlarge
16+
// +kubebuilder:validation:MinLength=1
17+
// +kubebuilder:validation:MaxLength=32
1418
InstanceType string `json:"instanceType,omitempty"`
1519

1620
AWSGenericNodeSpec `json:",inline"`
@@ -20,11 +24,15 @@ type AWSWorkerNodeSpec struct {
2024
// The IAM instance profile to use for the cluster Machines.
2125
// +kubebuilder:validation:Optional
2226
// +kubebuilder:default=nodes.cluster-api-provider-aws.sigs.k8s.io
27+
// +kubebuilder:validation:MinLength=1
28+
// +kubebuilder:validation:MaxLength=128
2329
IAMInstanceProfile string `json:"iamInstanceProfile,omitempty"`
2430

2531
// The AWS instance type to use for the cluster Machines.
2632
// +kubebuilder:validation:Optional
2733
// +kubebuilder:default=m5.2xlarge
34+
// +kubebuilder:validation:MinLength=1
35+
// +kubebuilder:validation:MaxLength=32
2836
InstanceType string `json:"instanceType,omitempty"`
2937

3038
AWSGenericNodeSpec `json:",inline"`
@@ -44,6 +52,7 @@ type AWSGenericNodeSpec struct {
4452
PlacementGroup *PlacementGroup `json:"placementGroup,omitempty"`
4553
}
4654

55+
// +kubebuilder:validation:MaxItems=32
4756
type AdditionalSecurityGroup []SecurityGroup
4857

4958
type PlacementGroup struct {
@@ -57,12 +66,16 @@ type PlacementGroup struct {
5766
type SecurityGroup struct {
5867
// ID is the id of the security group
5968
// +kubebuilder:validation:Optional
69+
// +kubebuilder:validation:Format=`^sg-[0-9a-f]{8}(?:[0-9a-f]{9})?$`
70+
// +kubebuilder:validation:MinLength=1
6071
ID string `json:"id,omitempty"`
6172
}
6273

6374
type AMISpec struct {
6475
// AMI ID is the reference to the AMI from which to create the machine instance.
6576
// +kubebuilder:validation:Optional
77+
// +kubebuilder:validation:Format=`^ami-[0-9a-f]{8}(?:[0-9a-f]{9})?$`
78+
// +kubebuilder:validation:MinLength=1
6679
ID string `json:"id,omitempty"`
6780

6881
// Lookup is the lookup arguments for the AMI.
@@ -75,13 +88,20 @@ type AMILookup struct {
7588
// base OS and kubernetes version.
7689
// +kubebuilder:validation:Optional
7790
// +kubebuilder:example=`capa-ami-{{.BaseOS}}-?{{.K8sVersion}}-*`
91+
// +kubebuilder:validation:MinLength=1
92+
// +kubebuilder:validation:MaxLength=128
7893
Format string `json:"format,omitempty"`
7994

8095
// The AWS Organization ID to use for image lookup.
8196
// +kubebuilder:validation:Optional
97+
// +kubebuilder:validation:Format=`^o-[0-9a-z]{10,32}$`
98+
// +kubebuilder:validation:MinLength=12
99+
// +kubebuilder:validation:MaxLength=34
82100
Org string `json:"org,omitempty"`
83101

84102
// The name of the base os for image lookup
85103
// +kubebuilder:validation:Optional
104+
// +kubebuilder:validation:MinLength=1
105+
// +kubebuilder:validation:MaxLength=32
86106
BaseOS string `json:"baseOS,omitempty"`
87107
}

0 commit comments

Comments
 (0)