rgw: allow users to add custom volume mounts

Allow RGW users to mount arbitrary volumes to RGW pods. This follows the
pattern that was established for NFS to support SSSD and LDAP, and
reuses much of the same code.

This opens the door wider for advanced users to take advantage of some
Ceph RGW features that Rook doesn't have first class support for.

Signed-off-by: Blaine Gardner <blaine.gardner@ibm.com>
(cherry picked from commit eea43b7b79)
This commit is contained in:
Blaine Gardner authored and Mergify committed 2024-08-28 22:51:16 +00:00
1 parent 23e2663504
commit ad5b332e77
16 files changed
+945 -293

No files matched your search

@@ -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: <name of TLS secret for automatic generation>
```
Example of external rgw endpoints to connect to:
```yaml
gateway:
port: 80
externalRgwEndpoints:
- ip: 192.168.39.182
# hostname: example.com
```
```
## Zone Settings
+76 -53
View File
@@ -2578,6 +2578,61 @@ string
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.AdditionalVolumeMount">AdditionalVolumeMount
</h3>
<div>
<p>AdditionalVolumeMount represents the source from where additional files in pod containers
should come from and what subdirectory they are made available in.</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>subPath</code><br/>
<em>
string
</em>
</td>
<td>
<p>SubPath defines the sub-path (subdirectory) of the directory root where the volumeSource will
be mounted. All files/keys in the volume source&rsquo;s volume will be mounted to the subdirectory.
This is not the same as the Kubernetes <code>subPath</code> volume mount option.
Each subPath definition must be unique and must not contain &lsquo;:&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>volumeSource</code><br/>
<em>
<a href="#ceph.rook.io/v1.ConfigFileVolumeSource">
ConfigFileVolumeSource
</a>
</em>
</td>
<td>
<p>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 <code>&lt;directory-root&gt;/&lt;subPath&gt;</code> 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.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.AdditionalVolumeMounts">AdditionalVolumeMounts
(<code>[]github.com/rook/rook/pkg/apis/ceph.rook.io/v1.AdditionalVolumeMount</code> alias)</h3>
<p>
(<em>Appears on:</em><a href="#ceph.rook.io/v1.GatewaySpec">GatewaySpec</a>, <a href="#ceph.rook.io/v1.SSSDSidecar">SSSDSidecar</a>)
</p>
<div>
</div>
<h3 id="ceph.rook.io/v1.AddressRangesSpec">AddressRangesSpec
</h3>
<p>
@@ -5089,7 +5144,7 @@ blocking deletion.</p>
<h3 id="ceph.rook.io/v1.ConfigFileVolumeSource">ConfigFileVolumeSource
</h3>
<p>
(<em>Appears on:</em><a href="#ceph.rook.io/v1.KerberosConfigFiles">KerberosConfigFiles</a>, <a href="#ceph.rook.io/v1.KerberosKeytabFile">KerberosKeytabFile</a>, <a href="#ceph.rook.io/v1.SSSDSidecarAdditionalFile">SSSDSidecarAdditionalFile</a>, <a href="#ceph.rook.io/v1.SSSDSidecarConfigFile">SSSDSidecarConfigFile</a>)
(<em>Appears on:</em><a href="#ceph.rook.io/v1.AdditionalVolumeMount">AdditionalVolumeMount</a>, <a href="#ceph.rook.io/v1.KerberosConfigFiles">KerberosConfigFiles</a>, <a href="#ceph.rook.io/v1.KerberosKeytabFile">KerberosKeytabFile</a>, <a href="#ceph.rook.io/v1.SSSDSidecarConfigFile">SSSDSidecarConfigFile</a>)
</p>
<div>
<p>Represents the source of a volume to mount.
@@ -6958,6 +7013,22 @@ bool
<p>Whether rgw dashboard is enabled for the rgw daemon. If not set, the rgw dashboard will be enabled.</p>
</td>
</tr>
<tr>
<td>
<code>additionalVolumeMounts</code><br/>
<em>
<a href="#ceph.rook.io/v1.AdditionalVolumeMounts">
AdditionalVolumeMounts
</a>
</em>
</td>
<td>
<p>AdditionalVolumeMounts allows additional volumes to be mounted to the RGW pod.
The root directory for each additional volume mount is <code>/var/rgw</code>.
Example: for an additional mount at subPath <code>ldap</code>, mounted from a secret that has key
<code>bindpass.secret</code>, the file would reside at <code>/var/rgw/ldap/bindpass.secret</code>.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.HTTPEndpointSpec">HTTPEndpointSpec
@@ -11452,15 +11523,16 @@ securely add the file via annotations on the CephNFS spec (passed to the NFS ser
<td>
<code>additionalFiles</code><br/>
<em>
<a href="#ceph.rook.io/v1.SSSDSidecarAdditionalFile">
[]SSSDSidecarAdditionalFile
<a href="#ceph.rook.io/v1.AdditionalVolumeMounts">
AdditionalVolumeMounts
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
sidecar with a directory root of <code>/etc/sssd/rook-additional/</code>.
These files may be referenced by the sssd.conf config file.</p>
</td>
</tr>
<tr>
@@ -11493,55 +11565,6 @@ this may be a value between 1 and 10. See SSSD docs for more info:
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.SSSDSidecarAdditionalFile">SSSDSidecarAdditionalFile
</h3>
<p>
(<em>Appears on:</em><a href="#ceph.rook.io/v1.SSSDSidecar">SSSDSidecar</a>)
</p>
<div>
<p>SSSDSidecarAdditionalFile represents the source from where additional files for the the SSSD
configuration should come from and are made available.</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>subPath</code><br/>
<em>
string
</em>
</td>
<td>
<p>SubPath defines the sub-path in <code>/etc/sssd/rook-additional/</code> where the additional file(s)
will be placed. Each subPath definition must be unique and must not contain &lsquo;:&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>volumeSource</code><br/>
<em>
<a href="#ceph.rook.io/v1.ConfigFileVolumeSource">
ConfigFileVolumeSource
</a>
</em>
</td>
<td>
<p>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 <code>/etc/sssd/rook-additional/&lt;subPath&gt;</code> 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.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.SSSDSidecarConfigFile">SSSDSidecarConfigFile
</h3>
<p>
@@ -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
+274 -5
View File
@@ -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
+8
View File
@@ -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
+78
View File
@@ -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:])
}
+80
View File
@@ -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'w<x,y>z", "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))
})
}
+5 -5
View File
@@ -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},
+21 -10
View File
@@ -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/<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.
// 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.
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 == ""`
+24
View File
@@ -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
@@ -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
+1 -23
View File
@@ -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"
+4
View File
@@ -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
}
+28
View File
@@ -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) {
-75
View File
@@ -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:])
}
-80
View File
@@ -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'w<x,y>z", "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))
})
}