From 4a02a71f1ec6d5120dae51192136f1ff31a8ef6c Mon Sep 17 00:00:00 2001 From: Justin Scott Date: Tue, 8 Aug 2017 11:38:38 -0700 Subject: [PATCH] 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 --- cmd/helm/helm.go | 1 + cmd/helm/template.go | 236 ++++++++++++++++++ cmd/helm/template_test.go | 160 ++++++++++++ docs/helm/helm.md | 3 +- docs/helm/helm_template.md | 50 ++++ .../subchart1/charts/subchartA/values.yaml | 2 +- .../charts/subchart1/templates/NOTES.txt | 1 + .../charts/subchart1/templates/service.yaml | 2 + pkg/tiller/hooks.go | 30 +-- pkg/tiller/hooks_test.go | 14 +- pkg/tiller/kind_sorter.go | 20 +- pkg/tiller/kind_sorter_test.go | 142 +++++------ pkg/tiller/release_modules.go | 2 +- pkg/tiller/release_server.go | 4 +- pkg/tiller/resource_policy.go | 14 +- 15 files changed, 570 insertions(+), 111 deletions(-) create mode 100644 cmd/helm/template.go create mode 100644 cmd/helm/template_test.go create mode 100644 docs/helm/helm_template.md create mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index bc8885b4b..61513770b 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -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), diff --git a/cmd/helm/template.go b/cmd/helm/template.go new file mode 100644 index 000000000..88ca92fc9 --- /dev/null +++ b/cmd/helm/template.go @@ -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 +} diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go new file mode 100644 index 000000000..b1e080493 --- /dev/null +++ b/cmd/helm/template_test.go @@ -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[]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() + }) + } +} diff --git a/docs/helm/helm.md b/docs/helm/helm.md index 2b15a3b12..d1749718a 100644 --- a/docs/helm/helm.md +++ b/docs/helm/helm.md @@ -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 diff --git a/docs/helm/helm_template.md b/docs/helm/helm_template.md new file mode 100644 index 000000000..80ecdec39 --- /dev/null +++ b/docs/helm/helm_template.md @@ -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 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml index 712b3a2fa..f0381ae6a 100644 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml @@ -3,7 +3,7 @@ # Declare variables to be passed into your templates. # subchartA service: - name: nginx + name: apache type: ClusterIP externalPort: 80 internalPort: 80 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt new file mode 100644 index 000000000..4bdf443f6 --- /dev/null +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt @@ -0,0 +1 @@ +Sample notes for {{ .Chart.Name }} \ No newline at end of file diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml index fdf75aa91..bf7672e12 100644 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml @@ -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: diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 8d72f95cf..d87bb8caa 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -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 } diff --git a/pkg/tiller/hooks_test.go b/pkg/tiller/hooks_test.go index 7a7d2a2b5..658f859f4 100644 --- a/pkg/tiller/hooks_test.go +++ b/pkg/tiller/hooks_test.go @@ -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) } } } diff --git a/pkg/tiller/kind_sorter.go b/pkg/tiller/kind_sorter.go index e0549e2f4..f2447143b 100644 --- a/pkg/tiller/kind_sorter.go +++ b/pkg/tiller/kind_sorter.go @@ -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 +} diff --git a/pkg/tiller/kind_sorter_test.go b/pkg/tiller/kind_sorter_test.go index a707176b3..f0ee66d9c 100644 --- a/pkg/tiller/kind_sorter_test.go +++ b/pkg/tiller/kind_sorter_test.go @@ -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) diff --git a/pkg/tiller/release_modules.go b/pkg/tiller/release_modules.go index 73b95e60c..b5fbbeb44 100644 --- a/pkg/tiller/release_modules.go +++ b/pkg/tiller/release_modules.go @@ -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 } diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index bab405e1f..44d5d847a 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -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 diff --git a/pkg/tiller/resource_policy.go b/pkg/tiller/resource_policy.go index 89f303197..2102ab66b 100644 --- a/pkg/tiller/resource_policy.go +++ b/pkg/tiller/resource_policy.go @@ -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