mirror of https://github.com/helm/helm
Merge pull request #2781 from jascott1/i2755-template
feat(helm): add `template` commandpull/2897/head
commit
bb5db61325
@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
@ -0,0 +1 @@
|
|||||||
|
Sample notes for {{ .Chart.Name }}
|
Loading…
Reference in new issue