Add AsSecrets, AsConfig methods for Files object. Move ToYaml to chartutil

This commit is contained in:
Andrew Stuart 2016-12-09 12:53:53 -07:00
parent 935779157b
commit 9771973888
No known key found for this signature in database
GPG Key ID: D409317C5B5ACD4D
5 changed files with 167 additions and 34 deletions

View File

@ -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.
<!-- (see https://github.com/jonschlinkert/markdown-toc) -->
<!-- toc -->
- [Basic example](#basic-example)
- [Glob patterns](#glob-patterns)
- [ConfigMap and Secrets utility functions](#configmap-and-secrets-utility-functions)
- [Secrets](#secrets)
<!-- tocstop -->
## 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:

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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.
f["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.
"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.

View File

@ -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()