// Copyright 2020-2026 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 package template import ( "bytes" "context" "encoding/json" "fmt" "strconv" "text/template" k8smeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcapsule/capsule/pkg/runtime/sanitize" ) // Additional Context to enhance templating // +kubebuilder:object:generate=true type TemplateContext struct { Resources []*TemplateResourceReference `json:"resources,omitempty"` } // +kubebuilder:object:generate=true type TemplateResourceReference struct { ResourceReference `json:",inline"` // Index to mount the resource in the template context Index string `json:"index,omitempty"` } func (t *TemplateContext) GatherContext( ctx context.Context, kubeClient client.Client, restMapper k8smeta.RESTMapper, data map[string]any, namespace string, additionSelectors []labels.Selector, ) (context ReferenceContext, errors []error) { context = ReferenceContext{} if t.Resources == nil { return } // Template Context for Tenant if len(data) != 0 { if err := t.selfTemplate(data); err != nil { return context, []error{fmt.Errorf("cloud not template: %w", err)} } } // Load external Resources for index, resource := range t.Resources { res, err := resource.LoadResources(ctx, kubeClient, restMapper, namespace, additionSelectors, map[string]string{}, true) if err != nil { errors = append(errors, err) continue } if len(res) > 0 { resourceIndex := resource.Index if resourceIndex == "" { resourceIndex = strconv.Itoa(index) } for _, u := range res { sanitize.SanitizeUnstructured(u, sanitize.DefaultSanitizeOptions()) } context[resourceIndex] = res } } return } // Templates itself with the option to populate tenant fields. func (t *TemplateContext) selfTemplate( data map[string]any, ) (err error) { dataBytes, err := json.Marshal(t) if err != nil { return fmt.Errorf("error marshaling TemplateContext: %w", err) } if err := json.Unmarshal(dataBytes, &data); err != nil { return fmt.Errorf("error unmarshaling TemplateContext into map: %w", err) } tmpl, err := template.New("tpl").Option("missingkey=error").Funcs(ExtraFuncMap()).Parse(string(dataBytes)) if err != nil { return fmt.Errorf("error parsing template: %w", err) } var rendered bytes.Buffer if err := tmpl.Execute(&rendered, data); err != nil { return fmt.Errorf("error executing template: %w", err) } tplContext := &TemplateContext{} if err := json.Unmarshal(rendered.Bytes(), tplContext); err != nil { return fmt.Errorf("error unmarshaling JSON into TemplateContext: %w", err) } // Reassing templated context *t = *tplContext return nil } // +kubebuilder:object:generate=false type ReferenceContext map[string]any func (t *ReferenceContext) String() (string, error) { dataBytes, err := json.Marshal(t) if err != nil { return "", fmt.Errorf("error marshaling TemplateContext: %w", err) } if err := json.Unmarshal(dataBytes, t); err != nil { return "", fmt.Errorf("error unmarshaling TemplateContext into map: %w", err) } return string(dataBytes), nil }