From 7bb4893cada2be2116d1fe36c0443008a27c17a2 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Date: Mon, 27 Jun 2016 17:47:18 -0700 Subject: [PATCH 1/2] Support Linter for Values --- cmd/tiller/release_server.go | 21 +++----------- pkg/chartutil/values.go | 21 ++++++++++++++ pkg/lint/rules/template.go | 28 +++++++------------ pkg/lint/rules/template_test.go | 26 +++++++++++++++-- .../testdata/albatross/templates/_helpers.tpl | 16 +++++++++++ .../albatross/templates/albatross.yaml | 2 -- .../testdata/albatross/templates/svc.yaml | 18 ++++++++++++ 7 files changed, 93 insertions(+), 39 deletions(-) create mode 100644 pkg/lint/rules/testdata/albatross/templates/_helpers.tpl delete mode 100644 pkg/lint/rules/testdata/albatross/templates/albatross.yaml create mode 100644 pkg/lint/rules/testdata/albatross/templates/svc.yaml diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index c422cb0a7..770231ade 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -220,33 +220,20 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea return nil, errMissingChart } - ts := timeconv.Now() name, err := s.uniqName(req.Name) if err != nil { return nil, err } - overrides := map[string]interface{}{ - "Release": map[string]interface{}{ - "Name": name, - "Time": ts, - "Namespace": s.env.Namespace, - "Service": "Tiller", - }, - "Chart": req.Chart.Metadata, - } - - // Render the templates - // TODO: Fix based on whether chart has `engine: SOMETHING` set. - vals, err := chartutil.CoalesceValues(req.Chart, req.Values, nil) + ts := timeconv.Now() + options := map[string]interface{}{"namespace": s.env.Namespace, "releaseName": name, "releaseTime": ts} + valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options) if err != nil { return nil, err } - overrides["Values"] = vals - renderer := s.engine(req.Chart) - files, err := renderer.Render(req.Chart, overrides) + files, err := renderer.Render(req.Chart, valuesToRender) if err != nil { return nil, err } diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 3987c5a16..c209f5f13 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -288,6 +288,27 @@ func coalesceTables(dst, src map[string]interface{}) map[string]interface{} { return dst } +// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files +func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options map[string]interface{}) (Values, error) { + overrides := map[string]interface{}{ + "Release": map[string]interface{}{ + "Name": options["releaseName"], + "Time": options["releaseTime"], + "Namespace": options["namespace"], + "Service": "Tiller", + }, + "Chart": chrt.Metadata, + } + + vals, err := CoalesceValues(chrt, chrtVals, nil) + if err != nil { + return nil, err + } + + overrides["Values"] = vals + return overrides, nil +} + // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. func istable(v interface{}) bool { _, ok := v.(map[string]interface{}) diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 47140eba8..9ea80e6a0 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -19,17 +19,18 @@ package rules import ( "bytes" "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" + "github.com/Masterminds/sprig" "gopkg.in/yaml.v2" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/lint/support" "k8s.io/helm/pkg/timeconv" - "os" - "path/filepath" - "regexp" - "strings" - "text/template" ) func Templates(linter *support.Linter) { @@ -51,18 +52,9 @@ func Templates(linter *support.Linter) { return } - // Based on cmd/tiller/release_server.go - overrides := map[string]interface{}{ - "Release": map[string]interface{}{ - "Name": "testRelease", - "Service": "Tiller", - "Time": timeconv.Now(), - }, - "Chart": chart.Metadata, - } - - chartValues, _ := chartutil.CoalesceValues(chart, chart.Values, overrides) - renderedContentMap, err := engine.New().Render(chart, chartValues) + options := map[string]interface{}{"namespace": "testNamespace", "releaseName": "testRelease", "releaseTime": timeconv.Now()} + valuesToRender, err := chartutil.ToRenderValues(chart, chart.Values, options) + renderedContentMap, err := engine.New().Render(chart, valuesToRender) renderOk := linter.RunLinterRule(support.ErrorSev, validateNoError(err)) @@ -88,7 +80,7 @@ func Templates(linter *support.Linter) { } // Check that all the templates have a matching value - linter.RunLinterRule(support.WarningSev, validateNonMissingValues(fileName, templatesPath, chartValues, preExecutedTemplate)) + linter.RunLinterRule(support.WarningSev, validateNonMissingValues(fileName, templatesPath, valuesToRender, preExecutedTemplate)) linter.RunLinterRule(support.WarningSev, validateQuotes(fileName, string(preExecutedTemplate))) diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index 8dc7e6dad..ecce95238 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -17,9 +17,12 @@ limitations under the License. package rules import ( - "k8s.io/helm/pkg/lint/support" + "os" + "path/filepath" "strings" "testing" + + "k8s.io/helm/pkg/lint/support" ) const templateTestBasedir = "./testdata/albatross" @@ -73,7 +76,7 @@ func TestValidateQuotes(t *testing.T) { } -func TestTemplate(t *testing.T) { +func TestTemplateParsing(t *testing.T) { linter := support.Linter{ChartDir: templateTestBasedir} Templates(&linter) res := linter.Messages @@ -86,3 +89,22 @@ func TestTemplate(t *testing.T) { t.Errorf("Unexpected error: %s", res[0]) } } + +var wrongTemplatePath string = filepath.Join(templateTestBasedir, "templates", "fail.yaml") +var ignoredTemplatePath string = filepath.Join(templateTestBasedir, "fail.yaml.ignored") + +// Test a template with all the existing features: +// namespaces, partial templates +func TestTemplateIntegrationHappyPath(t *testing.T) { + // Rename file so it gets ignored by the linter + os.Rename(wrongTemplatePath, ignoredTemplatePath) + defer os.Rename(ignoredTemplatePath, wrongTemplatePath) + + linter := support.Linter{ChartDir: templateTestBasedir} + Templates(&linter) + res := linter.Messages + + if len(res) != 0 { + t.Fatalf("Expected no error, got %d, %v", len(res), res) + } +} diff --git a/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl b/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl new file mode 100644 index 000000000..200aee93a --- /dev/null +++ b/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl @@ -0,0 +1,16 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{define "name"}}{{default "nginx" .Values.nameOverride | trunc 24 }}{{end}} + +{{/* +Create a default fully qualified app name. + +We truncate at 24 chars because some Kubernetes name fields are limited to this +(by the DNS naming spec). +*/}} +{{define "fullname"}} +{{- $name := default "nginx" .Values.nameOverride -}} +{{printf "%s-%s" .Release.Name $name | trunc 24 -}} +{{end}} diff --git a/pkg/lint/rules/testdata/albatross/templates/albatross.yaml b/pkg/lint/rules/testdata/albatross/templates/albatross.yaml deleted file mode 100644 index 6c2ceb8db..000000000 --- a/pkg/lint/rules/testdata/albatross/templates/albatross.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{.name | default "foo" | title}} diff --git a/pkg/lint/rules/testdata/albatross/templates/svc.yaml b/pkg/lint/rules/testdata/albatross/templates/svc.yaml new file mode 100644 index 000000000..2c44ea2c6 --- /dev/null +++ b/pkg/lint/rules/testdata/albatross/templates/svc.yaml @@ -0,0 +1,18 @@ +# This is a service gateway to the replica set created by the deployment. +# Take a look at the deployment.yaml for general notes about this chart. +apiVersion: v1 +kind: Service +metadata: + name: "{{ .Values.name }}" + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + ports: + - port: {{default 80 .Values.httpPort | quote}} + targetPort: 80 + protocol: TCP + name: http + selector: + app: {{template "fullname" .}} From 12aa72f1212f19c4f7c3d7de242902efba6a5585 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Date: Tue, 28 Jun 2016 19:27:20 -0700 Subject: [PATCH 2/2] Replacing options interface argument --- cmd/tiller/release_server.go | 2 +- pkg/chartutil/values.go | 17 +++++++++++++---- pkg/lint/rules/template.go | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index 770231ade..e4f84facf 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -226,7 +226,7 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea } ts := timeconv.Now() - options := map[string]interface{}{"namespace": s.env.Namespace, "releaseName": name, "releaseTime": ts} + options := chartutil.ReleaseOptions{Name: name, Time: ts, Namespace: s.env.Namespace} valuesToRender, err := chartutil.ToRenderValues(req.Chart, req.Values, options) if err != nil { return nil, err diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index c209f5f13..b14bc23ba 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -24,6 +24,7 @@ import ( "strings" "github.com/ghodss/yaml" + "github.com/golang/protobuf/ptypes/timestamp" "k8s.io/helm/pkg/proto/hapi/chart" ) @@ -288,13 +289,21 @@ func coalesceTables(dst, src map[string]interface{}) map[string]interface{} { return dst } +// ReleaseOptions represents the additional release options needed +// for the composition of the final values struct +type ReleaseOptions struct { + Name string + Time *timestamp.Timestamp + Namespace string +} + // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files -func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options map[string]interface{}) (Values, error) { +func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) { overrides := map[string]interface{}{ "Release": map[string]interface{}{ - "Name": options["releaseName"], - "Time": options["releaseTime"], - "Namespace": options["namespace"], + "Name": options.Name, + "Time": options.Time, + "Namespace": options.Namespace, "Service": "Tiller", }, "Chart": chrt.Metadata, diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 9ea80e6a0..0291d401d 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -52,7 +52,7 @@ func Templates(linter *support.Linter) { return } - options := map[string]interface{}{"namespace": "testNamespace", "releaseName": "testRelease", "releaseTime": timeconv.Now()} + options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: "testNamespace"} valuesToRender, err := chartutil.ToRenderValues(chart, chart.Values, options) renderedContentMap, err := engine.New().Render(chart, valuesToRender)