Skip to content

build: Add kube-api-linter #1209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,21 +132,16 @@ jobs:
with:
enable-cache: true

- name: Export golang and golangci-lint versions
id: versions
run: |
echo "golangci-lint=$(devbox run -- golangci-lint version --short)" >>"${GITHUB_OUTPUT}"
echo "golang=$(devbox run -- go version | grep -o "[[:digit:]]\+.[[:digit:]]\+\(.[[:digit:]]\+\)\?")" >>"${GITHUB_OUTPUT}"
- name: Install custom golangci-lint
run: devbox run -- make hack/tools/golangci-lint-kube-api-linter

- name: golangci-lint
uses: reviewdog/action-golangci-lint@v2
with:
fail_level: error
reporter: github-pr-review
golangci_lint_version: v${{ steps.versions.outputs.golangci-lint }}
go_version: v${{ steps.versions.outputs.golang }}
workdir: ${{ matrix.module }}
golangci_lint_flags: "--config=${{ github.workspace }}/.golangci.yml"
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
${{ github.workspace }}/hack/tools/golangci-lint-kube-api-linter run --config=${{ github.workspace }}/.golangci.yml --output.text.path=stdout ./... | \
devbox run -- reviewdog -f=golangci-lint -reporter=github-pr-review --fail-level=any
working-directory: ${{ matrix.module }}

