diff --git a/.golangci.yaml b/.golangci.yaml index 1fb84709..48ec708c 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,27 +1,11 @@ +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 @@ -29,9 +13,6 @@ linters: - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll @@ -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 diff --git a/Makefile b/Makefile index 50a7dd8f..a6eb14e8 100644 --- a/Makefile +++ b/Makefile @@ -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: @@ -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 @@ -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 @@ -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) diff --git a/client/.kubeapilint.yaml b/client/.kubeapilint.yaml new file mode 100644 index 00000000..6bc873fc --- /dev/null +++ b/client/.kubeapilint.yaml @@ -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 + + lintersConfig: + optionalfields: + pointers: + preference: WhenRequired diff --git a/client/apis/objectstorage/v1alpha2/bucket_types.go b/client/apis/objectstorage/v1alpha2/bucket_types.go index 2817ab8f..de61d68a 100644 --- a/client/apis/objectstorage/v1alpha2/bucket_types.go +++ b/client/apis/objectstorage/v1alpha2/bucket_types.go @@ -37,15 +37,15 @@ 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. @@ -53,7 +53,7 @@ type BucketSpec struct { // - 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. @@ -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__ 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__ 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"` @@ -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"` } // +kubebuilder:object:root=true diff --git a/client/apis/objectstorage/v1alpha2/bucketaccess_types.go b/client/apis/objectstorage/v1alpha2/bucketaccess_types.go index 9a38c277..2cde1de1 100644 --- a/client/apis/objectstorage/v1alpha2/bucketaccess_types.go +++ b/client/apis/objectstorage/v1alpha2/bucketaccess_types.go @@ -23,7 +23,7 @@ import ( // BucketAccessAuthenticationType specifies what authentication mechanism is used for provisioning // bucket access. // +enum -// +kubebuilder:validation:Enum:="";Key;ServiceAccount +// +kubebuilder:validation:Enum:=Key;ServiceAccount type BucketAccessAuthenticationType string const ( @@ -53,7 +53,7 @@ const ( ) // BucketAccessSpec defines the desired state of BucketAccess -// +kubebuilder:validation:XValidation:message="serviceAccountName is immutable",rule="has(oldSelf.serviceAccountName) == has(self.serviceAccountName)" +// +kubebuilder:validation:XValidation:message="serviceAccountName cannot be added or removed after creation",rule="has(oldSelf.serviceAccountName) == has(self.serviceAccountName)" type BucketAccessSpec struct { // bucketClaims is a list of BucketClaims the provisioned access must have permissions for, // along with per-BucketClaim access parameters and system output definitions. @@ -64,19 +64,19 @@ type BucketAccessSpec struct { // +listMapKey=bucketClaimName // +kubebuilder:validation:MinItems=1 // +kubebuilder:validation:XValidation:message="bucketClaims list is immutable",rule="self == oldSelf" - BucketClaims []BucketClaimAccess `json:"bucketClaims"` + BucketClaims []BucketClaimAccess `json:"bucketClaims,omitempty"` // bucketAccessClassName selects the BucketAccessClass for provisioning the access. // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="bucketAccessClassName is immutable",rule="self == oldSelf" - BucketAccessClassName string `json:"bucketAccessClassName"` + BucketAccessClassName string `json:"bucketAccessClassName,omitempty"` // protocol is the object storage protocol that the provisioned access must use. // +required // +kubebuilder:validation:XValidation:message="protocol is immutable",rule="self == oldSelf" - Protocol ObjectProtocol `json:"protocol"` + Protocol ObjectProtocol `json:"protocol,omitempty"` // serviceAccountName is the name of the Kubernetes ServiceAccount that user application Pods // intend to use for access to referenced BucketClaims. @@ -85,26 +85,29 @@ type BucketAccessSpec struct { // - ServiceAccount: This field is required. The driver should configure the system so that Pods // using the ServiceAccount authenticate to the object storage backend automatically. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="serviceAccountName is immutable",rule="self == oldSelf" ServiceAccountName string `json:"serviceAccountName,omitempty"` } // BucketAccessStatus defines the observed state of BucketAccess. -// +kubebuilder:validation:XValidation:message="accountID is immutable once set",rule="!has(oldSelf.accountID) || has(self.accountID)" -// +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="!has(oldSelf.accessedBuckets) || has(self.accessedBuckets)" -// +kubebuilder:validation:XValidation:message="driverName is immutable once set",rule="!has(oldSelf.driverName) || has(self.driverName)" -// +kubebuilder:validation:XValidation:message="authenticationType is immutable once set",rule="!has(oldSelf.authenticationType) || has(self.authenticationType)" -// +kubebuilder:validation:XValidation:message="parameters is immutable once set",rule="!has(oldSelf.parameters) || has(self.parameters)" +// +kubebuilder:validation:XValidation:message="accountID cannot be removed once set",rule="!has(oldSelf.accountID) || has(self.accountID)" +// +kubebuilder:validation:XValidation:message="accessedBuckets cannot be removed once set",rule="!has(oldSelf.accessedBuckets) || has(self.accessedBuckets)" +// +kubebuilder:validation:XValidation:message="driverName cannot be removed once set",rule="!has(oldSelf.driverName) || has(self.driverName)" +// +kubebuilder:validation:XValidation:message="authenticationType cannot be removed once set",rule="!has(oldSelf.authenticationType) || has(self.authenticationType)" +// +kubebuilder:validation:XValidation:message="parameters cannot be removed once set",rule="!has(oldSelf.parameters) || has(self.parameters)" type BucketAccessStatus struct { // readyToUse indicates that the BucketAccess is ready for consumption by workloads. - ReadyToUse bool `json:"readyToUse"` + // +required + ReadyToUse *bool `json:"readyToUse,omitempty"` // accountID is the unique identifier for the backend access known to the driver. // This field is populated by the COSI Sidecar once access has been successfully granted. // +optional - // +kubebuilder:validation:XValidation:message="accountId is immutable once set",rule="oldSelf == '' || self == oldSelf" - AccountID string `json:"accountID"` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:XValidation:message="accountId is immutable once set",rule="self == oldSelf" + AccountID string `json:"accountID,omitempty"` // accessedBuckets is a list of Buckets the provisioned access must have permissions for, along // with per-Bucket access options. This field is populated by the COSI Controller based on the @@ -112,25 +115,27 @@ type BucketAccessStatus struct { // +optional // +listType=map // +listMapKey=bucketName - // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="oldSelf.size() == 0 || self == oldSelf" - AccessedBuckets []AccessedBucket `json:"accessedBuckets"` + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="self == oldSelf" + AccessedBuckets []AccessedBucket `json:"accessedBuckets,omitempty"` // driverName holds a copy of the BucketAccessClass driver name from the time of BucketAccess // provisioning. This field is populated by the COSI Controller. // +optional - // +kubebuilder:validation:XValidation:message="driverName is immutable once set",rule="oldSelf == '' || self == oldSelf" - DriverName string `json:"driverName"` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:XValidation:message="driverName is immutable once set",rule="self == oldSelf" + DriverName string `json:"driverName,omitempty"` // authenticationType holds a copy of the BucketAccessClass authentication type from the time of // BucketAccess provisioning. This field is populated by the COSI Controller. // +optional - // +kubebuilder:validation:XValidation:message="authenticationType is immutable once set",rule="oldSelf == '' || self == oldSelf" - AuthenticationType BucketAccessAuthenticationType `json:"authenticationType"` + // +kubebuilder:validation:XValidation:message="authenticationType is immutable once set",rule="self == oldSelf" + AuthenticationType BucketAccessAuthenticationType `json:"authenticationType,omitempty"` // parameters holds a copy of the BucketAccessClass parameters from the time of BucketAccess // provisioning. This field is populated by the COSI Controller. // +optional - // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="oldSelf.size() == 0 || self == oldSelf" + // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="self == oldSelf" Parameters map[string]string `json:"parameters,omitempty"` // error holds the most recent error message, with a timestamp. @@ -148,12 +153,12 @@ type BucketClaimAccess struct { // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - BucketClaimName string `json:"bucketClaimName"` + BucketClaimName string `json:"bucketClaimName,omitempty"` // accessMode is the Read/Write access mode that the access should have for the bucket. // Possible values: ReadWrite, ReadOnly, WriteOnly. // +required - AccessMode BucketAccessMode `json:"accessMode"` + AccessMode BucketAccessMode `json:"accessMode,omitempty"` // accessSecretName is the name of a Kubernetes Secret that COSI should create and populate with // bucket info and access credentials for the bucket. @@ -164,7 +169,7 @@ type BucketClaimAccess struct { // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - AccessSecretName string `json:"accessSecretName"` + AccessSecretName string `json:"accessSecretName,omitempty"` } // AccessedBucket identifies a Bucket and correlates it to a BucketClaimAccess from the spec. @@ -173,13 +178,13 @@ type AccessedBucket struct { // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - BucketName string `json:"bucketName"` + BucketName string `json:"bucketName,omitempty"` // bucketClaimName must match a BucketClaimAccess's BucketClaimName from the spec. // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - BucketClaimName string `json:"bucketClaimName"` + BucketClaimName string `json:"bucketClaimName,omitempty"` } // +kubebuilder:object:root=true @@ -196,11 +201,11 @@ type BucketAccess struct { // spec defines the desired state of BucketAccess // +required - Spec BucketAccessSpec `json:"spec"` + Spec BucketAccessSpec `json:"spec,omitzero"` // status defines the observed state of BucketAccess // +optional - Status BucketAccessStatus `json:"status,omitempty,omitzero"` + Status BucketAccessStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true diff --git a/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go b/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go index 97b7ae38..ed645d08 100644 --- a/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go +++ b/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go @@ -25,7 +25,7 @@ type BucketAccessClassSpec struct { // driverName is the name of the driver that fulfills requests for this BucketAccessClass. // +required // +kubebuilder:validation:MinLength=1 - DriverName string `json:"driverName"` + DriverName string `json:"driverName,omitempty"` // authenticationType specifies which authentication mechanism is used bucket access. // Possible values: @@ -34,8 +34,7 @@ type BucketAccessClassSpec struct { // - ServiceAccount: The driver should configure the system such that Pods using the given // ServiceAccount authenticate to the backend object store automatically. // +required - // +kubebuilder:validation:Enum:=Key;ServiceAccount - AuthenticationType BucketAccessAuthenticationType `json:"authenticationType"` + AuthenticationType BucketAccessAuthenticationType `json:"authenticationType,omitempty"` // parameters is an opaque map of driver-specific configuration items passed to the driver that // fulfills requests for this BucketAccessClass. @@ -43,11 +42,13 @@ type BucketAccessClassSpec struct { Parameters map[string]string `json:"parameters,omitempty"` // featureOptions can be used to adjust various COSI access provisioning behaviors. + // If specified, at least one option must be set. // +optional - FeatureOptions BucketAccessFeatureOptions `json:"featureOptions,omitempty"` + FeatureOptions BucketAccessFeatureOptions `json:"featureOptions,omitzero"` } // BucketAccessFeatureOptions defines various COSI access provisioning behaviors. +// +kubebuilder:validation:MinProperties=1 type BucketAccessFeatureOptions struct { // disallowedBucketAccessModes is a list of disallowed Read/Write access modes. A BucketAccess // using this class will not be allowed to request access to a BucketClaim with any access mode @@ -59,11 +60,10 @@ type BucketAccessFeatureOptions struct { // disallowMultiBucketAccess disables the ability for a BucketAccess to reference multiple // BucketClaims when set. // +optional - DisallowMultiBucketAccess bool `json:"disallowMultiBucketAccess,omitempty"` + DisallowMultiBucketAccess *bool `json:"disallowMultiBucketAccess,omitempty"` } // +kubebuilder:object:root=true -// +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster // +kubebuilder:metadata:annotations="api-approved.kubernetes.io=unapproved, experimental v1alpha2 changes" @@ -73,12 +73,12 @@ type BucketAccessClass struct { // metadata is a standard object metadata // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + metav1.ObjectMeta `json:"metadata,omitzero"` // spec defines the desired state of BucketAccessClass // +required // +kubebuilder:validation:XValidation:message="BucketAccessClass spec is immutable",rule="self == oldSelf" - Spec BucketAccessClassSpec `json:"spec"` + Spec BucketAccessClassSpec `json:"spec,omitzero"` } // +kubebuilder:object:root=true diff --git a/client/apis/objectstorage/v1alpha2/bucketclaim_types.go b/client/apis/objectstorage/v1alpha2/bucketclaim_types.go index 5410a4ed..a71d31a0 100644 --- a/client/apis/objectstorage/v1alpha2/bucketclaim_types.go +++ b/client/apis/objectstorage/v1alpha2/bucketclaim_types.go @@ -21,15 +21,17 @@ import ( ) // BucketClaimSpec defines the desired state of BucketClaim +// +kubebuilder:validation:MinProperties=1 // +kubebuilder:validation:ExactlyOneOf=bucketClassName;existingBucketName -// +kubebuilder:validation:XValidation:message="bucketClassName is immutable",rule="has(oldSelf.bucketClassName) == has(self.bucketClassName)" -// +kubebuilder:validation:XValidation:message="existingBucketName is immutable",rule="has(oldSelf.existingBucketName) == has(self.existingBucketName)" -// +kubebuilder:validation:XValidation:message="protocols list is immutable",rule="has(oldSelf.protocols) == has(self.protocols)" +// +kubebuilder:validation:XValidation:message="bucketClassName cannot be added or removed after creation",rule="has(oldSelf.bucketClassName) == has(self.bucketClassName)" +// +kubebuilder:validation:XValidation:message="existingBucketName cannot be added or removed after creation",rule="has(oldSelf.existingBucketName) == has(self.existingBucketName)" +// +kubebuilder:validation:XValidation:message="protocols list cannot be added or removed after creation",rule="has(oldSelf.protocols) == has(self.protocols)" type BucketClaimSpec struct { // bucketClassName selects the BucketClass for provisioning the BucketClaim. // This field is used only for BucketClaim dynamic provisioning. // If unspecified, existingBucketName must be specified for binding to an existing Bucket. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="bucketClassName is immutable",rule="self == oldSelf" BucketClassName string `json:"bucketClassName,omitempty"` @@ -37,6 +39,7 @@ type BucketClaimSpec struct { // protocols lists object storage protocols that the provisioned Bucket must support. // If specified, COSI will verify that each item is advertised as supported by the driver. // +optional + // +listType=set // +kubebuilder:validation:XValidation:message="protocols list is immutable",rule="self == oldSelf" Protocols []ObjectProtocol `json:"protocols,omitempty"` @@ -45,29 +48,32 @@ type BucketClaimSpec struct { // This field is used only for BucketClaim static provisioning. // If unspecified, bucketClassName must be specified for dynamically provisioning a new bucket. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="existingBucketName is immutable",rule="self == oldSelf" ExistingBucketName string `json:"existingBucketName,omitempty"` } // BucketClaimStatus defines the observed state of BucketClaim. -// +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="!has(oldSelf.boundBucketName) || has(self.boundBucketName)" -// +kubebuilder:validation:XValidation:message="protocols is immutable once set",rule="!has(oldSelf.protocols) || has(self.protocols)" +// +kubebuilder:validation:XValidation:message="boundBucketName cannot be removed once set",rule="!has(oldSelf.boundBucketName) || has(self.boundBucketName)" +// +kubebuilder:validation:XValidation:message="protocols cannot be removed once set",rule="!has(oldSelf.protocols) || has(self.protocols)" type BucketClaimStatus struct { // boundBucketName is the name of the Bucket this BucketClaim is bound to. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - // +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="oldSelf == '' || self == oldSelf" - BoundBucketName string `json:"boundBucketName"` + // +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="self == oldSelf" + BoundBucketName string `json:"boundBucketName,omitempty"` // readyToUse indicates that the bucket is ready for consumption by workloads. - ReadyToUse bool `json:"readyToUse"` + // +required + ReadyToUse *bool `json:"readyToUse,omitempty"` // protocols is the set of protocols the bound 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"` // error holds the most recent error message, with a timestamp. // This is cleared when provisioning is successful. @@ -89,11 +95,11 @@ type BucketClaim struct { // spec defines the desired state of BucketClaim // +required - Spec BucketClaimSpec `json:"spec"` + Spec BucketClaimSpec `json:"spec,omitzero"` // status defines the observed state of BucketClaim // +optional - Status BucketClaimStatus `json:"status,omitempty,omitzero"` + Status BucketClaimStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true diff --git a/client/apis/objectstorage/v1alpha2/bucketclass_types.go b/client/apis/objectstorage/v1alpha2/bucketclass_types.go index 6b1b376c..d9087216 100644 --- a/client/apis/objectstorage/v1alpha2/bucketclass_types.go +++ b/client/apis/objectstorage/v1alpha2/bucketclass_types.go @@ -25,7 +25,7 @@ type BucketClassSpec struct { // driverName is the name of the driver that fulfills requests for this BucketClass. // +required // +kubebuilder:validation:MinLength=1 - DriverName string `json:"driverName"` + DriverName string `json:"driverName,omitempty"` // deletionPolicy determines whether a Bucket created through the BucketClass should be deleted // when its bound BucketClaim is deleted. @@ -33,7 +33,7 @@ type BucketClassSpec struct { // - 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 BucketClass. @@ -59,7 +59,7 @@ type BucketClass struct { // spec defines the BucketClass. spec is entirely immutable. // +required // +kubebuilder:validation:XValidation:message="BucketClass spec is immutable",rule="self == oldSelf" - Spec BucketClassSpec `json:"spec"` + Spec BucketClassSpec `json:"spec,omitzero"` } // +kubebuilder:object:root=true diff --git a/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go b/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go index e4d63e2e..fb1fa36f 100644 --- a/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go +++ b/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go @@ -182,6 +182,11 @@ func (in *BucketAccessFeatureOptions) DeepCopyInto(out *BucketAccessFeatureOptio *out = make([]BucketAccessMode, len(*in)) copy(*out, *in) } + if in.DisallowMultiBucketAccess != nil { + in, out := &in.DisallowMultiBucketAccess, &out.DisallowMultiBucketAccess + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BucketAccessFeatureOptions. @@ -249,6 +254,11 @@ func (in *BucketAccessSpec) DeepCopy() *BucketAccessSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BucketAccessStatus) DeepCopyInto(out *BucketAccessStatus) { *out = *in + if in.ReadyToUse != nil { + in, out := &in.ReadyToUse, &out.ReadyToUse + *out = new(bool) + **out = **in + } if in.AccessedBuckets != nil { in, out := &in.AccessedBuckets, &out.AccessedBuckets *out = make([]AccessedBucket, len(*in)) @@ -390,6 +400,11 @@ func (in *BucketClaimSpec) DeepCopy() *BucketClaimSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BucketClaimStatus) DeepCopyInto(out *BucketClaimStatus) { *out = *in + if in.ReadyToUse != nil { + in, out := &in.ReadyToUse, &out.ReadyToUse + *out = new(bool) + **out = **in + } if in.Protocols != nil { in, out := &in.Protocols, &out.Protocols *out = make([]ObjectProtocol, len(*in)) @@ -555,6 +570,11 @@ func (in *BucketSpec) DeepCopy() *BucketSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BucketStatus) DeepCopyInto(out *BucketStatus) { *out = *in + if in.ReadyToUse != nil { + in, out := &in.ReadyToUse, &out.ReadyToUse + *out = new(bool) + **out = **in + } if in.Protocols != nil { in, out := &in.Protocols, &out.Protocols *out = make([]ObjectProtocol, len(*in)) diff --git a/client/config/crd/objectstorage.k8s.io_bucketaccessclasses.yaml b/client/config/crd/objectstorage.k8s.io_bucketaccessclasses.yaml index 714896f2..14bebdc3 100644 --- a/client/config/crd/objectstorage.k8s.io_bucketaccessclasses.yaml +++ b/client/config/crd/objectstorage.k8s.io_bucketaccessclasses.yaml @@ -41,14 +41,6 @@ spec: description: spec defines the desired state of BucketAccessClass properties: authenticationType: - allOf: - - enum: - - "" - - Key - - ServiceAccount - - enum: - - Key - - ServiceAccount description: |- authenticationType specifies which authentication mechanism is used bucket access. Possible values: @@ -56,6 +48,9 @@ spec: authenticate to the backend object store. - ServiceAccount: The driver should configure the system such that Pods using the given ServiceAccount authenticate to the backend object store automatically. + enum: + - Key + - ServiceAccount type: string driverName: description: driverName is the name of the driver that fulfills requests @@ -63,8 +58,10 @@ spec: minLength: 1 type: string featureOptions: - description: featureOptions can be used to adjust various COSI access - provisioning behaviors. + description: |- + featureOptions can be used to adjust various COSI access provisioning behaviors. + If specified, at least one option must be set. + minProperties: 1 properties: disallowMultiBucketAccess: description: |- @@ -106,5 +103,3 @@ spec: type: object served: true storage: true - subresources: - status: {} diff --git a/client/config/crd/objectstorage.k8s.io_bucketaccesses.yaml b/client/config/crd/objectstorage.k8s.io_bucketaccesses.yaml index 4d28dc3c..5db22b55 100644 --- a/client/config/crd/objectstorage.k8s.io_bucketaccesses.yaml +++ b/client/config/crd/objectstorage.k8s.io_bucketaccesses.yaml @@ -121,6 +121,7 @@ spec: - ServiceAccount: This field is required. The driver should configure the system so that Pods using the ServiceAccount authenticate to the object storage backend automatically. maxLength: 253 + minLength: 1 type: string x-kubernetes-validations: - message: serviceAccountName is immutable @@ -131,7 +132,7 @@ spec: - protocol type: object x-kubernetes-validations: - - message: serviceAccountName is immutable + - message: serviceAccountName cannot be added or removed after creation rule: has(oldSelf.serviceAccountName) == has(self.serviceAccountName) status: description: status defines the observed state of BucketAccess @@ -161,41 +162,43 @@ spec: - bucketClaimName - bucketName type: object + minItems: 1 type: array x-kubernetes-list-map-keys: - bucketName x-kubernetes-list-type: map x-kubernetes-validations: - message: accessedBuckets is immutable once set - rule: oldSelf.size() == 0 || self == oldSelf + rule: self == oldSelf accountID: description: |- accountID is the unique identifier for the backend access known to the driver. This field is populated by the COSI Sidecar once access has been successfully granted. + minLength: 1 type: string x-kubernetes-validations: - message: accountId is immutable once set - rule: oldSelf == '' || self == oldSelf + rule: self == oldSelf authenticationType: description: |- authenticationType holds a copy of the BucketAccessClass authentication type from the time of BucketAccess provisioning. This field is populated by the COSI Controller. enum: - - "" - Key - ServiceAccount type: string x-kubernetes-validations: - message: authenticationType is immutable once set - rule: oldSelf == '' || self == oldSelf + rule: self == oldSelf driverName: description: |- driverName holds a copy of the BucketAccessClass driver name from the time of BucketAccess provisioning. This field is populated by the COSI Controller. + minLength: 1 type: string x-kubernetes-validations: - message: driverName is immutable once set - rule: oldSelf == '' || self == oldSelf + rule: self == oldSelf error: description: |- error holds the most recent error message, with a timestamp. @@ -220,7 +223,7 @@ spec: type: object x-kubernetes-validations: - message: accessedBuckets is immutable once set - rule: oldSelf.size() == 0 || self == oldSelf + rule: self == oldSelf readyToUse: description: readyToUse indicates that the BucketAccess is ready for consumption by workloads. @@ -229,15 +232,15 @@ spec: - readyToUse type: object x-kubernetes-validations: - - message: accountID is immutable once set + - message: accountID cannot be removed once set rule: '!has(oldSelf.accountID) || has(self.accountID)' - - message: accessedBuckets is immutable once set + - message: accessedBuckets cannot be removed once set rule: '!has(oldSelf.accessedBuckets) || has(self.accessedBuckets)' - - message: driverName is immutable once set + - message: driverName cannot be removed once set rule: '!has(oldSelf.driverName) || has(self.driverName)' - - message: authenticationType is immutable once set + - message: authenticationType cannot be removed once set rule: '!has(oldSelf.authenticationType) || has(self.authenticationType)' - - message: parameters is immutable once set + - message: parameters cannot be removed once set rule: '!has(oldSelf.parameters) || has(self.parameters)' required: - spec diff --git a/client/config/crd/objectstorage.k8s.io_bucketclaims.yaml b/client/config/crd/objectstorage.k8s.io_bucketclaims.yaml index 2695b208..129920e6 100644 --- a/client/config/crd/objectstorage.k8s.io_bucketclaims.yaml +++ b/client/config/crd/objectstorage.k8s.io_bucketclaims.yaml @@ -39,6 +39,7 @@ spec: type: object spec: description: spec defines the desired state of BucketClaim + minProperties: 1 properties: bucketClassName: description: |- @@ -46,6 +47,7 @@ spec: This field is used only for BucketClaim dynamic provisioning. If unspecified, existingBucketName must be specified for binding to an existing Bucket. maxLength: 253 + minLength: 1 type: string x-kubernetes-validations: - message: bucketClassName is immutable @@ -57,6 +59,7 @@ spec: This field is used only for BucketClaim static provisioning. If unspecified, bucketClassName must be specified for dynamically provisioning a new bucket. maxLength: 253 + minLength: 1 type: string x-kubernetes-validations: - message: existingBucketName is immutable @@ -73,16 +76,17 @@ spec: - GCS type: string type: array + x-kubernetes-list-type: set x-kubernetes-validations: - message: protocols list is immutable rule: self == oldSelf type: object x-kubernetes-validations: - - message: bucketClassName is immutable + - message: bucketClassName cannot be added or removed after creation rule: has(oldSelf.bucketClassName) == has(self.bucketClassName) - - message: existingBucketName is immutable + - message: existingBucketName cannot be added or removed after creation rule: has(oldSelf.existingBucketName) == has(self.existingBucketName) - - message: protocols list is immutable + - message: protocols list cannot be added or removed after creation rule: has(oldSelf.protocols) == has(self.protocols) - message: exactly one of the fields in [bucketClassName existingBucketName] must be set @@ -95,10 +99,11 @@ spec: description: boundBucketName is the name of the Bucket this BucketClaim is bound to. maxLength: 253 + minLength: 1 type: string x-kubernetes-validations: - message: boundBucketName is immutable once set - rule: oldSelf == '' || self == oldSelf + rule: self == oldSelf error: description: |- error holds the most recent error message, with a timestamp. @@ -135,9 +140,9 @@ spec: - readyToUse type: object x-kubernetes-validations: - - message: boundBucketName is immutable once set + - message: boundBucketName cannot be removed once set rule: '!has(oldSelf.boundBucketName) || has(self.boundBucketName)' - - message: protocols is immutable once set + - message: protocols cannot be removed once set rule: '!has(oldSelf.protocols) || has(self.protocols)' required: - spec diff --git a/client/config/crd/objectstorage.k8s.io_buckets.yaml b/client/config/crd/objectstorage.k8s.io_buckets.yaml index 3252b373..ea52a80c 100644 --- a/client/config/crd/objectstorage.k8s.io_buckets.yaml +++ b/client/config/crd/objectstorage.k8s.io_buckets.yaml @@ -55,16 +55,14 @@ spec: - message: name is immutable rule: self == oldSelf namespace: - description: |- - 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'. + description: namespace is the namespace of the BucketClaim being + referenced. maxLength: 253 - minLength: 0 + minLength: 1 type: string x-kubernetes-validations: - message: namespace is immutable - rule: (oldSelf == '' && self == 'default') || self == oldSelf + rule: self == oldSelf uid: description: uid is the UID of the BucketClaim being referenced. type: string @@ -73,11 +71,12 @@ spec: rule: oldSelf == '' || self == oldSelf required: - name + - namespace type: object x-kubernetes-validations: - - message: namespace is immutable once set + - message: namespace cannot be removed once set rule: '!has(oldSelf.namespace) || has(self.namespace)' - - message: uid is immutable once set + - message: uid cannot be removed once set rule: '!has(oldSelf.uid) || has(self.uid)' deletionPolicy: description: |- @@ -104,6 +103,7 @@ spec: 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. + minLength: 1 type: string x-kubernetes-validations: - message: existingBucketID is immutable @@ -140,11 +140,11 @@ spec: - driverName type: object x-kubernetes-validations: - - message: parameters map is immutable + - message: parameters map cannot be added or removed after creation rule: has(oldSelf.parameters) == has(self.parameters) - - message: protocols list is immutable + - message: protocols list cannot be added or removed after creation rule: has(oldSelf.protocols) == has(self.protocols) - - message: existingBucketID is immutable + - message: existingBucketID cannot be added or removed after creation rule: has(oldSelf.existingBucketID) == has(self.existingBucketID) status: description: status defines the observed state of Bucket @@ -152,21 +152,23 @@ spec: bucketID: description: bucketID is the unique identifier for the backend bucket known to the driver. + minLength: 1 type: string x-kubernetes-validations: - message: boundBucketName is immutable once set - rule: oldSelf == '' || self == oldSelf + rule: self == oldSelf bucketInfo: additionalProperties: type: string description: |- - BucketInfo reported by the driver, rendered in the COSI__ 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__ format used for the BucketAccess Secret. + e.g., COSI_S3_ENDPOINT, COSI_AZURE_STORAGE_ACCOUNT. This should not contain any sensitive information. type: object error: description: |- - 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. properties: message: @@ -200,9 +202,9 @@ spec: - readyToUse type: object x-kubernetes-validations: - - message: bucketID is immutable once set + - message: bucketID cannot be removed once set rule: '!has(oldSelf.bucketID) || has(self.bucketID)' - - message: protocols is immutable once set + - message: protocols cannot be removed once set rule: '!has(oldSelf.protocols) || has(self.protocols)' required: - spec diff --git a/controller/internal/reconciler/bucketaccess.go b/controller/internal/reconciler/bucketaccess.go index 83bf09cf..8e0f794b 100644 --- a/controller/internal/reconciler/bucketaccess.go +++ b/controller/internal/reconciler/bucketaccess.go @@ -27,6 +27,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -78,6 +79,9 @@ func (r *BucketAccessReconciler) Reconcile(ctx context.Context, req ctrl.Request err = fmt.Errorf("COSI Controller error: %w", err) // Record any error as a timestamped error in the status. + if access.Status.ReadyToUse == nil { + access.Status.ReadyToUse = ptr.To(false) + } access.Status.Error = cosiapi.NewTimestampedError(time.Now(), err.Error()) if updErr := r.Status().Update(ctx, access); updErr != nil { logger.Error(err, "failed to update BucketAccess status after reconcile error", "updateError", updErr) @@ -224,6 +228,9 @@ func (r *BucketAccessReconciler) reconcile( } // After this status update, resource management should be handed off to the Sidecar + if access.Status.ReadyToUse == nil { + access.Status.ReadyToUse = ptr.To(false) + } access.Status.AccessedBuckets = accessedBuckets access.Status.DriverName = class.Spec.DriverName access.Status.AuthenticationType = class.Spec.AuthenticationType @@ -363,7 +370,7 @@ func validateAccessAgainstClass( errs = append(errs, fmt.Errorf("serviceAccountName must be specified")) } - if class.FeatureOptions.DisallowMultiBucketAccess && len(access.BucketClaims) > 1 { + if ptr.Deref(class.FeatureOptions.DisallowMultiBucketAccess, false) && len(access.BucketClaims) > 1 { errs = append(errs, fmt.Errorf("multi-bucket access is disallowed")) } diff --git a/controller/internal/reconciler/bucketaccess_test.go b/controller/internal/reconciler/bucketaccess_test.go index ab18d561..1e8ff808 100644 --- a/controller/internal/reconciler/bucketaccess_test.go +++ b/controller/internal/reconciler/bucketaccess_test.go @@ -27,6 +27,7 @@ import ( meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2" "sigs.k8s.io/container-object-storage-interface/internal/handoff" ctrl "sigs.k8s.io/controller-runtime" @@ -168,7 +169,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) assert.Nil(t, status.Error) assert.Equal(t, "", status.AccountID) assert.Equal(t, @@ -255,7 +256,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.NotContains(t, *status.Error.Message, "readwrite-bucket") @@ -303,7 +304,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.Contains(t, *status.Error.Message, "readwrite-bucket") @@ -359,7 +360,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.Contains(t, *status.Error.Message, @@ -413,7 +414,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.Contains(t, *status.Error.Message, "readonly-bucket") @@ -463,7 +464,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.Contains(t, *status.Error.Message, "s3-class") @@ -491,7 +492,7 @@ func TestBucketAccessReconcile(t *testing.T) { t.Run("dynamic provisioning, bucketaccessclass disallows multi-bucket access", func(t *testing.T) { class := baseClass.DeepCopy() - class.Spec.FeatureOptions.DisallowMultiBucketAccess = true + class.Spec.FeatureOptions.DisallowMultiBucketAccess = ptr.To(true) c := newClient( baseAccess.DeepCopy(), @@ -515,7 +516,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.Contains(t, *status.Error.Message, "multi-bucket access") @@ -548,7 +549,7 @@ func TestBucketAccessReconcile(t *testing.T) { } class := baseClass.DeepCopy() - class.Spec.FeatureOptions.DisallowMultiBucketAccess = true + class.Spec.FeatureOptions.DisallowMultiBucketAccess = ptr.To(true) c := newClient( access, @@ -571,7 +572,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) assert.Nil(t, status.Error) assert.Equal(t, "", status.AccountID) assert.Equal(t, @@ -638,7 +639,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.Contains(t, *status.Error.Message, "ReadWrite") @@ -695,7 +696,7 @@ func TestBucketAccessReconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, access.GetFinalizers(), cosiapi.ProtectionFinalizer) status := access.Status - assert.False(t, status.ReadyToUse) + assert.False(t, *status.ReadyToUse) require.NotNil(t, status.Error) assert.NotNil(t, status.Error.Time) assert.Contains(t, *status.Error.Message, "readwrite-bucket") @@ -756,7 +757,7 @@ func Test_validateAccessAgainstClass(t *testing.T) { &cosiapi.BucketAccessClassSpec{ AuthenticationType: cosiapi.BucketAccessAuthenticationTypeKey, FeatureOptions: cosiapi.BucketAccessFeatureOptions{ - DisallowMultiBucketAccess: true, + DisallowMultiBucketAccess: ptr.To(true), }, }, &cosiapi.BucketAccessSpec{ @@ -849,7 +850,7 @@ func Test_validateAccessAgainstClass(t *testing.T) { &cosiapi.BucketAccessClassSpec{ AuthenticationType: cosiapi.BucketAccessAuthenticationTypeServiceAccount, FeatureOptions: cosiapi.BucketAccessFeatureOptions{ - DisallowMultiBucketAccess: true, + DisallowMultiBucketAccess: ptr.To(true), }, }, &cosiapi.BucketAccessSpec{ diff --git a/controller/internal/reconciler/bucketclaim.go b/controller/internal/reconciler/bucketclaim.go index 1c6b8a25..17f3a757 100644 --- a/controller/internal/reconciler/bucketclaim.go +++ b/controller/internal/reconciler/bucketclaim.go @@ -27,6 +27,7 @@ import ( meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -68,6 +69,9 @@ func (r *BucketClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) err := r.reconcile(ctx, logger, claim) if err != nil { // Record any error as a timestamped error in the status. + if claim.Status.ReadyToUse == nil { + claim.Status.ReadyToUse = ptr.To(false) + } claim.Status.Error = cosiapi.NewTimestampedError(time.Now(), err.Error()) if updErr := r.Status().Update(ctx, claim); updErr != nil { logger.Error(err, "failed to update BucketClaim status after reconcile error", "updateError", updErr) @@ -84,6 +88,9 @@ func (r *BucketClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) // On success, clear any errors in the status. if claim.Status.Error != nil && !claim.DeletionTimestamp.IsZero() { + if claim.Status.ReadyToUse == nil { + claim.Status.ReadyToUse = ptr.To(false) + } claim.Status.Error = nil if err := r.Status().Update(ctx, claim); err != nil { logger.Error(err, "failed to update BucketClaim status after reconcile success") @@ -156,6 +163,9 @@ func (r *BucketClaimReconciler) reconcile(ctx context.Context, logger logr.Logge if claim.Status.BoundBucketName == "" { logger.Info("binding BucketClaim to Bucket") + if claim.Status.ReadyToUse == nil { + claim.Status.ReadyToUse = ptr.To(false) + } claim.Status.BoundBucketName = bucketName if err := r.Status().Update(ctx, claim); err != nil { logger.Error(err, "failed to bind BucketClaim to Bucket") diff --git a/controller/internal/reconciler/bucketclaim_test.go b/controller/internal/reconciler/bucketclaim_test.go index 777017fe..0884b857 100644 --- a/controller/internal/reconciler/bucketclaim_test.go +++ b/controller/internal/reconciler/bucketclaim_test.go @@ -289,7 +289,7 @@ func TestBucketClaimReconcile(t *testing.T) { assert.Contains(t, claim.GetFinalizers(), cosiapi.ProtectionFinalizer) status := claim.Status assert.Equal(t, "bc-qwerty", status.BoundBucketName) - assert.Equal(t, false, status.ReadyToUse) + assert.Equal(t, false, *status.ReadyToUse) assert.Empty(t, status.Protocols) assert.Nil(t, status.Error) @@ -340,7 +340,7 @@ func TestBucketClaimReconcile(t *testing.T) { assert.Contains(t, claim.GetFinalizers(), cosiapi.ProtectionFinalizer) status := claim.Status assert.Equal(t, "bc-qwerty", status.BoundBucketName) - assert.Equal(t, false, status.ReadyToUse) + assert.Equal(t, false, *status.ReadyToUse) assert.Empty(t, status.Protocols) serr := status.Error require.NotNil(t, serr) diff --git a/docs/src/api/out.md b/docs/src/api/out.md index d20ba371..e1c5ff6c 100644 --- a/docs/src/api/out.md +++ b/docs/src/api/out.md @@ -91,7 +91,7 @@ BucketAccessAuthenticationType specifies what authentication mechanism is used f bucket access. _Validation:_ -- Enum: [ Key ServiceAccount] +- Enum: [Key ServiceAccount] _Appears in:_ - [BucketAccessClassSpec](#bucketaccessclassspec) @@ -156,7 +156,7 @@ _Appears in:_ | `driverName` _string_ | driverName is the name of the driver that fulfills requests for this BucketAccessClass. | | MinLength: 1
| | `authenticationType` _[BucketAccessAuthenticationType](#bucketaccessauthenticationtype)_ | authenticationType specifies which authentication mechanism is used bucket access.
Possible values:
- Key: The driver should generate a protocol-appropriate access key that clients can use to
authenticate to the backend object store.
- ServiceAccount: The driver should configure the system such that Pods using the given
ServiceAccount authenticate to the backend object store automatically. | | Enum: [Key ServiceAccount]
| | `parameters` _object (keys:string, values:string)_ | parameters is an opaque map of driver-specific configuration items passed to the driver that
fulfills requests for this BucketAccessClass. | | | -| `featureOptions` _[BucketAccessFeatureOptions](#bucketaccessfeatureoptions)_ | featureOptions can be used to adjust various COSI access provisioning behaviors. | | | +| `featureOptions` _[BucketAccessFeatureOptions](#bucketaccessfeatureoptions)_ | featureOptions can be used to adjust various COSI access provisioning behaviors.
If specified, at least one option must be set. | | MinProperties: 1
| #### BucketAccessFeatureOptions @@ -165,7 +165,8 @@ _Appears in:_ BucketAccessFeatureOptions defines various COSI access provisioning behaviors. - +_Validation:_ +- MinProperties: 1 _Appears in:_ - [BucketAccessClassSpec](#bucketaccessclassspec) @@ -232,7 +233,7 @@ _Appears in:_ | `bucketClaims` _[BucketClaimAccess](#bucketclaimaccess) array_ | bucketClaims is a list of BucketClaims the provisioned access must have permissions for,
along with per-BucketClaim access parameters and system output definitions.
At least one BucketClaim must be referenced.
Multiple references to the same BucketClaim are not permitted. | | MinItems: 1
| | `bucketAccessClassName` _string_ | bucketAccessClassName selects the BucketAccessClass for provisioning the access. | | MaxLength: 253
MinLength: 1
| | `protocol` _[ObjectProtocol](#objectprotocol)_ | protocol is the object storage protocol that the provisioned access must use. | | Enum: [S3 Azure GCS]
| -| `serviceAccountName` _string_ | serviceAccountName is the name of the Kubernetes ServiceAccount that user application Pods
intend to use for access to referenced BucketClaims.
This has different behavior based on the BucketAccessClass's defined AuthenticationType:
- Key: This field is ignored.
- ServiceAccount: This field is required. The driver should configure the system so that Pods
using the ServiceAccount authenticate to the object storage backend automatically. | | MaxLength: 253
| +| `serviceAccountName` _string_ | serviceAccountName is the name of the Kubernetes ServiceAccount that user application Pods
intend to use for access to referenced BucketClaims.
This has different behavior based on the BucketAccessClass's defined AuthenticationType:
- Key: This field is ignored.
- ServiceAccount: This field is required. The driver should configure the system so that Pods
using the ServiceAccount authenticate to the object storage backend automatically. | | MaxLength: 253
MinLength: 1
| #### BucketAccessStatus @@ -249,10 +250,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `readyToUse` _boolean_ | readyToUse indicates that the BucketAccess is ready for consumption by workloads. | | | -| `accountID` _string_ | accountID is the unique identifier for the backend access known to the driver.
This field is populated by the COSI Sidecar once access has been successfully granted. | | | -| `accessedBuckets` _[AccessedBucket](#accessedbucket) array_ | accessedBuckets is a list of Buckets the provisioned access must have permissions for, along
with per-Bucket access options. This field is populated by the COSI Controller based on the
referenced BucketClaims in the spec. | | | -| `driverName` _string_ | driverName holds a copy of the BucketAccessClass driver name from the time of BucketAccess
provisioning. This field is populated by the COSI Controller. | | | -| `authenticationType` _[BucketAccessAuthenticationType](#bucketaccessauthenticationtype)_ | authenticationType holds a copy of the BucketAccessClass authentication type from the time of
BucketAccess provisioning. This field is populated by the COSI Controller. | | Enum: [ Key ServiceAccount]
| +| `accountID` _string_ | accountID is the unique identifier for the backend access known to the driver.
This field is populated by the COSI Sidecar once access has been successfully granted. | | MinLength: 1
| +| `accessedBuckets` _[AccessedBucket](#accessedbucket) array_ | accessedBuckets is a list of Buckets the provisioned access must have permissions for, along
with per-Bucket access options. This field is populated by the COSI Controller based on the
referenced BucketClaims in the spec. | | MinItems: 1
| +| `driverName` _string_ | driverName holds a copy of the BucketAccessClass driver name from the time of BucketAccess
provisioning. This field is populated by the COSI Controller. | | MinLength: 1
| +| `authenticationType` _[BucketAccessAuthenticationType](#bucketaccessauthenticationtype)_ | authenticationType holds a copy of the BucketAccessClass authentication type from the time of
BucketAccess provisioning. This field is populated by the COSI Controller. | | Enum: [Key ServiceAccount]
| | `parameters` _object (keys:string, values:string)_ | parameters holds a copy of the BucketAccessClass parameters from the time of BucketAccess
provisioning. This field is populated by the COSI Controller. | | | | `error` _[TimestampedError](#timestampederror)_ | error holds the most recent error message, with a timestamp.
This is cleared when provisioning is successful. | | | @@ -275,7 +276,7 @@ _Appears in:_ | `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | | `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.34/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[BucketClaimSpec](#bucketclaimspec)_ | spec defines the desired state of BucketClaim | | | +| `spec` _[BucketClaimSpec](#bucketclaimspec)_ | spec defines the desired state of BucketClaim | | MinProperties: 1
| | `status` _[BucketClaimStatus](#bucketclaimstatus)_ | status defines the observed state of BucketClaim | | | @@ -333,7 +334,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | name is the name of the BucketClaim being referenced. | | MaxLength: 253
MinLength: 1
| -| `namespace` _string_ | 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'. | | MaxLength: 253
MinLength: 0
| +| `namespace` _string_ | namespace is the namespace of the BucketClaim being referenced. | | MaxLength: 253
MinLength: 1
| | `uid` _[UID](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.34/#uid-types-pkg)_ | uid is the UID of the BucketClaim being referenced. | | | @@ -343,16 +344,17 @@ _Appears in:_ BucketClaimSpec defines the desired state of BucketClaim - +_Validation:_ +- MinProperties: 1 _Appears in:_ - [BucketClaim](#bucketclaim) | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `bucketClassName` _string_ | bucketClassName selects the BucketClass for provisioning the BucketClaim.
This field is used only for BucketClaim dynamic provisioning.
If unspecified, existingBucketName must be specified for binding to an existing Bucket. | | MaxLength: 253
| +| `bucketClassName` _string_ | bucketClassName selects the BucketClass for provisioning the BucketClaim.
This field is used only for BucketClaim dynamic provisioning.
If unspecified, existingBucketName must be specified for binding to an existing Bucket. | | MaxLength: 253
MinLength: 1
| | `protocols` _[ObjectProtocol](#objectprotocol) array_ | protocols lists object storage protocols that the provisioned Bucket must support.
If specified, COSI will verify that each item is advertised as supported by the driver. | | Enum: [S3 Azure GCS]
| -| `existingBucketName` _string_ | existingBucketName selects the name of an existing Bucket resource that this BucketClaim
should bind to.
This field is used only for BucketClaim static provisioning.
If unspecified, bucketClassName must be specified for dynamically provisioning a new bucket. | | MaxLength: 253
| +| `existingBucketName` _string_ | existingBucketName selects the name of an existing Bucket resource that this BucketClaim
should bind to.
This field is used only for BucketClaim static provisioning.
If unspecified, bucketClassName must be specified for dynamically provisioning a new bucket. | | MaxLength: 253
MinLength: 1
| #### BucketClaimStatus @@ -368,7 +370,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `boundBucketName` _string_ | boundBucketName is the name of the Bucket this BucketClaim is bound to. | | MaxLength: 253
| +| `boundBucketName` _string_ | boundBucketName is the name of the Bucket this BucketClaim is bound to. | | MaxLength: 253
MinLength: 1
| | `readyToUse` _boolean_ | readyToUse indicates that the bucket is ready for consumption by workloads. | | | | `protocols` _[ObjectProtocol](#objectprotocol) array_ | protocols is the set of protocols the bound Bucket reports to support. BucketAccesses can
request access to this BucketClaim using any of the protocols reported here. | | Enum: [S3 Azure GCS]
| | `error` _[TimestampedError](#timestampederror)_ | error holds the most recent error message, with a timestamp.
This is cleared when provisioning is successful. | | | @@ -495,7 +497,7 @@ _Appears in:_ | `parameters` _object (keys:string, values:string)_ | parameters is an opaque map of driver-specific configuration items passed to the driver that
fulfills requests for this Bucket. | | | | `protocols` _[ObjectProtocol](#objectprotocol) array_ | protocols lists object store protocols that the provisioned Bucket must support.
If specified, COSI will verify that each item is advertised as supported by the driver. | | Enum: [S3 Azure GCS]
| | `bucketClaim` _[BucketClaimReference](#bucketclaimreference)_ | bucketClaim references the BucketClaim that resulted in the creation of this Bucket.
For statically-provisioned buckets, set the namespace and name of the BucketClaim that is
allowed to bind to this Bucket. | | | -| `existingBucketID` _string_ | 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. | | | +| `existingBucketID` _string_ | 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. | | MinLength: 1
| #### BucketStatus @@ -512,10 +514,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `readyToUse` _boolean_ | readyToUse indicates that the bucket is ready for consumption by workloads. | | | -| `bucketID` _string_ | bucketID is the unique identifier for the backend bucket known to the driver. | | | +| `bucketID` _string_ | bucketID is the unique identifier for the backend bucket known to the driver. | | MinLength: 1
| | `protocols` _[ObjectProtocol](#objectprotocol) array_ | 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. | | Enum: [S3 Azure GCS]
| -| `bucketInfo` _object (keys:string, values:string)_ | BucketInfo reported by the driver, rendered in the COSI__ format used for the
BucketAccess Secret. e.g., COSI_S3_ENDPOINT, COSI_AZURE_STORAGE_ACCOUNT.
This should not contain any sensitive information. | | | -| `error` _[TimestampedError](#timestampederror)_ | Error holds the most recent error message, with a timestamp.
This is cleared when provisioning is successful. | | | +| `bucketInfo` _object (keys:string, values:string)_ | bucketInfo contains info about the bucket reported by the driver, rendered in the same
COSI__ format used for the BucketAccess Secret.
e.g., COSI_S3_ENDPOINT, COSI_AZURE_STORAGE_ACCOUNT.
This should not contain any sensitive information. | | | +| `error` _[TimestampedError](#timestampederror)_ | error holds the most recent error message, with a timestamp.
This is cleared when provisioning is successful. | | | #### CosiEnvVar diff --git a/go.mod b/go.mod index da331106..bc330391 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( google.golang.org/grpc v1.75.1 k8s.io/apimachinery v0.34.2 k8s.io/client-go v0.34.2 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d sigs.k8s.io/container-object-storage-interface/client v0.0.0-20250925174816-5fce7c365e9c sigs.k8s.io/container-object-storage-interface/proto v0.0.0-00010101000000-000000000000 sigs.k8s.io/controller-runtime v0.22.1 @@ -104,7 +105,6 @@ require ( k8s.io/component-base v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect 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/randfill v1.0.0 // indirect diff --git a/sidecar/internal/reconciler/bucket.go b/sidecar/internal/reconciler/bucket.go index 253f9a15..63acad5a 100644 --- a/sidecar/internal/reconciler/bucket.go +++ b/sidecar/internal/reconciler/bucket.go @@ -27,6 +27,7 @@ import ( "google.golang.org/grpc/status" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -70,6 +71,9 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr err := r.reconcile(ctx, logger, bucket) if err != nil { // Record any error as a timestamped error in the status. + if bucket.Status.ReadyToUse == nil { + bucket.Status.ReadyToUse = ptr.To(false) + } bucket.Status.Error = cosiapi.NewTimestampedError(time.Now(), err.Error()) if updErr := r.Status().Update(ctx, bucket); updErr != nil { logger.Error(err, "failed to update Bucket status after reconcile error", "updateError", updErr) @@ -86,6 +90,9 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr // On success, clear any errors in the status. if bucket.Status.Error != nil && !bucket.DeletionTimestamp.IsZero() { + if bucket.Status.ReadyToUse == nil { + bucket.Status.ReadyToUse = ptr.To(false) + } bucket.Status.Error = nil if err := r.Status().Update(ctx, bucket); err != nil { logger.Error(err, "failed to update BucketClaim status after reconcile success") @@ -199,7 +206,7 @@ func (r *BucketReconciler) reconcile(ctx context.Context, logger logr.Logger, bu } bucket.Status = cosiapi.BucketStatus{ - ReadyToUse: true, + ReadyToUse: ptr.To(true), BucketID: provisionedBucket.bucketId, Protocols: provisionedBucket.supportedProtos, BucketInfo: provisionedBucket.allProtoBucketInfo, diff --git a/sidecar/internal/reconciler/bucket_test.go b/sidecar/internal/reconciler/bucket_test.go index 0449b708..2017563f 100644 --- a/sidecar/internal/reconciler/bucket_test.go +++ b/sidecar/internal/reconciler/bucket_test.go @@ -159,7 +159,7 @@ func TestBucketReconciler_Reconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, bucket.GetFinalizers(), cosiapi.ProtectionFinalizer) assert.Equal(t, b.Spec, bucket.Spec) // spec should not change - assert.True(t, bucket.Status.ReadyToUse) + assert.True(t, *bucket.Status.ReadyToUse) assert.Equal(t, "cosi-bc-qwerty", bucket.Status.BucketID) assert.Equal(t, []cosiapi.ObjectProtocol{cosiapi.ObjectProtocolS3}, @@ -410,7 +410,7 @@ func TestBucketReconciler_Reconcile(t *testing.T) { require.NoError(t, err) assert.Equal(t, b.Finalizers, bucket.Finalizers) assert.Equal(t, b.Spec, bucket.Spec) - assert.False(t, bucket.Status.ReadyToUse) // assume this means no other statuses were set + assert.False(t, *bucket.Status.ReadyToUse) // assume this means no other statuses were set serr := bucket.Status.Error require.NotNil(t, serr) assert.NotNil(t, serr.Time) @@ -476,7 +476,7 @@ func TestBucketReconciler_Reconcile(t *testing.T) { require.NoError(t, err) assert.Contains(t, bucket.GetFinalizers(), cosiapi.ProtectionFinalizer) assert.Equal(t, b.Spec, bucket.Spec) - assert.False(t, bucket.Status.ReadyToUse) // assume this means no other statuses were set + assert.False(t, *bucket.Status.ReadyToUse) // assume this means no other statuses were set serr := bucket.Status.Error require.NotNil(t, serr) assert.NotNil(t, serr.Time) diff --git a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucket_types.go b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucket_types.go index 2817ab8f..de61d68a 100644 --- a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucket_types.go +++ b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucket_types.go @@ -37,15 +37,15 @@ 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. @@ -53,7 +53,7 @@ type BucketSpec struct { // - 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. @@ -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__ 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__ 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"` @@ -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"` } // +kubebuilder:object:root=true diff --git a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccess_types.go b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccess_types.go index 9a38c277..2cde1de1 100644 --- a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccess_types.go +++ b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccess_types.go @@ -23,7 +23,7 @@ import ( // BucketAccessAuthenticationType specifies what authentication mechanism is used for provisioning // bucket access. // +enum -// +kubebuilder:validation:Enum:="";Key;ServiceAccount +// +kubebuilder:validation:Enum:=Key;ServiceAccount type BucketAccessAuthenticationType string const ( @@ -53,7 +53,7 @@ const ( ) // BucketAccessSpec defines the desired state of BucketAccess -// +kubebuilder:validation:XValidation:message="serviceAccountName is immutable",rule="has(oldSelf.serviceAccountName) == has(self.serviceAccountName)" +// +kubebuilder:validation:XValidation:message="serviceAccountName cannot be added or removed after creation",rule="has(oldSelf.serviceAccountName) == has(self.serviceAccountName)" type BucketAccessSpec struct { // bucketClaims is a list of BucketClaims the provisioned access must have permissions for, // along with per-BucketClaim access parameters and system output definitions. @@ -64,19 +64,19 @@ type BucketAccessSpec struct { // +listMapKey=bucketClaimName // +kubebuilder:validation:MinItems=1 // +kubebuilder:validation:XValidation:message="bucketClaims list is immutable",rule="self == oldSelf" - BucketClaims []BucketClaimAccess `json:"bucketClaims"` + BucketClaims []BucketClaimAccess `json:"bucketClaims,omitempty"` // bucketAccessClassName selects the BucketAccessClass for provisioning the access. // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="bucketAccessClassName is immutable",rule="self == oldSelf" - BucketAccessClassName string `json:"bucketAccessClassName"` + BucketAccessClassName string `json:"bucketAccessClassName,omitempty"` // protocol is the object storage protocol that the provisioned access must use. // +required // +kubebuilder:validation:XValidation:message="protocol is immutable",rule="self == oldSelf" - Protocol ObjectProtocol `json:"protocol"` + Protocol ObjectProtocol `json:"protocol,omitempty"` // serviceAccountName is the name of the Kubernetes ServiceAccount that user application Pods // intend to use for access to referenced BucketClaims. @@ -85,26 +85,29 @@ type BucketAccessSpec struct { // - ServiceAccount: This field is required. The driver should configure the system so that Pods // using the ServiceAccount authenticate to the object storage backend automatically. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="serviceAccountName is immutable",rule="self == oldSelf" ServiceAccountName string `json:"serviceAccountName,omitempty"` } // BucketAccessStatus defines the observed state of BucketAccess. -// +kubebuilder:validation:XValidation:message="accountID is immutable once set",rule="!has(oldSelf.accountID) || has(self.accountID)" -// +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="!has(oldSelf.accessedBuckets) || has(self.accessedBuckets)" -// +kubebuilder:validation:XValidation:message="driverName is immutable once set",rule="!has(oldSelf.driverName) || has(self.driverName)" -// +kubebuilder:validation:XValidation:message="authenticationType is immutable once set",rule="!has(oldSelf.authenticationType) || has(self.authenticationType)" -// +kubebuilder:validation:XValidation:message="parameters is immutable once set",rule="!has(oldSelf.parameters) || has(self.parameters)" +// +kubebuilder:validation:XValidation:message="accountID cannot be removed once set",rule="!has(oldSelf.accountID) || has(self.accountID)" +// +kubebuilder:validation:XValidation:message="accessedBuckets cannot be removed once set",rule="!has(oldSelf.accessedBuckets) || has(self.accessedBuckets)" +// +kubebuilder:validation:XValidation:message="driverName cannot be removed once set",rule="!has(oldSelf.driverName) || has(self.driverName)" +// +kubebuilder:validation:XValidation:message="authenticationType cannot be removed once set",rule="!has(oldSelf.authenticationType) || has(self.authenticationType)" +// +kubebuilder:validation:XValidation:message="parameters cannot be removed once set",rule="!has(oldSelf.parameters) || has(self.parameters)" type BucketAccessStatus struct { // readyToUse indicates that the BucketAccess is ready for consumption by workloads. - ReadyToUse bool `json:"readyToUse"` + // +required + ReadyToUse *bool `json:"readyToUse,omitempty"` // accountID is the unique identifier for the backend access known to the driver. // This field is populated by the COSI Sidecar once access has been successfully granted. // +optional - // +kubebuilder:validation:XValidation:message="accountId is immutable once set",rule="oldSelf == '' || self == oldSelf" - AccountID string `json:"accountID"` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:XValidation:message="accountId is immutable once set",rule="self == oldSelf" + AccountID string `json:"accountID,omitempty"` // accessedBuckets is a list of Buckets the provisioned access must have permissions for, along // with per-Bucket access options. This field is populated by the COSI Controller based on the @@ -112,25 +115,27 @@ type BucketAccessStatus struct { // +optional // +listType=map // +listMapKey=bucketName - // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="oldSelf.size() == 0 || self == oldSelf" - AccessedBuckets []AccessedBucket `json:"accessedBuckets"` + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="self == oldSelf" + AccessedBuckets []AccessedBucket `json:"accessedBuckets,omitempty"` // driverName holds a copy of the BucketAccessClass driver name from the time of BucketAccess // provisioning. This field is populated by the COSI Controller. // +optional - // +kubebuilder:validation:XValidation:message="driverName is immutable once set",rule="oldSelf == '' || self == oldSelf" - DriverName string `json:"driverName"` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:XValidation:message="driverName is immutable once set",rule="self == oldSelf" + DriverName string `json:"driverName,omitempty"` // authenticationType holds a copy of the BucketAccessClass authentication type from the time of // BucketAccess provisioning. This field is populated by the COSI Controller. // +optional - // +kubebuilder:validation:XValidation:message="authenticationType is immutable once set",rule="oldSelf == '' || self == oldSelf" - AuthenticationType BucketAccessAuthenticationType `json:"authenticationType"` + // +kubebuilder:validation:XValidation:message="authenticationType is immutable once set",rule="self == oldSelf" + AuthenticationType BucketAccessAuthenticationType `json:"authenticationType,omitempty"` // parameters holds a copy of the BucketAccessClass parameters from the time of BucketAccess // provisioning. This field is populated by the COSI Controller. // +optional - // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="oldSelf.size() == 0 || self == oldSelf" + // +kubebuilder:validation:XValidation:message="accessedBuckets is immutable once set",rule="self == oldSelf" Parameters map[string]string `json:"parameters,omitempty"` // error holds the most recent error message, with a timestamp. @@ -148,12 +153,12 @@ type BucketClaimAccess struct { // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - BucketClaimName string `json:"bucketClaimName"` + BucketClaimName string `json:"bucketClaimName,omitempty"` // accessMode is the Read/Write access mode that the access should have for the bucket. // Possible values: ReadWrite, ReadOnly, WriteOnly. // +required - AccessMode BucketAccessMode `json:"accessMode"` + AccessMode BucketAccessMode `json:"accessMode,omitempty"` // accessSecretName is the name of a Kubernetes Secret that COSI should create and populate with // bucket info and access credentials for the bucket. @@ -164,7 +169,7 @@ type BucketClaimAccess struct { // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - AccessSecretName string `json:"accessSecretName"` + AccessSecretName string `json:"accessSecretName,omitempty"` } // AccessedBucket identifies a Bucket and correlates it to a BucketClaimAccess from the spec. @@ -173,13 +178,13 @@ type AccessedBucket struct { // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - BucketName string `json:"bucketName"` + BucketName string `json:"bucketName,omitempty"` // bucketClaimName must match a BucketClaimAccess's BucketClaimName from the spec. // +required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - BucketClaimName string `json:"bucketClaimName"` + BucketClaimName string `json:"bucketClaimName,omitempty"` } // +kubebuilder:object:root=true @@ -196,11 +201,11 @@ type BucketAccess struct { // spec defines the desired state of BucketAccess // +required - Spec BucketAccessSpec `json:"spec"` + Spec BucketAccessSpec `json:"spec,omitzero"` // status defines the observed state of BucketAccess // +optional - Status BucketAccessStatus `json:"status,omitempty,omitzero"` + Status BucketAccessStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true diff --git a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go index 97b7ae38..ed645d08 100644 --- a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go +++ b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketaccessclass_types.go @@ -25,7 +25,7 @@ type BucketAccessClassSpec struct { // driverName is the name of the driver that fulfills requests for this BucketAccessClass. // +required // +kubebuilder:validation:MinLength=1 - DriverName string `json:"driverName"` + DriverName string `json:"driverName,omitempty"` // authenticationType specifies which authentication mechanism is used bucket access. // Possible values: @@ -34,8 +34,7 @@ type BucketAccessClassSpec struct { // - ServiceAccount: The driver should configure the system such that Pods using the given // ServiceAccount authenticate to the backend object store automatically. // +required - // +kubebuilder:validation:Enum:=Key;ServiceAccount - AuthenticationType BucketAccessAuthenticationType `json:"authenticationType"` + AuthenticationType BucketAccessAuthenticationType `json:"authenticationType,omitempty"` // parameters is an opaque map of driver-specific configuration items passed to the driver that // fulfills requests for this BucketAccessClass. @@ -43,11 +42,13 @@ type BucketAccessClassSpec struct { Parameters map[string]string `json:"parameters,omitempty"` // featureOptions can be used to adjust various COSI access provisioning behaviors. + // If specified, at least one option must be set. // +optional - FeatureOptions BucketAccessFeatureOptions `json:"featureOptions,omitempty"` + FeatureOptions BucketAccessFeatureOptions `json:"featureOptions,omitzero"` } // BucketAccessFeatureOptions defines various COSI access provisioning behaviors. +// +kubebuilder:validation:MinProperties=1 type BucketAccessFeatureOptions struct { // disallowedBucketAccessModes is a list of disallowed Read/Write access modes. A BucketAccess // using this class will not be allowed to request access to a BucketClaim with any access mode @@ -59,11 +60,10 @@ type BucketAccessFeatureOptions struct { // disallowMultiBucketAccess disables the ability for a BucketAccess to reference multiple // BucketClaims when set. // +optional - DisallowMultiBucketAccess bool `json:"disallowMultiBucketAccess,omitempty"` + DisallowMultiBucketAccess *bool `json:"disallowMultiBucketAccess,omitempty"` } // +kubebuilder:object:root=true -// +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster // +kubebuilder:metadata:annotations="api-approved.kubernetes.io=unapproved, experimental v1alpha2 changes" @@ -73,12 +73,12 @@ type BucketAccessClass struct { // metadata is a standard object metadata // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + metav1.ObjectMeta `json:"metadata,omitzero"` // spec defines the desired state of BucketAccessClass // +required // +kubebuilder:validation:XValidation:message="BucketAccessClass spec is immutable",rule="self == oldSelf" - Spec BucketAccessClassSpec `json:"spec"` + Spec BucketAccessClassSpec `json:"spec,omitzero"` } // +kubebuilder:object:root=true diff --git a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclaim_types.go b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclaim_types.go index 5410a4ed..a71d31a0 100644 --- a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclaim_types.go +++ b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclaim_types.go @@ -21,15 +21,17 @@ import ( ) // BucketClaimSpec defines the desired state of BucketClaim +// +kubebuilder:validation:MinProperties=1 // +kubebuilder:validation:ExactlyOneOf=bucketClassName;existingBucketName -// +kubebuilder:validation:XValidation:message="bucketClassName is immutable",rule="has(oldSelf.bucketClassName) == has(self.bucketClassName)" -// +kubebuilder:validation:XValidation:message="existingBucketName is immutable",rule="has(oldSelf.existingBucketName) == has(self.existingBucketName)" -// +kubebuilder:validation:XValidation:message="protocols list is immutable",rule="has(oldSelf.protocols) == has(self.protocols)" +// +kubebuilder:validation:XValidation:message="bucketClassName cannot be added or removed after creation",rule="has(oldSelf.bucketClassName) == has(self.bucketClassName)" +// +kubebuilder:validation:XValidation:message="existingBucketName cannot be added or removed after creation",rule="has(oldSelf.existingBucketName) == has(self.existingBucketName)" +// +kubebuilder:validation:XValidation:message="protocols list cannot be added or removed after creation",rule="has(oldSelf.protocols) == has(self.protocols)" type BucketClaimSpec struct { // bucketClassName selects the BucketClass for provisioning the BucketClaim. // This field is used only for BucketClaim dynamic provisioning. // If unspecified, existingBucketName must be specified for binding to an existing Bucket. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="bucketClassName is immutable",rule="self == oldSelf" BucketClassName string `json:"bucketClassName,omitempty"` @@ -37,6 +39,7 @@ type BucketClaimSpec struct { // protocols lists object storage protocols that the provisioned Bucket must support. // If specified, COSI will verify that each item is advertised as supported by the driver. // +optional + // +listType=set // +kubebuilder:validation:XValidation:message="protocols list is immutable",rule="self == oldSelf" Protocols []ObjectProtocol `json:"protocols,omitempty"` @@ -45,29 +48,32 @@ type BucketClaimSpec struct { // This field is used only for BucketClaim static provisioning. // If unspecified, bucketClassName must be specified for dynamically provisioning a new bucket. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 // +kubebuilder:validation:XValidation:message="existingBucketName is immutable",rule="self == oldSelf" ExistingBucketName string `json:"existingBucketName,omitempty"` } // BucketClaimStatus defines the observed state of BucketClaim. -// +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="!has(oldSelf.boundBucketName) || has(self.boundBucketName)" -// +kubebuilder:validation:XValidation:message="protocols is immutable once set",rule="!has(oldSelf.protocols) || has(self.protocols)" +// +kubebuilder:validation:XValidation:message="boundBucketName cannot be removed once set",rule="!has(oldSelf.boundBucketName) || has(self.boundBucketName)" +// +kubebuilder:validation:XValidation:message="protocols cannot be removed once set",rule="!has(oldSelf.protocols) || has(self.protocols)" type BucketClaimStatus struct { // boundBucketName is the name of the Bucket this BucketClaim is bound to. // +optional + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 - // +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="oldSelf == '' || self == oldSelf" - BoundBucketName string `json:"boundBucketName"` + // +kubebuilder:validation:XValidation:message="boundBucketName is immutable once set",rule="self == oldSelf" + BoundBucketName string `json:"boundBucketName,omitempty"` // readyToUse indicates that the bucket is ready for consumption by workloads. - ReadyToUse bool `json:"readyToUse"` + // +required + ReadyToUse *bool `json:"readyToUse,omitempty"` // protocols is the set of protocols the bound 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"` // error holds the most recent error message, with a timestamp. // This is cleared when provisioning is successful. @@ -89,11 +95,11 @@ type BucketClaim struct { // spec defines the desired state of BucketClaim // +required - Spec BucketClaimSpec `json:"spec"` + Spec BucketClaimSpec `json:"spec,omitzero"` // status defines the observed state of BucketClaim // +optional - Status BucketClaimStatus `json:"status,omitempty,omitzero"` + Status BucketClaimStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true diff --git a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclass_types.go b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclass_types.go index 6b1b376c..d9087216 100644 --- a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclass_types.go +++ b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/bucketclass_types.go @@ -25,7 +25,7 @@ type BucketClassSpec struct { // driverName is the name of the driver that fulfills requests for this BucketClass. // +required // +kubebuilder:validation:MinLength=1 - DriverName string `json:"driverName"` + DriverName string `json:"driverName,omitempty"` // deletionPolicy determines whether a Bucket created through the BucketClass should be deleted // when its bound BucketClaim is deleted. @@ -33,7 +33,7 @@ type BucketClassSpec struct { // - 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 BucketClass. @@ -59,7 +59,7 @@ type BucketClass struct { // spec defines the BucketClass. spec is entirely immutable. // +required // +kubebuilder:validation:XValidation:message="BucketClass spec is immutable",rule="self == oldSelf" - Spec BucketClassSpec `json:"spec"` + Spec BucketClassSpec `json:"spec,omitzero"` } // +kubebuilder:object:root=true diff --git a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go index e4d63e2e..fb1fa36f 100644 --- a/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go +++ b/vendor/sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2/zz_generated.deepcopy.go @@ -182,6 +182,11 @@ func (in *BucketAccessFeatureOptions) DeepCopyInto(out *BucketAccessFeatureOptio *out = make([]BucketAccessMode, len(*in)) copy(*out, *in) } + if in.DisallowMultiBucketAccess != nil { + in, out := &in.DisallowMultiBucketAccess, &out.DisallowMultiBucketAccess + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BucketAccessFeatureOptions. @@ -249,6 +254,11 @@ func (in *BucketAccessSpec) DeepCopy() *BucketAccessSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BucketAccessStatus) DeepCopyInto(out *BucketAccessStatus) { *out = *in + if in.ReadyToUse != nil { + in, out := &in.ReadyToUse, &out.ReadyToUse + *out = new(bool) + **out = **in + } if in.AccessedBuckets != nil { in, out := &in.AccessedBuckets, &out.AccessedBuckets *out = make([]AccessedBucket, len(*in)) @@ -390,6 +400,11 @@ func (in *BucketClaimSpec) DeepCopy() *BucketClaimSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BucketClaimStatus) DeepCopyInto(out *BucketClaimStatus) { *out = *in + if in.ReadyToUse != nil { + in, out := &in.ReadyToUse, &out.ReadyToUse + *out = new(bool) + **out = **in + } if in.Protocols != nil { in, out := &in.Protocols, &out.Protocols *out = make([]ObjectProtocol, len(*in)) @@ -555,6 +570,11 @@ func (in *BucketSpec) DeepCopy() *BucketSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BucketStatus) DeepCopyInto(out *BucketStatus) { *out = *in + if in.ReadyToUse != nil { + in, out := &in.ReadyToUse, &out.ReadyToUse + *out = new(bool) + **out = **in + } if in.Protocols != nil { in, out := &in.Protocols, &out.Protocols *out = make([]ObjectProtocol, len(*in))