From 770c3daa5f18f6eb3837882a8a683c9f0325b58e Mon Sep 17 00:00:00 2001 From: KUOKA Yusuke Date: Fri, 28 Sep 2018 11:44:49 +0900 Subject: [PATCH] feat: `get` and `getOrNil` template funcs to allow defaulting in templates (#370) * feat: `get` and `getOrNil` template funcs to allow defaulting in templates Ref #357 * Add docs about missing keys and default values in templates --- README.md | 9 +++- docs/writing-helmfile.md | 28 +++++++++++++ tmpl/funcs.go | 2 + tmpl/get_or_nil.go | 60 ++++++++++++++++++++++++++ tmpl/get_or_nil_test.go | 91 ++++++++++++++++++++++++++++++++++++++++ tmpl/tmpl_test.go | 50 ++++++++++++++++++++++ 6 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 tmpl/get_or_nil.go create mode 100644 tmpl/get_or_nil_test.go diff --git a/README.md b/README.md index 08c57eda..a4fa8a12 100644 --- a/README.md +++ b/README.md @@ -443,7 +443,7 @@ releaseName: prod `values.yaml.gotmpl` ```yaml -domain: {{ .Environment.Values.domain | default "dev.example.com" }} +domain: {{ .Environment.Values | getOrNil "my.domain" | default "dev.example.com" }} ``` `helmfile sync` installs `myapp` with the value `domain=dev.example.com`, @@ -704,6 +704,13 @@ Run `helmfile --environment staging sync` and see it results in helmfile running Voilà! You can mix helm releases that are backed by remote charts, local charts, and even kustomize overlays. +## Guides + +Use the [Helmfile Best Practices Guide](/docs/writing-helmfile.md) to write advanced helmfiles that features: + +- Default values +- Layering + ## Using env files helmfile itself doesn't have an ability to load env files. But you can write some bash script to achieve the goal: diff --git a/docs/writing-helmfile.md b/docs/writing-helmfile.md index 524fe21c..b90d19aa 100644 --- a/docs/writing-helmfile.md +++ b/docs/writing-helmfile.md @@ -2,6 +2,34 @@ This guide covers the Helmfile’s considered patterns for writing advanced helmfiles. It focuses on how helmfile should be structured and executed. +## Missing keys and Default values + +helmfile tries its best to inform users for noticing potential mistakes. + +One example of how helmfile achieves it is that, `helmfile` fails when you tried to access missing keys in environment values. + +That is, the following example let `helmfile` fail when you have no `eventApi.replicas` defined in environment values. + +``` +{{ .Environment.Values.eventApi.replicas | default 1 }} +``` + +In case it isn't a mistake and you do want to allow missing keys, use the `getOrNil` template function: + +``` +{{ .Environment.Values | getOrNil "eventApi.replicas" }} +``` + +This result in printing ` +` + expectedFilename := "values.yaml" + data := map[string]interface{}{} + ctx := &Context{readFile: func(filename string) ([]byte, error) { + if filename != expectedFilename { + return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) + } + return []byte(valuesYamlContent), nil + }} + buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actual := buf.String() + if !reflect.DeepEqual(actual, expected) { + t.Errorf("unexpected result: expected=%v, actual=%v", expected, actual) + } +} + +func TestRenderTemplate_Defaulting(t *testing.T) { + valuesYamlContent := `foo: + bar: {{ . | getOrNil "foo.bar" | default "DEFAULT" }} +` + expected := `foo: + bar: DEFAULT +` + expectedFilename := "values.yaml" + data := map[string]interface{}{} + ctx := &Context{readFile: func(filename string) ([]byte, error) { + if filename != expectedFilename { + return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) + } + return []byte(valuesYamlContent), nil + }} + buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actual := buf.String() + if !reflect.DeepEqual(actual, expected) { + t.Errorf("unexpected result: expected=%v, actual=%v", expected, actual) + } +} + func renderTemplateToString(s string) (string, error) { ctx := &Context{readFile: func(filename string) ([]byte, error) { return nil, fmt.Errorf("unexpected call to readFile: filename=%s", filename)