WIP feat(helm): add `template` command

This adds the functionality from the helm-template plugin to allow the
rendering of templates without Tiller.

Closes #2755
This commit is contained in:
Justin Scott 2017-08-08 11:38:38 -07:00
parent 1dbbace831
commit 4a02a71f1e
15 changed files with 570 additions and 111 deletions

View File

@ -125,6 +125,7 @@ func newRootCmd(args []string) *cobra.Command {
newHomeCmd(out),
newInitCmd(out),
newPluginCmd(out),
newTemplateCmd(out),
// Hidden documentation generator command: 'helm docs'
newDocsCmd(out),

236
cmd/helm/template.go Normal file
View File

@ -0,0 +1,236 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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.
*/
package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/engine"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/tiller"
"k8s.io/helm/pkg/timeconv"
)
const templateDesc = `
Render chart templates locally and display the output.
This does not require Tiller. However, any values that would normally be
looked up or retrieved in-cluster will be faked locally. Additionally, none
of the server-side testing of chart validity (e.g. whether an API is supported)
is done.
To render just one template in a chart, use '-x':
$ helm template mychart -x templates/deployment.yaml
`
type templateCmd struct {
namespace string
valueFiles valueFiles
chartPath string
out io.Writer
client helm.Interface
values []string
nameTemplate string
showNotes bool
releaseName string
renderFiles []string
}
func newTemplateCmd(out io.Writer) *cobra.Command {
t := &templateCmd{
out: out,
}
cmd := &cobra.Command{
Use: "template [flags] CHART",
Short: fmt.Sprintf("locally render templates"),
Long: templateDesc,
RunE: t.run,
}
f := cmd.Flags()
f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well")
f.StringVarP(&t.releaseName, "name", "n", "RELEASE-NAME", "release name")
f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates")
f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)")
f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into")
f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release")
return cmd
}
func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("chart is required")
}
// verify chart path exists
if _, err := os.Stat(args[0]); err == nil {
if t.chartPath, err = filepath.Abs(args[0]); err != nil {
return err
}
} else {
return err
}
// verify specified templates exist relative to chart
rf := []string{}
var af string
var err error
if len(t.renderFiles) > 0 {
for _, f := range t.renderFiles {
if !filepath.IsAbs(f) {
af, err = filepath.Abs(t.chartPath + "/" + f)
if err != nil {
return fmt.Errorf("could not resolve template path: %s", err)
}
} else {
af = f
}
rf = append(rf, af)
if _, err := os.Stat(af); err != nil {
return fmt.Errorf("could not resolve template path: %s", err)
}
}
}
if t.namespace == "" {
t.namespace = defaultNamespace()
}
// get combined values and create config
rawVals, err := vals(t.valueFiles, t.values)
if err != nil {
return err
}
config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}}
// If template is specified, try to run the template.
if t.nameTemplate != "" {
t.releaseName, err = generateName(t.nameTemplate)
if err != nil {
return err
}
}
// Check chart requirements to make sure all dependencies are present in /charts
c, err := chartutil.Load(t.chartPath)
if err != nil {
return prettyError(err)
}
if req, err := chartutil.LoadRequirements(c); err == nil {
if err := checkDependencies(c, req); err != nil {
return prettyError(err)
}
} else if err != chartutil.ErrRequirementsNotFound {
return fmt.Errorf("cannot load requirements: %v", err)
}
options := chartutil.ReleaseOptions{
Name: t.releaseName,
Time: timeconv.Now(),
Namespace: t.namespace,
}
err = chartutil.ProcessRequirementsEnabled(c, config)
if err != nil {
return err
}
err = chartutil.ProcessRequirementsImportValues(c)
if err != nil {
return err
}
// Set up engine.
renderer := engine.New()
vals, err := chartutil.ToRenderValues(c, config, options)
if err != nil {
return err
}
out, err := renderer.Render(c, vals)
listManifests := []tiller.Manifest{}
if err != nil {
return err
}
// extract kind and name
re := regexp.MustCompile("kind:(.*)\n")
for k, v := range out {
match := re.FindStringSubmatch(v)
h := "Unknown"
if len(match) == 2 {
h = strings.TrimSpace(match[1])
}
m := tiller.Manifest{Name: k, Content: v, Head: &util.SimpleHead{Kind: h}}
listManifests = append(listManifests, m)
}
in := func(needle string, haystack []string) bool {
// make needle path absolute
d := strings.Split(needle, "/")
dd := d[1:]
an := t.chartPath + "/" + strings.Join(dd, "/")
for _, h := range haystack {
if h == an {
return true
}
}
return false
}
if settings.Debug {
rel := &release.Release{
Name: t.releaseName,
Chart: c,
Config: config,
Version: 1,
Namespace: t.namespace,
Info: &release.Info{LastDeployed: timeconv.Timestamp(time.Now())},
}
printRelease(os.Stdout, rel)
}
for _, m := range tiller.SortByKind(listManifests) {
if len(t.renderFiles) > 0 && in(m.Name, rf) == false {
continue
}
data := m.Content
b := filepath.Base(m.Name)
if !t.showNotes && b == "NOTES.txt" {
continue
}
if strings.HasPrefix(b, "_") {
continue
}
fmt.Printf("---\n# Source: %s\n", m.Name)
fmt.Println(data)
}
return nil
}

