diff --git a/.golangci-kal.yml b/.golangci-kal.yml new file mode 100644 index 000000000..2be82468c --- /dev/null +++ b/.golangci-kal.yml @@ -0,0 +1,101 @@ +version: "2" +run: + allow-parallel-runners: true +linters: + default: none + enable: + - kubeapilinter # linter for Kube API conventions + settings: + 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. + - "conflictingmarkers" + - "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. + - "nodurations" # Prevents usage of `Duration` types. + - "nofloats" # Ensure floats are not used. + - "nomaps" # Ensure maps are not used. + - "nonullable" # Ensure that types and fields do not have the nullable marker. + - "notimestamp" # Prevents usage of 'Timestamp' fields + - "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. + - "nophase" # Phase fields are discouraged by the Kube API conventions, use conditions instead. + + # 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: + conflictingmarkers: + conflicts: + - name: "default_vs_required" + sets: + - ["default", "kubebuilder:default"] + - ["required", "kubebuilder:validation:Required", "k8s:required"] + description: "A field with a default value cannot be required" + conditions: + isFirstField: Warn # Require conditions to be the first field in the status struct. + usePatchStrategy: Ignore # Ignore patchStrategy markers on the Conditions field. + useProtobuf: Forbid # We don't use protobuf, so protobuf tags are not required. + optionalfields: + pointers: + preference: WhenRequired # Always | WhenRequired # Whether to always require pointers, or only when required. Defaults to `Always`. + policy: SuggestFix # SuggestFix | Warn # The policy for pointers in optional fields. Defaults to `SuggestFix`. + omitempty: + policy: SuggestFix # SuggestFix | Warn | Ignore # The policy for omitempty in optional fields. Defaults to `SuggestFix`. + + exclusions: + generated: strict + rules: + ## KAL should only run on API folders. + - path-except: "pkg/apis" + linters: + - kubeapilinter + ## Breaking changes: These fields would need to be pointers but changing them is a breaking API change. + ## KueueConfiguration optional fields should be pointers + - path: "pkg/apis/kueueoperator/v1/types.go" + text: "optionalfields: field KueueConfiguration.WorkloadManagement" + linters: + - kubeapilinter + - path: "pkg/apis/kueueoperator/v1/types.go" + text: "optionalfields: field KueueConfiguration.GangScheduling" + linters: + - kubeapilinter + - path: "pkg/apis/kueueoperator/v1/types.go" + text: "optionalfields: field KueueConfiguration.Preemption" + linters: + - kubeapilinter + ## Required fields with valid zero values should be pointers + - path: "pkg/apis/kueueoperator/v1/types.go" + text: "requiredfields: field GangScheduling.Policy" + linters: + - kubeapilinter + - path: "pkg/apis/kueueoperator/v1/types.go" + text: "requiredfields: field ByWorkload.Admission" + linters: + - kubeapilinter + - path: "pkg/apis/kueueoperator/v1/types.go" + text: "requiredfields: field WorkloadManagement.LabelPolicy" + linters: + - kubeapilinter + - path: "pkg/apis/kueueoperator/v1/types.go" + text: "requiredfields: field Preemption.PreemptionPolicy" + linters: + - kubeapilinter + +issues: + max-same-issues: 0 diff --git a/Makefile b/Makefile index 507f3f27c..a3316fbb4 100644 --- a/Makefile +++ b/Makefile @@ -140,17 +140,30 @@ lint: golangci-lint ## Run golangci-lint linter lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes $(GOLANGCI_LINT) run --fix --timeout 30m +.PHONY: lint-api +lint-api: golangci-lint-kal + export HOME=/tmp; export GOCACHE=/tmp/; export GOLANGCI_LINT_CACHE=/tmp/.cache; $(GOLANGCI_LINT_KAL) run -v --config .golangci-kal.yml + +.PHONY: lint-api-fix +lint-api-fix: golangci-lint-kal + $(GOLANGCI_LINT_KAL) run -v --config .golangci-kal.yml --fix + ## Tool Versions CONTROLLER_TOOLS_VERSION ?= v0.17.1 GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint GOLANGCI_LINT_VERSION ?= v2.7.2 +GOLANGCI_LINT_KAL = $(shell pwd)/bin/golangci-lint-kube-api-linter golangci-lint: @[ -f $(GOLANGCI_LINT) ] || { \ set -e ;\ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\ } +.PHONY: golangci-lint-kal +golangci-lint-kal: golangci-lint ## Build golangci-lint-kal from custom configuration. + export HOME=/tmp; export GOCACHE=/tmp/; export GOLANGCI_LINT_CACHE=/tmp/.cache; cd hack/golangci-kal; $(GOLANGCI_LINT) custom; mv bin/golangci-lint-kube-api-linter ${LOCALBIN} + .PHONY: operator-sdk OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk operator-sdk: ## Download operator-sdk locally if necessary. diff --git a/go.mod b/go.mod index e0af282a4..9b5943f3e 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( sigs.k8s.io/controller-tools v0.4.1 sigs.k8s.io/jobset v0.10.1 sigs.k8s.io/kueue v0.15.0 + sigs.k8s.io/lws v0.7.0 sigs.k8s.io/structured-merge-diff/v6 v6.3.1 sigs.k8s.io/yaml v1.6.0 ) @@ -144,6 +145,5 @@ require ( sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 // indirect - sigs.k8s.io/lws v0.7.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect ) diff --git a/hack/golangci-kal/.custom-gcl.yaml b/hack/golangci-kal/.custom-gcl.yaml new file mode 100644 index 000000000..8b9812d3f --- /dev/null +++ b/hack/golangci-kal/.custom-gcl.yaml @@ -0,0 +1,6 @@ +version: v2.7.2 +name: golangci-lint-kube-api-linter +destination: ./bin +plugins: +- module: 'sigs.k8s.io/kube-api-linter' + version: v0.0.0-20251208100930-d3015c953951 diff --git a/pkg/apis/kueueoperator/v1/types.go b/pkg/apis/kueueoperator/v1/types.go index 63b9d0327..7d7a7e0b7 100644 --- a/pkg/apis/kueueoperator/v1/types.go +++ b/pkg/apis/kueueoperator/v1/types.go @@ -24,11 +24,11 @@ type Kueue struct { // spec holds user settable values for configuration // +required - Spec KueueOperandSpec `json:"spec"` + Spec KueueOperandSpec `json:"spec,omitzero"` // status holds observed values from the cluster. // They may not be overridden. // +optional - Status KueueStatus `json:"status,omitempty"` + Status KueueStatus `json:"status,omitzero,omitempty"` } type KueueOperandSpec struct { @@ -36,7 +36,7 @@ type KueueOperandSpec struct { // config is the desired configuration // for the Kueue operator. // +required - Config KueueConfiguration `json:"config"` + Config KueueConfiguration `json:"config,omitzero"` } type KueueConfiguration struct { @@ -45,7 +45,7 @@ type KueueConfiguration struct { // known as external frameworks. // Kueue will only manage workloads that correspond to the specified integrations. // +required - Integrations Integrations `json:"integrations"` + Integrations Integrations `json:"integrations,omitzero"` // workloadManagement controls how Kueue manages workloads. // By default Kueue will manage workloads that have a queue-name label. // Workloads that are missing the queue-name will be ignored by Kueue. @@ -133,7 +133,7 @@ type ExternalFramework struct { // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:XValidation:rule="self.matches(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$')" // +required - Group string `json:"group"` + Group string `json:"group,omitempty"` // resource is the Resource type of the external framework. // Resource types are lowercase and plural (e.g. pods, deployments). // Must be a valid DNS 1123 label consisting of a lower-case alphanumeric string @@ -144,7 +144,7 @@ type ExternalFramework struct { // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:XValidation:rule="self.matches(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')" // +required - Resource string `json:"resource"` + Resource string `json:"resource,omitempty"` // version is the version of the api (e.g. v1alpha1, v1beta1, v1). // Must be a valid DNS 1035 label consisting of a lower-case alphanumeric string // and hyphens of at most 63 characters in length. @@ -154,7 +154,7 @@ type ExternalFramework struct { // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:XValidation:rule="self.matches(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')" // +required - Version string `json:"version"` + Version string `json:"version,omitempty"` } // This is the integrations for Kueue. @@ -171,7 +171,7 @@ type Integrations struct { // +kubebuilder:validation:XValidation:rule="self.all(x, self.exists_one(y, x == y))",message="each item in frameworks must be unique" // +listType=set // +required - Frameworks []KueueIntegration `json:"frameworks"` + Frameworks []KueueIntegration `json:"frameworks,omitempty"` // externalFrameworks are a list of GroupVersionResources // that are managed for Kueue by external controllers. // externalFrameworks are optional and should only be used if you have an external controller @@ -181,7 +181,7 @@ type Integrations struct { // +listMapKey=group // +kubebuilder:validation:MaxItems=32 // +optional - ExternalFrameworks []ExternalFramework `json:"externalFrameworks"` + ExternalFrameworks []ExternalFramework `json:"externalFrameworks,omitempty"` // labelKeysToCopy are a list of label keys that are copied once a workload is created. // These keys are persisted to the internal Kueue workload object. // If not specified, only the Kueue labels will be copied. @@ -190,7 +190,7 @@ type Integrations struct { // +listType=map // +listMapKey=key // +optional - LabelKeysToCopy []LabelKeys `json:"labelKeysToCopy"` + LabelKeysToCopy []LabelKeys `json:"labelKeysToCopy,omitempty"` } type LabelKeys struct { @@ -208,7 +208,7 @@ type LabelKeys struct { // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:XValidation:rule="self.matches(r'^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([a-z0-9]([-a-z0-9]*[a-z0-9])?)$')" // +required - Key string `json:"key"` + Key string `json:"key,omitempty"` } // +kubebuilder:validation:Enum=ByWorkload;None;""