2016-06-23 02:28:45 +08:00
/ *
2018-08-25 03:03:55 +08:00
Copyright The Helm Authors .
2016-06-23 02:28:45 +08:00
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
http : //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 .
* /
2016-04-13 06:18:42 +08:00
package engine
import (
"fmt"
2023-02-02 00:35:19 +08:00
"path"
2019-03-07 07:45:52 +08:00
"strings"
2016-04-13 06:18:42 +08:00
"sync"
"testing"
2025-04-24 09:56:30 +08:00
"text/template"
2016-04-22 04:50:16 +08:00
2025-01-25 22:26:02 +08:00
"github.com/stretchr/testify/assert"
2024-01-03 19:04:00 +08:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/fake"
2016-04-22 04:50:16 +08:00
2025-02-26 04:20:44 +08:00
chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
2016-04-13 06:18:42 +08:00
)
2017-05-27 07:06:41 +08:00
func TestSortTemplates ( t * testing . T ) {
tpls := map [ string ] renderable {
"/mychart/templates/foo.tpl" : { } ,
"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl" : { } ,
"/mychart/templates/bar.tpl" : { } ,
"/mychart/templates/charts/foo/templates/bar.tpl" : { } ,
"/mychart/templates/_foo.tpl" : { } ,
"/mychart/templates/charts/foo/templates/foo.tpl" : { } ,
"/mychart/templates/charts/bar/templates/foo.tpl" : { } ,
}
got := sortTemplates ( tpls )
if len ( got ) != len ( tpls ) {
t . Fatal ( "Sorted results are missing templates" )
}
expect := [ ] string {
"/mychart/templates/charts/foo/charts/bar/templates/foo.tpl" ,
"/mychart/templates/charts/foo/templates/foo.tpl" ,
"/mychart/templates/charts/foo/templates/bar.tpl" ,
"/mychart/templates/charts/bar/templates/foo.tpl" ,
"/mychart/templates/foo.tpl" ,
"/mychart/templates/bar.tpl" ,
"/mychart/templates/_foo.tpl" ,
}
for i , e := range expect {
if got [ i ] != e {
2019-03-07 07:45:52 +08:00
t . Fatalf ( "\n\tExp:\n%s\n\tGot:\n%s" ,
strings . Join ( expect , "\n" ) ,
strings . Join ( got , "\n" ) ,
)
2016-04-13 06:18:42 +08:00
}
}
}
2016-10-15 06:28:43 +08:00
func TestFuncMap ( t * testing . T ) {
2019-03-07 07:45:52 +08:00
fns := funcMap ( )
2016-10-15 06:28:43 +08:00
forbidden := [ ] string { "env" , "expandenv" }
for _ , f := range forbidden {
if _ , ok := fns [ f ] ; ok {
t . Errorf ( "Forbidden function %s exists in FuncMap." , f )
}
}
// Test for Engine-specific template functions.
2023-04-25 19:47:00 +08:00
expect := [ ] string { "include" , "required" , "tpl" , "toYaml" , "fromYaml" , "toToml" , "fromToml" , "toJson" , "fromJson" , "lookup" }
2016-10-15 06:28:43 +08:00
for _ , f := range expect {
if _ , ok := fns [ f ] ; ! ok {
t . Errorf ( "Expected add-on function %q" , f )
}
}
}
2016-04-13 06:18:42 +08:00
func TestRender ( t * testing . T ) {
2016-05-05 07:27:00 +08:00
c := & chart . Chart {
Metadata : & chart . Metadata {
Name : "moby" ,
Version : "1.2.3" ,
} ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2019-03-07 07:45:52 +08:00
{ Name : "templates/test1" , Data : [ ] byte ( "{{.Values.outer | title }} {{.Values.inner | title}}" ) } ,
{ Name : "templates/test2" , Data : [ ] byte ( "{{.Values.global.callme | lower }}" ) } ,
2016-10-22 07:12:41 +08:00
{ Name : "templates/test3" , Data : [ ] byte ( "{{.noValue}}" ) } ,
2019-03-07 07:45:52 +08:00
{ Name : "templates/test4" , Data : [ ] byte ( "{{toJson .Values}}" ) } ,
2023-02-02 00:35:19 +08:00
{ Name : "templates/test5" , Data : [ ] byte ( "{{getHostByName \"helm.sh\"}}" ) } ,
2016-05-05 07:27:00 +08:00
} ,
2018-08-30 00:56:19 +08:00
Values : map [ string ] interface { } { "outer" : "DEFAULT" , "inner" : "DEFAULT" } ,
2016-05-05 07:27:00 +08:00
}
2018-12-05 07:57:24 +08:00
vals := map [ string ] interface { } {
2019-03-07 07:45:52 +08:00
"Values" : map [ string ] interface { } {
"outer" : "spouter" ,
"inner" : "inn" ,
"global" : map [ string ] interface { } {
"callme" : "Ishmael" ,
} ,
2018-12-05 07:57:24 +08:00
} ,
}
2016-05-05 07:27:00 +08:00
2016-07-22 05:55:35 +08:00
v , err := chartutil . CoalesceValues ( c , vals )
2016-06-14 03:11:39 +08:00
if err != nil {
t . Fatalf ( "Failed to coalesce values: %s" , err )
}
2019-03-07 07:45:52 +08:00
out , err := Render ( c , v )
2016-05-05 07:27:00 +08:00
if err != nil {
t . Errorf ( "Failed to render templates: %s" , err )
}
2019-03-07 07:45:52 +08:00
expect := map [ string ] string {
"moby/templates/test1" : "Spouter Inn" ,
"moby/templates/test2" : "ishmael" ,
"moby/templates/test3" : "" ,
"moby/templates/test4" : ` { "global": { "callme":"Ishmael"},"inner":"inn","outer":"spouter"} ` ,
2023-02-02 00:35:19 +08:00
"moby/templates/test5" : "" ,
2016-06-25 06:25:43 +08:00
}
2016-06-15 01:10:24 +08:00
2019-03-07 07:45:52 +08:00
for name , data := range expect {
if out [ name ] != data {
t . Errorf ( "Expected %q, got %q" , data , out [ name ] )
}
2016-06-10 02:09:00 +08:00
}
2016-04-13 06:18:42 +08:00
}
2020-04-22 03:16:55 +08:00
func TestRenderRefsOrdering ( t * testing . T ) {
parentChart := & chart . Chart {
Metadata : & chart . Metadata {
Name : "parent" ,
Version : "1.2.3" ,
} ,
Templates : [ ] * chart . File {
{ Name : "templates/_helpers.tpl" , Data : [ ] byte ( ` {{- define "test" -}} parent value {{- end -}} ` ) } ,
{ Name : "templates/test.yaml" , Data : [ ] byte ( ` {{ tpl "{{ include \"test\" . }}" . }} ` ) } ,
} ,
}
childChart := & chart . Chart {
Metadata : & chart . Metadata {
Name : "child" ,
Version : "1.2.3" ,
} ,
Templates : [ ] * chart . File {
{ Name : "templates/_helpers.tpl" , Data : [ ] byte ( ` {{- define "test" -}} child value {{- end -}} ` ) } ,
} ,
}
parentChart . AddDependency ( childChart )
expect := map [ string ] string {
"parent/templates/test.yaml" : "parent value" ,
}
for i := 0 ; i < 100 ; i ++ {
out , err := Render ( parentChart , chartutil . Values { } )
if err != nil {
t . Fatalf ( "Failed to render templates: %s" , err )
}
for name , data := range expect {
if out [ name ] != data {
2021-03-16 09:11:57 +08:00
t . Fatalf ( "Expected %q, got %q (iteration %d)" , data , out [ name ] , i + 1 )
2020-04-22 03:16:55 +08:00
}
}
}
}
2016-04-13 06:18:42 +08:00
func TestRenderInternals ( t * testing . T ) {
// Test the internals of the rendering tool.
2016-04-22 06:45:42 +08:00
vals := chartutil . Values { "Name" : "one" , "Value" : "two" }
tpls := map [ string ] renderable {
"one" : { tpl : ` Hello {{ title .Name }} ` , vals : vals } ,
"two" : { tpl : ` Goodbye {{ upper .Value }} ` , vals : vals } ,
2016-04-13 06:18:42 +08:00
// Test whether a template can reliably reference another template
// without regard for ordering.
2016-04-22 06:45:42 +08:00
"three" : { tpl : ` {{ template "two" dict "Value" "three" }} ` , vals : vals } ,
2016-04-13 06:18:42 +08:00
}
2019-03-07 07:45:52 +08:00
out , err := new ( Engine ) . render ( tpls )
2016-04-13 06:18:42 +08:00
if err != nil {
t . Fatalf ( "Failed template rendering: %s" , err )
}
if len ( out ) != 3 {
t . Fatalf ( "Expected 3 templates, got %d" , len ( out ) )
}
if out [ "one" ] != "Hello One" {
t . Errorf ( "Expected 'Hello One', got %q" , out [ "one" ] )
}
if out [ "two" ] != "Goodbye TWO" {
t . Errorf ( "Expected 'Goodbye TWO'. got %q" , out [ "two" ] )
}
if out [ "three" ] != "Goodbye THREE" {
t . Errorf ( "Expected 'Goodbye THREE'. got %q" , out [ "two" ] )
}
}
2024-01-03 19:04:00 +08:00
func TestRenderWithDNS ( t * testing . T ) {
2023-02-02 00:35:19 +08:00
c := & chart . Chart {
Metadata : & chart . Metadata {
Name : "moby" ,
Version : "1.2.3" ,
} ,
Templates : [ ] * chart . File {
{ Name : "templates/test1" , Data : [ ] byte ( "{{getHostByName \"helm.sh\"}}" ) } ,
} ,
Values : map [ string ] interface { } { } ,
}
vals := map [ string ] interface { } {
"Values" : map [ string ] interface { } { } ,
}
v , err := chartutil . CoalesceValues ( c , vals )
if err != nil {
t . Fatalf ( "Failed to coalesce values: %s" , err )
}
var e Engine
e . EnableDNS = true
out , err := e . Render ( c , v )
if err != nil {
t . Errorf ( "Failed to render templates: %s" , err )
}
for _ , val := range c . Templates {
fp := path . Join ( "moby" , val . Name )
if out [ fp ] == "" {
t . Errorf ( "Expected IP address, got %q" , out [ fp ] )
}
}
}
2024-01-03 19:04:00 +08:00
type kindProps struct {
shouldErr error
gvr schema . GroupVersionResource
namespaced bool
}
type testClientProvider struct {
t * testing . T
scheme map [ string ] kindProps
objects [ ] runtime . Object
}
func ( p * testClientProvider ) GetClientFor ( apiVersion , kind string ) ( dynamic . NamespaceableResourceInterface , bool , error ) {
props := p . scheme [ path . Join ( apiVersion , kind ) ]
if props . shouldErr != nil {
return nil , false , props . shouldErr
}
return fake . NewSimpleDynamicClient ( runtime . NewScheme ( ) , p . objects ... ) . Resource ( props . gvr ) , props . namespaced , nil
}
var _ ClientProvider = & testClientProvider { }
// makeUnstructured is a convenience function for single-line creation of Unstructured objects.
func makeUnstructured ( apiVersion , kind , name , namespace string ) * unstructured . Unstructured {
ret := & unstructured . Unstructured { Object : map [ string ] interface { } {
"apiVersion" : apiVersion ,
"kind" : kind ,
"metadata" : map [ string ] interface { } {
"name" : name ,
} ,
} }
if namespace != "" {
ret . Object [ "metadata" ] . ( map [ string ] interface { } ) [ "namespace" ] = namespace
}
return ret
}
func TestRenderWithClientProvider ( t * testing . T ) {
provider := & testClientProvider {
t : t ,
scheme : map [ string ] kindProps {
"v1/Namespace" : {
gvr : schema . GroupVersionResource {
Version : "v1" ,
Resource : "namespaces" ,
} ,
} ,
"v1/Pod" : {
gvr : schema . GroupVersionResource {
Version : "v1" ,
Resource : "pods" ,
} ,
namespaced : true ,
} ,
} ,
objects : [ ] runtime . Object {
makeUnstructured ( "v1" , "Namespace" , "default" , "" ) ,
makeUnstructured ( "v1" , "Pod" , "pod1" , "default" ) ,
makeUnstructured ( "v1" , "Pod" , "pod2" , "ns1" ) ,
makeUnstructured ( "v1" , "Pod" , "pod3" , "ns1" ) ,
} ,
}
type testCase struct {
template string
output string
}
cases := map [ string ] testCase {
"ns-single" : {
template : ` {{ ( lookup "v1" "Namespace" "" "default" ) .metadata .name }} ` ,
output : "default" ,
} ,
"ns-list" : {
template : ` {{ ( lookup "v1" "Namespace" "" "" ) .items | len }} ` ,
output : "1" ,
} ,
"ns-missing" : {
template : ` {{ ( lookup "v1" "Namespace" "" "absent" ) }} ` ,
output : "map[]" ,
} ,
"pod-single" : {
template : ` {{ ( lookup "v1" "Pod" "default" "pod1" ) .metadata .name }} ` ,
output : "pod1" ,
} ,
"pod-list" : {
template : ` {{ ( lookup "v1" "Pod" "ns1" "" ) .items | len }} ` ,
output : "2" ,
} ,
"pod-all" : {
template : ` {{ ( lookup "v1" "Pod" "" "" ) .items | len }} ` ,
output : "3" ,
} ,
"pod-missing" : {
template : ` {{ ( lookup "v1" "Pod" "" "ns2" ) }} ` ,
output : "map[]" ,
} ,
}
c := & chart . Chart {
Metadata : & chart . Metadata {
Name : "moby" ,
Version : "1.2.3" ,
} ,
Values : map [ string ] interface { } { } ,
}
for name , exp := range cases {
c . Templates = append ( c . Templates , & chart . File {
Name : path . Join ( "templates" , name ) ,
Data : [ ] byte ( exp . template ) ,
} )
}
vals := map [ string ] interface { } {
"Values" : map [ string ] interface { } { } ,
}
v , err := chartutil . CoalesceValues ( c , vals )
if err != nil {
t . Fatalf ( "Failed to coalesce values: %s" , err )
}
out , err := RenderWithClientProvider ( c , v , provider )
if err != nil {
t . Errorf ( "Failed to render templates: %s" , err )
}
for name , want := range cases {
t . Run ( name , func ( t * testing . T ) {
key := path . Join ( "moby/templates" , name )
if out [ key ] != want . output {
t . Errorf ( "Expected %q, got %q" , want , out [ key ] )
}
} )
}
}
func TestRenderWithClientProvider_error ( t * testing . T ) {
c := & chart . Chart {
Metadata : & chart . Metadata {
Name : "moby" ,
Version : "1.2.3" ,
} ,
Templates : [ ] * chart . File {
{ Name : "templates/error" , Data : [ ] byte ( ` {{ lookup "v1" "Error" "" "" }} ` ) } ,
} ,
Values : map [ string ] interface { } { } ,
}
vals := map [ string ] interface { } {
"Values" : map [ string ] interface { } { } ,
}
v , err := chartutil . CoalesceValues ( c , vals )
if err != nil {
t . Fatalf ( "Failed to coalesce values: %s" , err )
}
provider := & testClientProvider {
t : t ,
scheme : map [ string ] kindProps {
"v1/Error" : {
shouldErr : fmt . Errorf ( "kaboom" ) ,
} ,
} ,
}
_ , err = RenderWithClientProvider ( c , v , provider )
if err == nil || ! strings . Contains ( err . Error ( ) , "kaboom" ) {
t . Errorf ( "Expected error from client provider when rendering, got %q" , err )
}
}
2016-04-13 06:18:42 +08:00
func TestParallelRenderInternals ( t * testing . T ) {
// Make sure that we can use one Engine to run parallel template renders.
2019-03-07 07:45:52 +08:00
e := new ( Engine )
2016-04-13 06:18:42 +08:00
var wg sync . WaitGroup
for i := 0 ; i < 20 ; i ++ {
wg . Add ( 1 )
go func ( i int ) {
tt := fmt . Sprintf ( "expect-%d" , i )
2019-03-07 07:45:52 +08:00
tpls := map [ string ] renderable {
"t" : {
tpl : ` {{ .val }} ` ,
vals : map [ string ] interface { } { "val" : tt } ,
} ,
}
2019-03-03 13:09:03 +08:00
out , err := e . render ( tpls )
2016-04-13 06:18:42 +08:00
if err != nil {
t . Errorf ( "Failed to render %s: %s" , tt , err )
}
2019-03-07 07:45:52 +08:00
if out [ "t" ] != tt {
t . Errorf ( "Expected %q, got %q" , tt , out [ "t" ] )
2016-04-13 06:18:42 +08:00
}
wg . Done ( )
} ( i )
}
wg . Wait ( )
}
2016-04-22 04:50:16 +08:00
2019-07-24 00:33:03 +08:00
func TestParseErrors ( t * testing . T ) {
vals := chartutil . Values { "Values" : map [ string ] interface { } { } }
tplsUndefinedFunction := map [ string ] renderable {
"undefined_function" : { tpl : ` {{ foo }} ` , vals : vals } ,
}
_ , err := new ( Engine ) . render ( tplsUndefinedFunction )
if err == nil {
t . Fatalf ( "Expected failures while rendering: %s" , err )
}
expected := ` parse error at (undefined_function:1): function "foo" not defined `
if err . Error ( ) != expected {
t . Errorf ( "Expected '%s', got %q" , expected , err . Error ( ) )
}
}
func TestExecErrors ( t * testing . T ) {
2019-04-09 05:45:29 +08:00
vals := chartutil . Values { "Values" : map [ string ] interface { } { } }
2021-08-11 00:38:43 +08:00
cases := [ ] struct {
name string
tpls map [ string ] renderable
expected string
} {
{
name : "MissingRequired" ,
tpls : map [ string ] renderable {
"missing_required" : { tpl : ` {{ required "foo is required" .Values .foo }} ` , vals : vals } ,
} ,
expected : ` execution error at (missing_required:1:2): foo is required ` ,
} ,
{
name : "MissingRequiredWithColons" ,
tpls : map [ string ] renderable {
"missing_required_with_colons" : { tpl : ` {{ required ":this: message: has many: colons:" .Values .foo }} ` , vals : vals } ,
} ,
expected : ` execution error at (missing_required_with_colons:1:2): :this: message: has many: colons: ` ,
} ,
{
name : "Issue6044" ,
tpls : map [ string ] renderable {
"issue6044" : {
vals : vals ,
tpl : ` { { $ someEmptyValue := "" } }
2019-07-24 00:33:03 +08:00
{ { $ myvar := "abc" } }
2021-08-11 00:38:43 +08:00
{ { - required ( printf "%s: something is missing" $ myvar ) $ someEmptyValue | repeat 0 } } ` ,
} ,
} ,
expected : ` execution error at (issue6044:3:4): abc: something is missing ` ,
} ,
{
name : "MissingRequiredWithNewlines" ,
tpls : map [ string ] renderable {
"issue9981" : { tpl : ` {{ required "foo is required\nmore info after the break" .Values .foo }} ` , vals : vals } ,
} ,
expected : ` execution error at ( issue9981 : 1 : 2 ) : foo is required
more info after the break ` ,
} ,
{
name : "FailWithNewlines" ,
tpls : map [ string ] renderable {
"issue9981" : { tpl : ` {{ fail "something is wrong\nlinebreak" }} ` , vals : vals } ,
} ,
expected : ` execution error at ( issue9981 : 1 : 2 ) : something is wrong
linebreak ` ,
} ,
2019-04-09 05:45:29 +08:00
}
2021-08-11 00:38:43 +08:00
for _ , tt := range cases {
t . Run ( tt . name , func ( t * testing . T ) {
_ , err := new ( Engine ) . render ( tt . tpls )
if err == nil {
t . Fatalf ( "Expected failures while rendering: %s" , err )
}
if err . Error ( ) != tt . expected {
t . Errorf ( "Expected %q, got %q" , tt . expected , err . Error ( ) )
}
} )
2019-04-09 05:45:29 +08:00
}
}
2019-07-24 00:33:03 +08:00
2020-11-18 17:45:45 +08:00
func TestFailErrors ( t * testing . T ) {
vals := chartutil . Values { "Values" : map [ string ] interface { } { } }
2020-12-03 02:23:17 +08:00
failtpl := ` All your base are belong to us {{ fail "This is an error" }} `
2020-11-18 17:45:45 +08:00
tplsFailed := map [ string ] renderable {
"failtpl" : { tpl : failtpl , vals : vals } ,
}
_ , err := new ( Engine ) . render ( tplsFailed )
if err == nil {
t . Fatalf ( "Expected failures while rendering: %s" , err )
}
2020-12-07 22:58:30 +08:00
expected := ` execution error at (failtpl:1:33): This is an error `
2020-11-18 17:45:45 +08:00
if err . Error ( ) != expected {
t . Errorf ( "Expected '%s', got %q" , expected , err . Error ( ) )
}
2020-12-03 02:23:17 +08:00
var e Engine
e . LintMode = true
out , err := e . render ( tplsFailed )
if err != nil {
t . Fatal ( err )
}
expectStr := "All your base are belong to us"
if gotStr := out [ "failtpl" ] ; gotStr != expectStr {
t . Errorf ( "Expected %q, got %q (%v)" , expectStr , gotStr , out )
}
2020-11-18 17:45:45 +08:00
}
2016-04-22 04:50:16 +08:00
func TestAllTemplates ( t * testing . T ) {
ch1 := & chart . Chart {
2016-06-29 01:57:47 +08:00
Metadata : & chart . Metadata { Name : "ch1" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-10-22 07:12:41 +08:00
{ Name : "templates/foo" , Data : [ ] byte ( "foo" ) } ,
{ Name : "templates/bar" , Data : [ ] byte ( "bar" ) } ,
2016-04-22 04:50:16 +08:00
} ,
2018-08-25 02:28:29 +08:00
}
dep1 := & chart . Chart {
Metadata : & chart . Metadata { Name : "laboratory mice" } ,
Templates : [ ] * chart . File {
{ Name : "templates/pinky" , Data : [ ] byte ( "pinky" ) } ,
{ Name : "templates/brain" , Data : [ ] byte ( "brain" ) } ,
} ,
}
ch1 . AddDependency ( dep1 )
dep2 := & chart . Chart {
Metadata : & chart . Metadata { Name : "same thing we do every night" } ,
Templates : [ ] * chart . File {
{ Name : "templates/innermost" , Data : [ ] byte ( "innermost" ) } ,
2016-04-22 04:50:16 +08:00
} ,
}
2018-08-25 02:28:29 +08:00
dep1 . AddDependency ( dep2 )
2016-04-22 04:50:16 +08:00
2019-03-07 07:45:52 +08:00
tpls := allTemplates ( ch1 , chartutil . Values { } )
2016-04-22 05:52:16 +08:00
if len ( tpls ) != 5 {
t . Errorf ( "Expected 5 charts, got %d" , len ( tpls ) )
2016-04-22 04:50:16 +08:00
}
}
2021-08-24 02:06:52 +08:00
func TestChartValuesContainsIsRoot ( t * testing . T ) {
ch1 := & chart . Chart {
Metadata : & chart . Metadata { Name : "parent" } ,
Templates : [ ] * chart . File {
{ Name : "templates/isroot" , Data : [ ] byte ( "{{.Chart.IsRoot}}" ) } ,
} ,
}
dep1 := & chart . Chart {
Metadata : & chart . Metadata { Name : "child" } ,
Templates : [ ] * chart . File {
{ Name : "templates/isroot" , Data : [ ] byte ( "{{.Chart.IsRoot}}" ) } ,
} ,
}
ch1 . AddDependency ( dep1 )
out , err := Render ( ch1 , chartutil . Values { } )
if err != nil {
t . Fatalf ( "failed to render templates: %s" , err )
}
expects := map [ string ] string {
"parent/charts/child/templates/isroot" : "false" ,
"parent/templates/isroot" : "true" ,
}
for file , expect := range expects {
if out [ file ] != expect {
t . Errorf ( "Expected %q, got %q" , expect , out [ file ] )
}
}
}
2016-04-22 04:50:16 +08:00
func TestRenderDependency ( t * testing . T ) {
deptpl := ` {{ define "myblock" }} World {{ end }} `
toptpl := ` Hello {{ template "myblock" }} `
ch := & chart . Chart {
2016-07-07 05:31:12 +08:00
Metadata : & chart . Metadata { Name : "outerchart" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-10-22 07:12:41 +08:00
{ Name : "templates/outer" , Data : [ ] byte ( toptpl ) } ,
2016-04-22 04:50:16 +08:00
} ,
}
2018-08-25 02:28:29 +08:00
ch . AddDependency ( & chart . Chart {
Metadata : & chart . Metadata { Name : "innerchart" } ,
Templates : [ ] * chart . File {
{ Name : "templates/inner" , Data : [ ] byte ( deptpl ) } ,
} ,
} )
2016-04-22 04:50:16 +08:00
2019-03-07 07:45:52 +08:00
out , err := Render ( ch , map [ string ] interface { } { } )
2016-04-22 04:50:16 +08:00
if err != nil {
t . Fatalf ( "failed to render chart: %s" , err )
}
if len ( out ) != 2 {
t . Errorf ( "Expected 2, got %d" , len ( out ) )
}
expect := "Hello World"
2016-10-22 07:12:41 +08:00
if out [ "outerchart/templates/outer" ] != expect {
2016-04-22 04:50:16 +08:00
t . Errorf ( "Expected %q, got %q" , expect , out [ "outer" ] )
}
}
2016-04-22 06:45:42 +08:00
func TestRenderNestedValues ( t * testing . T ) {
2016-07-07 05:31:12 +08:00
innerpath := "templates/inner.tpl"
2016-04-22 06:45:42 +08:00
outerpath := "templates/outer.tpl"
2016-07-07 05:31:12 +08:00
// Ensure namespacing rules are working.
deepestpath := "templates/inner.tpl"
checkrelease := "templates/release.tpl"
2021-05-27 01:04:34 +08:00
// Ensure subcharts scopes are working.
subchartspath := "templates/subcharts.tpl"
2016-04-22 06:45:42 +08:00
deepest := & chart . Chart {
Metadata : & chart . Metadata { Name : "deepest" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-06-23 11:24:34 +08:00
{ Name : deepestpath , Data : [ ] byte ( ` And this same {{ .Values .what }} that smiles {{ .Values .global .when }} ` ) } ,
2016-06-24 06:06:53 +08:00
{ Name : checkrelease , Data : [ ] byte ( ` Tomorrow will be {{ default "happy" .Release .Name }} ` ) } ,
2016-04-22 06:45:42 +08:00
} ,
2021-05-27 01:04:34 +08:00
Values : map [ string ] interface { } { "what" : "milkshake" , "where" : "here" } ,
2016-04-22 06:45:42 +08:00
}
inner := & chart . Chart {
Metadata : & chart . Metadata { Name : "herrick" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-06-23 11:24:34 +08:00
{ Name : innerpath , Data : [ ] byte ( ` Old {{ .Values .who }} is still a-flyin' ` ) } ,
2016-04-22 06:45:42 +08:00
} ,
2021-05-27 01:04:34 +08:00
Values : map [ string ] interface { } { "who" : "Robert" , "what" : "glasses" } ,
2016-04-22 06:45:42 +08:00
}
2018-08-25 02:28:29 +08:00
inner . AddDependency ( deepest )
2016-04-22 06:45:42 +08:00
outer := & chart . Chart {
Metadata : & chart . Metadata { Name : "top" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-06-23 11:24:34 +08:00
{ Name : outerpath , Data : [ ] byte ( ` Gather ye {{ .Values .what }} while ye may ` ) } ,
2021-05-27 01:04:34 +08:00
{ Name : subchartspath , Data : [ ] byte ( ` The glorious Lamp of {{ .Subcharts .herrick .Subcharts .deepest .Values .where }} , the {{ .Subcharts .herrick .Values .what }} ` ) } ,
2016-04-22 06:45:42 +08:00
} ,
2018-08-30 00:56:19 +08:00
Values : map [ string ] interface { } {
"what" : "stinkweed" ,
"who" : "me" ,
"herrick" : map [ string ] interface { } {
2021-05-27 01:04:34 +08:00
"who" : "time" ,
"what" : "Sun" ,
2018-08-30 00:56:19 +08:00
} ,
} ,
2016-04-22 06:45:42 +08:00
}
2018-08-25 02:28:29 +08:00
outer . AddDependency ( inner )
2016-04-22 06:45:42 +08:00
2018-12-05 07:57:24 +08:00
injValues := map [ string ] interface { } {
"what" : "rosebuds" ,
"herrick" : map [ string ] interface { } {
"deepest" : map [ string ] interface { } {
2021-05-27 01:04:34 +08:00
"what" : "flower" ,
"where" : "Heaven" ,
2018-12-05 07:57:24 +08:00
} ,
} ,
"global" : map [ string ] interface { } {
"when" : "to-day" ,
} ,
}
2016-04-22 06:45:42 +08:00
2018-04-20 15:13:19 +08:00
tmp , err := chartutil . CoalesceValues ( outer , injValues )
2016-06-14 03:11:39 +08:00
if err != nil {
t . Fatalf ( "Failed to coalesce values: %s" , err )
}
2016-06-23 11:24:34 +08:00
inject := chartutil . Values {
"Values" : tmp ,
"Chart" : outer . Metadata ,
"Release" : chartutil . Values {
2016-06-24 06:06:53 +08:00
"Name" : "dyin" ,
2016-06-23 11:24:34 +08:00
} ,
}
2016-06-14 03:11:39 +08:00
t . Logf ( "Calculated values: %v" , inject )
2019-03-07 07:45:52 +08:00
out , err := Render ( outer , inject )
2016-04-22 06:45:42 +08:00
if err != nil {
t . Fatalf ( "failed to render templates: %s" , err )
}
2016-08-31 00:13:34 +08:00
fullouterpath := "top/" + outerpath
if out [ fullouterpath ] != "Gather ye rosebuds while ye may" {
t . Errorf ( "Unexpected outer: %q" , out [ fullouterpath ] )
2016-04-22 06:45:42 +08:00
}
2016-08-31 00:13:34 +08:00
fullinnerpath := "top/charts/herrick/" + innerpath
if out [ fullinnerpath ] != "Old time is still a-flyin'" {
t . Errorf ( "Unexpected inner: %q" , out [ fullinnerpath ] )
2016-04-22 06:45:42 +08:00
}
2016-08-31 00:13:34 +08:00
fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath
if out [ fulldeepestpath ] != "And this same flower that smiles to-day" {
t . Errorf ( "Unexpected deepest: %q" , out [ fulldeepestpath ] )
2016-04-22 06:45:42 +08:00
}
2016-06-24 06:06:53 +08:00
2016-08-31 00:13:34 +08:00
fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease
if out [ fullcheckrelease ] != "Tomorrow will be dyin" {
t . Errorf ( "Unexpected release: %q" , out [ fullcheckrelease ] )
2016-06-24 06:06:53 +08:00
}
2021-05-27 01:04:34 +08:00
fullchecksubcharts := "top/" + subchartspath
if out [ fullchecksubcharts ] != "The glorious Lamp of Heaven, the Sun" {
t . Errorf ( "Unexpected subcharts: %q" , out [ fullchecksubcharts ] )
}
2016-04-22 06:45:42 +08:00
}
2016-06-29 01:57:47 +08:00
func TestRenderBuiltinValues ( t * testing . T ) {
inner := & chart . Chart {
Metadata : & chart . Metadata { Name : "Latium" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-10-22 07:12:41 +08:00
{ Name : "templates/Lavinia" , Data : [ ] byte ( ` {{ .Template .Name }} {{ .Chart .Name }} {{ .Release .Name }} ` ) } ,
{ Name : "templates/From" , Data : [ ] byte ( ` {{ .Files .author | printf "%s" }} {{ .Files .Get "book/title.txt" }} ` ) } ,
2016-06-29 01:57:47 +08:00
} ,
2018-04-19 07:28:50 +08:00
Files : [ ] * chart . File {
{ Name : "author" , Data : [ ] byte ( "Virgil" ) } ,
{ Name : "book/title.txt" , Data : [ ] byte ( "Aeneid" ) } ,
2016-07-28 02:04:11 +08:00
} ,
2016-06-29 01:57:47 +08:00
}
outer := & chart . Chart {
Metadata : & chart . Metadata { Name : "Troy" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-10-22 07:12:41 +08:00
{ Name : "templates/Aeneas" , Data : [ ] byte ( ` {{ .Template .Name }} {{ .Chart .Name }} {{ .Release .Name }} ` ) } ,
2021-05-27 01:04:34 +08:00
{ Name : "templates/Amata" , Data : [ ] byte ( ` {{ .Subcharts .Latium .Chart .Name }} {{ .Subcharts .Latium .Files .author | printf "%s" }} ` ) } ,
2016-06-29 01:57:47 +08:00
} ,
}
2018-08-25 02:28:29 +08:00
outer . AddDependency ( inner )
2016-06-29 01:57:47 +08:00
inject := chartutil . Values {
2018-04-20 15:13:19 +08:00
"Values" : "" ,
2016-06-29 01:57:47 +08:00
"Chart" : outer . Metadata ,
"Release" : chartutil . Values {
"Name" : "Aeneid" ,
} ,
}
t . Logf ( "Calculated values: %v" , outer )
2019-03-07 07:45:52 +08:00
out , err := Render ( outer , inject )
2016-06-29 01:57:47 +08:00
if err != nil {
t . Fatalf ( "failed to render templates: %s" , err )
}
expects := map [ string ] string {
2016-10-22 07:12:41 +08:00
"Troy/charts/Latium/templates/Lavinia" : "Troy/charts/Latium/templates/LaviniaLatiumAeneid" ,
"Troy/templates/Aeneas" : "Troy/templates/AeneasTroyAeneid" ,
2021-05-27 01:04:34 +08:00
"Troy/templates/Amata" : "Latium Virgil" ,
2016-10-22 07:12:41 +08:00
"Troy/charts/Latium/templates/From" : "Virgil Aeneid" ,
2016-06-29 01:57:47 +08:00
}
for file , expect := range expects {
if out [ file ] != expect {
t . Errorf ( "Expected %q, got %q" , expect , out [ file ] )
}
}
}
2016-07-30 07:06:09 +08:00
2018-08-25 02:28:29 +08:00
func TestAlterFuncMap_include ( t * testing . T ) {
2016-07-30 07:06:09 +08:00
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "conrad" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2016-10-22 07:12:41 +08:00
{ Name : "templates/quote" , Data : [ ] byte ( ` {{ include "conrad/templates/_partial" . | indent 2 }} dead. ` ) } ,
{ Name : "templates/_partial" , Data : [ ] byte ( ` {{ .Release .Name }} - he ` ) } ,
2016-07-30 07:06:09 +08:00
} ,
}
2019-12-02 22:57:51 +08:00
// Check nested reference in include FuncMap
d := & chart . Chart {
Metadata : & chart . Metadata { Name : "nested" } ,
Templates : [ ] * chart . File {
{ Name : "templates/quote" , Data : [ ] byte ( ` {{ include "nested/templates/quote" . | indent 2 }} dead. ` ) } ,
{ Name : "templates/_partial" , Data : [ ] byte ( ` {{ .Release .Name }} - he ` ) } ,
} ,
}
2016-07-30 07:06:09 +08:00
v := chartutil . Values {
2018-04-20 15:13:19 +08:00
"Values" : "" ,
2016-07-30 07:06:09 +08:00
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "Mistah Kurtz" ,
} ,
}
2019-03-07 07:45:52 +08:00
out , err := Render ( c , v )
2016-07-30 07:06:09 +08:00
if err != nil {
t . Fatal ( err )
}
expect := " Mistah Kurtz - he dead."
2016-10-22 07:12:41 +08:00
if got := out [ "conrad/templates/quote" ] ; got != expect {
2016-07-30 07:06:09 +08:00
t . Errorf ( "Expected %q, got %q (%v)" , expect , got , out )
}
2019-12-02 22:57:51 +08:00
_ , err = Render ( d , v )
expectErrName := "nested/templates/quote"
if err == nil {
t . Errorf ( "Expected err of nested reference name: %v" , expectErrName )
}
2018-08-25 02:28:29 +08:00
}
2017-03-02 23:37:14 +08:00
2018-08-25 02:28:29 +08:00
func TestAlterFuncMap_require ( t * testing . T ) {
c := & chart . Chart {
2017-03-02 23:37:14 +08:00
Metadata : & chart . Metadata { Name : "conan" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2017-03-02 23:37:14 +08:00
{ Name : "templates/quote" , Data : [ ] byte ( ` All your base are belong to {{ required "A valid 'who' is required" .Values .who }} ` ) } ,
{ Name : "templates/bases" , Data : [ ] byte ( ` All {{ required "A valid 'bases' is required" .Values .bases }} of them! ` ) } ,
} ,
}
2018-08-25 02:28:29 +08:00
v := chartutil . Values {
2017-03-02 23:37:14 +08:00
"Values" : chartutil . Values {
"who" : "us" ,
"bases" : 2 ,
} ,
2018-08-25 02:28:29 +08:00
"Chart" : c . Metadata ,
2017-03-02 23:37:14 +08:00
"Release" : chartutil . Values {
"Name" : "That 90s meme" ,
} ,
}
2019-03-07 07:45:52 +08:00
out , err := Render ( c , v )
2017-03-02 23:37:14 +08:00
if err != nil {
t . Fatal ( err )
}
expectStr := "All your base are belong to us"
2018-08-25 02:28:29 +08:00
if gotStr := out [ "conan/templates/quote" ] ; gotStr != expectStr {
t . Errorf ( "Expected %q, got %q (%v)" , expectStr , gotStr , out )
2017-03-02 23:37:14 +08:00
}
expectNum := "All 2 of them!"
2018-08-25 02:28:29 +08:00
if gotNum := out [ "conan/templates/bases" ] ; gotNum != expectNum {
t . Errorf ( "Expected %q, got %q (%v)" , expectNum , gotNum , out )
2017-03-02 23:37:14 +08:00
}
2019-06-14 01:52:00 +08:00
// test required without passing in needed values with lint mode on
// verifies lint replaces required with an empty string (should not fail)
lintValues := chartutil . Values {
"Values" : chartutil . Values {
"who" : "us" ,
} ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "That 90s meme" ,
} ,
}
var e Engine
e . LintMode = true
out , err = e . Render ( c , lintValues )
if err != nil {
t . Fatal ( err )
}
expectStr = "All your base are belong to us"
if gotStr := out [ "conan/templates/quote" ] ; gotStr != expectStr {
t . Errorf ( "Expected %q, got %q (%v)" , expectStr , gotStr , out )
}
expectNum = "All of them!"
if gotNum := out [ "conan/templates/bases" ] ; gotNum != expectNum {
t . Errorf ( "Expected %q, got %q (%v)" , expectNum , gotNum , out )
}
2018-08-25 02:28:29 +08:00
}
2017-03-02 23:37:14 +08:00
2018-08-25 02:28:29 +08:00
func TestAlterFuncMap_tpl ( t * testing . T ) {
c := & chart . Chart {
2017-04-29 00:21:42 +08:00
Metadata : & chart . Metadata { Name : "TplFunction" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2017-04-29 00:21:42 +08:00
{ Name : "templates/base" , Data : [ ] byte ( ` Evaluate tpl {{ tpl "Value: {{ .Values.value}}" . }} ` ) } ,
} ,
}
2018-08-25 02:28:29 +08:00
v := chartutil . Values {
2017-04-29 00:21:42 +08:00
"Values" : chartutil . Values {
"value" : "myvalue" ,
} ,
2018-08-25 02:28:29 +08:00
"Chart" : c . Metadata ,
2017-04-29 00:21:42 +08:00
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
2019-03-07 07:45:52 +08:00
out , err := Render ( c , v )
2017-04-29 00:21:42 +08:00
if err != nil {
t . Fatal ( err )
}
2018-08-25 02:28:29 +08:00
expect := "Evaluate tpl Value: myvalue"
if got := out [ "TplFunction/templates/base" ] ; got != expect {
t . Errorf ( "Expected %q, got %q (%v)" , expect , got , out )
2017-04-29 00:21:42 +08:00
}
2018-08-25 02:28:29 +08:00
}
2017-04-29 00:21:42 +08:00
2018-08-25 02:28:29 +08:00
func TestAlterFuncMap_tplfunc ( t * testing . T ) {
c := & chart . Chart {
2017-05-02 15:41:58 +08:00
Metadata : & chart . Metadata { Name : "TplFunction" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2017-05-02 15:41:58 +08:00
{ Name : "templates/base" , Data : [ ] byte ( ` Evaluate tpl {{ tpl "Value: {{ .Values.value | quote}}" . }} ` ) } ,
} ,
}
2018-08-25 02:28:29 +08:00
v := chartutil . Values {
2017-05-02 15:41:58 +08:00
"Values" : chartutil . Values {
"value" : "myvalue" ,
} ,
2018-08-25 02:28:29 +08:00
"Chart" : c . Metadata ,
2017-05-02 15:41:58 +08:00
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
2019-03-07 07:45:52 +08:00
out , err := Render ( c , v )
2017-05-02 15:41:58 +08:00
if err != nil {
t . Fatal ( err )
}
2018-08-25 02:28:29 +08:00
expect := "Evaluate tpl Value: \"myvalue\""
if got := out [ "TplFunction/templates/base" ] ; got != expect {
t . Errorf ( "Expected %q, got %q (%v)" , expect , got , out )
2017-05-02 15:41:58 +08:00
}
2018-08-25 02:28:29 +08:00
}
2017-05-02 15:41:58 +08:00
2018-08-25 02:28:29 +08:00
func TestAlterFuncMap_tplinclude ( t * testing . T ) {
c := & chart . Chart {
2017-05-28 16:55:15 +08:00
Metadata : & chart . Metadata { Name : "TplFunction" } ,
2018-04-19 07:28:50 +08:00
Templates : [ ] * chart . File {
2017-05-28 16:55:15 +08:00
{ Name : "templates/base" , Data : [ ] byte ( ` {{ tpl " { { include ` + "`" + ` TplFunction/templates/_partial ` + "`" + ` . | quote }}" .}} ` ) } ,
2017-12-04 00:39:34 +08:00
{ Name : "templates/_partial" , Data : [ ] byte ( ` {{ .Template .Name }} ` ) } ,
2017-05-28 16:55:15 +08:00
} ,
}
2018-08-25 02:28:29 +08:00
v := chartutil . Values {
2017-05-28 16:55:15 +08:00
"Values" : chartutil . Values {
"value" : "myvalue" ,
} ,
2018-08-25 02:28:29 +08:00
"Chart" : c . Metadata ,
2017-05-28 16:55:15 +08:00
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
2019-03-07 07:45:52 +08:00
out , err := Render ( c , v )
2017-05-28 16:55:15 +08:00
if err != nil {
t . Fatal ( err )
}
2018-08-25 02:28:29 +08:00
expect := "\"TplFunction/templates/base\""
if got := out [ "TplFunction/templates/base" ] ; got != expect {
t . Errorf ( "Expected %q, got %q (%v)" , expect , got , out )
2017-05-28 16:55:15 +08:00
}
2016-07-30 07:06:09 +08:00
}
2020-02-11 01:25:41 +08:00
func TestRenderRecursionLimit ( t * testing . T ) {
// endless recursion should produce an error
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "bad" } ,
Templates : [ ] * chart . File {
{ Name : "templates/base" , Data : [ ] byte ( ` {{ include "recursion" . }} ` ) } ,
{ Name : "templates/recursion" , Data : [ ] byte ( ` {{ define "recursion" }} {{ include "recursion" . }} {{ end }} ` ) } ,
} ,
}
v := chartutil . Values {
"Values" : "" ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
expectErr := "rendering template has a nested reference name: recursion: unable to execute template"
_ , err := Render ( c , v )
if err == nil || ! strings . HasSuffix ( err . Error ( ) , expectErr ) {
t . Errorf ( "Expected err with suffix: %s" , expectErr )
}
// calling the same function many times is ok
times := 4000
phrase := "All work and no play makes Jack a dull boy"
printFunc := ` {{ define "overlook" }} {{ printf " ` + phrase + ` \n"}} {{ end }} `
var repeatedIncl string
for i := 0 ; i < times ; i ++ {
repeatedIncl += ` {{ include "overlook" . }} `
}
d := & chart . Chart {
Metadata : & chart . Metadata { Name : "overlook" } ,
Templates : [ ] * chart . File {
{ Name : "templates/quote" , Data : [ ] byte ( repeatedIncl ) } ,
{ Name : "templates/_function" , Data : [ ] byte ( printFunc ) } ,
} ,
}
out , err := Render ( d , v )
if err != nil {
t . Fatal ( err )
}
var expect string
for i := 0 ; i < times ; i ++ {
expect += phrase + "\n"
}
if got := out [ "overlook/templates/quote" ] ; got != expect {
t . Errorf ( "Expected %q, got %q (%v)" , expect , got , out )
}
}
2022-10-20 23:25:09 +08:00
func TestRenderLoadTemplateForTplFromFile ( t * testing . T ) {
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "TplLoadFromFile" } ,
Templates : [ ] * chart . File {
{ Name : "templates/base" , Data : [ ] byte ( ` {{ tpl ( .Files .Get .Values .filename ) . }} ` ) } ,
{ Name : "templates/_function" , Data : [ ] byte ( ` {{ define "test-function" }} test-function {{ end }} ` ) } ,
} ,
Files : [ ] * chart . File {
{ Name : "test" , Data : [ ] byte ( ` {{ tpl ( .Files .Get .Values .filename2 ) . }} ` ) } ,
{ Name : "test2" , Data : [ ] byte ( ` {{ include "test-function" . }} {{ define "nested-define" }} nested-define-content {{ end }} {{ include "nested-define" . }} ` ) } ,
} ,
}
v := chartutil . Values {
"Values" : chartutil . Values {
"filename" : "test" ,
"filename2" : "test2" ,
} ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
out , err := Render ( c , v )
if err != nil {
t . Fatal ( err )
}
expect := "test-function nested-define-content"
if got := out [ "TplLoadFromFile/templates/base" ] ; got != expect {
t . Fatalf ( "Expected %q, got %q" , expect , got )
}
}
2022-10-20 23:24:05 +08:00
func TestRenderTplEmpty ( t * testing . T ) {
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "TplEmpty" } ,
Templates : [ ] * chart . File {
{ Name : "templates/empty-string" , Data : [ ] byte ( ` {{ tpl "" . }} ` ) } ,
{ Name : "templates/empty-action" , Data : [ ] byte ( ` {{ tpl "{{ \"\"}}" . }} ` ) } ,
{ Name : "templates/only-defines" , Data : [ ] byte ( ` {{ tpl "{{define \"not-invoked\"}}not-rendered{{end}}" . }} ` ) } ,
} ,
}
v := chartutil . Values {
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
out , err := Render ( c , v )
if err != nil {
t . Fatal ( err )
}
expects := map [ string ] string {
"TplEmpty/templates/empty-string" : "" ,
"TplEmpty/templates/empty-action" : "" ,
"TplEmpty/templates/only-defines" : "" ,
}
for file , expect := range expects {
if out [ file ] != expect {
t . Errorf ( "Expected %q, got %q" , expect , out [ file ] )
}
}
}
2022-10-20 23:24:05 +08:00
func TestRenderTplTemplateNames ( t * testing . T ) {
// .Template.BasePath and .Name make it through
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "TplTemplateNames" } ,
Templates : [ ] * chart . File {
{ Name : "templates/default-basepath" , Data : [ ] byte ( ` {{ tpl "{{ .Template.BasePath }}" . }} ` ) } ,
{ Name : "templates/default-name" , Data : [ ] byte ( ` {{ tpl "{{ .Template.Name }}" . }} ` ) } ,
{ Name : "templates/modified-basepath" , Data : [ ] byte ( ` {{ tpl "{{ .Template.BasePath }}" .Values .dot }} ` ) } ,
{ Name : "templates/modified-name" , Data : [ ] byte ( ` {{ tpl "{{ .Template.Name }}" .Values .dot }} ` ) } ,
{ Name : "templates/modified-field" , Data : [ ] byte ( ` {{ tpl "{{ .Template.Field }}" .Values .dot }} ` ) } ,
} ,
}
v := chartutil . Values {
"Values" : chartutil . Values {
"dot" : chartutil . Values {
"Template" : chartutil . Values {
"BasePath" : "path/to/template" ,
"Name" : "name-of-template" ,
"Field" : "extra-field" ,
} ,
} ,
} ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
out , err := Render ( c , v )
if err != nil {
t . Fatal ( err )
}
expects := map [ string ] string {
"TplTemplateNames/templates/default-basepath" : "TplTemplateNames/templates" ,
"TplTemplateNames/templates/default-name" : "TplTemplateNames/templates/default-name" ,
"TplTemplateNames/templates/modified-basepath" : "path/to/template" ,
"TplTemplateNames/templates/modified-name" : "name-of-template" ,
2022-10-21 00:08:59 +08:00
"TplTemplateNames/templates/modified-field" : "extra-field" ,
2022-10-20 23:24:05 +08:00
}
for file , expect := range expects {
if out [ file ] != expect {
t . Errorf ( "Expected %q, got %q" , expect , out [ file ] )
}
}
}
2022-10-20 23:24:05 +08:00
func TestRenderTplRedefines ( t * testing . T ) {
// Redefining a template inside 'tpl' does not affect the outer definition
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "TplRedefines" } ,
Templates : [ ] * chart . File {
{ Name : "templates/_partials" , Data : [ ] byte ( ` {{ define "partial" }} original-in-partial {{ end }} ` ) } ,
{ Name : "templates/partial" , Data : [ ] byte (
` before: {{ include "partial" . }} \n {{ tpl .Values .partialText . }} \nafter: {{ include "partial" . }} ` ,
) } ,
{ Name : "templates/manifest" , Data : [ ] byte (
` {{ define "manifest" }} original-in-manifest {{ end }} ` +
` before: {{ include "manifest" . }} \n {{ tpl .Values .manifestText . }} \nafter: {{ include "manifest" . }} ` ,
) } ,
2022-10-21 00:08:59 +08:00
{ Name : "templates/manifest-only" , Data : [ ] byte (
` {{ define "manifest-only" }} only-in-manifest {{ end }} ` +
` before: {{ include "manifest-only" . }} \n {{ tpl .Values .manifestOnlyText . }} \nafter: {{ include "manifest-only" . }} ` ,
) } ,
2022-10-21 00:08:59 +08:00
{ Name : "templates/nested" , Data : [ ] byte (
` {{ define "nested" }} original-in-manifest {{ end }} ` +
` {{ define "nested-outer" }} original-outer-in-manifest {{ end }} ` +
` before: {{ include "nested" . }} {{ include "nested-outer" . }} \n ` +
` {{ tpl .Values .nestedText . }} \n ` +
` after: {{ include "nested" . }} {{ include "nested-outer" . }} ` ,
) } ,
2022-10-20 23:24:05 +08:00
} ,
}
v := chartutil . Values {
"Values" : chartutil . Values {
"partialText" : ` {{ define "partial" }} redefined-in-tpl {{ end }} tpl: {{ include "partial" . }} ` ,
"manifestText" : ` {{ define "manifest" }} redefined-in-tpl {{ end }} tpl: {{ include "manifest" . }} ` ,
"manifestOnlyText" : ` tpl: {{ include "manifest-only" . }} ` ,
2022-10-21 00:08:59 +08:00
"nestedText" : ` {{ define "nested" }} redefined-in-tpl {{ end }} ` +
` {{ define "nested-outer" }} redefined-outer-in-tpl {{ end }} ` +
` before-inner-tpl: {{ include "nested" . }} {{ include "nested-outer" . }} \n ` +
` {{ tpl .Values .innerText . }} \n ` +
` after-inner-tpl: {{ include "nested" . }} {{ include "nested-outer" . }} ` ,
"innerText" : ` {{ define "nested" }} redefined-in-inner-tpl {{ end }} inner-tpl: {{ include "nested" . }} {{ include "nested-outer" . }} ` ,
2022-10-20 23:24:05 +08:00
} ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
out , err := Render ( c , v )
if err != nil {
t . Fatal ( err )
}
expects := map [ string ] string {
2022-10-21 00:08:59 +08:00
"TplRedefines/templates/partial" : ` before: original-in-partial\ntpl: redefined-in-tpl\nafter: original-in-partial ` ,
"TplRedefines/templates/manifest" : ` before: original-in-manifest\ntpl: redefined-in-tpl\nafter: original-in-manifest ` ,
"TplRedefines/templates/manifest-only" : ` before: only-in-manifest\ntpl: only-in-manifest\nafter: only-in-manifest ` ,
2022-10-21 00:08:59 +08:00
"TplRedefines/templates/nested" : ` before: original-in-manifest original-outer-in-manifest\n ` +
` before-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n ` +
` inner-tpl: redefined-in-inner-tpl redefined-outer-in-tpl\n ` +
` after-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n ` +
` after: original-in-manifest original-outer-in-manifest ` ,
2022-10-20 23:24:05 +08:00
}
for file , expect := range expects {
if out [ file ] != expect {
t . Errorf ( "Expected %q, got %q" , expect , out [ file ] )
}
}
}
2023-05-12 05:09:35 +08:00
func TestRenderTplMissingKey ( t * testing . T ) {
// Rendering a missing key results in empty/zero output.
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "TplMissingKey" } ,
Templates : [ ] * chart . File {
{ Name : "templates/manifest" , Data : [ ] byte (
` missingValue: {{ tpl "{{.Values.noSuchKey}}" . }} ` ,
) } ,
} ,
}
v := chartutil . Values {
"Values" : chartutil . Values { } ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
out , err := Render ( c , v )
if err != nil {
t . Fatal ( err )
}
expects := map [ string ] string {
"TplMissingKey/templates/manifest" : ` missingValue: ` ,
}
for file , expect := range expects {
if out [ file ] != expect {
t . Errorf ( "Expected %q, got %q" , expect , out [ file ] )
}
}
}
func TestRenderTplMissingKeyString ( t * testing . T ) {
// Rendering a missing key results in error
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "TplMissingKeyStrict" } ,
Templates : [ ] * chart . File {
{ Name : "templates/manifest" , Data : [ ] byte (
` missingValue: {{ tpl "{{.Values.noSuchKey}}" . }} ` ,
) } ,
} ,
}
v := chartutil . Values {
"Values" : chartutil . Values { } ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
e := new ( Engine )
e . Strict = true
out , err := e . Render ( c , v )
if err == nil {
t . Errorf ( "Expected error, got %v" , out )
return
}
2025-01-25 23:06:06 +08:00
errTxt := fmt . Sprint ( err )
if ! strings . Contains ( errTxt , "noSuchKey" ) {
t . Errorf ( "Expected error to contain 'noSuchKey', got %s" , errTxt )
2023-05-12 05:09:35 +08:00
}
2025-01-25 23:06:06 +08:00
2023-05-12 05:09:35 +08:00
}
2025-04-07 22:47:40 +08:00
2024-12-04 07:32:56 +08:00
func TestNestedHelpersProducesMultilineStacktrace ( t * testing . T ) {
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "NestedHelperFunctions" } ,
Templates : [ ] * chart . File {
{ Name : "templates/svc.yaml" , Data : [ ] byte (
` name: {{ include "nested_helper.name" . }} ` ,
) } ,
{ Name : "templates/_helpers_1.tpl" , Data : [ ] byte (
` {{- define "nested_helper.name" -}} {{- include "common.names.get_name" . -}} {{- end -}} ` ,
) } ,
{ Name : "charts/common/templates/_helpers_2.tpl" , Data : [ ] byte (
` {{- define "common.names.get_name" -}} {{- .Release .Name | trunc 63 | trimSuffix "-" -}} {{- end -}} ` ,
) } ,
} ,
}
2025-01-01 07:54:34 +08:00
expectedErrorMessage := ` NestedHelperFunctions / templates / svc . yaml : 1 : 9
2025-02-23 00:58:11 +08:00
executing "NestedHelperFunctions/templates/svc.yaml" at < include "nested_helper.name" . > :
2025-01-01 07:54:34 +08:00
error calling include :
NestedHelperFunctions / templates / _helpers_1 . tpl : 1 : 39
2025-02-23 00:58:11 +08:00
executing "nested_helper.name" at < include "common.names.get_name" . > :
2025-01-01 07:54:34 +08:00
error calling include :
NestedHelperFunctions / charts / common / templates / _helpers_2 . tpl : 1 : 50
2025-02-23 00:58:11 +08:00
executing "common.names.get_name" at < . Release . Name > :
2025-01-01 07:54:34 +08:00
nil pointer evaluating interface { } . Name
`
2024-12-04 07:32:56 +08:00
v := chartutil . Values { }
2024-12-03 10:52:41 +08:00
val , _ := chartutil . CoalesceValues ( c , v )
vals := map [ string ] interface { } {
"Values" : val . AsMap ( ) ,
}
2024-12-04 07:32:56 +08:00
_ , err := Render ( c , vals )
2024-12-03 10:52:41 +08:00
2024-12-04 07:32:56 +08:00
assert . NotNil ( t , err )
2025-01-01 07:54:34 +08:00
assert . Equal ( t , expectedErrorMessage , err . Error ( ) )
2024-12-03 10:52:41 +08:00
}
2025-04-24 09:56:30 +08:00
func TestMultilineNoTemplateAssociatedError ( t * testing . T ) {
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "multiline" } ,
Templates : [ ] * chart . File {
{ Name : "templates/svc.yaml" , Data : [ ] byte (
` name: {{ include "nested_helper.name" . }} ` ,
) } ,
{ Name : "templates/test.yaml" , Data : [ ] byte (
` {{ toYaml .Values }} ` ,
) } ,
{ Name : "charts/common/templates/_helpers_2.tpl" , Data : [ ] byte (
` {{ toYaml .Values }} ` ,
) } ,
} ,
}
expectedErrorMessage := ` multiline / templates / svc . yaml : 1 : 9
executing "multiline/templates/svc.yaml" at < include "nested_helper.name" . > :
error calling include :
template : no template "nested_helper.name" associated with template "gotpl"
`
v := chartutil . Values { }
val , _ := chartutil . CoalesceValues ( c , v )
vals := map [ string ] interface { } {
"Values" : val . AsMap ( ) ,
}
_ , err := Render ( c , vals )
assert . NotNil ( t , err )
assert . Equal ( t , expectedErrorMessage , err . Error ( ) )
}
2025-04-07 22:47:40 +08:00
func TestRenderCustomTemplateFuncs ( t * testing . T ) {
2025-04-17 06:30:43 +08:00
// Create a chart with two templates that use custom functions
2025-04-07 22:47:40 +08:00
c := & chart . Chart {
Metadata : & chart . Metadata { Name : "CustomFunc" } ,
Templates : [ ] * chart . File {
{
Name : "templates/manifest" ,
Data : [ ] byte ( ` {{ exclaim .Values .message }} ` ) ,
} ,
2025-04-15 06:55:28 +08:00
{
Name : "templates/override" ,
Data : [ ] byte ( ` {{ upper .Values .message }} ` ) ,
} ,
2025-04-07 22:47:40 +08:00
} ,
}
v := chartutil . Values {
"Values" : chartutil . Values {
"message" : "hello" ,
} ,
"Chart" : c . Metadata ,
"Release" : chartutil . Values {
"Name" : "TestRelease" ,
} ,
}
2025-04-17 06:30:43 +08:00
// Define a custom template function "exclaim" that appends "!!!" to a string and override "upper" function
2025-04-07 22:47:40 +08:00
customFuncs := template . FuncMap {
"exclaim" : func ( input string ) string {
return input + "!!!"
} ,
2025-04-15 06:55:28 +08:00
"upper" : func ( s string ) string {
return "custom:" + s
} ,
2025-04-07 22:47:40 +08:00
}
// 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 )
}
2025-04-15 06:55:28 +08:00
// 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 )
}
2025-04-07 22:47:40 +08:00
}