lint-gha:
runs-on: ubuntu-24.04
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ test/e2e/config/caren-envsubst.yaml
hack/tools/fetch-images/fetch-images
caren-images.txt
hack/examples/release/*-cluster-class.yaml
golangci-lint-kube-api-linter
85 changes: 79 additions & 6 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ linters:
- govet
- importas
- ineffassign
- kubeapilinter
- lll
- misspell
- nolintlint
Expand Down Expand Up @@ -74,8 +75,55 @@ linters:
- github.com/onsi/gomega
testifylint:
enable-all: true
custom:
kubeapilinter:
type: module
description: KAL is the Kube-API-Linter and lints Kube like APIs based on API conventions and best practices.
settings:
linters:
enable:
# - "commentstart" # Ensure comments start with the serialized version of the field name.
# - "conditions" # Ensure conditions have the correct json tags and markers.
- "duplicatemarkers" # Ensure there are no exact duplicate markers. for types and fields.
- "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.
- "nobools" # Bools do not evolve over time, should use enums instead.
- "nofloats" # Ensure floats are not used.
# - "nomaps" # Ensure maps are not used.
# - "nophase" # Ensure phases are not used, as they are not extensible.
- "optionalfields" # Ensure that all fields marked as optional adhere to being pointers and
# having the `omitempty` value in their `json` tag where appropriate.
- "optionalorrequired" # Every field should be marked as `+optional` or `+required`.
- "requiredfields" # Required fields should not be pointers, and should not have `omitempty`.
# - "ssatags" # Ensure array fields have the appropriate listType markers
# - "statusoptional" # Ensure all first children within status should be optional.
# - "statussubresource" # All root objects that have a `status` field should have a status subresource.
- "uniquemarkers" # Ensure that types and fields do not contain more than a single definition of a marker that should only be present once.

# Linters below this line are disabled, pending conversation on how and when to enable them.
disable:
- "*" # We will manually enable new linters after understanding the impact. Disable all by default.
lintersConfig:
optionalfields:
pointers:
preference: WhenRequired # Always | WhenRequired # Whether to always require pointers, or only when required. Defaults to `Always`.
jsontags:
jsonTagRegex: "^[a-z][a-z0-9-]*(?:[A-Z][a-z0-9]*)*$" # The default regex is appropriate for our use case.
optionalorrequired:
preferredOptionalMarker: kubebuilder:validation:Optional # The preferred optional marker to use, fixes will suggest to use this marker. Defaults to `optional`.
preferredRequiredMarker: kubebuilder:validation:Required # The preferred required marker to use, fixes will suggest to use this marker. Defaults to `required`.
exclusions:
generated: lax
generated: strict
paths:
# Ignore generated files.
- zz_generated.*\.go$
# Ignore external API packages.
- external/
# Ignore test files.
- '.+_test\.go$'
# Ignore aggregate types.
- 'aggregate_types\.go$'
presets:
- comments
- common-false-positives
Expand Down Expand Up @@ -103,11 +151,36 @@ linters:
- gocritic
path: internal/test/envtest
text: 'hugeParam: input is heavy'
paths:
- external
- third_party$
- builtin$
- examples$

# START kube-api-linter rules
- path: ".*"
text: "optionalorrequired: embedded field must be marked as kubebuilder:validation:Optional or kubebuilder:validation:Required"
linters:
- kubeapilinter

# kube-api-linter does not handle formats correctly yet.
- path: '/addon_types\.go$'
text: "maxlength: field (Start|End) must have a maximum length, add kubebuilder:validation:MaxLength marker"
linters:
- kubeapilinter
- path: '/common_types\.go$'
text: "maxlength: field Address must have a maximum length, add kubebuilder:validation:MaxLength marker"
linters:
- kubeapilinter

# kube-api-linter does not handle patterns correctly yet.
- path: '/aws_(clusterconfig|node)_types\.go$'
text: "maxlength: field (ID|IAMInstanceProfile|InstanceType|Org) must have a maximum length, add kubebuilder:validation:MaxLength marker"
linters:
- kubeapilinter
- path: '/(nutanix_)?(clusterconfig)_types\.go$'
text: "maxlength: field (URL|Tag) must have a maximum length, add kubebuilder:validation:MaxLength marker"
linters:
- kubeapilinter
- path-except: "^/api/"
linters:
- kubeapilinter
# END kube-api-linter rules
formatters:
enable:
- gci
Expand Down
28 changes: 20 additions & 8 deletions api/v1alpha1/addon_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ type CNI struct {

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

// AddonConfig contains the configuration for the CNI provider.
// +kubebuilder:validation:Optional
Expand Down Expand Up @@ -153,24 +154,27 @@ type ValuesReference struct {
// Name is the name of resource being referenced.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
Name string `json:"name"`
}

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

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

type GenericCSI struct {
Expand All @@ -185,15 +189,17 @@ type GenericCSI struct {
type GenericCOSI struct {
// Addon strategy used to deploy the COSI controller to the workload cluster.
// +kubebuilder:default=HelmAddon
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Enum=HelmAddon
Strategy *AddonStrategy `json:"strategy,omitempty"`
Strategy AddonStrategy `json:"strategy,omitzero"`
}

type SnapshotController struct {
// Addon strategy used to deploy the snapshot controller to the workload cluster.
// +kubebuilder:default=HelmAddon
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
Strategy *AddonStrategy `json:"strategy,omitempty"`
Strategy AddonStrategy `json:"strategy,omitzero"`
}

type DefaultStorage struct {
Expand All @@ -205,6 +211,7 @@ type DefaultStorage struct {
// Name of the default storage class config the specified default provider.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=242
StorageClassConfig string `json:"storageClassConfig"`
}

Expand Down Expand Up @@ -251,8 +258,9 @@ type CSIProvider struct {

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

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

// If the storage class should allow volume expanding
// +kubebuilder:validation:Optional
// +kubebuilder:default=false
AllowExpansion bool `json:"allowExpansion,omitempty"`
}

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

// Addon strategy used to deploy the CCM to the workload cluster.
// +kubebuilder:default=HelmAddon
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Enum=ClusterResourceSet;HelmAddon
Strategy *AddonStrategy `json:"strategy,omitempty"`
Strategy AddonStrategy `json:"strategy,omitzero"`
}

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

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

// +kubebuilder:validation:Format=ipv4
// +kubebuilder:validation:Required
End string `json:"end"`
}

type RegistryAddon struct {
// The OCI registry provider to deploy.
// +kubebuilder:default="CNCF Distribution"
// +kubebuilder:validation:Required
// +kubebuilder:validation:Enum="CNCF Distribution"
Provider string `json:"provider"`
}
7 changes: 5 additions & 2 deletions api/v1alpha1/aws_clusterconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type AWSSpec struct {
ControlPlaneLoadBalancer *AWSLoadBalancerSpec `json:"controlPlaneLoadBalancer,omitempty"`
}

// +kubebuilder:validation:MinLength=4
// +kubebuilder:validation:MaxLength=16
type Region string

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

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

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

Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/aws_node_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ type AWSControlPlaneNodeSpec struct {
// The IAM instance profile to use for the cluster Machines.
// +kubebuilder:validation:Optional
// +kubebuilder:default=control-plane.cluster-api-provider-aws.sigs.k8s.io
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=128
IAMInstanceProfile string `json:"iamInstanceProfile,omitempty"`

// +kubebuilder:validation:Optional
// +kubebuilder:default=m5.xlarge
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=32
InstanceType string `json:"instanceType,omitempty"`

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

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

AWSGenericNodeSpec `json:",inline"`
Expand All @@ -44,6 +52,7 @@ type AWSGenericNodeSpec struct {
PlacementGroup *PlacementGroup `json:"placementGroup,omitempty"`
}

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

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

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

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

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

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