Skip to content
Merged
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
53 changes: 27 additions & 26 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,37 +1,18 @@
version: "2"

run:
timeout: 5m
allow-parallel-runners: true

issues:
# don't skip warning about doc comments
# don't exclude the default set of lint
exclude-use-default: false
# restore some of the defaults
# (fill in the rest as needed)
exclude-rules:
- path: "apis/*"
linters:
- lll
- path: "internal/*"
linters:
- dupl
- lll
- path: "_test.go"
linters:
- goconst

linters:
disable-all: true
default: none
enable:
- copyloopvar
- dupl
- errcheck
- ginkgolinter
- goconst
- gocyclo
- gofmt
- goimports
- gosimple
- govet
- ineffassign
- lll
Expand All @@ -40,12 +21,32 @@ linters:
- prealloc
- revive
- staticcheck
- typecheck
- unconvert
- unparam
- unused

linters-settings:
revive:
settings:
revive:
rules:
- name: comment-spacings

exclusions:
generated: lax
rules:
- name: comment-spacings
- linters:
- lll
path: apis/*
- linters:
- dupl
- lll
path: internal/*
- linters:
- goconst
path: _test.go

formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
13 changes: 11 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ export
all: prebuild build ## Build all container images, plus their prerequisites (faster with 'make -j')

.PHONY: lint
lint: golangci-lint.client golangci-lint.controller golangci-lint.sidecar spell-lint dockerfiles-lint ## Run all linters (suggest `make -k`)
lint: golangci-lint.client golangci-lint.controller golangci-lint.sidecar kubeapi-lint spell-lint dockerfiles-lint ## Run all linters (suggest `make -k`)
golangci-lint.%: golangci-lint
cd $* && $(GOLANGCI_LINT) run $(GOLANGCI_LINT_RUN_OPTS) --config $(CURDIR)/.golangci.yaml --new
kubeapi-lint: kube-api-linter
cd client/apis && $(KUBEAPI_LINT) run --config $(CURDIR)/client/.kubeapilint.yaml
spell-lint:
git ls-files | grep -v -e CHANGELOG -e go.mod -e go.sum -e vendor | xargs $(SPELL_LINT) -i "Creater,creater,ect" -error -o stderr
dockerfiles-lint:
Expand Down Expand Up @@ -192,6 +194,7 @@ CRD_REF_DOCS ?= $(TOOLBIN)/crd-ref-docs
CTLPTL ?= $(TOOLBIN)/ctlptl
GOLANGCI_LINT ?= $(TOOLBIN)/golangci-lint
KIND ?= $(TOOLBIN)/kind
KUBEAPI_LINT ?= $(TOOLBIN)/golangci-lint-kube-api-linter
KUSTOMIZE ?= $(TOOLBIN)/kustomize
MDBOOK ?= $(TOOLBIN)/mdbook
SPELL_LINT ?= $(TOOLBIN)/spell-lint
Expand All @@ -201,8 +204,9 @@ CHAINSAW_VERSION ?= v0.2.12
CONTROLLER_TOOLS_VERSION ?= v0.19.0
CRD_REF_DOCS_VERSION ?= v0.2.0
CTLPTL_VERSION ?= v0.8.39
GOLANGCI_LINT_VERSION ?= v1.64.7
GOLANGCI_LINT_VERSION ?= v2.7.2
KIND_VERSION ?= v0.27.0
KUBEAPI_LINT_VERSION ?= v0.0.0-20251208100930-d3015c953951
KUSTOMIZE_VERSION ?= v5.6.0
MDBOOK_VERSION ?= v0.4.47
SPELL_LINT_VERSION ?= v0.6.0
Expand Down Expand Up @@ -238,6 +242,11 @@ kind: $(KIND)-$(KIND_VERSION)
$(KIND)-$(KIND_VERSION): $(TOOLBIN)
$(call go-install-tool,$(KIND),sigs.k8s.io/kind,$(KIND_VERSION))

.PHONY: kube-api-linter
kube-api-linter: $(KUBEAPI_LINT)-$(KUBEAPI_LINT_VERSION)
$(KUBEAPI_LINT)-$(KUBEAPI_LINT_VERSION): $(TOOLBIN)
$(call go-install-tool,$(KUBEAPI_LINT),sigs.k8s.io/kube-api-linter/cmd/golangci-lint-kube-api-linter,$(KUBEAPI_LINT_VERSION))

.PHONY: kustomize
kustomize: $(KUSTOMIZE)-$(KUSTOMIZE_VERSION)
$(KUSTOMIZE)-$(KUSTOMIZE_VERSION): $(TOOLBIN)
Expand Down
28 changes: 28 additions & 0 deletions client/.kubeapilint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This is a golangci-lint configuration file specifically for kube-api-linter.

version: "2"

linters:
default: none
enable:
- kubeapilinter

settings:
custom:
kubeapilinter:
type: module
description: Kube API Linter lints Kube like APIs based on API conventions and best practices.
settings:
linters:
enable:
- statussubresource
- optionalfields # instead of nonpointerstructs
- requiredfields # instead of nonpointerstructs
disable:
- nonpointerstructs # not intended for CRDs
- statusoptional
Comment on lines +15 to +23
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoelSpeed do these linters make sense for our CRD-based project? I am not 100 certain about statusoptional, but I presume this to be reasonable based on your suggestion to make a status field required.

The others are documented well regarding CRD usage.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid statusoptional personally. It's a preference for some (historically all status fields were optional) but I've discussed this with other API reviewers and I think that's a legacy pattern that we don't need to repeat


lintersConfig:
optionalfields:
pointers:
preference: WhenRequired
56 changes: 29 additions & 27 deletions client/apis/objectstorage/v1alpha2/bucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,23 @@ const (
)

// BucketSpec defines the desired state of Bucket
// +kubebuilder:validation:XValidation:message="parameters map is immutable",rule="has(oldSelf.parameters) == has(self.parameters)"
// +kubebuilder:validation:XValidation:message="protocols list is immutable",rule="has(oldSelf.protocols) == has(self.protocols)"
// +kubebuilder:validation:XValidation:message="existingBucketID is immutable",rule="has(oldSelf.existingBucketID) == has(self.existingBucketID)"
// +kubebuilder:validation:XValidation:message="parameters map cannot be added or removed after creation",rule="has(oldSelf.parameters) == has(self.parameters)"
// +kubebuilder:validation:XValidation:message="protocols list cannot be added or removed after creation",rule="has(oldSelf.protocols) == has(self.protocols)"
// +kubebuilder:validation:XValidation:message="existingBucketID cannot be added or removed after creation",rule="has(oldSelf.existingBucketID) == has(self.existingBucketID)"
type BucketSpec struct {
// driverName is the name of the driver that fulfills requests for this Bucket.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:XValidation:message="driverName is immutable",rule="self == oldSelf"
DriverName string `json:"driverName"`
DriverName string `json:"driverName,omitempty"`

// deletionPolicy determines whether a Bucket should be deleted when its bound BucketClaim is
// deleted. This is mutable to allow Admins to change the policy after creation.
// Possible values:
// - Retain: keep both the Bucket object and the backend bucket
// - Delete: delete both the Bucket object and the backend bucket
// +required
DeletionPolicy BucketDeletionPolicy `json:"deletionPolicy"`
DeletionPolicy BucketDeletionPolicy `json:"deletionPolicy,omitempty"`

// parameters is an opaque map of driver-specific configuration items passed to the driver that
// fulfills requests for this Bucket.
Expand All @@ -72,68 +72,70 @@ type BucketSpec struct {
// For statically-provisioned buckets, set the namespace and name of the BucketClaim that is
// allowed to bind to this Bucket.
// +required
BucketClaimRef BucketClaimReference `json:"bucketClaim"`
BucketClaimRef BucketClaimReference `json:"bucketClaim,omitzero"`

// existingBucketID is the unique identifier for an existing backend bucket known to the driver.
// Use driver documentation to determine how to set this value.
// This field is used only for Bucket static provisioning.
// This field will be empty when the Bucket is dynamically provisioned from a BucketClaim.
// +optional
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:XValidation:message="existingBucketID is immutable",rule="self == oldSelf"
ExistingBucketID string `json:"existingBucketID,omitempty"`
}

// BucketClaimReference is a reference to a BucketClaim object.
// +kubebuilder:validation:XValidation:message="namespace is immutable once set",rule="!has(oldSelf.namespace) || has(self.namespace)"
// +kubebuilder:validation:XValidation:message="uid is immutable once set",rule="!has(oldSelf.uid) || has(self.uid)"
// +kubebuilder:validation:XValidation:message="namespace cannot be removed once set",rule="!has(oldSelf.namespace) || has(self.namespace)"
// +kubebuilder:validation:XValidation:message="uid cannot be removed once set",rule="!has(oldSelf.uid) || has(self.uid)"
type BucketClaimReference struct {
// name is the name of the BucketClaim being referenced.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:XValidation:message="name is immutable",rule="self == oldSelf"
Name string `json:"name"`
Name string `json:"name,omitempty"`

// namespace is the namespace of the BucketClaim being referenced.
// If empty, the Kubernetes 'default' namespace is assumed.
// namespace is immutable except to update '' to 'default'.
// +optional
// +kubebuilder:validation:MinLength=0
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:XValidation:message="namespace is immutable",rule="(oldSelf == '' && self == 'default') || self == oldSelf"
Namespace string `json:"namespace"`
// +kubebuilder:validation:XValidation:message="namespace is immutable",rule="self == oldSelf"
Namespace string `json:"namespace,omitempty"`

// uid is the UID of the BucketClaim being referenced.
// +optional
// +kubebuilder:validation:XValidation:message="uid is immutable once set",rule="oldSelf == '' || self == oldSelf"
UID types.UID `json:"uid"`
UID types.UID `json:"uid,omitempty"`
}

// BucketStatus defines the observed state of Bucket.
// +kubebuilder:validation:XValidation:message="bucketID is immutable once set",rule="!has(oldSelf.bucketID) || has(self.bucketID)"
// +kubebuilder:validation:XValidation:message="protocols is immutable once set",rule="!has(oldSelf.protocols) || has(self.protocols)"
// +kubebuilder:validation:XValidation:message="bucketID cannot be removed once set",rule="!has(oldSelf.bucketID) || has(self.bucketID)"
// +kubebuilder:validation:XValidation:message="protocols cannot be removed once set",rule="!has(oldSelf.protocols) || has(self.protocols)"
type BucketStatus struct {
// readyToUse indicates that the bucket is ready for consumption by workloads.
ReadyToUse bool `json:"readyToUse"`
// +required
ReadyToUse *bool `json:"readyToUse,omitempty"`

// bucketID is the unique identifier for the backend bucket known to the driver.
// +optional
// +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="oldSelf == '' || self == oldSelf"
BucketID string `json:"bucketID"`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="self == oldSelf"
BucketID string `json:"bucketID,omitempty"`

// protocols is the set of protocols the Bucket reports to support. BucketAccesses can request
// access to this BucketClaim using any of the protocols reported here.
// +optional
// +listType=set
Protocols []ObjectProtocol `json:"protocols"`
Protocols []ObjectProtocol `json:"protocols,omitempty"`

// BucketInfo reported by the driver, rendered in the COSI_<PROTOCOL>_<KEY> format used for the
// BucketAccess Secret. e.g., COSI_S3_ENDPOINT, COSI_AZURE_STORAGE_ACCOUNT.
// bucketInfo contains info about the bucket reported by the driver, rendered in the same
// COSI_<PROTOCOL>_<KEY> format used for the BucketAccess Secret.
// e.g., COSI_S3_ENDPOINT, COSI_AZURE_STORAGE_ACCOUNT.
// This should not contain any sensitive information.
// +optional
BucketInfo map[string]string `json:"bucketInfo,omitempty"`

// Error holds the most recent error message, with a timestamp.
// error holds the most recent error message, with a timestamp.
// This is cleared when provisioning is successful.
// +optional
Error *TimestampedError `json:"error,omitempty"`
Expand All @@ -154,11 +156,11 @@ type Bucket struct {

// spec defines the desired state of Bucket
// +required
Spec BucketSpec `json:"spec"`
Spec BucketSpec `json:"spec,omitzero"`

// status defines the observed state of Bucket
// +optional
Status BucketStatus `json:"status,omitempty,omitzero"`
Status BucketStatus `json:"status,omitzero"`
Comment on lines 159 to +163
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoelSpeed I'm having some challenges coming up with the best action to take based KAL's response on the status here. Do you have a recommended pattern for validations to apply here to avoid a pointer status and allow KAL to pass?

apis/objectstorage/v1alpha2/bucket_types.go:163:2: optionalfields: field Bucket.Status has a valid zero value ({}), but the validation is not complete (e.g. min properties/adding required fields). The field should be a pointer to allow the zero value to be set. If the zero value is not a valid use case, complete the validation and remove the pointer. (kubeapilinter)
        Status BucketStatus `json:"status,omitzero"`
        ^

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KAL will only suggest this to be a pointer if your validations allow {} to be persisted. That means your validations imply a semantic difference between omitted and the empty struct. That's not desirable in 99% of cases.

The solution is to make {} invalid. That can be done by adding a required field to status (this is probably the right path, which fields should the status always have?) or to add a +kubebuilder:validation:MinProperties=1 marker to make sure at least one property is set.

My advice would be to add a required field.

optionalfields: field Bucket.Status has a valid zero value ({}), but the validation is not complete (e.g. min properties/adding required fields). The field should be a pointer to allow the zero value to be set. If the zero value is not a valid use case, complete the validation and remove the pointer.

I think I'm just repeating the error message here no? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I finally got my head around this. Thanks for your patience. For now, I made ReadyToUse required, and I realized I needed to configure KAL slightly for CRDs.

}

// +kubebuilder:object:root=true
Expand Down
Loading