From 9073bcf53ef248daca0fb741b6ef9cfa4142ee1a Mon Sep 17 00:00:00 2001 From: Stepan Paksashvili Date: Mon, 7 Apr 2025 17:47:40 +0300 Subject: [PATCH 1/4] feat(pkg/engine): add support for custom template funcs Enhances the template engine and action config to allow users to inject custom template functions via an action config when using Helm as a library. Closes #30733 Signed-off-by: Stepan Paksashvili --- pkg/action/action.go | 6 +++++ pkg/engine/engine.go | 7 ++++++ pkg/engine/engine_test.go | 46 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/pkg/action/action.go b/pkg/action/action.go index 937b42537..e6b943fba 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -25,6 +25,7 @@ import ( "path" "path/filepath" "strings" + "text/template" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -80,6 +81,9 @@ type Configuration struct { // Capabilities describes the capabilities of the Kubernetes cluster. Capabilities *chartutil.Capabilities + // CustomTemplateFuncs is defined by users to provide custom template funcs + CustomTemplateFuncs template.FuncMap + // HookOutputFunc called with container name and returns and expects writer that will receive the log output. HookOutputFunc func(namespace, pod, container string) io.Writer } @@ -118,6 +122,8 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu } e := engine.New(restConfig) e.EnableDNS = enableDNS + e.CustomTemplateFuncs = cfg.CustomTemplateFuncs + files, err2 = e.Render(ch, values) } else { var e engine.Engine diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 7235b026a..ea1e1d2af 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -44,6 +44,8 @@ type Engine struct { clientProvider *ClientProvider // EnableDNS tells the engine to allow DNS lookups when rendering templates EnableDNS bool + // CustomTemplateFuncs is defined by users to provide custom template funcs + CustomTemplateFuncs template.FuncMap } // New creates a new instance of Engine using the passed in rest config. @@ -244,6 +246,11 @@ func (e Engine) initFunMap(t *template.Template) { } } + // Set custom template func, do it here to give opportunity to override standard functions + for k, v := range e.CustomTemplateFuncs { + funcMap[k] = v + } + t.Funcs(funcMap) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index a54e99cad..cab1615bd 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1300,3 +1300,49 @@ func TestRenderTplMissingKeyString(t *testing.T) { t.Fatal(err) } } + +func TestRenderCustomTemplateFuncs(t *testing.T) { + // Create a chart with a single template that uses a custom function "exclaim" + c := &chart.Chart{ + Metadata: &chart.Metadata{Name: "CustomFunc"}, + Templates: []*chart.File{ + { + Name: "templates/manifest", + Data: []byte(`{{exclaim .Values.message}}`), + }, + }, + } + v := chartutil.Values{ + "Values": chartutil.Values{ + "message": "hello", + }, + "Chart": c.Metadata, + "Release": chartutil.Values{ + "Name": "TestRelease", + }, + } + + // Define a custom template function "exclaim" that appends "!!!" to a string. + customFuncs := template.FuncMap{ + "exclaim": func(input string) string { + return input + "!!!" + }, + } + + // Create an engine instance and set the CustomTemplateFuncs. + e := new(Engine) + e.CustomTemplateFuncs = customFuncs + + // Render the chart. + out, err := e.Render(c, v) + if err != nil { + t.Fatal(err) + } + + // Expected output should be "hello!!!". + expected := "hello!!!" + key := "CustomFunc/templates/manifest" + if rendered, ok := out[key]; !ok || rendered != expected { + t.Errorf("Expected %q, got %q", expected, rendered) + } +} From 8982b57e5e4b69630e3b4c23414e902bd5659170 Mon Sep 17 00:00:00 2001 From: Stepan Paksashvili Date: Tue, 15 Apr 2025 01:55:28 +0300 Subject: [PATCH 2/4] feat(pkg/engine): and custom funcs overriding test Signed-off-by: Stepan Paksashvili --- pkg/engine/engine.go | 10 +++++----- pkg/engine/engine_test.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index ea1e1d2af..eaf2ca856 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -196,6 +196,11 @@ func (e Engine) initFunMap(t *template.Template) { funcMap := funcMap() includedNames := make(map[string]int) + // Set custom template funcs + for k, v := range e.CustomTemplateFuncs { + funcMap[k] = v + } + // Add the template-rendering functions here so we can close over t. funcMap["include"] = includeFun(t, includedNames) funcMap["tpl"] = tplFun(t, includedNames, e.Strict) @@ -246,11 +251,6 @@ func (e Engine) initFunMap(t *template.Template) { } } - // Set custom template func, do it here to give opportunity to override standard functions - for k, v := range e.CustomTemplateFuncs { - funcMap[k] = v - } - t.Funcs(funcMap) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index cab1615bd..bf27f982f 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1310,6 +1310,10 @@ func TestRenderCustomTemplateFuncs(t *testing.T) { Name: "templates/manifest", Data: []byte(`{{exclaim .Values.message}}`), }, + { + Name: "templates/override", + Data: []byte(`{{ upper .Values.message }}`), + }, }, } v := chartutil.Values{ @@ -1327,6 +1331,9 @@ func TestRenderCustomTemplateFuncs(t *testing.T) { "exclaim": func(input string) string { return input + "!!!" }, + "upper": func(s string) string { + return "custom:" + s + }, } // Create an engine instance and set the CustomTemplateFuncs. @@ -1345,4 +1352,11 @@ func TestRenderCustomTemplateFuncs(t *testing.T) { if rendered, ok := out[key]; !ok || rendered != expected { t.Errorf("Expected %q, got %q", expected, rendered) } + + // Verify that the rendered template used the custom "upper" function. + expected = "custom:hello" + key = "CustomFunc/templates/override" + if rendered, ok := out[key]; !ok || rendered != expected { + t.Errorf("Expected %q, got %q", expected, rendered) + } } From 5c2f89307d1a8100d6441411c18b149c5bd36c5a Mon Sep 17 00:00:00 2001 From: Stepan Paksashvili Date: Thu, 17 Apr 2025 00:53:44 +0300 Subject: [PATCH 3/4] feat(pkg/engine): and custom funcs to action config Signed-off-by: Stepan Paksashvili --- pkg/action/action.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/action/action.go b/pkg/action/action.go index e6b943fba..e91054a28 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -128,6 +128,8 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu } else { var e engine.Engine e.EnableDNS = enableDNS + e.CustomTemplateFuncs = cfg.CustomTemplateFuncs + files, err2 = e.Render(ch, values) } From b54349d9b29756b95653986c07a687bf4215a075 Mon Sep 17 00:00:00 2001 From: Stepan Paksashvili Date: Thu, 17 Apr 2025 01:30:43 +0300 Subject: [PATCH 4/4] fix(pkg/engine): allow to override all functions Signed-off-by: Stepan Paksashvili --- pkg/engine/engine.go | 10 +++++----- pkg/engine/engine_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index eaf2ca856..0b0933def 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -196,11 +196,6 @@ func (e Engine) initFunMap(t *template.Template) { funcMap := funcMap() includedNames := make(map[string]int) - // Set custom template funcs - for k, v := range e.CustomTemplateFuncs { - funcMap[k] = v - } - // Add the template-rendering functions here so we can close over t. funcMap["include"] = includeFun(t, includedNames) funcMap["tpl"] = tplFun(t, includedNames, e.Strict) @@ -251,6 +246,11 @@ func (e Engine) initFunMap(t *template.Template) { } } + // Set custom template funcs + for k, v := range e.CustomTemplateFuncs { + funcMap[k] = v + } + t.Funcs(funcMap) } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index bf27f982f..68e0158fa 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1302,7 +1302,7 @@ func TestRenderTplMissingKeyString(t *testing.T) { } func TestRenderCustomTemplateFuncs(t *testing.T) { - // Create a chart with a single template that uses a custom function "exclaim" + // Create a chart with two templates that use custom functions c := &chart.Chart{ Metadata: &chart.Metadata{Name: "CustomFunc"}, Templates: []*chart.File{ @@ -1326,7 +1326,7 @@ func TestRenderCustomTemplateFuncs(t *testing.T) { }, } - // Define a custom template function "exclaim" that appends "!!!" to a string. + // Define a custom template function "exclaim" that appends "!!!" to a string and override "upper" function customFuncs := template.FuncMap{ "exclaim": func(input string) string { return input + "!!!"