From c861fdbb5fc454af3731a460b2d2830daf30e674 Mon Sep 17 00:00:00 2001 From: Christoph Barbian Date: Wed, 21 Oct 2026 21:51:59 +0200 Subject: [PATCH] allow to map/filter substitute secret data --- api/v1alpha1/types.go | 16 ++++++++++- api/v1alpha1/zz_generated.deepcopy.go | 38 ++++++++++++++++++++++++++- crds/core.cs.sap.com_components.yaml | 20 ++++++++++++-- internal/generator/generator.go | 9 ++++++- internal/generator/util.go | 13 +++++++++ 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go index 983f473..6f51163 100644 --- a/api/v1alpha1/types.go +++ b/api/v1alpha1/types.go @@ -296,13 +296,19 @@ type PostBuild struct { // Variables to be substituted in the renderered manifests. Substitute map[string]string `json:"substitute,omitempty"` // Secrets containing variables to be used for substitution. - SubstituteFrom []component.SecretReference `json:"substituteFrom,omitempty" notFoundPolicy:"ignoreOnDeletion"` + SubstituteFrom []SubstituteFrom `json:"substituteFrom,omitempty" notFoundPolicy:"ignoreOnDeletion"` // A list of kustomize patches. Patches []manifests.KustomizePatch `json:"patches,omitempty"` // A list of kustomize image replacements. Images []manifests.KustomizeImage `json:"images,omitempty"` } +// SubstituteFrom represents a source of secret items to be used for variable substitution. +type SubstituteFrom struct { + component.SecretReference `json:",inline"` + Items []SecretItem `json:"items,omitempty"` +} + // Dependency models a dependency of the containing component to another Component (referenced by namespace and name). type Dependency struct { NamespacedName `json:",inline"` @@ -315,6 +321,14 @@ type NamespacedName struct { Name string `json:"name"` } +// SecretItem represents a single item within a secret, including its key and the renamed key. +type SecretItem struct { + // The key of the secret item. + Key string `json:"key"` + // The renamed key. + As string `json:"as"` +} + // Return a copy of the given NamespacedName, using the specified namespace if none is set. // The retrieving NamespaceName remains unchanged. func (n NamespacedName) WithDefaultNamespace(namespace string) NamespacedName { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bacc54f..ccdf751 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -472,7 +472,7 @@ func (in *PostBuild) DeepCopyInto(out *PostBuild) { } if in.SubstituteFrom != nil { in, out := &in.SubstituteFrom, &out.SubstituteFrom - *out = make([]component.SecretReference, len(*in)) + *out = make([]SubstituteFrom, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -501,6 +501,21 @@ func (in *PostBuild) DeepCopy() *PostBuild { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretItem) DeepCopyInto(out *SecretItem) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretItem. +func (in *SecretItem) DeepCopy() *SecretItem { + if in == nil { + return nil + } + out := new(SecretItem) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SourceReference) DeepCopyInto(out *SourceReference) { *out = *in @@ -562,3 +577,24 @@ func (in *SourceReferenceStatus) DeepCopy() *SourceReferenceStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubstituteFrom) DeepCopyInto(out *SubstituteFrom) { + *out = *in + in.SecretReference.DeepCopyInto(&out.SecretReference) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SecretItem, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubstituteFrom. +func (in *SubstituteFrom) DeepCopy() *SubstituteFrom { + if in == nil { + return nil + } + out := new(SubstituteFrom) + in.DeepCopyInto(out) + return out +} diff --git a/crds/core.cs.sap.com_components.yaml b/crds/core.cs.sap.com_components.yaml index 74a637f..dca3b2b 100644 --- a/crds/core.cs.sap.com_components.yaml +++ b/crds/core.cs.sap.com_components.yaml @@ -210,9 +210,25 @@ spec: substituteFrom: description: Secrets containing variables to be used for substitution. items: - description: SecretReference defines a loadable reference to - a secret. + description: SubstituteFrom represents a source of secret items + to be used for variable substitution. properties: + items: + items: + description: SecretItem represents a single item within + a secret, including its key and the renamed key. + properties: + as: + description: The renamed key. + type: string + key: + description: The key of the secret item. + type: string + required: + - as + - key + type: object + type: array name: minLength: 1 type: string diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 38402c3..32ef514 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -83,7 +83,14 @@ func (g *Generator) Generate(ctx context.Context, namespace string, name string, if len(spec.PostBuild.Substitute) > 0 || len(spec.PostBuild.SubstituteFrom) > 0 { substitutions := make(map[string]string) for _, ref := range spec.PostBuild.SubstituteFrom { - shallowMerge(substitutions, maps.Collect(ref.Data(), func(x []byte) string { return string(x) })) + data := maps.Collect(ref.Data(), func(x []byte) string { return string(x) }) + if len(ref.Items) > 0 { + data, err = mapAndFilter(data, maps.CollectSlice(ref.Items, func(item operatorv1alpha1.SecretItem) (string, string) { return item.Key, item.As })) + if err != nil { + return nil, err + } + } + shallowMerge(substitutions, data) } shallowMerge(substitutions, spec.PostBuild.Substitute) transformer, err := manifests.NewSubstitutionObjectTransformer(substitutions, componentoperatorruntimetypes.SelectorFunc[client.Object](func(object client.Object) bool { diff --git a/internal/generator/util.go b/internal/generator/util.go index 3c1d3be..1975253 100644 --- a/internal/generator/util.go +++ b/internal/generator/util.go @@ -9,6 +9,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "fmt" ) // TODO: consolidate all the util files into an internal reuse package @@ -49,3 +50,15 @@ func calculateDigest(values ...any) string { } return sha256hex(raw) } + +func mapAndFilter[T any](data map[string]T, keyMap map[string]string) (map[string]T, error) { + mappedData := make(map[string]T) + for key, mappedKey := range keyMap { + value, ok := data[key] + if !ok { + return nil, fmt.Errorf("key %s not found in data", key) + } + mappedData[mappedKey] = value + } + return mappedData, nil +}