ref: Use action.Install to run a helm template command

Now that there is no Tiller, it does not make sense to have a completely forked set of logic for `helm template`. This change rewrites template to use the Install action as a dry-run, but with Kubernetes fake fixtures instead of with the real thing.

Signed-off-by: Matt Butcher <matt.butcher@microsoft.com>
pull/5171/head
Matt Butcher 7 years ago
parent b0f9e1a39f
commit 1968c074bd
No known key found for this signature in database
GPG Key ID: DCD5F5E5EF32C345

@ -42,33 +42,23 @@ import (
"k8s.io/helm/pkg/tiller/environment" "k8s.io/helm/pkg/tiller/environment"
) )
//const defaultDirectoryPermission = 0755 // defaultKubeVersion is the default value of --kube-version flag
var defaultKubeVersion = fmt.Sprintf("%s.%s", chartutil.DefaultKubeVersion.Major, chartutil.DefaultKubeVersion.Minor)
var (
//whitespaceRegex = regexp.MustCompile(`^\s*$`)
// defaultKubeVersion is the default value of --kube-version flag
defaultKubeVersion = fmt.Sprintf("%s.%s", chartutil.DefaultKubeVersion.Major, chartutil.DefaultKubeVersion.Minor)
)
const templateDesc = ` const templateDesc = `
Render chart templates locally and display the output. Render chart templates locally and display the output.
This does not require Tiller. However, any values that would normally be This does not require a Kubernetes connection. However, any values that would normally
looked up or retrieved in-cluster will be faked locally. Additionally, none be retrieved in-cluster will be faked locally. Additionally, no validation is
of the server-side testing of chart validity (e.g. whether an API is supported) performed on the resulting manifest files. As a result, there is no assurance that a
is done. file generated from this command will be valid to Kubernetes.
To render just one template in a chart, use '-x':
$ helm template mychart -x templates/deployment.yaml
` `
type templateOptions struct { type templateOptions struct {
nameTemplate string // --name-template nameTemplate string // --name-template
showNotes bool // --notes showNotes bool // --notes
releaseName string // --name releaseName string // --name
renderFiles []string // --execute
kubeVersion string // --kube-version kubeVersion string // --kube-version
outputDir string // --output-dir outputDir string // --output-dir
@ -101,7 +91,6 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.showNotes, "notes", false, "show the computed NOTES.txt file as well") f.BoolVar(&o.showNotes, "notes", false, "show the computed NOTES.txt file as well")
f.StringVarP(&o.releaseName, "name", "", "RELEASE-NAME", "release name") f.StringVarP(&o.releaseName, "name", "", "RELEASE-NAME", "release name")
f.StringArrayVarP(&o.renderFiles, "execute", "x", []string{}, "only execute the given templates")
f.StringVar(&o.nameTemplate, "name-template", "", "specify template used to name the release") f.StringVar(&o.nameTemplate, "name-template", "", "specify template used to name the release")
f.StringVar(&o.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor") f.StringVar(&o.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.StringVar(&o.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") f.StringVar(&o.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
@ -111,28 +100,6 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
} }
func (o *templateOptions) run(out io.Writer) error { func (o *templateOptions) run(out io.Writer) error {
// verify specified templates exist relative to chart
rf := []string{}
var af string
var err error
if len(o.renderFiles) > 0 {
for _, f := range o.renderFiles {
if !filepath.IsAbs(f) {
af, err = filepath.Abs(filepath.Join(o.chartPath, f))
if err != nil {
return errors.Wrap(err, "could not resolve template path")
}
} else {
af = f
}
rf = append(rf, af)
if _, err := os.Stat(af); err != nil {
return errors.Wrap(err, "could not resolve template path")
}
}
}
// get combined values and create config // get combined values and create config
config, err := o.mergedValues() config, err := o.mergedValues()
if err != nil { if err != nil {
@ -187,15 +154,15 @@ func (o *templateOptions) run(out io.Writer) error {
return err return err
} }
if o.showNotes {
fmt.Fprintln(out, rel.Info.Notes)
}
if o.outputDir != "" { if o.outputDir != "" {
return o.writeAsFiles(rel) return o.writeAsFiles(rel)
} }
fmt.Fprintln(out, rel.Manifest) fmt.Fprintln(out, rel.Manifest)
if o.showNotes {
fmt.Fprintf(out, "---\n# Source: %s/templates/NOTES.txt\n", c.Name())
fmt.Fprintln(out, rel.Info.Notes)
}
return nil return nil
/* /*

@ -17,17 +17,12 @@ limitations under the License.
package main package main
import ( import (
"path/filepath"
"testing" "testing"
) )
var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1" var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1"
func TestTemplateCmd(t *testing.T) { func TestTemplateCmd(t *testing.T) {
absChartPath, err := filepath.Abs(chartPath)
if err != nil {
t.Fatal(err)
}
tests := []cmdTestCase{ tests := []cmdTestCase{
{ {
name: "check name", name: "check name",
@ -35,15 +30,10 @@ func TestTemplateCmd(t *testing.T) {
golden: "output/template.txt", golden: "output/template.txt",
}, },
{ {
name: "check set name", name: "check set name", //
cmd: "template -x templates/service.yaml --set service.name=apache " + chartPath, cmd: "template --set service.name=apache " + chartPath,
golden: "output/template-set.txt", golden: "output/template-set.txt",
}, },
{
name: "check execute absolute",
cmd: "template -x " + absChartPath + "/templates/service.yaml --set service.name=apache " + chartPath,
golden: "output/template-absolute.txt",
},
{ {
name: "check release name", name: "check release name",
cmd: "template --name test " + chartPath, cmd: "template --name test " + chartPath,

@ -1,7 +1,7 @@
info: info:
deleted: 0001-01-01T00:00:00Z deleted: "0001-01-01T00:00:00Z"
first_deployed: 0001-01-01T00:00:00Z first_deployed: "0001-01-01T00:00:00Z"
last_deployed: 2016-01-16T00:00:00Z last_deployed: "2016-01-16T00:00:00Z"
resources: | resources: |
resource A resource A
resource B resource B

@ -1,22 +0,0 @@
---
# Source: subchart1/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart1
labels:
chart: "subchart1-0.1.0"
release-name: "RELEASE-NAME"
kube-version/major: "1"
kube-version/minor: "9"
kube-version/gitversion: "v1.9.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app: subchart1

@ -1,3 +1,4 @@
--- ---
# Source: subchart1/charts/subcharta/templates/service.yaml # Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -15,7 +16,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +33,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +54,3 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchart1 app: subchart1

@ -1,3 +1,4 @@
--- ---
# Source: subchart1/charts/subcharta/templates/service.yaml # Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -15,7 +16,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +33,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +54,3 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchart1 app: subchart1

@ -1,3 +1,4 @@
--- ---
# Source: subchart1/charts/subcharta/templates/service.yaml # Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -15,7 +16,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +33,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +54,3 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchart1 app: subchart1

@ -1,3 +1,4 @@
--- ---
# Source: subchart1/charts/subcharta/templates/service.yaml # Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -15,7 +16,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +33,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,7 +54,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchart1 app: subchart1
--- ---
# Source: subchart1/templates/NOTES.txt # Source: subchart1/templates/NOTES.txt
Sample notes for subchart1 Sample notes for subchart1

@ -1,3 +1,38 @@
---
# Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app: subcharta
---
# Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -19,4 +54,3 @@ spec:
name: apache name: apache
selector: selector:
app: subchart1 app: subchart1

@ -1,3 +1,4 @@
--- ---
# Source: subchart1/charts/subcharta/templates/service.yaml # Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -15,7 +16,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +33,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +54,3 @@ spec:
name: apache name: apache
selector: selector:
app: subchart1 app: subchart1

@ -1,3 +1,4 @@
--- ---
# Source: subchart1/charts/subcharta/templates/service.yaml # Source: subchart1/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -15,7 +16,6 @@ spec:
name: apache name: apache
selector: selector:
app: subcharta app: subcharta
--- ---
# Source: subchart1/charts/subchartb/templates/service.yaml # Source: subchart1/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -33,7 +33,6 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchartb app: subchartb
--- ---
# Source: subchart1/templates/service.yaml # Source: subchart1/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -55,4 +54,3 @@ spec:
name: nginx name: nginx
selector: selector:
app: subchart1 app: subchart1

@ -23,6 +23,7 @@ import (
"path/filepath" "path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
) )
// UpdateGolden writes out the golden files with the latest values, rather than failing the test. // UpdateGolden writes out the golden files with the latest values, rather than failing the test.
@ -32,6 +33,7 @@ var updateGolden = flag.Bool("update", false, "update golden files")
type TestingT interface { type TestingT interface {
Fatal(...interface{}) Fatal(...interface{})
Fatalf(string, ...interface{}) Fatalf(string, ...interface{})
Errorf(string, ...interface{})
HelperT HelperT
} }
@ -40,6 +42,45 @@ type HelperT interface {
Helper() Helper()
} }
// GoldenFile executes tests that run against canonical "golden" test files
type GoldenFile struct {
t TestingT
isUpdate bool
is *assert.Assertions
}
func New(t TestingT) *GoldenFile {
return &GoldenFile{
t: t,
isUpdate: *updateGolden,
is: assert.New(t),
}
}
func (g *GoldenFile) AssertString(actual, filename string) {
g.t.Helper()
g.compareStr(actual, path(filename))
}
func (g *GoldenFile) compareStr(actual string, filename string) {
if err := g.update(filename, []byte(actual)); err != nil {
g.t.Fatal(err)
}
expected, err := ioutil.ReadFile(filename)
if err != nil {
g.t.Fatalf("unable to read testdata %s: %s", filename, err)
}
g.is.Equal(string(expected), actual, "does not match golden file %s", filename)
}
func (g *GoldenFile) update(filename string, in []byte) error {
if !*updateGolden {
return nil
}
return ioutil.WriteFile(filename, normalize(in), 0666)
}
// AssertGoldenBytes asserts that the give actual content matches the contents of the given filename // AssertGoldenBytes asserts that the give actual content matches the contents of the given filename
func AssertGoldenBytes(t TestingT, actual []byte, filename string) { func AssertGoldenBytes(t TestingT, actual []byte, filename string) {
t.Helper() t.Helper()
@ -51,11 +92,29 @@ func AssertGoldenBytes(t TestingT, actual []byte, filename string) {
// AssertGoldenString asserts that the given string matches the contents of the given file. // AssertGoldenString asserts that the given string matches the contents of the given file.
func AssertGoldenString(t TestingT, actual, filename string) { func AssertGoldenString(t TestingT, actual, filename string) {
New(t).AssertString(actual, filename)
/*
t.Helper() t.Helper()
if err := compare([]byte(actual), path(filename)); err != nil { if err := compare([]byte(actual), path(filename)); err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
*/
}
// AssertGolden compares the given actual data with the contents of a file.AssertGolden//
//
// Mismatches produce diffs of actual vs desired.
func AssertGolden(t TestingT, actual, filename string) {
t.Helper()
is := assert.New(t)
expected, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("unable to read testdata %s: %s", filename, err)
}
// This generates diffs and similarly helpful output for us.
is.Equal(string(expected), actual, "does not match golden file %s", filename)
} }
func path(filename string) string { func path(filename string) string {

Loading…
Cancel
Save