mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 09:59:57 +00:00
fix(controller): allow no spaces in template references (#1789)
* fix(controller): decode old object for delete requests Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(controller): allow no spaces in template references Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(controller): allow no spaces in template references Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> --------- Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"maps"
|
||||
"sync"
|
||||
|
||||
"github.com/valyala/fasttemplate"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -25,6 +24,7 @@ import (
|
||||
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
tpl "github.com/projectcapsule/capsule/pkg/template"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -243,12 +243,7 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant
|
||||
for rawIndex, item := range spec.RawItems {
|
||||
template := string(item.Raw)
|
||||
|
||||
t := fasttemplate.New(template, "{{ ", " }}")
|
||||
|
||||
tmplString := t.ExecuteString(map[string]any{
|
||||
"tenant.name": tnt.Name,
|
||||
"namespace": ns.Name,
|
||||
})
|
||||
tmplString := tpl.TemplateForTenantAndNamespace(template, &tnt, &ns)
|
||||
|
||||
obj, keysAndValues := unstructured.Unstructured{}, []any{"index", rawIndex}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/valyala/fasttemplate"
|
||||
@@ -12,19 +13,32 @@ import (
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
)
|
||||
|
||||
// TemplateForTenantAndNamespace applies templating to all values in the provided map in place.
|
||||
func TemplateForTenantAndNamespace(m map[string]string, tnt *capsulev1beta2.Tenant, ns *corev1.Namespace) {
|
||||
for k, v := range m {
|
||||
if !strings.Contains(v, "{{ ") && !strings.Contains(v, " }}") {
|
||||
continue
|
||||
// TemplateForTenantAndNamespace applies templatingto the provided string.
|
||||
func TemplateForTenantAndNamespace(template string, tnt *capsulev1beta2.Tenant, ns *corev1.Namespace) string {
|
||||
if !strings.Contains(template, "{{") && !strings.Contains(template, "}}") {
|
||||
return template
|
||||
}
|
||||
|
||||
t := fasttemplate.New(template, "{{", "}}")
|
||||
|
||||
values := map[string]string{
|
||||
"tenant.name": tnt.Name,
|
||||
"namespace": ns.Name,
|
||||
}
|
||||
|
||||
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
|
||||
key := strings.TrimSpace(tag)
|
||||
if v, ok := values[key]; ok {
|
||||
return w.Write([]byte(v))
|
||||
}
|
||||
|
||||
t := fasttemplate.New(v, "{{ ", " }}")
|
||||
tmplString := t.ExecuteString(map[string]any{
|
||||
"tenant.name": tnt.Name,
|
||||
"namespace": ns.Name,
|
||||
})
|
||||
return 0, nil
|
||||
})
|
||||
}
|
||||
|
||||
m[k] = tmplString
|
||||
// TemplateForTenantAndNamespace applies templating to all values in the provided map in place.
|
||||
func TemplateForTenantAndNamespaceMap(m map[string]string, tnt *capsulev1beta2.Tenant, ns *corev1.Namespace) {
|
||||
for k, v := range m {
|
||||
m[k] = TemplateForTenantAndNamespace(v, tnt, ns)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package template
|
||||
package template_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
tpl "github.com/projectcapsule/capsule/pkg/template"
|
||||
)
|
||||
|
||||
func newTenant(name string) *capsulev1beta2.Tenant {
|
||||
@@ -31,12 +33,90 @@ func TestTemplateForTenantAndNamespace_ReplacesPlaceholders(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
got := tpl.TemplateForTenantAndNamespace(
|
||||
"tenant={{tenant.name}}, ns={{namespace}}",
|
||||
tnt,
|
||||
ns,
|
||||
)
|
||||
|
||||
want := "tenant=tenant-a, ns=ns-1"
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_ReplacesPlaceholdersSpaces(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
got := tpl.TemplateForTenantAndNamespace(
|
||||
"tenant={{ tenant.name }}, ns={{ namespace }}",
|
||||
tnt,
|
||||
ns,
|
||||
)
|
||||
|
||||
want := "tenant=tenant-a, ns=ns-1"
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_OnlyTenant(t *testing.T) {
|
||||
tnt := newTenant("tenant-x")
|
||||
ns := newNamespace("ns-y")
|
||||
|
||||
got := tpl.TemplateForTenantAndNamespace("T={{tenant.name}}", tnt, ns)
|
||||
want := "T=tenant-x"
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_OnlyNamespace(t *testing.T) {
|
||||
tnt := newTenant("tenant-x")
|
||||
ns := newNamespace("ns-y")
|
||||
|
||||
got := tpl.TemplateForTenantAndNamespace("N={{namespace}}", tnt, ns)
|
||||
want := "N=ns-y"
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_NoDelimitersReturnsEmpty(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
got := tpl.TemplateForTenantAndNamespace("plain-value-without-templates", tnt, ns)
|
||||
if got != "plain-value-without-templates" {
|
||||
t.Fatalf("expected empty string for input without delimiters, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_UnknownKeyBecomesEmpty(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
got := tpl.TemplateForTenantAndNamespace("X={{unknown.key}}", tnt, ns)
|
||||
want := "X="
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespaceMap_ReplacesPlaceholders(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
m := map[string]string{
|
||||
"key1": "tenant={{ tenant.name }}, ns={{ namespace }}",
|
||||
"key1": "tenant={{tenant.name}}, ns={{namespace}}",
|
||||
"key2": "plain-value",
|
||||
}
|
||||
|
||||
TemplateForTenantAndNamespace(m, tnt, ns)
|
||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
||||
|
||||
if got := m["key1"]; got != "tenant=tenant-a, ns=ns-1" {
|
||||
t.Fatalf("key1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
||||
@@ -47,7 +127,27 @@ func TestTemplateForTenantAndNamespace_ReplacesPlaceholders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_SkipsValuesWithoutDelimiters(t *testing.T) {
|
||||
func TestTemplateForTenantAndNamespaceMap_ReplacesPlaceholdersSpaces(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
m := map[string]string{
|
||||
"key1": "tenant={{ tenant.name }}, ns={{ namespace }}",
|
||||
"key2": "plain-value",
|
||||
}
|
||||
|
||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
||||
|
||||
if got := m["key1"]; got != "tenant=tenant-a, ns=ns-1" {
|
||||
t.Fatalf("key1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
||||
}
|
||||
|
||||
if got := m["key2"]; got != "plain-value" {
|
||||
t.Fatalf("key2: expected %q to remain unchanged, got %q", "plain-value", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespaceMap_SkipsValuesWithoutDelimiters(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
@@ -57,20 +157,19 @@ func TestTemplateForTenantAndNamespace_SkipsValuesWithoutDelimiters(t *testing.T
|
||||
"noTemplate2": "namespace {{namespace}}",
|
||||
}
|
||||
|
||||
original1 := m["noTemplate1"]
|
||||
original2 := m["noTemplate2"]
|
||||
|
||||
TemplateForTenantAndNamespace(m, tnt, ns)
|
||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
||||
|
||||
if got := m["noTemplate1"]; got != original1 {
|
||||
t.Fatalf("noTemplate1: expected %q to remain unchanged, got %q", original1, got)
|
||||
if got := m["noTemplate1"]; got != "hello tenant-a" {
|
||||
t.Fatalf("noTemplate1: expected %q, got %q", "tenant=tenant-a, ns=ns-1", got)
|
||||
}
|
||||
if got := m["noTemplate2"]; got != original2 {
|
||||
if got := m["noTemplate2"]; got != "namespace ns-1" {
|
||||
t.Fatalf("noTemplate2: expected %q to remain unchanged, got %q", original2, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_MixedKeys(t *testing.T) {
|
||||
func TestTemplateForTenantAndNamespaceMap_MixedKeys(t *testing.T) {
|
||||
tnt := newTenant("tenant-x")
|
||||
ns := newNamespace("ns-x")
|
||||
|
||||
@@ -80,7 +179,7 @@ func TestTemplateForTenantAndNamespace_MixedKeys(t *testing.T) {
|
||||
"none": "static",
|
||||
}
|
||||
|
||||
TemplateForTenantAndNamespace(m, tnt, ns)
|
||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
||||
|
||||
if got := m["onlyTenant"]; got != "T=tenant-x" {
|
||||
t.Fatalf("onlyTenant: expected %q, got %q", "T=tenant-x", got)
|
||||
@@ -93,7 +192,7 @@ func TestTemplateForTenantAndNamespace_MixedKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateForTenantAndNamespace_UnknownKeyBecomesEmpty(t *testing.T) {
|
||||
func TestTemplateForTenantAndNamespaceMap_UnknownKeyBecomesEmpty(t *testing.T) {
|
||||
tnt := newTenant("tenant-a")
|
||||
ns := newNamespace("ns-1")
|
||||
|
||||
@@ -101,7 +200,7 @@ func TestTemplateForTenantAndNamespace_UnknownKeyBecomesEmpty(t *testing.T) {
|
||||
"unknown": "X={{ unknown.key }}",
|
||||
}
|
||||
|
||||
TemplateForTenantAndNamespace(m, tnt, ns)
|
||||
tpl.TemplateForTenantAndNamespaceMap(m, tnt, ns)
|
||||
|
||||
// fasttemplate with missing key returns an empty string for that placeholder
|
||||
if got := m["unknown"]; got != "X=" {
|
||||
|
||||
@@ -59,8 +59,8 @@ func BuildNamespaceMetadataForTenant(ns *corev1.Namespace, tnt *capsulev1beta2.T
|
||||
continue
|
||||
}
|
||||
|
||||
template.TemplateForTenantAndNamespace(md.Labels, tnt, ns)
|
||||
template.TemplateForTenantAndNamespace(md.Annotations, tnt, ns)
|
||||
template.TemplateForTenantAndNamespaceMap(md.Labels, tnt, ns)
|
||||
template.TemplateForTenantAndNamespaceMap(md.Annotations, tnt, ns)
|
||||
|
||||
utils.MapMergeNoOverrite(labels, md.Labels)
|
||||
utils.MapMergeNoOverrite(annotations, md.Annotations)
|
||||
|
||||
Reference in New Issue
Block a user