pull/2890/merge
John Bonham 8 years ago committed by GitHub
commit 26be21a7ac

@ -106,6 +106,7 @@ func newRootCmd(args []string) *cobra.Command {
newSearchCmd(out), newSearchCmd(out),
newServeCmd(out), newServeCmd(out),
newVerifyCmd(out), newVerifyCmd(out),
newTemplateCmd(out),
// release commands // release commands
addFlagsTLS(newDeleteCmd(nil, out)), addFlagsTLS(newDeleteCmd(nil, out)),

@ -0,0 +1,199 @@
/*
Copyright 2017 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"
"io/ioutil"
"path/filepath"
"sort"
"strings"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/engine"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/strvals"
"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 mychart/templates/deployment.yaml
`
type templateCmd struct {
setVals []string
valsFiles valueFiles
flagVerbose bool
showNotes bool
releaseName string
namespace string
renderFiles []string
out io.Writer
}
func newTemplateCmd(out io.Writer) *cobra.Command {
tem := &templateCmd{
out: out,
}
cmd := &cobra.Command{
Use: "template [flags] CHART",
Short: "locally render templates",
Long: templateDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("chart is required")
}
return tem.run(args)
},
}
f := cmd.Flags()
f.StringArrayVar(&tem.setVals, "set", []string{}, "set values on the command line. See 'helm install -h'")
f.VarP(&tem.valsFiles, "values", "f", "specify one or more YAML files of values")
f.BoolVarP(&tem.flagVerbose, "verbose", "v", false, "show the computed YAML values as well.")
f.BoolVar(&tem.showNotes, "notes", false, "show the computed NOTES.txt file as well.")
f.StringVarP(&tem.releaseName, "release", "r", "RELEASE-NAME", "release name")
f.StringVarP(&tem.namespace, "namespace", "n", "NAMESPACE", "namespace")
f.StringArrayVarP(&tem.renderFiles, "execute", "x", []string{}, "only execute the given templates.")
return cmd
}
func (tc *templateCmd) run(args []string) error {
c, err := chartutil.Load(args[0])
if err != nil {
return err
}
vv, err := tc.vals()
if err != nil {
return err
}
config := &chart.Config{Raw: string(vv), Values: map[string]*chart.Value{}}
if tc.flagVerbose {
fmt.Fprintf(tc.out, "---\n# merged values")
fmt.Fprintf(tc.out, "%s\n", string(vv))
}
options := chartutil.ReleaseOptions{
Name: tc.releaseName,
Time: timeconv.Now(),
Namespace: tc.namespace,
//Revision: 1,
//IsInstall: true,
}
// Set up engine.
renderer := engine.New()
vals, err := chartutil.ToRenderValues(c, config, options)
if err != nil {
return err
}
out, err := renderer.Render(c, vals)
if err != nil {
return err
}
in := func(needle string, haystack []string) bool {
for _, h := range haystack {
if h == needle {
return true
}
}
return false
}
sortedKeys := make([]string, 0, len(out))
for key := range out {
sortedKeys = append(sortedKeys, key)
}
sort.Strings(sortedKeys)
// If renderFiles is set, we ONLY print those.
if len(tc.renderFiles) > 0 {
for _, name := range sortedKeys {
data := out[name]
if in(name, tc.renderFiles) {
fmt.Fprintf(tc.out, "---\n# Source: %s\n", name)
fmt.Fprintf(tc.out, "%s\n", data)
}
}
return nil
}
for _, name := range sortedKeys {
data := out[name]
b := filepath.Base(name)
if !tc.showNotes && b == "NOTES.txt" {
continue
}
if strings.HasPrefix(b, "_") {
continue
}
fmt.Fprintf(tc.out, "---\n# Source: %s\n", name)
fmt.Fprintf(tc.out, "%s\n", data)
}
return nil
}
func (tc *templateCmd) vals() ([]byte, error) {
base := map[string]interface{}{}
// User specified a values files via -f/--values
for _, filePath := range tc.valsFiles {
currentMap := map[string]interface{}{}
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
return []byte{}, err
}
if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
}
// Merge with the previous map
base = mergeValues(base, currentMap)
}
// User specified a value via --set
for _, value := range tc.setVals {
if err := strvals.ParseInto(value, base); err != nil {
return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
}
}
return yaml.Marshal(base)
}

@ -0,0 +1,130 @@
/*
Copyright 2017 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 (
"bytes"
"regexp"
"testing"
)
type templateCase struct {
name string
args []string
flags []string
// expected and notExpected are strings to be matched. This supports regular expressions.
expected []string
notExpected []string
err bool
}
func TestTemplate(t *testing.T) {
testCases := []templateCase{
{
name: "template basic",
args: []string{"testdata/testcharts/templatetest"},
expected: []string{
"name: defaultname",
"Source: templatetest/templates/deployment.yaml",
"Source: templatetest/templates/service.yaml",
},
notExpected: []string{
"1. These are the notes",
"merged values",
},
},
{
name: "template missing chart",
args: []string{""},
err: true,
},
{
name: "template with valid value file",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--values", "testdata/testcharts/templatetest/other_values.yaml"},
expected: []string{"name: othername"},
},
{
name: "template with invalid value file",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--values", ""},
err: true,
},
{
name: "template set existing key",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--set", "name=customname"},
expected: []string{"name: customname"},
},
{
name: "template set non-existing key",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--set", "invalid=customvalue"},
notExpected: []string{"customvalue"},
},
{
name: "template include notes",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--notes"},
expected: []string{"1. These are the notes"},
},
{
name: "template verbose output",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--verbose"},
expected: []string{"merged values"},
},
{
name: "template render specific existing file",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--execute", "templatetest/templates/deployment.yaml"},
expected: []string{"Source: templatetest/templates/deployment.yaml"},
notExpected: []string{"Source: templatetest/templates/service.yaml"},
},
{
name: "template render specific non-existing file",
args: []string{"testdata/testcharts/templatetest"},
flags: []string{"--execute", "templatetest/templates/ingress.yaml"},
notExpected: []string{"Source: templatetest/templates/ingress.yaml"},
},
}
var buf bytes.Buffer
for _, tc := range testCases {
cmd := newTemplateCmd(&buf)
cmd.ParseFlags(tc.flags)
err := cmd.RunE(cmd, tc.args)
if (err != nil) != tc.err {
t.Errorf("%q. expected error, got '%v'", tc.name, err)
}
by := buf.Bytes()
for _, exp := range tc.expected {
re := regexp.MustCompile(exp)
if !re.Match(by) {
t.Errorf("%q. expected\n%q\ngot\n%q", tc.name, exp, buf.String())
}
}
for _, exp := range tc.notExpected {
re := regexp.MustCompile(exp)
if re.Match(by) {
t.Errorf("%q. not expected\n%q\nin\n%q", tc.name, exp, buf.String())
}
}
buf.Reset()
}
}

@ -0,0 +1,3 @@
description: A Helm chart for Kubernetes
name: templatetest
version: 0.1.0

@ -0,0 +1,4 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ .Values.name }}

@ -0,0 +1,4 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.name }}

@ -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 1-Sep-2017

@ -0,0 +1,49 @@
## 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 mychart/templates/deployment.yaml
```
helm template [flags] CHART
```
### Options
```
-x, --execute stringArray only execute the given templates.
-n, --namespace string namespace (default "NAMESPACE")
--notes show the computed NOTES.txt file as well.
-r, --release string release name (default "RELEASE-NAME")
--set stringArray set values on the command line. See 'helm install -h'
-f, --values valueFiles specify one or more YAML files of values (default [])
-v, --verbose show the computed YAML values as well.
```
### 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 1-Sep-2017
Loading…
Cancel
Save