From a430f76a657ba45fb3c628b672e76cd5b64868ff Mon Sep 17 00:00:00 2001 From: Martin Briatka Date: Mon, 9 Nov 2026 13:44:22 +0100 Subject: [PATCH] Allow configuring additional annotations on the generated workload Add a spec.annotations (map[string]string) field. The operator merges its entries onto the metadata of every generated StatefulSet. The merge is done in the operator rather than in the chart templates, so it is independent of the vendored chart. The field is empty by default, so existing deployments are unaffected. --- api/v1alpha1/types.go | 3 + api/v1alpha1/zz_generated.deepcopy.go | 7 +++ crds/cache.cs.sap.com_valkeys.yaml | 6 ++ internal/transformer/annotation_generator.go | 64 ++++++++++++++++++++ pkg/operator/operator.go | 2 +- 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 internal/transformer/annotation_generator.go diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go index a537b1a9..c8aba559 100644 --- a/api/v1alpha1/types.go +++ b/api/v1alpha1/types.go @@ -32,6 +32,9 @@ type ValkeySpec struct { Binding *BindingProperties `json:"binding,omitempty"` ExtraEnvVars []corev1.EnvVar `json:"extraEnvVars,omitempty"` ExtraFlags []string `json:"extraFlags,omitempty"` + // Additional annotations added to the generated workload metadata. + // +optional + Annotations map[string]string `json:"annotations,omitempty"` } // SentinelProperties models attributes of the sentinel sidecar diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c5c0804e..9c038154 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -337,6 +337,13 @@ func (in *ValkeySpec) DeepCopyInto(out *ValkeySpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValkeySpec. diff --git a/crds/cache.cs.sap.com_valkeys.yaml b/crds/cache.cs.sap.com_valkeys.yaml index 592b216f..29aaeb91 100644 --- a/crds/cache.cs.sap.com_valkeys.yaml +++ b/crds/cache.cs.sap.com_valkeys.yaml @@ -958,6 +958,12 @@ spec: x-kubernetes-list-type: atomic type: object type: object + annotations: + additionalProperties: + type: string + description: Additional annotations added to the generated workload + metadata. + type: object binding: description: BindingProperties models custom properties for the generated binding secret diff --git a/internal/transformer/annotation_generator.go b/internal/transformer/annotation_generator.go new file mode 100644 index 00000000..913c016b --- /dev/null +++ b/internal/transformer/annotation_generator.go @@ -0,0 +1,64 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and valkey-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package transformer + +import ( + "context" + + "github.com/sap/component-operator-runtime/pkg/manifests" + componentoperatorruntimetypes "github.com/sap/component-operator-runtime/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// annotationGenerator wraps a generator and merges the component's spec.annotations +// onto the metadata of every generated StatefulSet. It is applied in the operator +// (not the vendored chart) so the behaviour is independent of the chart templates. +type annotationGenerator struct { + generator manifests.Generator +} + +func NewAnnotationGenerator(generator manifests.Generator) *annotationGenerator { + return &annotationGenerator{generator: generator} +} + +func (g *annotationGenerator) Generate(ctx context.Context, namespace string, name string, parameters componentoperatorruntimetypes.Unstructurable) ([]client.Object, error) { + objects, err := g.generator.Generate(ctx, namespace, name, parameters) + if err != nil { + return nil, err + } + annotations := annotationsFromParameters(parameters) + if len(annotations) == 0 { + return objects, nil + } + for i := 0; i < len(objects); i++ { + if statefulSet := asStatefulSet(objects[i]); statefulSet != nil { + merged := statefulSet.GetAnnotations() + if merged == nil { + merged = make(map[string]string, len(annotations)) + } + for key, value := range annotations { + merged[key] = value + } + statefulSet.SetAnnotations(merged) + objects[i] = asUnstructurable(statefulSet) + } + } + return objects, nil +} + +func annotationsFromParameters(parameters componentoperatorruntimetypes.Unstructurable) map[string]string { + raw, ok := parameters.ToUnstructured()["annotations"].(map[string]any) + if !ok { + return nil + } + annotations := make(map[string]string, len(raw)) + for key, value := range raw { + if s, ok := value.(string); ok { + annotations[key] = s + } + } + return annotations +} diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 7cbdfc9e..e7465100 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -122,7 +122,7 @@ func (o *Operator) Setup(mgr ctrl.Manager) error { if err := component.NewReconciler[*operatorv1alpha1.Valkey]( o.options.Name, - resourceGenerator, + transformer.NewAnnotationGenerator(resourceGenerator), component.ReconcilerOptions{}, ).WithPostReconcileHook( reconcileBinding,