From 1d4b13744808eea9199bf196333376a67d342deb Mon Sep 17 00:00:00 2001 From: zhangtao <111836083+sophon-zt@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:00:23 +0800 Subject: [PATCH] feat: support user-defined configmap/secrets (#4885) (#5442) --- apis/apps/v1alpha1/cluster_types.go | 61 +++++ apis/apps/v1alpha1/zz_generated.deepcopy.go | 88 +++++++ .../bases/apps.kubeblocks.io_clusters.yaml | 222 ++++++++++++++++++ controllers/apps/components/component.go | 1 + controllers/apps/components/component_test.go | 2 + .../components/component_workload_builder.go | 63 +++++ .../crds/apps.kubeblocks.io_clusters.yaml | 222 ++++++++++++++++++ pkg/testutil/apps/cluster_factory.go | 44 ++++ 8 files changed, 703 insertions(+) diff --git a/apis/apps/v1alpha1/cluster_types.go b/apis/apps/v1alpha1/cluster_types.go index dec68f06800..8ee63e1a03b 100644 --- a/apis/apps/v1alpha1/cluster_types.go +++ b/apis/apps/v1alpha1/cluster_types.go @@ -177,6 +177,63 @@ type ClusterStorage struct { Size resource.Quantity `json:"size,omitempty"` } +type ResourceMeta struct { + // name is the name of the referenced the Configmap/Secret object. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Pattern:=`^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$` + Name string `json:"name"` + + // mountPath is the path at which to mount the volume. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=256 + // +kubebuilder:validation:Pattern:=`^/[a-z]([a-z0-9\-]*[a-z0-9])?$` + MountPoint string `json:"mountPoint"` + + // subPath is a relative file path within the volume to mount. + // +optional + SubPath string `json:"subPath,omitempty"` + + // asVolumeFrom defines the list of containers where volumeMounts will be injected into. + // +listType=set + // +optional + AsVolumeFrom []string `json:"asVolumeFrom,omitempty"` +} + +type SecretRef struct { + ResourceMeta `json:",inline"` + + // secret defines the secret volume source. + // +kubebuilder:validation:Required + Secret corev1.SecretVolumeSource `json:"secret"` +} + +type ConfigMapRef struct { + ResourceMeta `json:",inline"` + + // configMap defines the configmap volume source. + // +kubebuilder:validation:Required + ConfigMap corev1.ConfigMapVolumeSource `json:"configMap"` +} + +type UserResourceRefs struct { + // secretRefs defines the user-defined secrets. + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +optional + SecretRefs []SecretRef `json:"secretRefs,omitempty"` + + // configMapRefs defines the user-defined configmaps. + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + // +listType=map + // +listMapKey=name + // +optional + ConfigMapRefs []ConfigMapRef `json:"configMapRefs,omitempty"` +} + // ClusterStatus defines the observed state of Cluster. type ClusterStatus struct { // observedGeneration is the most recent generation observed for this @@ -312,6 +369,10 @@ type ClusterComponentSpec struct { // +kubebuilder:default=false // +optional NoCreatePDB bool `json:"noCreatePDB,omitempty"` + + // userResourceRefs defines the user-defined volumes. + // +optional + UserResourceRefs *UserResourceRefs `json:"userResourceRefs,omitempty"` } // GetMinAvailable wraps the 'prefer' value return. As for component replicaCount <= 1, it will return 0, diff --git a/apis/apps/v1alpha1/zz_generated.deepcopy.go b/apis/apps/v1alpha1/zz_generated.deepcopy.go index 5bc1faab4b0..218f76de103 100644 --- a/apis/apps/v1alpha1/zz_generated.deepcopy.go +++ b/apis/apps/v1alpha1/zz_generated.deepcopy.go @@ -552,6 +552,11 @@ func (in *ClusterComponentSpec) DeepCopyInto(out *ClusterComponentSpec) { *out = new(Issuer) (*in).DeepCopyInto(*out) } + if in.UserResourceRefs != nil { + in, out := &in.UserResourceRefs, &out.UserResourceRefs + *out = new(UserResourceRefs) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterComponentSpec. @@ -1822,6 +1827,23 @@ func (in *ConfigConstraintStatus) DeepCopy() *ConfigConstraintStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapRef) DeepCopyInto(out *ConfigMapRef) { + *out = *in + in.ResourceMeta.DeepCopyInto(&out.ResourceMeta) + in.ConfigMap.DeepCopyInto(&out.ConfigMap) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapRef. +func (in *ConfigMapRef) DeepCopy() *ConfigMapRef { + if in == nil { + return nil + } + out := new(ConfigMapRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConfigParams) DeepCopyInto(out *ConfigParams) { *out = *in @@ -3326,6 +3348,26 @@ func (in *ResourceConstraintRule) DeepCopy() *ResourceConstraintRule { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceMeta) DeepCopyInto(out *ResourceMeta) { + *out = *in + if in.AsVolumeFrom != nil { + in, out := &in.AsVolumeFrom, &out.AsVolumeFrom + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceMeta. +func (in *ResourceMeta) DeepCopy() *ResourceMeta { + if in == nil { + return nil + } + out := new(ResourceMeta) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RestoreFromSpec) DeepCopyInto(out *RestoreFromSpec) { *out = *in @@ -3481,6 +3523,23 @@ func (in *ScriptSpecSelector) DeepCopy() *ScriptSpecSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretRef) DeepCopyInto(out *SecretRef) { + *out = *in + in.ResourceMeta.DeepCopyInto(&out.ResourceMeta) + in.Secret.DeepCopyInto(&out.Secret) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretRef. +func (in *SecretRef) DeepCopy() *SecretRef { + if in == nil { + return nil + } + out := new(SecretRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceDescriptor) DeepCopyInto(out *ServiceDescriptor) { *out = *in @@ -4074,6 +4133,35 @@ func (in *Upgrade) DeepCopy() *Upgrade { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserResourceRefs) DeepCopyInto(out *UserResourceRefs) { + *out = *in + if in.SecretRefs != nil { + in, out := &in.SecretRefs, &out.SecretRefs + *out = make([]SecretRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ConfigMapRefs != nil { + in, out := &in.ConfigMapRefs, &out.ConfigMapRefs + *out = make([]ConfigMapRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceRefs. +func (in *UserResourceRefs) DeepCopy() *UserResourceRefs { + if in == nil { + return nil + } + out := new(UserResourceRefs) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ValueFrom) DeepCopyInto(out *ValueFrom) { *out = *in diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml index af998c8e481..4fb79fa1c0d 100644 --- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml +++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml @@ -549,6 +549,228 @@ spec: type: object type: array x-kubernetes-preserve-unknown-fields: true + userResourceRefs: + description: userResourceRefs defines the user-defined volumes. + properties: + configMapRefs: + description: configMapRefs defines the user-defined configmaps. + items: + properties: + asVolumeFrom: + description: asVolumeFrom defines the list of containers + where volumeMounts will be injected into. + items: + type: string + type: array + x-kubernetes-list-type: set + configMap: + description: configMap defines the configmap volume + source. + properties: + defaultMode: + description: 'defaultMode is optional: mode bits + used to set permissions on created files by + default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. + Defaults to 0644. Directories within the path + are not affected by this setting. This might + be in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + items: + description: items if unspecified, each key-value + pair in the Data field of the referenced ConfigMap + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the ConfigMap, the volume + setup will error unless it is marked optional. + Paths must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits + used to set permissions on this file. + Must be an octal value between 0000 and + 0777 or a decimal value between 0 and + 511. YAML accepts both octal and decimal + values, JSON requires decimal values for + mode bits. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of + the file to map the key to. May not be + an absolute path. May not contain the + path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + mountPoint: + description: mountPath is the path at which to mount + the volume. + maxLength: 256 + pattern: ^/[a-z]([a-z0-9\-]*[a-z0-9])?$ + type: string + name: + description: name is the name of the referenced the + Configmap/Secret object. + maxLength: 63 + pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ + type: string + subPath: + description: subPath is a relative file path within + the volume to mount. + type: string + required: + - configMap + - mountPoint + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + secretRefs: + description: secretRefs defines the user-defined secrets. + items: + properties: + asVolumeFrom: + description: asVolumeFrom defines the list of containers + where volumeMounts will be injected into. + items: + type: string + type: array + x-kubernetes-list-type: set + mountPoint: + description: mountPath is the path at which to mount + the volume. + maxLength: 256 + pattern: ^/[a-z]([a-z0-9\-]*[a-z0-9])?$ + type: string + name: + description: name is the name of the referenced the + Configmap/Secret object. + maxLength: 63 + pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ + type: string + secret: + description: secret defines the secret volume source. + properties: + defaultMode: + description: 'defaultMode is Optional: mode bits + used to set permissions on created files by + default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. + Defaults to 0644. Directories within the path + are not affected by this setting. This might + be in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + items: + description: items If unspecified, each key-value + pair in the Data field of the referenced Secret + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the Secret, the volume setup + will error unless it is marked optional. Paths + must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits + used to set permissions on this file. + Must be an octal value between 0000 and + 0777 or a decimal value between 0 and + 511. YAML accepts both octal and decimal + values, JSON requires decimal values for + mode bits. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of + the file to map the key to. May not be + an absolute path. May not contain the + path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: 'secretName is the name of the secret + in the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + subPath: + description: subPath is a relative file path within + the volume to mount. + type: string + required: + - mountPoint + - name + - secret + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object volumeClaimTemplates: description: volumeClaimTemplates information for statefulset.spec.volumeClaimTemplates. items: diff --git a/controllers/apps/components/component.go b/controllers/apps/components/component.go index f6d2d671e65..ba9d13b8419 100644 --- a/controllers/apps/components/component.go +++ b/controllers/apps/components/component.go @@ -187,6 +187,7 @@ func (c *rsmComponent) init(reqCtx intctrlutil.RequestCtx, cli client.Client, bu if err = builder.BuildEnv(). BuildWorkload(). BuildPDB(). + BuildCustomVolumes(). BuildConfig(). BuildTLSVolume(). BuildVolumeMount(). diff --git a/controllers/apps/components/component_test.go b/controllers/apps/components/component_test.go index 9bc403b45e1..35875d2ef63 100644 --- a/controllers/apps/components/component_test.go +++ b/controllers/apps/components/component_test.go @@ -115,6 +115,8 @@ var _ = Describe("Component", func() { SetReplicas(int32(defaultReplicas)). AddVolumeClaimTemplate(testapps.LogVolumeName, testapps.NewPVCSpec(defaultVolumeSize)). AddVolumeClaimTemplate(testapps.DataVolumeName, testapps.NewPVCSpec(defaultVolumeSize)). + AddUserSecretVolume("secret_volumes", "/opt/secrets", "secret_name", "mysql"). + AddUserConfigmapVolume("configmap_volumes", "/opt/scripts", "user_config_name", "mysql"). GetObject() clusterObj.SetUID(types.UID(clusterObj.Name)) diff --git a/controllers/apps/components/component_workload_builder.go b/controllers/apps/components/component_workload_builder.go index cd3ffa32169..e10702300b5 100644 --- a/controllers/apps/components/component_workload_builder.go +++ b/controllers/apps/components/component_workload_builder.go @@ -22,6 +22,7 @@ package components import ( "fmt" + "golang.org/x/exp/slices" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -44,6 +45,7 @@ type componentWorkloadBuilder interface { BuildVolumeMount() componentWorkloadBuilder BuildTLSCert() componentWorkloadBuilder BuildTLSVolume() componentWorkloadBuilder + BuildCustomVolumes() componentWorkloadBuilder Complete() error } @@ -71,6 +73,12 @@ func (b *rsmComponentWorkloadBuilder) BuildEnv() componentWorkloadBuilder { return b.BuildWrapper(buildfn) } +func (b *rsmComponentWorkloadBuilder) BuildCustomVolumes() componentWorkloadBuilder { + return b.BuildWrapper(func() ([]client.Object, error) { + return nil, doBuildCustomVolumes(b.getRuntime(), b.comp.GetCluster(), b.comp.GetName(), b.comp.GetNamespace()) + }) +} + func (b *rsmComponentWorkloadBuilder) BuildConfig() componentWorkloadBuilder { buildfn := func() ([]client.Object, error) { if b.workload == nil { @@ -319,3 +327,58 @@ func composeTLSVolumeMount() corev1.VolumeMount { ReadOnly: true, } } + +func newSourceFromResource(name string, source any) corev1.Volume { + volume := corev1.Volume{ + Name: name, + } + switch t := source.(type) { + default: + panic(fmt.Sprintf("unknown volume source type: %T", t)) + case *corev1.ConfigMapVolumeSource: + volume.VolumeSource.ConfigMap = t + case *corev1.SecretVolumeSource: + volume.VolumeSource.Secret = t + } + return volume +} + +func doBuildCustomVolumes(podSpec *corev1.PodSpec, cluster *appsv1alpha1.Cluster, componentName string, namespace string) error { + comp := cluster.Spec.GetComponentByName(componentName) + if comp == nil || comp.UserResourceRefs == nil { + return nil + } + + volumes := podSpec.Volumes + for _, configMap := range comp.UserResourceRefs.ConfigMapRefs { + volumes = append(volumes, newSourceFromResource(configMap.Name, configMap.ConfigMap.DeepCopy())) + } + for _, secret := range comp.UserResourceRefs.SecretRefs { + volumes = append(volumes, newSourceFromResource(secret.Name, secret.Secret.DeepCopy())) + } + podSpec.Volumes = volumes + buildVolumeMountForContainers(podSpec, *comp.UserResourceRefs) + return nil +} + +func buildVolumeMountForContainers(podSpec *corev1.PodSpec, resourceRefs appsv1alpha1.UserResourceRefs) { + for _, configMap := range resourceRefs.ConfigMapRefs { + newVolumeMount(podSpec, configMap.ResourceMeta) + } + for _, secret := range resourceRefs.SecretRefs { + newVolumeMount(podSpec, secret.ResourceMeta) + } +} + +func newVolumeMount(podSpec *corev1.PodSpec, res appsv1alpha1.ResourceMeta) { + for i := range podSpec.Containers { + container := &podSpec.Containers[i] + if slices.Contains(res.AsVolumeFrom, container.Name) { + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: res.Name, + MountPath: res.MountPoint, + SubPath: res.SubPath, + }) + } + } +} diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml index af998c8e481..4fb79fa1c0d 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml @@ -549,6 +549,228 @@ spec: type: object type: array x-kubernetes-preserve-unknown-fields: true + userResourceRefs: + description: userResourceRefs defines the user-defined volumes. + properties: + configMapRefs: + description: configMapRefs defines the user-defined configmaps. + items: + properties: + asVolumeFrom: + description: asVolumeFrom defines the list of containers + where volumeMounts will be injected into. + items: + type: string + type: array + x-kubernetes-list-type: set + configMap: + description: configMap defines the configmap volume + source. + properties: + defaultMode: + description: 'defaultMode is optional: mode bits + used to set permissions on created files by + default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. + Defaults to 0644. Directories within the path + are not affected by this setting. This might + be in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + items: + description: items if unspecified, each key-value + pair in the Data field of the referenced ConfigMap + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the ConfigMap, the volume + setup will error unless it is marked optional. + Paths must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits + used to set permissions on this file. + Must be an octal value between 0000 and + 0777 or a decimal value between 0 and + 511. YAML accepts both octal and decimal + values, JSON requires decimal values for + mode bits. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of + the file to map the key to. May not be + an absolute path. May not contain the + path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + mountPoint: + description: mountPath is the path at which to mount + the volume. + maxLength: 256 + pattern: ^/[a-z]([a-z0-9\-]*[a-z0-9])?$ + type: string + name: + description: name is the name of the referenced the + Configmap/Secret object. + maxLength: 63 + pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ + type: string + subPath: + description: subPath is a relative file path within + the volume to mount. + type: string + required: + - configMap + - mountPoint + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + secretRefs: + description: secretRefs defines the user-defined secrets. + items: + properties: + asVolumeFrom: + description: asVolumeFrom defines the list of containers + where volumeMounts will be injected into. + items: + type: string + type: array + x-kubernetes-list-type: set + mountPoint: + description: mountPath is the path at which to mount + the volume. + maxLength: 256 + pattern: ^/[a-z]([a-z0-9\-]*[a-z0-9])?$ + type: string + name: + description: name is the name of the referenced the + Configmap/Secret object. + maxLength: 63 + pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ + type: string + secret: + description: secret defines the secret volume source. + properties: + defaultMode: + description: 'defaultMode is Optional: mode bits + used to set permissions on created files by + default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. + Defaults to 0644. Directories within the path + are not affected by this setting. This might + be in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + items: + description: items If unspecified, each key-value + pair in the Data field of the referenced Secret + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the Secret, the volume setup + will error unless it is marked optional. Paths + must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits + used to set permissions on this file. + Must be an octal value between 0000 and + 0777 or a decimal value between 0 and + 511. YAML accepts both octal and decimal + values, JSON requires decimal values for + mode bits. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: path is the relative path of + the file to map the key to. May not be + an absolute path. May not contain the + path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: 'secretName is the name of the secret + in the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + subPath: + description: subPath is a relative file path within + the volume to mount. + type: string + required: + - mountPoint + - name + - secret + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object volumeClaimTemplates: description: volumeClaimTemplates information for statefulset.spec.volumeClaimTemplates. items: diff --git a/pkg/testutil/apps/cluster_factory.go b/pkg/testutil/apps/cluster_factory.go index 417f5267aa0..1f7b153d4d2 100644 --- a/pkg/testutil/apps/cluster_factory.go +++ b/pkg/testutil/apps/cluster_factory.go @@ -180,3 +180,47 @@ func (factory *MockClusterFactory) SetServiceRefs(serviceRefs []appsv1alpha1.Ser comp.ServiceRefs = serviceRefs }) } + +func (factory *MockClusterFactory) AddUserSecretVolume(name, mountPoint, resName, containerName string) *MockClusterFactory { + secretResource := appsv1alpha1.SecretRef{ + ResourceMeta: appsv1alpha1.ResourceMeta{ + Name: name, + MountPoint: mountPoint, + AsVolumeFrom: []string{containerName}, + }, + Secret: corev1.SecretVolumeSource{ + SecretName: resName, + }, + } + return factory.lastComponentRef(func(comp *appsv1alpha1.ClusterComponentSpec) { + userResourcesRefs := comp.UserResourceRefs + if userResourcesRefs == nil { + userResourcesRefs = &appsv1alpha1.UserResourceRefs{} + comp.UserResourceRefs = userResourcesRefs + } + userResourcesRefs.SecretRefs = append(userResourcesRefs.SecretRefs, secretResource) + }) +} + +func (factory *MockClusterFactory) AddUserConfigmapVolume(name, mountPoint, resName, containerName string) *MockClusterFactory { + cmResource := appsv1alpha1.ConfigMapRef{ + ResourceMeta: appsv1alpha1.ResourceMeta{ + Name: name, + MountPoint: mountPoint, + AsVolumeFrom: []string{containerName}, + }, + ConfigMap: corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: resName, + }, + }, + } + return factory.lastComponentRef(func(comp *appsv1alpha1.ClusterComponentSpec) { + userResourcesRefs := comp.UserResourceRefs + if userResourcesRefs == nil { + userResourcesRefs = &appsv1alpha1.UserResourceRefs{} + comp.UserResourceRefs = userResourcesRefs + } + userResourcesRefs.ConfigMapRefs = append(userResourcesRefs.ConfigMapRefs, cmResource) + }) +}