diff --git a/README.md b/README.md index 982f4364..3d3dbcc5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ which can be used to deploy valkey caches for cluster-internal usage. For exampl ```yaml apiVersion: cache.cs.sap.com/v1alpha1 -kind: Valkey +kind: Valkey metadata: name: test spec: @@ -30,6 +30,75 @@ to install valkey in the cluster. As a consequence of this fact, the following t Sharding (valkey-cluster) scenarios are not supported. +### Image and version + +By default the operator deploys the `bitnamilegacy/valkey` image from Docker Hub, at the tag the underlying +chart pins. The image can be customized through the following attributes: + +- `spec.version` sets the image tag (shorthand for `spec.image.tag`). +- `spec.image.registry`, `spec.image.repository` and `spec.image.tag` address the Valkey server image. + `spec.image.tag` takes precedence over `spec.version`. +- `spec.image.pullPolicy` sets the pull policy of the server, sentinel and exporter images. +- `spec.image.pullSecrets` references secrets used to pull from a private registry; they are set on the + pod, so they cover its sidecar images too. +- `spec.sentinel.image` and `spec.metrics.image` address the sentinel and metrics exporter images, each + taking its own `registry`, `repository` and `tag`. `spec.sentinel.image` covers the sidecar only; the + Valkey server container in a sentinel pod takes `spec.image`. + +Both sidecars inherit `registry` from `spec.image`; the sentinel inherits `tag` too, while the exporter +carries its own version rather than Valkey's. `repository` never inherits: each image keeps its own +default, so mirroring into a flat path means setting `repository` on each image. + +The tag is used verbatim, so it is a literal image tag rather than a bare Valkey version. The bundled +Bitnami-based images carry an OS/revision suffix, e.g. `8.1.2-debian-12-r0`, and a bare `8.1.2` only works +if your registry publishes it. + +For example, to pull all images from a private mirror of the bundled repositories: + +```yaml +spec: + image: + registry: registry.example.com + tag: 8.1.2-debian-12-r0 + pullSecrets: + - my-registry-secret +``` + +`spec.image.registry` replaces the registry segment only, so the images resolve to: + +- `registry.example.com/bitnamilegacy/valkey:8.1.2-debian-12-r0` +- `registry.example.com/bitnamilegacy/valkey-sentinel:8.1.2-debian-12-r0` (sentinel mode) +- `registry.example.com/bitnamilegacy/redis-exporter:` (if metrics are enabled) + +If your mirror uses a different layout, spell each image out in full. A `registry` may include a path prefix: + +```yaml +spec: + image: + registry: harbor.example.com/dockerhub + repository: mycorp/valkey + tag: 8.1.2-debian-12-r0 + sentinel: + enabled: true + image: + repository: mycorp/valkey-sentinel + metrics: + enabled: true + image: + registry: quay.example.com + repository: mycorp/redis-exporter + tag: 1.67.0-debian-12-r0 +``` + +The pull secrets have to exist in the namespace of the `Valkey` object; the operator does not create them. + +Without `spec.metrics.image.tag` the exporter uses whatever `metrics.image.tag` is pinned to in +[the bundled chart](pkg/operator/data/charts/valkey/values.yaml). That pin moves when the operator bumps the +chart, so set the tag explicitly if you mirror the exporter. + +Server, sentinel and exporter are the only images the operator renders itself. It passes images from +`spec.sidecars` through unchanged. + ### Sentinel mode If `spec.sentinel.enabled` is false, one valkey primary node will be deployed, and `spec.replicas - 1` read replicas. diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go index a537b1a9..bef57901 100644 --- a/api/v1alpha1/types.go +++ b/api/v1alpha1/types.go @@ -24,6 +24,7 @@ type ValkeySpec struct { Replicas int `json:"replicas,omitempty"` component.KubernetesPodProperties `json:",inline"` component.KubernetesContainerProperties `json:",inline"` + Image *ImageProperties `json:"image,omitempty"` Sidecars []corev1.Container `json:"sidecars,omitempty"` Sentinel *SentinelProperties `json:"sentinel,omitempty"` Metrics *MetricsProperties `json:"metrics,omitempty"` @@ -34,15 +35,41 @@ type ValkeySpec struct { ExtraFlags []string `json:"extraFlags,omitempty"` } +// ImageProperties addresses the Valkey server image. Pull policy applies to the server, sentinel +// and exporter images. Pull secrets are set on the pod, so they cover its sidecar images too. +// The sentinel also inherits registry and tag; the exporter inherits only registry. +type ImageProperties struct { + Registry string `json:"registry,omitempty"` + Repository string `json:"repository,omitempty"` + Tag string `json:"tag,omitempty"` + // +kubebuilder:validation:Enum=Always;Never;IfNotPresent + PullPolicy corev1.PullPolicy `json:"pullPolicy,omitempty"` + PullSecrets []string `json:"pullSecrets,omitempty"` +} + +// ImageOverride addresses one image on its own. Registry falls back to spec.image, and so does the +// sentinel's tag. Repository never falls back: each image keeps its own default. Pull policy and +// pull secrets always come from spec.image. +type ImageOverride struct { + Registry string `json:"registry,omitempty"` + Repository string `json:"repository,omitempty"` + Tag string `json:"tag,omitempty"` +} + // SentinelProperties models attributes of the sentinel sidecar type SentinelProperties struct { - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled,omitempty"` + // Image of the sentinel sidecar. The valkey server container in the same pod takes its image + // from spec.image. + Image *ImageOverride `json:"image,omitempty"` component.KubernetesContainerProperties `json:",inline"` } // MetricsProperties models attributes of the metrics exporter sidecar type MetricsProperties struct { - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled,omitempty"` + // Image of the metrics exporter. Its tag defaults to the version bundled with the chart. + Image *ImageOverride `json:"image,omitempty"` component.KubernetesContainerProperties `json:",inline"` ServiceMonitor *MetricsServiceMonitorProperties `json:"monitor,omitempty"` PrometheusRule *MetricsPrometheusRuleProperties `json:"prometheusRule,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c5c0804e..a7e77c6c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -55,6 +55,41 @@ func (in *CertManagerProperties) DeepCopy() *CertManagerProperties { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageOverride) DeepCopyInto(out *ImageOverride) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageOverride. +func (in *ImageOverride) DeepCopy() *ImageOverride { + if in == nil { + return nil + } + out := new(ImageOverride) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageProperties) DeepCopyInto(out *ImageProperties) { + *out = *in + if in.PullSecrets != nil { + in, out := &in.PullSecrets, &out.PullSecrets + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageProperties. +func (in *ImageProperties) DeepCopy() *ImageProperties { + if in == nil { + return nil + } + out := new(ImageProperties) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MetricsPrometheusRuleProperties) DeepCopyInto(out *MetricsPrometheusRuleProperties) { *out = *in @@ -87,6 +122,11 @@ func (in *MetricsPrometheusRuleProperties) DeepCopy() *MetricsPrometheusRuleProp // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MetricsProperties) DeepCopyInto(out *MetricsProperties) { *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(ImageOverride) + **out = **in + } in.KubernetesContainerProperties.DeepCopyInto(&out.KubernetesContainerProperties) if in.ServiceMonitor != nil { in, out := &in.ServiceMonitor, &out.ServiceMonitor @@ -196,6 +236,11 @@ func (in *PersistenceProperties) DeepCopy() *PersistenceProperties { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SentinelProperties) DeepCopyInto(out *SentinelProperties) { *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(ImageOverride) + **out = **in + } in.KubernetesContainerProperties.DeepCopyInto(&out.KubernetesContainerProperties) } @@ -293,6 +338,11 @@ func (in *ValkeySpec) DeepCopyInto(out *ValkeySpec) { *out = *in in.KubernetesPodProperties.DeepCopyInto(&out.KubernetesPodProperties) in.KubernetesContainerProperties.DeepCopyInto(&out.KubernetesContainerProperties) + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(ImageProperties) + (*in).DeepCopyInto(*out) + } if in.Sidecars != nil { in, out := &in.Sidecars, &out.Sidecars *out = make([]v1.Container, len(*in)) diff --git a/crds/cache.cs.sap.com_valkeys.yaml b/crds/cache.cs.sap.com_valkeys.yaml index f7e9a6c2..4967251b 100644 --- a/crds/cache.cs.sap.com_valkeys.yaml +++ b/crds/cache.cs.sap.com_valkeys.yaml @@ -1127,12 +1127,48 @@ spec: items: type: string type: array + image: + description: |- + ImageProperties addresses the Valkey server image. Pull policy applies to the server, sentinel + and exporter images. Pull secrets are set on the pod, so they cover its sidecar images too. + The sentinel also inherits registry and tag; the exporter inherits only registry. + properties: + pullPolicy: + description: PullPolicy describes a policy for if/when to pull + a container image + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecrets: + items: + type: string + type: array + registry: + type: string + repository: + type: string + tag: + type: string + type: object metrics: description: MetricsProperties models attributes of the metrics exporter sidecar properties: enabled: type: boolean + image: + description: Image of the metrics exporter. Its tag defaults to + the version bundled with the chart. + properties: + registry: + type: string + repository: + type: string + tag: + type: string + type: object monitor: properties: additionalLabels: @@ -4081,6 +4117,18 @@ spec: properties: enabled: type: boolean + image: + description: |- + Image of the sentinel sidecar. The valkey server container in the same pod takes its image + from spec.image. + properties: + registry: + type: string + repository: + type: string + tag: + type: string + type: object resources: description: ResourceRequirements describes the compute resource requirements. diff --git a/pkg/operator/data/binding.yaml b/pkg/operator/data/binding.yaml index 9dacbef8..d0a771f3 100644 --- a/pkg/operator/data/binding.yaml +++ b/pkg/operator/data/binding.yaml @@ -11,7 +11,7 @@ primaryPort: {{ .primaryPort }} replicaHost: {{ .replicaHost }} replicaPort: {{ .replicaPort }} {{- end }} -password: {{ .password }} +password: {{ .password | quote }} {{- if .tlsEnabled }} tlsEnabled: true caData: | diff --git a/pkg/operator/data/parameters.yaml b/pkg/operator/data/parameters.yaml index 1665b162..0234ca14 100644 --- a/pkg/operator/data/parameters.yaml +++ b/pkg/operator/data/parameters.yaml @@ -14,18 +14,52 @@ fullnameOverride: {{ $fullname }} {{- $bindingSecretName = printf "%s-binding" $fullname }} {{- end }} +{{- /* the chart's other two images, os-shell and kubectl, stay unrendered: + volumePermissions and sentinel.service.createPrimary are off */}} +{{- $imageRegistry := (dig "image" "registry" "" .) }} +{{- $imageRepository := (dig "image" "repository" "bitnamilegacy/valkey" .) }} +{{- $imageTag := (dig "image" "tag" (dig "version" "" .) .) }} +{{- /* the chart has no global.imagePullPolicy, so it is set on every image block below */}} +{{- $imagePullPolicy := (dig "image" "pullPolicy" "" .) }} +{{- $imagePullSecrets := (dig "image" "pullSecrets" (list) .) }} +{{- /* global.imageRegistry would win over these, so each image carries its own registry */}} +{{- $sentinelImageRegistry := (dig "sentinel" "image" "registry" $imageRegistry .) }} +{{- $sentinelImageRepository := (dig "sentinel" "image" "repository" "bitnamilegacy/valkey-sentinel" .) }} +{{- $sentinelImageTag := (dig "sentinel" "image" "tag" $imageTag .) }} +{{- $metricsImageRegistry := (dig "metrics" "image" "registry" $imageRegistry .) }} +{{- $metricsImageRepository := (dig "metrics" "image" "repository" "bitnamilegacy/redis-exporter" .) }} +{{- /* the exporter is versioned on its own, so its tag does not follow spec.version */}} +{{- $metricsImageTag := (dig "metrics" "image" "tag" "" .) }} + +{{- $storageClass := "" }} +{{- if $persistenceEnabled }} +{{- $storageClass = (dig "persistence" "storageClass" "" .) }} +{{- end }} + image: - repository: bitnamilegacy/valkey - {{- with .Version}} - tag: {{ . }} + {{- with $imageRegistry }} + registry: {{ . | quote }} + {{- end }} + repository: {{ $imageRepository | quote }} + {{- with $imageTag }} + tag: {{ . | quote }} + {{- end }} + {{- with $imagePullPolicy }} + pullPolicy: {{ . | quote }} {{- end }} -{{- if $persistenceEnabled }} -{{- with .persistence.storageClass }} global: - storageClass: {{ . }} -{{- end }} -{{- end }} + {{- with $storageClass }} + storageClass: {{ . | quote }} + {{- end }} + {{- with $imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- /* the renderer skips NOTES.txt, where this check lives, so it is inert here. + `helm template` still needs it: the repositories above are always relocated */}} + security: + allowInsecureImages: true {{- if gt .replicas 1 }} pdb: @@ -52,7 +86,7 @@ primary: {{- toYaml . | nindent 2 }} {{- end }} {{- with .priorityClassName }} - priorityClassName: {{ . }} + priorityClassName: {{ . | quote }} {{- end }} {{- with .podSecurityContext }} podSecurityContext: @@ -78,7 +112,7 @@ primary: enabled: {{ $persistenceEnabled }} {{- if $persistenceEnabled }} {{- with .persistence.size }} - size: {{ . }} + size: {{ . | quote }} {{- end }} {{- end }} {{- if .persistence }} @@ -121,7 +155,7 @@ replica: {{- toYaml . | nindent 2 }} {{- end }} {{- with .priorityClassName }} - priorityClassName: {{ . }} + priorityClassName: {{ . | quote }} {{- end }} {{- with .podSecurityContext }} podSecurityContext: @@ -147,7 +181,7 @@ replica: enabled: {{ $persistenceEnabled }} {{- if $persistenceEnabled }} {{- with .persistence.size }} - size: {{ . }} + size: {{ . | quote }} {{- end }} {{- end }} {{- if .persistence }} @@ -175,9 +209,15 @@ replica: sentinel: enabled: true image: - repository: bitnamilegacy/valkey-sentinel - {{- with .version }} - tag: {{ . }} + {{- with $sentinelImageRegistry }} + registry: {{ . | quote }} + {{- end }} + repository: {{ $sentinelImageRepository | quote }} + {{- with $sentinelImageTag }} + tag: {{ . | quote }} + {{- end }} + {{- with $imagePullPolicy }} + pullPolicy: {{ . | quote }} {{- end }} {{- with .sentinel.resources }} resources: @@ -197,7 +237,16 @@ sentinel: metrics: enabled: true image: - repository: bitnamilegacy/redis-exporter + {{- with $metricsImageRegistry }} + registry: {{ . | quote }} + {{- end }} + repository: {{ $metricsImageRepository | quote }} + {{- with $metricsImageTag }} + tag: {{ . | quote }} + {{- end }} + {{- with $imagePullPolicy }} + pullPolicy: {{ . | quote }} + {{- end }} {{- with .metrics.resources }} resources: {{- toYaml . | nindent 4 }} @@ -234,16 +283,12 @@ tls: networkPolicy: enabled: true - extraEgress: - - ports: - - protocol: UDP - - protocol: TCP extraDeploy: - apiVersion: v1 kind: Secret metadata: - name: {{ $bindingSecretName }} + name: {{ $bindingSecretName | quote }} annotations: helm.sh/hook: post-install type: Opaque diff --git a/pkg/operator/parameters_test.go b/pkg/operator/parameters_test.go new file mode 100644 index 00000000..459f948e --- /dev/null +++ b/pkg/operator/parameters_test.go @@ -0,0 +1,246 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and valkey-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package operator + +import ( + "strings" + "testing" + + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/sap/component-operator-runtime/pkg/component" + "github.com/sap/component-operator-runtime/pkg/manifests" + + operatorv1alpha1 "github.com/sap/valkey-operator/api/v1alpha1" +) + +func TestImageParameters(t *testing.T) { + transformer, err := manifests.NewTemplateParameterTransformer(data, "data/parameters.yaml") + NewWithT(t).Expect(err).NotTo(HaveOccurred()) + + render := func(t *testing.T, spec *operatorv1alpha1.ValkeySpec) map[string]any { + t.Helper() + parameters, err := transformer.TransformParameters("testns", "test", spec) + NewWithT(t).Expect(err).NotTo(HaveOccurred()) + return parameters.ToUnstructured() + } + + // pull secrets are pod-level, so they stay global. the registry cannot: global.imageRegistry + // would win over every per-image registry + t.Run("registry is per image, pull secrets stay global", func(t *testing.T) { + g := NewWithT(t) + values := render(t, &operatorv1alpha1.ValkeySpec{ + Replicas: 1, + Image: &operatorv1alpha1.ImageProperties{ + Registry: "registry.example.com", + Repository: "mirror/valkey", + PullSecrets: []string{"my-pull-secret"}, + }, + }) + + g.Expect(values["image"]).To(HaveKeyWithValue("registry", "registry.example.com")) + g.Expect(values["image"]).To(HaveKeyWithValue("repository", "mirror/valkey")) + g.Expect(values["global"]).NotTo(HaveKey("imageRegistry")) + g.Expect(values["global"]).To(HaveKeyWithValue("imagePullSecrets", []any{"my-pull-secret"})) + }) + + // the sidecars default to spec.image. the chart has no global pull policy, so pullPolicy is + // repeated per image + t.Run("sidecars inherit from spec.image", func(t *testing.T) { + g := NewWithT(t) + values := render(t, &operatorv1alpha1.ValkeySpec{ + Replicas: 3, + Version: "8.1.3-debian-12-r0", + Image: &operatorv1alpha1.ImageProperties{ + Registry: "registry.example.com", + PullPolicy: "Always", + }, + Sentinel: &operatorv1alpha1.SentinelProperties{Enabled: true}, + Metrics: &operatorv1alpha1.MetricsProperties{Enabled: true}, + }) + + g.Expect(values["image"]).To(HaveKeyWithValue("tag", "8.1.3-debian-12-r0")) + g.Expect(values["image"]).To(HaveKeyWithValue("pullPolicy", "Always")) + g.Expect(values["sentinel"]).To(HaveKeyWithValue("image", map[string]any{ + "registry": "registry.example.com", + "repository": "bitnamilegacy/valkey-sentinel", + "tag": "8.1.3-debian-12-r0", + "pullPolicy": "Always", + })) + // the exporter takes the registry but keeps its own version + g.Expect(values["metrics"]).To(HaveKeyWithValue("image", map[string]any{ + "registry": "registry.example.com", + "repository": "bitnamilegacy/redis-exporter", + "pullPolicy": "Always", + })) + }) + + // every image is addressable on its own, so a mirror can use any layout + t.Run("each image takes a full path", func(t *testing.T) { + g := NewWithT(t) + values := render(t, &operatorv1alpha1.ValkeySpec{ + Replicas: 3, + Image: &operatorv1alpha1.ImageProperties{ + Registry: "one.example.com/dockerhub", + Repository: "mycorp/valkey", + Tag: "8.1.3-debian-12-r0", + }, + Sentinel: &operatorv1alpha1.SentinelProperties{ + Enabled: true, + Image: &operatorv1alpha1.ImageOverride{ + Registry: "two.example.com", + Repository: "mycorp/valkey-sentinel", + Tag: "8.1.4-debian-12-r0", + }, + }, + Metrics: &operatorv1alpha1.MetricsProperties{ + Enabled: true, + Image: &operatorv1alpha1.ImageOverride{ + Registry: "three.example.com", + Repository: "mycorp/redis-exporter", + Tag: "1.67.0-debian-12-r0", + }, + }, + }) + + g.Expect(values["image"]).To(Equal(map[string]any{ + "registry": "one.example.com/dockerhub", + "repository": "mycorp/valkey", + "tag": "8.1.3-debian-12-r0", + })) + g.Expect(values["sentinel"]).To(HaveKeyWithValue("image", map[string]any{ + "registry": "two.example.com", + "repository": "mycorp/valkey-sentinel", + "tag": "8.1.4-debian-12-r0", + })) + g.Expect(values["metrics"]).To(HaveKeyWithValue("image", map[string]any{ + "registry": "three.example.com", + "repository": "mycorp/redis-exporter", + "tag": "1.67.0-debian-12-r0", + })) + }) + + // default repository, plus the waiver the chart needs for relocated repositories + t.Run("defaults", func(t *testing.T) { + g := NewWithT(t) + values := render(t, &operatorv1alpha1.ValkeySpec{Replicas: 1}) + + g.Expect(values["image"]).To(Equal(map[string]any{"repository": "bitnamilegacy/valkey"})) + g.Expect(values["global"]).To(Equal(map[string]any{ + "security": map[string]any{"allowInsecureImages": true}, + })) + }) + + t.Run("image.tag wins over version", func(t *testing.T) { + values := render(t, &operatorv1alpha1.ValkeySpec{ + Replicas: 1, + Version: "8.1.3-debian-12-r0", + Image: &operatorv1alpha1.ImageProperties{Tag: "8.1.4-debian-12-r0"}, + }) + + NewWithT(t).Expect(values["image"]).To(HaveKeyWithValue("tag", "8.1.4-debian-12-r0")) + }) + + // spec values are interpolated into yaml. unquoted, any of them could inject arbitrary chart + // values, e.g. turn authentication off + t.Run("interpolated values cannot inject chart values", func(t *testing.T) { + size := resource.MustParse("1Gi") + + // the first three add a sibling key at 0, 2 and 4 spaces of indentation, reaching a value at + // any depth. the last overwrites a key instead of adding one. + // payloads must not contain "", which the renderer strips after quoting + payloads := []string{ + "\nauth:\n enabled: false", + "\n imageRegistry: evil.example.com", + "\n pullPolicy: Never", + "\nfullnameOverride: valkey-other", + } + + // takes the payload back out of the strings it landed in. a render that quoted every value + // normalizes back to the payload-free one. a render that injected does not: it has extra + // keys, overwritten ones, or no longer parses as yaml + var strip func(value any, payload string) any + strip = func(value any, payload string) any { + switch value := value.(type) { + case map[string]any: + stripped := make(map[string]any, len(value)) + for key, item := range value { + stripped[key] = strip(item, payload) + } + return stripped + case []any: + stripped := make([]any, len(value)) + for i, item := range value { + stripped[i] = strip(item, payload) + } + return stripped + case string: + return strings.ReplaceAll(value, payload, "") + } + return value + } + + // the payload goes into every field of the block, so one unquoted field fails the case + for name, build := range map[string]func(payload string) *operatorv1alpha1.ValkeySpec{ + "server image": func(payload string) *operatorv1alpha1.ValkeySpec { + return &operatorv1alpha1.ValkeySpec{Replicas: 1, Image: &operatorv1alpha1.ImageProperties{ + Registry: "registry.example.com" + payload, + Repository: "mirror/valkey" + payload, + Tag: "8.1.3" + payload, + }} + }, + "sentinel image": func(payload string) *operatorv1alpha1.ValkeySpec { + return &operatorv1alpha1.ValkeySpec{Replicas: 1, Sentinel: &operatorv1alpha1.SentinelProperties{ + Enabled: true, + Image: &operatorv1alpha1.ImageOverride{ + Registry: "two.example.com" + payload, + Repository: "mirror/valkey-sentinel" + payload, + Tag: "8.1.3" + payload, + }, + }} + }, + "metrics image": func(payload string) *operatorv1alpha1.ValkeySpec { + return &operatorv1alpha1.ValkeySpec{Replicas: 1, Metrics: &operatorv1alpha1.MetricsProperties{ + Enabled: true, + Image: &operatorv1alpha1.ImageOverride{ + Registry: "three.example.com" + payload, + Repository: "mirror/redis-exporter" + payload, + Tag: "1.67.0" + payload, + }, + }} + }, + "storage class": func(payload string) *operatorv1alpha1.ValkeySpec { + return &operatorv1alpha1.ValkeySpec{Replicas: 1, Persistence: &operatorv1alpha1.PersistenceProperties{ + Enabled: true, + Size: &size, + StorageClass: "fast" + payload, + }} + }, + "binding secret name": func(payload string) *operatorv1alpha1.ValkeySpec { + return &operatorv1alpha1.ValkeySpec{Replicas: 1, Binding: &operatorv1alpha1.BindingProperties{ + SecretName: "my-binding" + payload, + }} + }, + "priority class name": func(payload string) *operatorv1alpha1.ValkeySpec { + priorityClassName := "high" + payload + return &operatorv1alpha1.ValkeySpec{Replicas: 1, KubernetesPodProperties: component.KubernetesPodProperties{ + PriorityClassName: &priorityClassName, + }} + }, + } { + t.Run(name, func(t *testing.T) { + g := NewWithT(t) + want := render(t, build("")) + + for _, payload := range payloads { + g.Expect(strip(render(t, build(payload)), payload)).To(Equal(want)) + } + }) + } + }) +}