/* Copyright © 2025 ESO Maintainer Team Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package pushsecret import ( "context" "fmt" "maps" v1 "k8s.io/api/core/v1" esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1" "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" "github.com/external-secrets/external-secrets/pkg/controllers/templating" "github.com/external-secrets/external-secrets/runtime/esutils" "github.com/external-secrets/external-secrets/runtime/template" _ "github.com/external-secrets/external-secrets/pkg/register" // Loading registered providers. ) const ( errFetchTplFrom = "error fetching templateFrom data: %w" errExecTpl = "could not execute template: %w" ) // applyTemplate merges template in the following order: // * template.Data (highest precedence) // * template.templateFrom // * secret via ps.data or ps.dataFrom. // Apply template modifications for the source secret. These modifications will only live in memory as we will // never modify it. func (r *Reconciler) applyTemplate(ctx context.Context, ps *v1alpha1.PushSecret, secret *v1.Secret) error { // no template: nothing to do if ps.Spec.Template == nil { return nil } if err := setMetadata(secret, ps); err != nil { return err } execute, err := template.EngineForVersion(esv1.TemplateEngineV2) if err != nil { return err } // Copies secret.Data to dataMap to avoid modifying the original secret // This avoids uncertain behavior if kube-apiserver sends the // template map in a different order on each reconcile loop // ref: https://github.com/external-secrets/external-secrets/issues/5018 dataMap := make(map[string][]byte) maps.Copy(dataMap, secret.Data) p := templating.Parser{ Client: r.Client, TargetSecret: secret, DataMap: dataMap, Exec: execute, } // apply templates defined in template.templateFrom err = p.MergeTemplateFrom(ctx, ps.Namespace, ps.Spec.Template) if err != nil { return fmt.Errorf(errFetchTplFrom, err) } // explicitly defined template.Data takes precedence over templateFrom err = p.MergeMap(ps.Spec.Template.Data, esv1.TemplateTargetData) if err != nil { return fmt.Errorf(errExecTpl, err) } // get template data for labels err = p.MergeMap(ps.Spec.Template.Metadata.Labels, esv1.TemplateTargetLabels) if err != nil { return fmt.Errorf(errExecTpl, err) } // get template data for annotations err = p.MergeMap(ps.Spec.Template.Metadata.Annotations, esv1.TemplateTargetAnnotations) if err != nil { return fmt.Errorf(errExecTpl, err) } return nil } // setMetadata sets Labels and Annotations in the source secret, but we will never write them back. // It is only set to satisfy templated changes. func setMetadata(secret *v1.Secret, ps *v1alpha1.PushSecret) error { if secret.Labels == nil { secret.Labels = make(map[string]string) } if secret.Annotations == nil { secret.Annotations = make(map[string]string) } secret.Type = ps.Spec.Template.Type esutils.MergeStringMap(secret.ObjectMeta.Labels, ps.Spec.Template.Metadata.Labels) esutils.MergeStringMap(secret.ObjectMeta.Annotations, ps.Spec.Template.Metadata.Annotations) return nil }