From 97719738886f79f784ee2343a4c170dafb9bfbb6 Mon Sep 17 00:00:00 2001 From: Andrew Stuart Date: Fri, 9 Dec 2016 12:53:53 -0700 Subject: [PATCH] Add AsSecrets, AsConfig methods for Files object. Move ToYaml to chartutil --- docs/chart_template_guide/accessing_files.md | 44 ++++++++++++ pkg/chartutil/files.go | 71 ++++++++++++++++++++ pkg/chartutil/files_test.go | 40 +++++++++-- pkg/engine/engine.go | 33 ++++----- pkg/engine/engine_test.go | 13 ---- 5 files changed, 167 insertions(+), 34 deletions(-) diff --git a/docs/chart_template_guide/accessing_files.md b/docs/chart_template_guide/accessing_files.md index e14417fde..ca87469f3 100644 --- a/docs/chart_template_guide/accessing_files.md +++ b/docs/chart_template_guide/accessing_files.md @@ -9,6 +9,17 @@ Helm provides access to files through the `.Files` object. Before we get going w - Files in `templates/` cannot be accessed. - Charts to not preserve UNIX mode information, so file-level permissions will have no impact on the availability of a file when it comes to the `.Files` object. + + + + +- [Basic example](#basic-example) +- [Glob patterns](#glob-patterns) +- [ConfigMap and Secrets utility functions](#configmap-and-secrets-utility-functions) +- [Secrets](#secrets) + + + ## Basic example With those caveats behind, let's write a template that reads three files into our ConfigMap. To get started, we will add three files to the chart, putting all three directly inside of the `mychart/` directory. @@ -73,6 +84,9 @@ As your chart grows, you may find you have a greater need to organize your files more, and so we provide a `Files.Glob(pattern string)` method to assist in extracting certain files with all the flexibility of [glob patterns](//godoc.org/github.com/gobwas/glob). +`.Glob` returns a `Files` type, so you may call any of the `Files` methods on +the returned object. + For example, imagine the directory structure: ``` @@ -101,6 +115,36 @@ Or {{ end }} ``` +## ConfigMap and Secrets utility functions + +(Not present in version 2.0.2 or prior) + +It is very common to want to place file content into both configmaps and +secrets, for mounting into your pods at run time. To help with this, we provide a +couple utility methods on the `Files` type. + +For further organization, it is especially useful to use these methods in +conjunction with the `Glob` method. + +Given the directory structure from the [Glob][Glob patterns] example above: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: conf +data: +{{ (.Files.Glob "foo/*").AsConfig | indent 2 }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: very-secret +type: Opaque +data: +{{ (.Files.Glob "bar/*").AsSecrets | indent 2 }} +``` + ## Secrets When working with a Secret resource, you can import a file and have the template base-64 encode it for you: diff --git a/pkg/chartutil/files.go b/pkg/chartutil/files.go index ba07e5ebb..5aa3ee302 100644 --- a/pkg/chartutil/files.go +++ b/pkg/chartutil/files.go @@ -16,6 +16,11 @@ limitations under the License. package chartutil import ( + "encoding/base64" + "path" + + yaml "gopkg.in/yaml.v2" + "github.com/gobwas/glob" "github.com/golang/protobuf/ptypes/any" ) @@ -83,3 +88,69 @@ func (f Files) Glob(pattern string) Files { return nf } + +// AsConfig turns a Files group and flattens it to a YAML map suitable for +// including in the `data` section of a kubernetes ConfigMap definition. +// Duplicate keys will be overwritten, so be aware that your filenames +// (regardless of path) should be unique. +// +// This is designed to be called from a template, and will return empty string +// (via ToYaml function) if it cannot be serialized to YAML, or if the Files +// object is nil. +// +// The output will not be indented, so you will want to pipe this to the +// `indent` template function. +// +// data: +// {{ .Files.Glob("config/**").AsConfig() | indent 4 }} +func (f Files) AsConfig() string { + if f == nil { + return "" + } + + m := map[string]string{} + + // Explicitly convert to strings, and file names + for k, v := range f { + m[path.Base(k)] = string(v) + } + + return ToYaml(m) +} + +// AsSecrets returns the value of a Files object as base64 suitable for +// including in the `data` section of a kubernetes Secret definition. +// Duplicate keys will be overwritten, so be aware that your filenames +// (regardless of path) should be unique. +// +// This is designed to be called from a template, and will return empty string +// (via ToYaml function) if it cannot be serialized to YAML, or if the Files +// object is nil. +// +// The output will not be indented, so you will want to pipe this to the +// `indent` template function. +// +// data: +// {{ .Files.Glob("secrets/*").AsSecrets() }} +func (f Files) AsSecrets() string { + if f == nil { + return "" + } + + m := map[string]string{} + + for k, v := range f { + m[path.Base(k)] = base64.StdEncoding.EncodeToString(v) + } + + return ToYaml(m) +} + +func ToYaml(v interface{}) string { + data, err := yaml.Marshal(v) + if err != nil { + // Swallow errors inside of a template. + return "" + } + return string(data) +} diff --git a/pkg/chartutil/files_test.go b/pkg/chartutil/files_test.go index 268ac1abd..afc8214b2 100644 --- a/pkg/chartutil/files_test.go +++ b/pkg/chartutil/files_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/golang/protobuf/ptypes/any" + "github.com/stretchr/testify/assert" ) var cases = []struct { @@ -55,16 +56,45 @@ func TestNewFiles(t *testing.T) { } func TestFileGlob(t *testing.T) { + as := assert.New(t) + f := NewFiles(getTestFiles()) matched := f.Glob("story/**") - if len(matched) != 2 { - t.Errorf("Expected two files in glob story/**, got %d", len(matched)) + as.Len(matched, 2, "Should be two files in glob story/**") + as.Equal("Joseph Conrad", matched.Get("story/author.txt")) +} + +func TestToConfig(t *testing.T) { + as := assert.New(t) + + f := NewFiles(getTestFiles()) + out := f.Glob("**/captain.txt").AsConfig() + as.Equal("captain.txt: The Captain\n", out) + + out = f.Glob("ship/**").AsConfig() + as.Equal("captain.txt: The Captain\nstowaway.txt: Legatt\n", out) +} + +func TestToSecret(t *testing.T) { + as := assert.New(t) + + f := NewFiles(getTestFiles()) + + out := f.Glob("ship/**").AsSecrets() + as.Equal("captain.txt: VGhlIENhcHRhaW4=\nstowaway.txt: TGVnYXR0\n", out) +} + +func TestToYaml(t *testing.T) { + expect := "foo: bar\n" + v := struct { + Foo string `json:"foo"` + }{ + Foo: "bar", } - m, expect := matched.Get("story/author.txt"), "Joseph Conrad" - if m != expect { - t.Errorf("Wrong globbed file content. Expected %s, got %s", expect, m) + if got := ToYaml(v); got != expect { + t.Errorf("Expected %q, got %q", expect, got) } } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index bdc4011d7..c7998b78d 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -24,7 +24,6 @@ import ( "text/template" "github.com/Masterminds/sprig" - "github.com/ghodss/yaml" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/proto/hapi/chart" @@ -69,26 +68,28 @@ func FuncMap() template.FuncMap { delete(f, "env") delete(f, "expandenv") - // Add a function to convert to YAML: - f["toYaml"] = toYaml + // Add some extra functionality + extra := template.FuncMap{ + "toYaml": files.ToYaml, + "base": path.Base, + "dir": path.Dir, + "ext": path.Ext, + "isAbs": path.IsAbs, + "clean": path.Clean, + + // This is a placeholder for the "include" function, which is + // late-bound to a template. By declaring it here, we preserve the + // integrity of the linter. + "include": func(string, interface{}) string { return "not implemented" }, + } - // This is a placeholder for the "include" function, which is - // late-bound to a template. By declaring it here, we preserve the - // integrity of the linter. - f["include"] = func(string, interface{}) string { return "not implemented" } + for k, v := range extra { + f[k] = v + } return f } -func toYaml(v interface{}) string { - data, err := yaml.Marshal(v) - if err != nil { - // Swallow errors inside of a template. - return "" - } - return string(data) -} - // Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. // // Render can be called repeatedly on the same engine. diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 2804dfbab..3ee94cac1 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -27,19 +27,6 @@ import ( "github.com/golang/protobuf/ptypes/any" ) -func TestToYaml(t *testing.T) { - expect := "foo: bar\n" - v := struct { - Foo string `json:"foo"` - }{ - Foo: "bar", - } - - if got := toYaml(v); got != expect { - t.Errorf("Expected %q, got %q", expect, got) - } -} - func TestEngine(t *testing.T) { e := New()