160
cmd/helm/template_test.go Normal file
View File

@ -0,0 +1,160 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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.
*/
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
)
var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1"
func TestTemplateCmd(t *testing.T) {
absChartPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
desc string
args []string
expectKey string
expectValue string
}{
{
name: "check_name",
desc: "check for a known name in chart",
args: []string{chartPath},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: nginx",
},
{
name: "check_set_name",
desc: "verify --set values exist",
args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_execute",
desc: "verify --execute single template",
args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_execute_absolute",
desc: "verify --execute single template",
args: []string{chartPath, "-x", absChartPath + "/" + "templates/service.yaml", "--set", "service.name=apache"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "protocol: TCP\n name: apache",
},
{
name: "check_namespace",
desc: "verify --namespace",
args: []string{chartPath, "--namespace", "test"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "namespace: \"test\"",
},
{
name: "check_release_name",
desc: "verify --release exists",
args: []string{chartPath, "--name", "test"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-name: \"test\"",
},
{
name: "check_notes",
desc: "verify --notes shows notes",
args: []string{chartPath, "--notes", "true"},
expectKey: "subchart1/templates/NOTES.txt",
expectValue: "Sample notes for subchart1",
},
{
name: "check_values_files",
desc: "verify --values files values exist",
args: []string{chartPath, "--values", chartPath + "/charts/subchartA/values.yaml"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "name: apache",
},
{
name: "check_name_template",
desc: "verify --name-template result exists",
args: []string{chartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"},
expectKey: "subchart1/templates/service.yaml",
expectValue: "release-name: \"foobar-YWJj-baz\"",
},
}
var buf bytes.Buffer
for _, tt := range tests {
t.Run(tt.name, func(T *testing.T) {
// capture stdout
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
// execute template command
out := bytes.NewBuffer(nil)
cmd := newTemplateCmd(out)
cmd.SetArgs(tt.args)
err := cmd.Execute()
if err != nil {
t.Errorf("expected: %v, got %v", tt.expectValue, err)
}
// restore stdout
w.Close()
os.Stdout = old
var b bytes.Buffer
io.Copy(&b, r)
r.Close()
// scan yaml into map[<path>]yaml
scanner := bufio.NewScanner(&b)
next := false
lastKey := ""
m := map[string]string{}
for scanner.Scan() {
if scanner.Text() == "---" {
next = true
} else if next {
// remove '# Source: '
head := "# Source: "
lastKey = scanner.Text()[len(head):]
next = false
} else {
m[lastKey] = m[lastKey] + scanner.Text() + "\n"
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
if v, ok := m[tt.expectKey]; ok {
if strings.Contains(v, tt.expectValue) == false {
t.Errorf("failed to match expected value %s in %s", tt.expectValue, v)
}
} else {
t.Errorf("could not find key %s", tt.expectKey)
}
buf.Reset()
})
}
}

View File

@ -61,9 +61,10 @@ Environment:
* [helm search](helm_search.md) - search for a keyword in charts
* [helm serve](helm_serve.md) - start a local http web server
* [helm status](helm_status.md) - displays the status of the named release
* [helm template](helm_template.md) - locally render templates
* [helm test](helm_test.md) - test a release
* [helm upgrade](helm_upgrade.md) - upgrade a release
* [helm verify](helm_verify.md) - verify that a chart at the given path has been signed and is valid
* [helm version](helm_version.md) - print the client/server version information
###### Auto generated by spf13/cobra on 23-Jun-2017
###### Auto generated by spf13/cobra on 8-Aug-2017

View File

@ -0,0 +1,50 @@
## helm template
locally render templates
### Synopsis
Render chart templates locally and display the output.
This does not require Tiller. However, any values that would normally be
looked up or retrieved in-cluster will be faked locally. Additionally, none
of the server-side testing of chart validity (e.g. whether an API is supported)
is done.
To render just one template in a chart, use '-x':
$ helm template mychart -x templates/deployment.yaml
```
helm template [flags] CHART
```
### Options
```
-x, --execute stringArray only execute the given templates
-n, --name string release name (default "RELEASE-NAME")
--name-template string specify template used to name the release
--namespace string namespace to install the release into
--notes show the computed NOTES.txt file as well
--set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
-f, --values valueFiles specify values in a YAML file (can specify multiple) (default [])
```
### Options inherited from parent commands
```
--debug enable verbose output
--home string location of your Helm config. Overrides $HELM_HOME (default "$HOME/.helm")
--host string address of Tiller. Overrides $HELM_HOST
--kube-context string name of the kubeconfig context to use
--tiller-namespace string namespace of Tiller (default "kube-system")
```
### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 24-Aug-2017

View File

@ -3,7 +3,7 @@
# Declare variables to be passed into your templates.
# subchartA
service:
name: nginx
name: apache
type: ClusterIP
externalPort: 80
internalPort: 80

View File

@ -0,0 +1 @@
Sample notes for {{ .Chart.Name }}

View File

@ -4,6 +4,8 @@ metadata:
name: {{ .Chart.Name }}
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
namespace: "{{ .Release.Namespace }}"
release-name: "{{ .Release.Name }}"
spec:
type: {{ .Values.service.type }}
ports:

View File

@ -50,16 +50,16 @@ var deletePolices = map[string]release.Hook_DeletePolicy{
hooks.HookFailed: release.Hook_FAILED,
}
// manifest represents a manifest file, which has a name and some content.
type manifest struct {
name string
content string
head *util.SimpleHead
// Manifest represents a manifest file, which has a name and some content.
type Manifest struct {
Name string
Content string
Head *util.SimpleHead
}
type result struct {
hooks []*release.Hook
generic []manifest
generic []Manifest
}
type manifestFile struct {
@ -77,7 +77,7 @@ type manifestFile struct {
//
// Files that do not parse into the expected format are simply placed into a map and
// returned.
func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []manifest, error) {
func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) {
result := &result{}
for filePath, c := range files {
@ -141,20 +141,20 @@ func (file *manifestFile) sort(result *result) error {
}
if !hasAnyAnnotation(entry) {
result.generic = append(result.generic, manifest{
name: file.path,
content: m,
head: &entry,
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
})
continue
}
hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
if !ok {
result.generic = append(result.generic, manifest{
name: file.path,
content: m,
head: &entry,
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
})
continue
}

View File

@ -194,7 +194,7 @@ metadata:
}
// Verify the sort order
sorted := []manifest{}
sorted := []Manifest{}
for _, s := range data {
manifests := util.SplitManifests(s.manifest)
@ -211,10 +211,10 @@ metadata:
//only keep track of non-hook manifests
if err == nil && s.hooks[name] == nil {
another := manifest{
content: m,
name: name,
head: &sh,
another := Manifest{
Content: m,
Name: name,
Head: &sh,
}
sorted = append(sorted, another)
}
@ -223,8 +223,8 @@ metadata:
sorted = sortByKind(sorted, InstallOrder)
for i, m := range generic {
if m.content != sorted[i].content {
t.Errorf("Expected %q, got %q", m.content, sorted[i].content)
if m.Content != sorted[i].Content {
t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content)
}
}
}

View File

@ -84,7 +84,7 @@ var UninstallOrder SortOrder = []string{
// sortByKind does an in-place sort of manifests by Kind.
//
// Results are sorted by 'ordering'
func sortByKind(manifests []manifest, ordering SortOrder) []manifest {
func sortByKind(manifests []Manifest, ordering SortOrder) []Manifest {
ks := newKindSorter(manifests, ordering)
sort.Sort(ks)
return ks.manifests
@ -92,10 +92,10 @@ func sortByKind(manifests []manifest, ordering SortOrder) []manifest {
type kindSorter struct {
ordering map[string]int
manifests []manifest
manifests []Manifest
}
func newKindSorter(m []manifest, s SortOrder) *kindSorter {
func newKindSorter(m []Manifest, s SortOrder) *kindSorter {
o := make(map[string]int, len(s))
for v, k := range s {
o[k] = v
@ -114,11 +114,11 @@ func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifes
func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i]
b := k.manifests[j]
first, aok := k.ordering[a.head.Kind]
second, bok := k.ordering[b.head.Kind]
first, aok := k.ordering[a.Head.Kind]
second, bok := k.ordering[b.Head.Kind]
if first == second {
// same kind (including unknown) so sub sort alphanumeric
return a.name < b.name
return a.Name < b.Name
}
// unknown kind is last
if !aok {
@ -130,3 +130,11 @@ func (k *kindSorter) Less(i, j int) bool {
// sort different kinds
return first < second
}
// SortByKind sorts manifests in InstallOrder
func SortByKind(manifests []Manifest) []Manifest {
ordering := InstallOrder
ks := newKindSorter(manifests, ordering)
sort.Sort(ks)
return ks.manifests
}

View File

@ -24,103 +24,103 @@ import (
)
func TestKindSorter(t *testing.T) {
manifests := []manifest{
manifests := []Manifest{
{
name: "i",
head: &util.SimpleHead{Kind: "ClusterRole"},
Name: "i",
Head: &util.SimpleHead{Kind: "ClusterRole"},
},
{
name: "j",
head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
Name: "j",
Head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
},
{
name: "e",
head: &util.SimpleHead{Kind: "ConfigMap"},
Name: "e",
Head: &util.SimpleHead{Kind: "ConfigMap"},
},
{
name: "u",
head: &util.SimpleHead{Kind: "CronJob"},
Name: "u",
Head: &util.SimpleHead{Kind: "CronJob"},
},
{
name: "n",
head: &util.SimpleHead{Kind: "DaemonSet"},
Name: "n",
Head: &util.SimpleHead{Kind: "DaemonSet"},
},
{
name: "r",
head: &util.SimpleHead{Kind: "Deployment"},
Name: "r",
Head: &util.SimpleHead{Kind: "Deployment"},
},
{
name: "!",
head: &util.SimpleHead{Kind: "HonkyTonkSet"},
Name: "!",
Head: &util.SimpleHead{Kind: "HonkyTonkSet"},
},
{
name: "v",
head: &util.SimpleHead{Kind: "Ingress"},
Name: "v",
Head: &util.SimpleHead{Kind: "Ingress"},
},
{
name: "t",
head: &util.SimpleHead{Kind: "Job"},
Name: "t",
Head: &util.SimpleHead{Kind: "Job"},
},
{
name: "c",
head: &util.SimpleHead{Kind: "LimitRange"},
Name: "c",
Head: &util.SimpleHead{Kind: "LimitRange"},
},
{
name: "a",
head: &util.SimpleHead{Kind: "Namespace"},
Name: "a",
Head: &util.SimpleHead{Kind: "Namespace"},
},
{
name: "f",
head: &util.SimpleHead{Kind: "PersistentVolume"},
Name: "f",
Head: &util.SimpleHead{Kind: "PersistentVolume"},
},
{
name: "g",
head: &util.SimpleHead{Kind: "PersistentVolumeClaim"},
Name: "g",
Head: &util.SimpleHead{Kind: "PersistentVolumeClaim"},
},
{
name: "o",
head: &util.SimpleHead{Kind: "Pod"},
Name: "o",
Head: &util.SimpleHead{Kind: "Pod"},
},
{
name: "q",
head: &util.SimpleHead{Kind: "ReplicaSet"},
Name: "q",
Head: &util.SimpleHead{Kind: "ReplicaSet"},
},
{
name: "p",
head: &util.SimpleHead{Kind: "ReplicationController"},
Name: "p",
Head: &util.SimpleHead{Kind: "ReplicationController"},
},
{
name: "b",
head: &util.SimpleHead{Kind: "ResourceQuota"},
Name: "b",
Head: &util.SimpleHead{Kind: "ResourceQuota"},
},
{
name: "k",
head: &util.SimpleHead{Kind: "Role"},
Name: "k",
Head: &util.SimpleHead{Kind: "Role"},
},
{
name: "l",
head: &util.SimpleHead{Kind: "RoleBinding"},
Name: "l",
Head: &util.SimpleHead{Kind: "RoleBinding"},
},
{
name: "d",
head: &util.SimpleHead{Kind: "Secret"},
Name: "d",
Head: &util.SimpleHead{Kind: "Secret"},
},
{
name: "m",
head: &util.SimpleHead{Kind: "Service"},
Name: "m",
Head: &util.SimpleHead{Kind: "Service"},
},
{
name: "h",
head: &util.SimpleHead{Kind: "ServiceAccount"},
Name: "h",
Head: &util.SimpleHead{Kind: "ServiceAccount"},
},
{
name: "s",
head: &util.SimpleHead{Kind: "StatefulSet"},
Name: "s",
Head: &util.SimpleHead{Kind: "StatefulSet"},
},
{
name: "w",
content: "",
head: &util.SimpleHead{Kind: "APIService"},
Name: "w",
Content: "",
Head: &util.SimpleHead{Kind: "APIService"},
},
}
@ -139,7 +139,7 @@ func TestKindSorter(t *testing.T) {
}
defer buf.Reset()
for _, r := range sortByKind(manifests, test.order) {
buf.WriteString(r.name)
buf.WriteString(r.Name)
}
if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got)
@ -150,42 +150,42 @@ func TestKindSorter(t *testing.T) {
// TestKindSorterSubSort verifies manifests of same kind are also sorted alphanumeric
func TestKindSorterSubSort(t *testing.T) {
manifests := []manifest{
manifests := []Manifest{
{
name: "a",
head: &util.SimpleHead{Kind: "ClusterRole"},
Name: "a",
Head: &util.SimpleHead{Kind: "ClusterRole"},
},
{
name: "A",
head: &util.SimpleHead{Kind: "ClusterRole"},
Name: "A",
Head: &util.SimpleHead{Kind: "ClusterRole"},
},
{
name: "0",
head: &util.SimpleHead{Kind: "ConfigMap"},
Name: "0",
Head: &util.SimpleHead{Kind: "ConfigMap"},
},
{
name: "1",
head: &util.SimpleHead{Kind: "ConfigMap"},
Name: "1",
Head: &util.SimpleHead{Kind: "ConfigMap"},
},
{
name: "z",
head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
Name: "z",
Head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
},
{
name: "!",
head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
Name: "!",
Head: &util.SimpleHead{Kind: "ClusterRoleBinding"},
},
{
name: "u3",
head: &util.SimpleHead{Kind: "Unknown"},
Name: "u3",
Head: &util.SimpleHead{Kind: "Unknown"},
},
{
name: "u1",
head: &util.SimpleHead{Kind: "Unknown"},
Name: "u1",
Head: &util.SimpleHead{Kind: "Unknown"},
},
{
name: "u2",
head: &util.SimpleHead{Kind: "Unknown"},
Name: "u2",
Head: &util.SimpleHead{Kind: "Unknown"},
},
}
for _, test := range []struct {
@ -200,7 +200,7 @@ func TestKindSorterSubSort(t *testing.T) {
t.Run(test.description, func(t *testing.T) {
defer buf.Reset()
for _, r := range sortByKind(manifests, test.order) {
buf.WriteString(r.name)
buf.WriteString(r.Name)
}
if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got)

View File

@ -166,7 +166,7 @@ func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient env
errs = []error{}
for _, file := range filesToDelete {
b := bytes.NewBufferString(strings.TrimSpace(file.content))
b := bytes.NewBufferString(strings.TrimSpace(file.Content))
if b.Len() == 0 {
continue
}

View File

@ -300,8 +300,8 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
// Aggregate all valid manifests into one big doc.
b := bytes.NewBuffer(nil)
for _, m := range manifests {
b.WriteString("\n---\n# Source: " + m.name + "\n")
b.WriteString(m.content)
b.WriteString("\n---\n# Source: " + m.Name + "\n")
b.WriteString(m.Content)
}
return hooks, b, notes, nil

View File

@ -29,18 +29,18 @@ const resourcePolicyAnno = "helm.sh/resource-policy"
// during an uninstallRelease action.
const keepPolicy = "keep"
func filterManifestsToKeep(manifests []manifest) ([]manifest, []manifest) {
remaining := []manifest{}
keep := []manifest{}
func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) {
remaining := []Manifest{}
keep := []Manifest{}
for _, m := range manifests {
if m.head.Metadata == nil || m.head.Metadata.Annotations == nil || len(m.head.Metadata.Annotations) == 0 {
if m.Head.Metadata == nil || m.Head.Metadata.Annotations == nil || len(m.Head.Metadata.Annotations) == 0 {
remaining = append(remaining, m)
continue
}
resourcePolicyType, ok := m.head.Metadata.Annotations[resourcePolicyAnno]
resourcePolicyType, ok := m.Head.Metadata.Annotations[resourcePolicyAnno]
if !ok {
remaining = append(remaining, m)
continue
@ -55,10 +55,10 @@ func filterManifestsToKeep(manifests []manifest) ([]manifest, []manifest) {
return keep, remaining
}
func summarizeKeptManifests(manifests []manifest) string {
func summarizeKeptManifests(manifests []Manifest) string {
message := "These resources were kept due to the resource policy:\n"
for _, m := range manifests {
details := "[" + m.head.Kind + "] " + m.head.Metadata.Name + "\n"
details := "[" + m.Head.Kind + "] " + m.Head.Metadata.Name + "\n"
message = message + details
}
return message