Merge pull request #2781 from jascott1/i2755-template

feat(helm): add `template` command
pull/2897/head
Matthew Fisher 7 years ago committed by GitHub
commit bb5db61325

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

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

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

@ -61,9 +61,10 @@ Environment:
* [helm search](helm_search.md) - search for a keyword in charts * [helm search](helm_search.md) - search for a keyword in charts
* [helm serve](helm_serve.md) - start a local http web server * [helm serve](helm_serve.md) - start a local http web server
* [helm status](helm_status.md) - displays the status of the named release * [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 test](helm_test.md) - test a release
* [helm upgrade](helm_upgrade.md) - upgrade 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 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 * [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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save