diff --git a/Documentation/CRDs/Cluster/ceph-cluster-crd.md b/Documentation/CRDs/Cluster/ceph-cluster-crd.md index af35aa3eccdf..67a4d3945de7 100755 --- a/Documentation/CRDs/Cluster/ceph-cluster-crd.md +++ b/Documentation/CRDs/Cluster/ceph-cluster-crd.md @@ -559,6 +559,8 @@ You can set priority class names for Rook components for the list of key value p * `mon`: Set priority class names for Mons. Examples default to system-node-critical. * `osd`: Set priority class names for OSDs. Examples default to system-node-critical. * `crashcollector`: Set priority class names for crashcollectors. +* `exporter`: Set priority class names for exporters. +* `cleanup`: Set priority class names for cleanup Jobs. The specific component keys will act as overrides to `all`. diff --git a/Documentation/CRDs/Object-Storage/ceph-object-store-crd.md b/Documentation/CRDs/Object-Storage/ceph-object-store-crd.md index 9ef7ca235e59..4b4efb8f0418 100644 --- a/Documentation/CRDs/Object-Storage/ceph-object-store-crd.md +++ b/Documentation/CRDs/Object-Storage/ceph-object-store-crd.md @@ -190,29 +190,35 @@ The gateway settings correspond to the RGW daemon settings. for more details. Multiple endpoints can be given, but for stability of ObjectBucketClaims, we highly recommend that users give only a single external RGW endpoint that is a load balancer that sends requests to the multiple RGWs. + + Example of external rgw endpoints to connect to: + + ```yaml + gateway: + port: 80 + externalRgwEndpoints: + - ip: 192.168.39.182 + # hostname: example.com + ``` + * `annotations`: Key value pair list of annotations to add. * `labels`: Key value pair list of labels to add. * `placement`: The Kubernetes placement settings to determine where the RGW pods should be started in the cluster. * `resources`: Set resource requests/limits for the Gateway Pod(s), see [Resource Requirements/Limits](../Cluster/ceph-cluster-crd.md#resource-requirementslimits). * `priorityClassName`: Set priority class name for the Gateway Pod(s) +* `additionalVolumeMounts`: additional volumes to be mounted to the RGW pod. The root directory for + each additional volume mount is `/var/rgw`. Each volume mount has a `subPath` that defines the + subdirectory where that volumes files will be mounted. Rook supports several standard Kubernetes + volume types. Example: for an additional mount at subPath `ldap`, mounted from a secret that has + key `bindpass.secret`, the file would reside at `/var/rgw/ldap/bindpass.secret`. * `service`: The annotations to set on to the Kubernetes Service of RGW. The [service serving cert](https://docs.openshift.com/container-platform/4.6/security/certificates/service-serving-certificate.html) feature supported in Openshift is enabled by the following example: -```yaml -gateway: - service: - annotations: + ```yaml + gateway: + service: + annotations: service.beta.openshift.io/serving-cert-secret-name: -``` - -Example of external rgw endpoints to connect to: - -```yaml -gateway: - port: 80 - externalRgwEndpoints: - - ip: 192.168.39.182 - # hostname: example.com -``` + ``` ## Zone Settings diff --git a/Documentation/CRDs/specification.md b/Documentation/CRDs/specification.md index 301b3c969429..4e796082477e 100644 --- a/Documentation/CRDs/specification.md +++ b/Documentation/CRDs/specification.md @@ -2578,6 +2578,61 @@ string +

AdditionalVolumeMount +

+
+

AdditionalVolumeMount represents the source from where additional files in pod containers +should come from and what subdirectory they are made available in.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+subPath
+ +string + +
+

SubPath defines the sub-path (subdirectory) of the directory root where the volumeSource will +be mounted. All files/keys in the volume source’s volume will be mounted to the subdirectory. +This is not the same as the Kubernetes subPath volume mount option. +Each subPath definition must be unique and must not contain ‘:’.

+
+volumeSource
+ + +ConfigFileVolumeSource + + +
+

VolumeSource accepts a pared down version of the standard Kubernetes VolumeSource for the +additional file(s) like what is normally used to configure Volumes for a Pod. Fore example, a +ConfigMap, Secret, or HostPath. Each VolumeSource adds one or more additional files to the +container <directory-root>/<subPath> directory. +Be aware that some files may need to have a specific file mode like 0600 due to application +requirements. For example, CA or TLS certificates.

+
+

AdditionalVolumeMounts +([]github.com/rook/rook/pkg/apis/ceph.rook.io/v1.AdditionalVolumeMount alias)

+

+(Appears on:GatewaySpec, SSSDSidecar) +

+
+

AddressRangesSpec

@@ -5089,7 +5144,7 @@ blocking deletion.

ConfigFileVolumeSource

-(Appears on:KerberosConfigFiles, KerberosKeytabFile, SSSDSidecarAdditionalFile, SSSDSidecarConfigFile) +(Appears on:AdditionalVolumeMount, KerberosConfigFiles, KerberosKeytabFile, SSSDSidecarConfigFile)

Represents the source of a volume to mount. @@ -6958,6 +7013,22 @@ bool

Whether rgw dashboard is enabled for the rgw daemon. If not set, the rgw dashboard will be enabled.

+ + +additionalVolumeMounts
+ + +AdditionalVolumeMounts + + + + +

AdditionalVolumeMounts allows additional volumes to be mounted to the RGW pod. +The root directory for each additional volume mount is /var/rgw. +Example: for an additional mount at subPath ldap, mounted from a secret that has key +bindpass.secret, the file would reside at /var/rgw/ldap/bindpass.secret.

+ +

HTTPEndpointSpec @@ -11452,15 +11523,16 @@ securely add the file via annotations on the CephNFS spec (passed to the NFS ser additionalFiles
- -[]SSSDSidecarAdditionalFile + +AdditionalVolumeMounts (Optional)

AdditionalFiles defines any number of additional files that should be mounted into the SSSD -sidecar. These files may be referenced by the sssd.conf config file.

+sidecar with a directory root of /etc/sssd/rook-additional/. +These files may be referenced by the sssd.conf config file.

@@ -11493,55 +11565,6 @@ this may be a value between 1 and 10. See SSSD docs for more info: -

SSSDSidecarAdditionalFile -

-

-(Appears on:SSSDSidecar) -

-
-

SSSDSidecarAdditionalFile represents the source from where additional files for the the SSSD -configuration should come from and are made available.

-
- - - - - - - - - - - - - - - - - -
FieldDescription
-subPath
- -string - -
-

SubPath defines the sub-path in /etc/sssd/rook-additional/ where the additional file(s) -will be placed. Each subPath definition must be unique and must not contain ‘:’.

-
-volumeSource
- - -ConfigFileVolumeSource - - -
-

VolumeSource accepts a pared down version of the standard Kubernetes VolumeSource for the -additional file(s) like what is normally used to configure Volumes for a Pod. Fore example, a -ConfigMap, Secret, or HostPath. Each VolumeSource adds one or more additional files to the -SSSD sidecar container in the /etc/sssd/rook-additional/<subPath> directory. -Be aware that some files may need to have a specific file mode like 0600 due to requirements -by SSSD for some files. For example, CA or TLS certificates.

-

SSSDSidecarConfigFile

diff --git a/PendingReleaseNotes.md b/PendingReleaseNotes.md index 2ef38449c60b..779cd62017d5 100644 --- a/PendingReleaseNotes.md +++ b/PendingReleaseNotes.md @@ -1,28 +1,6 @@ -# v1.15 Pending Release Notes +# v1.16 Pending Release Notes ## Breaking Changes -- Rook has deprecated CSI network "holder" pods. - If there are pods named `csi-*plugin-holder-*` in the Rook operator namespace, see the - [detailed documentation](../CRDs/Cluster/network-providers.md#holder-pod-deprecation) - to disable them. This deprecation process is required before upgrading to the future Rook v1.16. -- Ceph COSI driver images have been updated. This impacts existing COSI Buckets, BucketClaims, and - BucketAccesses. Update existing clusters following the guide - [here](https://github.com/rook/rook/discussions/14297). -- During CephBlockPool updates, Rook will now return an error if an invalid device class is - specified. Pools with invalid device classes may start failing until the correct device class is - specified. For more info, see [#14057](https://github.com/rook/rook/pull/14057). -- CephObjectStore, CephObjectStoreUser, and OBC endpoint behavior has changed when CephObjectStore - `spec.hosting` configurations are set. Use the new `spec.hosting.advertiseEndpoint` config to - define required behavior as - [documented](../Storage-Configuration/Object-Storage-RGW/object-storage.md#object-store-endpoint). -- Minimum version of Kubernetes supported is increased to K8s v1.26. ## Features - -- Added support for Ceph Squid (v19) -- Allow updating the device class of OSDs, if `allowDeviceClassUpdate: true` is set -- CephObjectStore support for keystone authentication for S3 and Swift - (see [#9088](https://github.com/rook/rook/issues/9088)). -- Support K8s versions v1.26 through v1.31. -- Use fully-qualified image names (`docker.io/rook/ceph`) in operator manifests and helm charts diff --git a/build/csv/ceph/ceph.rook.io_cephobjectstores.yaml b/build/csv/ceph/ceph.rook.io_cephobjectstores.yaml index 9bb9dee6b533..cafd46dba004 100644 --- a/build/csv/ceph/ceph.rook.io_cephobjectstores.yaml +++ b/build/csv/ceph/ceph.rook.io_cephobjectstores.yaml @@ -203,6 +203,259 @@ spec: gateway: nullable: true properties: + additionalVolumeMounts: + items: + properties: + subPath: + minLength: 1 + pattern: ^[^:]+$ + type: string + volumeSource: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + type: object + required: + - subPath + - volumeSource + type: object + type: array annotations: additionalProperties: type: string diff --git a/deploy/charts/rook-ceph/templates/resources.yaml b/deploy/charts/rook-ceph/templates/resources.yaml index 568c976430f7..e7954efe7b9e 100644 --- a/deploy/charts/rook-ceph/templates/resources.yaml +++ b/deploy/charts/rook-ceph/templates/resources.yaml @@ -8997,16 +8997,19 @@ spec: additionalFiles: description: |- AdditionalFiles defines any number of additional files that should be mounted into the SSSD - sidecar. These files may be referenced by the sssd.conf config file. + sidecar with a directory root of `/etc/sssd/rook-additional/`. + These files may be referenced by the sssd.conf config file. items: description: |- - SSSDSidecarAdditionalFile represents the source from where additional files for the the SSSD - configuration should come from and are made available. + AdditionalVolumeMount represents the source from where additional files in pod containers + should come from and what subdirectory they are made available in. properties: subPath: description: |- - SubPath defines the sub-path in `/etc/sssd/rook-additional/` where the additional file(s) - will be placed. Each subPath definition must be unique and must not contain ':'. + SubPath defines the sub-path (subdirectory) of the directory root where the volumeSource will + be mounted. All files/keys in the volume source's volume will be mounted to the subdirectory. + This is not the same as the Kubernetes `subPath` volume mount option. + Each subPath definition must be unique and must not contain ':'. minLength: 1 pattern: ^[^:]+$ type: string @@ -10753,6 +10756,272 @@ spec: description: The rgw pod info nullable: true properties: + additionalVolumeMounts: + description: |- + AdditionalVolumeMounts allows additional volumes to be mounted to the RGW pod. + The root directory for each additional volume mount is `/var/rgw`. + Example: for an additional mount at subPath `ldap`, mounted from a secret that has key + `bindpass.secret`, the file would reside at `/var/rgw/ldap/bindpass.secret`. + items: + description: |- + AdditionalVolumeMount represents the source from where additional files in pod containers + should come from and what subdirectory they are made available in. + properties: + subPath: + description: |- + SubPath defines the sub-path (subdirectory) of the directory root where the volumeSource will + be mounted. All files/keys in the volume source's volume will be mounted to the subdirectory. + This is not the same as the Kubernetes `subPath` volume mount option. + Each subPath definition must be unique and must not contain ':'. + minLength: 1 + pattern: ^[^:]+$ + type: string + volumeSource: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + type: object + required: + - subPath + - volumeSource + type: object + type: array annotations: additionalProperties: type: string diff --git a/deploy/charts/rook-ceph/templates/securityContextConstraints.yaml b/deploy/charts/rook-ceph/templates/securityContextConstraints.yaml index b18ea33a877d..23664fd93b84 100644 --- a/deploy/charts/rook-ceph/templates/securityContextConstraints.yaml +++ b/deploy/charts/rook-ceph/templates/securityContextConstraints.yaml @@ -72,9 +72,9 @@ supplementalGroups: # The type of volumes which are mounted to csi pods volumes: - configMap - - projected - emptyDir - hostPath + - projected users: # A user needs to be added for each rook service account. - system:serviceaccount:{{ .Release.Namespace }}:rook-csi-rbd-plugin-sa diff --git a/deploy/examples/crds.yaml b/deploy/examples/crds.yaml index 03d4288cdbb6..5978262293d4 100644 --- a/deploy/examples/crds.yaml +++ b/deploy/examples/crds.yaml @@ -8990,16 +8990,19 @@ spec: additionalFiles: description: |- AdditionalFiles defines any number of additional files that should be mounted into the SSSD - sidecar. These files may be referenced by the sssd.conf config file. + sidecar with a directory root of `/etc/sssd/rook-additional/`. + These files may be referenced by the sssd.conf config file. items: description: |- - SSSDSidecarAdditionalFile represents the source from where additional files for the the SSSD - configuration should come from and are made available. + AdditionalVolumeMount represents the source from where additional files in pod containers + should come from and what subdirectory they are made available in. properties: subPath: description: |- - SubPath defines the sub-path in `/etc/sssd/rook-additional/` where the additional file(s) - will be placed. Each subPath definition must be unique and must not contain ':'. + SubPath defines the sub-path (subdirectory) of the directory root where the volumeSource will + be mounted. All files/keys in the volume source's volume will be mounted to the subdirectory. + This is not the same as the Kubernetes `subPath` volume mount option. + Each subPath definition must be unique and must not contain ':'. minLength: 1 pattern: ^[^:]+$ type: string @@ -10744,6 +10747,272 @@ spec: description: The rgw pod info nullable: true properties: + additionalVolumeMounts: + description: |- + AdditionalVolumeMounts allows additional volumes to be mounted to the RGW pod. + The root directory for each additional volume mount is `/var/rgw`. + Example: for an additional mount at subPath `ldap`, mounted from a secret that has key + `bindpass.secret`, the file would reside at `/var/rgw/ldap/bindpass.secret`. + items: + description: |- + AdditionalVolumeMount represents the source from where additional files in pod containers + should come from and what subdirectory they are made available in. + properties: + subPath: + description: |- + SubPath defines the sub-path (subdirectory) of the directory root where the volumeSource will + be mounted. All files/keys in the volume source's volume will be mounted to the subdirectory. + This is not the same as the Kubernetes `subPath` volume mount option. + Each subPath definition must be unique and must not contain ':'. + minLength: 1 + pattern: ^[^:]+$ + type: string + volumeSource: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + type: object + required: + - subPath + - volumeSource + type: object + type: array annotations: additionalProperties: type: string diff --git a/deploy/examples/object.yaml b/deploy/examples/object.yaml index ba22f3a94f15..bee795abb9d0 100644 --- a/deploy/examples/object.yaml +++ b/deploy/examples/object.yaml @@ -99,6 +99,14 @@ spec: # cpu: "500m" # memory: "1024Mi" priorityClassName: system-cluster-critical + # # Add arbitrary volume mounts to RGW pods + # additionalVolumeMounts: + # # 'rgw-ldap' secret keys will show up as files in RGW container in dir /var/rgw/ldap + # - subPath: ldap + # volumeSource: + # secret: + # secretName: rgw-ldap + # defaultMode: 0600 #zone: #name: zone-a # service endpoint healthcheck diff --git a/deploy/examples/operator-openshift.yaml b/deploy/examples/operator-openshift.yaml index bd26983924f2..730d2327dca4 100644 --- a/deploy/examples/operator-openshift.yaml +++ b/deploy/examples/operator-openshift.yaml @@ -83,9 +83,9 @@ supplementalGroups: # The type of volumes which are mounted to csi pods volumes: - configMap - - projected - emptyDir - hostPath + - projected users: # A user needs to be added for each rook service account. # This assumes running in the default sample "rook-ceph" namespace. diff --git a/pkg/apis/ceph.rook.io/v1/labels.go b/pkg/apis/ceph.rook.io/v1/labels.go index 6825838f8354..d019aba2641b 100644 --- a/pkg/apis/ceph.rook.io/v1/labels.go +++ b/pkg/apis/ceph.rook.io/v1/labels.go @@ -17,7 +17,10 @@ limitations under the License. package v1 import ( + "strings" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation" ) const ( @@ -138,3 +141,78 @@ func (a Labels) Merge(with Labels) Labels { } return ret } + +// ToValidDNSLabel converts a given string to a valid DNS-1035 spec label. The DNS-1035 spec +// follows the regex '[a-z]([-a-z0-9]*[a-z0-9])?' and is at most 63 chars long. DNS-1035 is used +// over DNS-1123 because it is more strict. Kubernetes docs are not always clear when a DNS_LABEL is +// supposed to be 1035 or 1123 compliant, so we use the more strict version for ease of use. +// - Any input symbol that is not valid is converted to a dash ('-'). +// - Multiple resultant dashes in a row are compressed to a single dash. +// - If the starting character is a number, a 'd' is prepended to preserve the number. +// - Any non-alphanumeric starting or ending characters are removed. +// - If the resultant string is longer than the maximum-allowed 63 characters], characters are +// removed from the middle and replaced with a double dash ('--') to reduce the string to 63 +// characters. +func ToValidDNSLabel(input string) string { + maxl := validation.DNS1035LabelMaxLength + + if input == "" { + return "" + } + + outbuf := make([]byte, len(input)+1) + j := 0 // position in output buffer + last := byte('-') + for _, c := range []byte(input) { + switch { + case c >= 'a' && c <= 'z': + outbuf[j] = c + case c >= '0' && c <= '9': + // if the first char is a number, add a 'd' (for decimal) in front + if j == 0 { + outbuf[j] = 'd' // for decimal + j++ + } + outbuf[j] = c + case c >= 'A' && c <= 'Z': + // convert to lower case + outbuf[j] = c - 'A' + 'a' // convert to lower case + default: + if last == '-' { + // don't write two dashes in a row + continue + } + outbuf[j] = byte('-') + } + last = outbuf[j] + j++ + } + + // set the length of the output buffer to the number of chars we copied to it so there aren't + // \0x00 chars at the end + outbuf = outbuf[:j] + + // trim any leading or trailing dashes + out := strings.Trim(string(outbuf), "-") + + // if string is longer than max length, cut content from the middle to get it to length + if len(out) > maxl { + out = cutMiddle(out, maxl) + } + + return out +} + +// don't use this function for anything less than toSize=4 chars long +func cutMiddle(input string, toSize int) string { + if len(input) <= toSize { + return input + } + + lenLeft := toSize / 2 // truncation rounds down the left side + lenRight := toSize/2 + (toSize % 2) // modulo rounds up the right side + + buf := []byte(input) + + return string(buf[:lenLeft-1]) + "--" + string(buf[len(input)-lenRight+1:]) +} diff --git a/pkg/apis/ceph.rook.io/v1/labels_test.go b/pkg/apis/ceph.rook.io/v1/labels_test.go index 86668fb25f66..a4163e24d07b 100644 --- a/pkg/apis/ceph.rook.io/v1/labels_test.go +++ b/pkg/apis/ceph.rook.io/v1/labels_test.go @@ -236,3 +236,83 @@ func TestLabelsMerge(t *testing.T) { var empty Labels assert.Equal(t, map[string]string(testLabelsPart3), map[string]string(empty.Merge(testLabelsPart3))) } + +func TestToValidDNSLabel(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + {"empty string", "", ""}, + {"single dash", "-", ""}, + {"multiple dashes", "----", ""}, + {"lc a", "a", "a"}, + {"lc z", "z", "z"}, + {"lc alphabet", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"}, + {"UC A", "A", "a"}, + {"UC Z", "Z", "z"}, + {"UC ALPHABET", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"}, + {"mixed case AlPhAbEt", "AbCdEfGhIjKlMnOpQrStUvWxYz", "abcdefghijklmnopqrstuvwxyz"}, + {"single 0", "0", "d0"}, + {"single 9", "9", "d9"}, + {"single 1", "1", "d1"}, + {"numbers", "01234567890", "d01234567890"}, + {"letters with numbers", "1a0b1c2d3e4f5g6h7i8j9k0", "d1a0b1c2d3e4f5g6h7i8j9k0"}, + {"single / symbol", "/", ""}, + {"single : symbol", ":", ""}, + {"single . symbol", ".", ""}, + {"bunch of symbols", "`~!@#$%^&*()_+-={}[]\\|;':\",.<>/?", ""}, + {"alphabet with symbols", + "a~b!c@d#e$f^g&h*i(j)k_l-m+n+o[p]q{r}s|t:u;v'wz", "a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z"}, + {"multiple symbols between letters", "a//b//c", "a-b-c"}, + {"symbol before", "/a/b/c", "a-b-c"}, + {"symbol after", "a/b/c/", "a-b-c"}, + {"symbols before and after", "/a/b/c/", "a-b-c"}, + {"multiple symbols before after between", "//a//b//c//", "a-b-c"}, + {"mix of all tests except length", "//1a//B-c/d_f/../00-thing.ini/", "d1a-b-c-d-f-00-thing-ini"}, + {"too long input -> middle trim", + "qwertyuiopqwertyuiopqwertyuiopaaqwertyuiopqwertyuiopqwertyuiopaa", + "qwertyuiopqwertyuiopqwertyuiop--wertyuiopqwertyuiopqwertyuiopaa"}, + {"too long input but symbols allow for no middle trim", + "/qwertyuiopqwerty/uiopqwertyuiop//qwertyuiopqwerty/uiopqwertyuiop/", + "qwertyuiopqwerty-uiopqwertyuiop-qwertyuiopqwerty-uiopqwertyuiop"}, + {"max allowed length but starts with number -> middle trim", + "123qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiop", + "d123qwertyuiopqwertyuiopqwerty--pqwertyuiopqwertyuiopqwertyuiop"}, + {"max allowed length ok", + "qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiop123", + "qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiop123"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, ToValidDNSLabel(tt.input)) + }) + } +} + +func Test_cutMiddle(t *testing.T) { + // not an exported function, so don't bother with extreme cases like 0, 1, 2, or 3 len inputs + t.Run("len 8 -> 6", func(t *testing.T) { + assert.Equal(t, "ab--gh", cutMiddle("abcdefgh", 6)) + }) + t.Run("len 9 -> 6", func(t *testing.T) { + assert.Equal(t, "ab--hi", cutMiddle("abcdefghi", 6)) + }) + t.Run("len 9 -> 7", func(t *testing.T) { + assert.Equal(t, "ab--ghi", cutMiddle("abcdefghi", 7)) + }) + t.Run("len 10 -> 10", func(t *testing.T) { + assert.Equal(t, "qwertyuiop", cutMiddle("qwertyuiop", 10)) + }) + // below is what we really want to test + t.Run("len 63 -> 63", func(t *testing.T) { + assert.Equal(t, + "qwertyuiopqwertyuiopqwertyuiop12qwertyuiopqwertyuiopqwertyuiop1", + cutMiddle("qwertyuiopqwertyuiopqwertyuiop12qwertyuiopqwertyuiopqwertyuiop1", 63)) + }) + t.Run("len 64 -> 63", func(t *testing.T) { + assert.Equal(t, + "qwertyuiopqwertyuiopqwertyuiop--wertyuiopqwertyuiopqwertyuiop12", + cutMiddle("qwertyuiopqwertyuiopqwertyuiop12qwertyuiopqwertyuiopqwertyuiop12", 63)) + }) +} diff --git a/pkg/apis/ceph.rook.io/v1/nfs_test.go b/pkg/apis/ceph.rook.io/v1/nfs_test.go index b2ba10c8f2e8..7591760e206f 100644 --- a/pkg/apis/ceph.rook.io/v1/nfs_test.go +++ b/pkg/apis/ceph.rook.io/v1/nfs_test.go @@ -92,7 +92,7 @@ func TestNFSSecuritySpec_Validate(t *testing.T) { withSSSD(&SSSDSpec{ Sidecar: &SSSDSidecar{ Image: "myimage", - AdditionalFiles: []SSSDSidecarAdditionalFile{}, + AdditionalFiles: AdditionalVolumeMounts{}, }, }), isOkay}, @@ -100,7 +100,7 @@ func TestNFSSecuritySpec_Validate(t *testing.T) { withSSSD(&SSSDSpec{ Sidecar: &SSSDSidecar{ Image: "myimage", - AdditionalFiles: []SSSDSidecarAdditionalFile{ + AdditionalFiles: AdditionalVolumeMounts{ {SubPath: "one", VolumeSource: configMapVolumeSource}, {SubPath: "two", VolumeSource: configMapVolumeSource}, {SubPath: "three", VolumeSource: configMapVolumeSource}, @@ -112,7 +112,7 @@ func TestNFSSecuritySpec_Validate(t *testing.T) { withSSSD(&SSSDSpec{ Sidecar: &SSSDSidecar{ Image: "myimage", - AdditionalFiles: []SSSDSidecarAdditionalFile{ + AdditionalFiles: AdditionalVolumeMounts{ {SubPath: "one", VolumeSource: configMapVolumeSource}, {SubPath: "", VolumeSource: configMapVolumeSource}, {SubPath: "three", VolumeSource: configMapVolumeSource}, @@ -124,7 +124,7 @@ func TestNFSSecuritySpec_Validate(t *testing.T) { withSSSD(&SSSDSpec{ Sidecar: &SSSDSidecar{ Image: "myimage", - AdditionalFiles: []SSSDSidecarAdditionalFile{ + AdditionalFiles: AdditionalVolumeMounts{ {SubPath: "one", VolumeSource: configMapVolumeSource}, {SubPath: "two", VolumeSource: configMapVolumeSource}, {SubPath: "one", VolumeSource: configMapVolumeSource}, @@ -136,7 +136,7 @@ func TestNFSSecuritySpec_Validate(t *testing.T) { withSSSD(&SSSDSpec{ Sidecar: &SSSDSidecar{ Image: "myimage", - AdditionalFiles: []SSSDSidecarAdditionalFile{ + AdditionalFiles: AdditionalVolumeMounts{ {SubPath: "one", VolumeSource: configMapVolumeSource}, {SubPath: "", VolumeSource: &ConfigFileVolumeSource{}}, {SubPath: "three", VolumeSource: configMapVolumeSource}, diff --git a/pkg/apis/ceph.rook.io/v1/types.go b/pkg/apis/ceph.rook.io/v1/types.go index 12a756231c17..380c1f3cd23d 100755 --- a/pkg/apis/ceph.rook.io/v1/types.go +++ b/pkg/apis/ceph.rook.io/v1/types.go @@ -1637,6 +1637,12 @@ type GatewaySpec struct { // +nullable // +optional DashboardEnabled *bool `json:"dashboardEnabled,omitempty"` + + // AdditionalVolumeMounts allows additional volumes to be mounted to the RGW pod. + // The root directory for each additional volume mount is `/var/rgw`. + // Example: for an additional mount at subPath `ldap`, mounted from a secret that has key + // `bindpass.secret`, the file would reside at `/var/rgw/ldap/bindpass.secret`. + AdditionalVolumeMounts AdditionalVolumeMounts `json:"additionalVolumeMounts,omitempty"` } // EndpointAddress is a tuple that describes a single IP address or host name. This is a subset of @@ -2477,9 +2483,10 @@ type SSSDSidecar struct { SSSDConfigFile SSSDSidecarConfigFile `json:"sssdConfigFile"` // AdditionalFiles defines any number of additional files that should be mounted into the SSSD - // sidecar. These files may be referenced by the sssd.conf config file. + // sidecar with a directory root of `/etc/sssd/rook-additional/`. + // These files may be referenced by the sssd.conf config file. // +optional - AdditionalFiles []SSSDSidecarAdditionalFile `json:"additionalFiles,omitempty"` + AdditionalFiles AdditionalVolumeMounts `json:"additionalFiles,omitempty"` // Resources allow specifying resource requests/limits on the SSSD sidecar container. // +optional @@ -2507,11 +2514,13 @@ type SSSDSidecarConfigFile struct { VolumeSource *ConfigFileVolumeSource `json:"volumeSource,omitempty"` } -// SSSDSidecarAdditionalFile represents the source from where additional files for the the SSSD -// configuration should come from and are made available. -type SSSDSidecarAdditionalFile struct { - // SubPath defines the sub-path in `/etc/sssd/rook-additional/` where the additional file(s) - // will be placed. Each subPath definition must be unique and must not contain ':'. +// AdditionalVolumeMount represents the source from where additional files in pod containers +// should come from and what subdirectory they are made available in. +type AdditionalVolumeMount struct { + // SubPath defines the sub-path (subdirectory) of the directory root where the volumeSource will + // be mounted. All files/keys in the volume source's volume will be mounted to the subdirectory. + // This is not the same as the Kubernetes `subPath` volume mount option. + // Each subPath definition must be unique and must not contain ':'. // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:Pattern=`^[^:]+$` @@ -2520,12 +2529,14 @@ type SSSDSidecarAdditionalFile struct { // VolumeSource accepts a pared down version of the standard Kubernetes VolumeSource for the // additional file(s) like what is normally used to configure Volumes for a Pod. Fore example, a // ConfigMap, Secret, or HostPath. Each VolumeSource adds one or more additional files to the - // SSSD sidecar container in the `/etc/sssd/rook-additional/` directory. - // Be aware that some files may need to have a specific file mode like 0600 due to requirements - // by SSSD for some files. For example, CA or TLS certificates. + // container `/` directory. + // Be aware that some files may need to have a specific file mode like 0600 due to application + // requirements. For example, CA or TLS certificates. VolumeSource *ConfigFileVolumeSource `json:"volumeSource"` } +type AdditionalVolumeMounts []AdditionalVolumeMount + // NetworkSpec for Ceph includes backward compatibility code // +kubebuilder:validation:XValidation:message="at least one network selector must be specified when using multus",rule="!has(self.provider) || (self.provider != 'multus' || (self.provider == 'multus' && size(self.selectors) > 0))" // +kubebuilder:validation:XValidation:message=`the legacy hostNetwork setting can only be set if the network.provider is set to the empty string`,rule=`!has(self.hostNetwork) || self.hostNetwork == false || !has(self.provider) || self.provider == ""` diff --git a/pkg/apis/ceph.rook.io/v1/volume.go b/pkg/apis/ceph.rook.io/v1/volume.go index 9acb4d8e1de7..d713d976bd98 100644 --- a/pkg/apis/ceph.rook.io/v1/volume.go +++ b/pkg/apis/ceph.rook.io/v1/volume.go @@ -17,9 +17,11 @@ limitations under the License. package v1 import ( + "path/filepath" "reflect" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" ) func (src *ConfigFileVolumeSource) ToKubernetesVolumeSource() *corev1.VolumeSource { @@ -49,6 +51,28 @@ func (src *ConfigFileVolumeSource) ToKubernetesVolumeSource() *corev1.VolumeSour return dst } +// GenerateVolumesAndMounts converts Rook's AdditionalVolumeMounts type to a list of volumes and +// corresponding mounts that can be added to Kubernetes pod specs. +func (v *AdditionalVolumeMounts) GenerateVolumesAndMounts(rootDir string) ([]v1.Volume, []v1.VolumeMount) { + vols := []v1.Volume{} + mounts := []v1.VolumeMount{} + + for _, addVolMnt := range *v { + mountPath := filepath.Join(rootDir, addVolMnt.SubPath) + volName := ToValidDNSLabel(mountPath) + vols = append(vols, v1.Volume{ + Name: volName, + VolumeSource: *addVolMnt.VolumeSource.ToKubernetesVolumeSource(), + }) + mounts = append(mounts, v1.VolumeMount{ + Name: volName, + MountPath: mountPath, + }) + } + + return vols, mounts +} + func (t *VolumeClaimTemplate) ToPVC() *corev1.PersistentVolumeClaim { if t == nil { return nil diff --git a/pkg/apis/ceph.rook.io/v1/zz_generated.deepcopy.go b/pkg/apis/ceph.rook.io/v1/zz_generated.deepcopy.go index fc927f6369d6..bd6bf6821cef 100644 --- a/pkg/apis/ceph.rook.io/v1/zz_generated.deepcopy.go +++ b/pkg/apis/ceph.rook.io/v1/zz_generated.deepcopy.go @@ -43,6 +43,49 @@ func (in *AMQPEndpointSpec) DeepCopy() *AMQPEndpointSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalVolumeMount) DeepCopyInto(out *AdditionalVolumeMount) { + *out = *in + if in.VolumeSource != nil { + in, out := &in.VolumeSource, &out.VolumeSource + *out = new(ConfigFileVolumeSource) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalVolumeMount. +func (in *AdditionalVolumeMount) DeepCopy() *AdditionalVolumeMount { + if in == nil { + return nil + } + out := new(AdditionalVolumeMount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in AdditionalVolumeMounts) DeepCopyInto(out *AdditionalVolumeMounts) { + { + in := &in + *out = make(AdditionalVolumeMounts, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalVolumeMounts. +func (in AdditionalVolumeMounts) DeepCopy() AdditionalVolumeMounts { + if in == nil { + return nil + } + out := new(AdditionalVolumeMounts) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AddressRangesSpec) DeepCopyInto(out *AddressRangesSpec) { *out = *in @@ -2645,6 +2688,13 @@ func (in *GatewaySpec) DeepCopyInto(out *GatewaySpec) { *out = new(bool) **out = **in } + if in.AdditionalVolumeMounts != nil { + in, out := &in.AdditionalVolumeMounts, &out.AdditionalVolumeMounts + *out = make(AdditionalVolumeMounts, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -4315,7 +4365,7 @@ func (in *SSSDSidecar) DeepCopyInto(out *SSSDSidecar) { in.SSSDConfigFile.DeepCopyInto(&out.SSSDConfigFile) if in.AdditionalFiles != nil { in, out := &in.AdditionalFiles, &out.AdditionalFiles - *out = make([]SSSDSidecarAdditionalFile, len(*in)) + *out = make(AdditionalVolumeMounts, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -4334,27 +4384,6 @@ func (in *SSSDSidecar) DeepCopy() *SSSDSidecar { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SSSDSidecarAdditionalFile) DeepCopyInto(out *SSSDSidecarAdditionalFile) { - *out = *in - if in.VolumeSource != nil { - in, out := &in.VolumeSource, &out.VolumeSource - *out = new(ConfigFileVolumeSource) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSSDSidecarAdditionalFile. -func (in *SSSDSidecarAdditionalFile) DeepCopy() *SSSDSidecarAdditionalFile { - if in == nil { - return nil - } - out := new(SSSDSidecarAdditionalFile) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SSSDSidecarConfigFile) DeepCopyInto(out *SSSDSidecarConfigFile) { *out = *in diff --git a/pkg/operator/ceph/nfs/security.go b/pkg/operator/ceph/nfs/security.go index 7d859858cf59..96a1e3dc230a 100644 --- a/pkg/operator/ceph/nfs/security.go +++ b/pkg/operator/ceph/nfs/security.go @@ -18,10 +18,8 @@ package nfs import ( "fmt" - "path/filepath" cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1" - "github.com/rook/rook/pkg/operator/k8sutil" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" ) @@ -144,7 +142,7 @@ func generateSssdSidecarResources(nfs *cephv1.CephNFS, sidecarCfg *cephv1.SSSDSi sssdMounts = append(sssdMounts, mount) } - genericVols, genericMounts := generateGenericFileVolsAndMounts(sidecarCfg.AdditionalFiles) + genericVols, genericMounts := sidecarCfg.AdditionalFiles.GenerateVolumesAndMounts("/etc/sssd/rook-additional/") volumes = append(volumes, genericVols...) sssdMounts = append(sssdMounts, genericMounts...) @@ -293,26 +291,6 @@ func sssdConfigVolAndMount(volSource v1.VolumeSource) (v1.Volume, v1.VolumeMount return vol, mount } -func generateGenericFileVolsAndMounts(additionalFiles []cephv1.SSSDSidecarAdditionalFile) ([]v1.Volume, []v1.VolumeMount) { - vols := []v1.Volume{} - mounts := []v1.VolumeMount{} - - for _, additionalFile := range additionalFiles { - mountPath := filepath.Join("/etc/sssd/rook-additional/", additionalFile.SubPath) - volName := k8sutil.ToValidDNSLabel(mountPath) - vols = append(vols, v1.Volume{ - Name: volName, - VolumeSource: *additionalFile.VolumeSource.ToKubernetesVolumeSource(), - }) - mounts = append(mounts, v1.VolumeMount{ - Name: volName, - MountPath: mountPath, - }) - } - - return vols, mounts -} - func generateSssdNsswitchConfResources(r *ReconcileCephNFS, nfs *cephv1.CephNFS) (*v1.Container, *v1.Volume, *v1.VolumeMount) { volName := "nsswitch-conf" diff --git a/pkg/operator/ceph/object/spec.go b/pkg/operator/ceph/object/spec.go index 701042d950bb..cd34dab8d9fa 100644 --- a/pkg/operator/ceph/object/spec.go +++ b/pkg/operator/ceph/object/spec.go @@ -254,6 +254,10 @@ func (c *clusterConfig) makeRGWPodSpec(rgwConfig *rgwConfig) (v1.PodTemplateSpec } } + addVols, addMounts := c.store.Spec.Gateway.AdditionalVolumeMounts.GenerateVolumesAndMounts("/var/rgw/") + podTemplateSpec.Spec.Volumes = append(podTemplateSpec.Spec.Volumes, addVols...) + podTemplateSpec.Spec.Containers[0].VolumeMounts = append(podTemplateSpec.Spec.Containers[0].VolumeMounts, addMounts...) + return podTemplateSpec, nil } diff --git a/pkg/operator/ceph/object/spec_test.go b/pkg/operator/ceph/object/spec_test.go index e13df82650a1..aeeaa368130e 100644 --- a/pkg/operator/ceph/object/spec_test.go +++ b/pkg/operator/ceph/object/spec_test.go @@ -260,6 +260,34 @@ func TestSSLPodSpec(t *testing.T) { assert.True(t, s.Spec.HostNetwork) assert.Equal(t, v1.DNSClusterFirstWithHostNet, s.Spec.DNSPolicy) + + // add user-defined volume mount + c.store.Spec.Gateway.AdditionalVolumeMounts = cephv1.AdditionalVolumeMounts{ + { + SubPath: "ldap", + VolumeSource: &cephv1.ConfigFileVolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: "my-rgw-ldap-secret", + }, + }, + }, + } + s, err = c.makeRGWPodSpec(rgwConfig) + assert.NoError(t, err) + podTemplate = cephtest.NewPodTemplateSpecTester(t, &s) // checks that vols have corresponding mounts + podTemplate.RunFullSuite(cephconfig.RgwType, "default", "rook-ceph-rgw", "mycluster", "quay.io/ceph/ceph:myversion", + "200", "100", "1337", "500", /* resources */ + "my-priority-class", "default", "cephobjectstores.ceph.rook.io", "ceph-rgw") + assert.True(t, hasSecretVolWithName(s.Spec.Volumes, "my-rgw-ldap-secret")) +} + +func hasSecretVolWithName(vols []v1.Volume, secretName string) bool { + for _, v := range vols { + if v.Secret != nil && v.Secret.SecretName == secretName { + return true + } + } + return false } func TestValidateSpec(t *testing.T) { diff --git a/pkg/operator/k8sutil/k8sutil.go b/pkg/operator/k8sutil/k8sutil.go index bc0ac15b2f7a..79aa66dbadbd 100644 --- a/pkg/operator/k8sutil/k8sutil.go +++ b/pkg/operator/k8sutil/k8sutil.go @@ -192,78 +192,3 @@ func validateLabelValue(value string) string { } return sanitized } - -// ToValidDNSLabel converts a given string to a valid DNS-1035 spec label. The DNS-1035 spec -// follows the regex '[a-z]([-a-z0-9]*[a-z0-9])?' and is at most 63 chars long. DNS-1035 is used -// over DNS-1123 because it is more strict. Kubernetes docs are not always clear when a DNS_LABEL is -// supposed to be 1035 or 1123 compliant, so we use the more strict version for ease of use. -// - Any input symbol that is not valid is converted to a dash ('-'). -// - Multiple resultant dashes in a row are compressed to a single dash. -// - If the starting character is a number, a 'd' is prepended to preserve the number. -// - Any non-alphanumeric starting or ending characters are removed. -// - If the resultant string is longer than the maximum-allowed 63 characters], characters are -// removed from the middle and replaced with a double dash ('--') to reduce the string to 63 -// characters. -func ToValidDNSLabel(input string) string { - maxl := validation.DNS1035LabelMaxLength - - if input == "" { - return "" - } - - outbuf := make([]byte, len(input)+1) - j := 0 // position in output buffer - last := byte('-') - for _, c := range []byte(input) { - switch { - case c >= 'a' && c <= 'z': - outbuf[j] = c - case c >= '0' && c <= '9': - // if the first char is a number, add a 'd' (for decimal) in front - if j == 0 { - outbuf[j] = 'd' // for decimal - j++ - } - outbuf[j] = c - case c >= 'A' && c <= 'Z': - // convert to lower case - outbuf[j] = c - 'A' + 'a' // convert to lower case - default: - if last == '-' { - // don't write two dashes in a row - continue - } - outbuf[j] = byte('-') - } - last = outbuf[j] - j++ - } - - // set the length of the output buffer to the number of chars we copied to it so there aren't - // \0x00 chars at the end - outbuf = outbuf[:j] - - // trim any leading or trailing dashes - out := strings.Trim(string(outbuf), "-") - - // if string is longer than max length, cut content from the middle to get it to length - if len(out) > maxl { - out = cutMiddle(out, maxl) - } - - return out -} - -// don't use this function for anything less than toSize=4 chars long -func cutMiddle(input string, toSize int) string { - if len(input) <= toSize { - return input - } - - lenLeft := toSize / 2 // truncation rounds down the left side - lenRight := toSize/2 + (toSize % 2) // modulo rounds up the right side - - buf := []byte(input) - - return string(buf[:lenLeft-1]) + "--" + string(buf[len(input)-lenRight+1:]) -} diff --git a/pkg/operator/k8sutil/k8sutil_test.go b/pkg/operator/k8sutil/k8sutil_test.go index a4d2a0eff690..173d83f44804 100644 --- a/pkg/operator/k8sutil/k8sutil_test.go +++ b/pkg/operator/k8sutil/k8sutil_test.go @@ -88,83 +88,3 @@ func TestValidateLabelValue(t *testing.T) { assert.Equal(t, result, validateLabelValue(input)) } } - -func TestToValidDNSLabel(t *testing.T) { - tests := []struct { - name string - input string - want string - }{ - {"empty string", "", ""}, - {"single dash", "-", ""}, - {"multiple dashes", "----", ""}, - {"lc a", "a", "a"}, - {"lc z", "z", "z"}, - {"lc alphabet", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"}, - {"UC A", "A", "a"}, - {"UC Z", "Z", "z"}, - {"UC ALPHABET", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"}, - {"mixed case AlPhAbEt", "AbCdEfGhIjKlMnOpQrStUvWxYz", "abcdefghijklmnopqrstuvwxyz"}, - {"single 0", "0", "d0"}, - {"single 9", "9", "d9"}, - {"single 1", "1", "d1"}, - {"numbers", "01234567890", "d01234567890"}, - {"letters with numbers", "1a0b1c2d3e4f5g6h7i8j9k0", "d1a0b1c2d3e4f5g6h7i8j9k0"}, - {"single / symbol", "/", ""}, - {"single : symbol", ":", ""}, - {"single . symbol", ".", ""}, - {"bunch of symbols", "`~!@#$%^&*()_+-={}[]\\|;':\",.<>/?", ""}, - {"alphabet with symbols", - "a~b!c@d#e$f^g&h*i(j)k_l-m+n+o[p]q{r}s|t:u;v'wz", "a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z"}, - {"multiple symbols between letters", "a//b//c", "a-b-c"}, - {"symbol before", "/a/b/c", "a-b-c"}, - {"symbol after", "a/b/c/", "a-b-c"}, - {"symbols before and after", "/a/b/c/", "a-b-c"}, - {"multiple symbols before after between", "//a//b//c//", "a-b-c"}, - {"mix of all tests except length", "//1a//B-c/d_f/../00-thing.ini/", "d1a-b-c-d-f-00-thing-ini"}, - {"too long input -> middle trim", - "qwertyuiopqwertyuiopqwertyuiopaaqwertyuiopqwertyuiopqwertyuiopaa", - "qwertyuiopqwertyuiopqwertyuiop--wertyuiopqwertyuiopqwertyuiopaa"}, - {"too long input but symbols allow for no middle trim", - "/qwertyuiopqwerty/uiopqwertyuiop//qwertyuiopqwerty/uiopqwertyuiop/", - "qwertyuiopqwerty-uiopqwertyuiop-qwertyuiopqwerty-uiopqwertyuiop"}, - {"max allowed length but starts with number -> middle trim", - "123qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiop", - "d123qwertyuiopqwertyuiopqwerty--pqwertyuiopqwertyuiopqwertyuiop"}, - {"max allowed length ok", - "qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiop123", - "qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiop123"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, ToValidDNSLabel(tt.input)) - }) - } -} - -func Test_cutMiddle(t *testing.T) { - // not an exported function, so don't bother with extreme cases like 0, 1, 2, or 3 len inputs - t.Run("len 8 -> 6", func(t *testing.T) { - assert.Equal(t, "ab--gh", cutMiddle("abcdefgh", 6)) - }) - t.Run("len 9 -> 6", func(t *testing.T) { - assert.Equal(t, "ab--hi", cutMiddle("abcdefghi", 6)) - }) - t.Run("len 9 -> 7", func(t *testing.T) { - assert.Equal(t, "ab--ghi", cutMiddle("abcdefghi", 7)) - }) - t.Run("len 10 -> 10", func(t *testing.T) { - assert.Equal(t, "qwertyuiop", cutMiddle("qwertyuiop", 10)) - }) - // below is what we really want to test - t.Run("len 63 -> 63", func(t *testing.T) { - assert.Equal(t, - "qwertyuiopqwertyuiopqwertyuiop12qwertyuiopqwertyuiopqwertyuiop1", - cutMiddle("qwertyuiopqwertyuiopqwertyuiop12qwertyuiopqwertyuiopqwertyuiop1", 63)) - }) - t.Run("len 64 -> 63", func(t *testing.T) { - assert.Equal(t, - "qwertyuiopqwertyuiopqwertyuiop--wertyuiopqwertyuiopqwertyuiop12", - cutMiddle("qwertyuiopqwertyuiopqwertyuiop12qwertyuiopqwertyuiopqwertyuiop12", 63)) - }) -}