Adding support for associated templates

Fixing unit test
pull/857/head
Miguel Martinez 8 years ago
parent 03d27779d3
commit 81ac98ad9a

@ -5,11 +5,11 @@ kind: ConfigMap
metadata: metadata:
name: {{template "fullname" .}} name: {{template "fullname" .}}
labels: labels:
release: {{.Release.Name}} release: {{ .Release.Name | quote }}
app: {{template "fullname" .}} app: {{template "fullname" .}}
heritage: {{.Release.Service}} heritage: {{.Release.Service | quote }}
data: data:
# When the config map is mounted as a volume, these will be created as # When the config map is mounted as a volume, these will be created as
# files. # files.
index.html: {{default "Hello" .index | squote}} index.html: {{ default "Hello" .index | quote }}
test.txt: test test.txt: test

@ -9,18 +9,18 @@ metadata:
# The "heritage" label is used to track which tool deployed a given chart. # The "heritage" label is used to track which tool deployed a given chart.
# It is useful for admins who want to see what releases a particular tool # It is useful for admins who want to see what releases a particular tool
# is responsible for. # is responsible for.
heritage: {{.Release.Service}} heritage: {{ .Release.Service | quote }}
# This makes it easy to search for all components of a release using kubectl. # This makes it easy to search for all components of a release using kubectl.
release: {{.Release.Name}} release: {{ .Release.Name | quote }}
# This makes it easy to audit chart usage. # This makes it easy to audit chart usage.
chart: {{.Chart.Name}}-{{.Chart.Version}} chart: "{{.Chart.Name}}-{{.Chart.Version}}"
spec: spec:
replicas: {{default 1 .replicaCount}} replicas: {{ default 1 .replicaCount | quote }}
template: template:
metadata: metadata:
labels: labels:
app: {{template "fullname" .}} app: {{template "fullname" .}}
release: {{.Release.Name}} release: {{.Release.Name | quote }}
spec: spec:
containers: containers:
- name: {{template "fullname" .}} - name: {{template "fullname" .}}

@ -5,12 +5,12 @@ kind: Service
metadata: metadata:
name: {{template "fullname" .}} name: {{template "fullname" .}}
labels: labels:
heritage: {{.Release.Service}} heritage: {{ .Release.Service | quote }}
release: {{.Release.Name}} release: {{ .Release.Name | quote }}
chart: {{.Chart.Name}}-{{.Chart.Version}} chart: "{{.Chart.Name}}-{{.Chart.Version}}"
spec: spec:
ports: ports:
- port: {{default 80 .httpPort}} - port: {{ default 80 .httpPort | quote }}
targetPort: 80 targetPort: 80
protocol: TCP protocol: TCP
name: http name: http

@ -35,7 +35,7 @@ import (
func Templates(linter *support.Linter) { func Templates(linter *support.Linter) {
templatesPath := filepath.Join(linter.ChartDir, "templates") templatesPath := filepath.Join(linter.ChartDir, "templates")
templatesDirExist := linter.RunLinterRule(support.WarningSev, validateTemplatesDir(linter, templatesPath)) templatesDirExist := linter.RunLinterRule(support.WarningSev, validateTemplatesDir(templatesPath))
// Templates directory is optional for now // Templates directory is optional for now
if !templatesDirExist { if !templatesDirExist {
@ -80,14 +80,15 @@ func Templates(linter *support.Linter) {
for _, template := range chart.Templates { for _, template := range chart.Templates {
fileName, preExecutedTemplate := template.Name, template.Data fileName, preExecutedTemplate := template.Name, template.Data
yamlFile := linter.RunLinterRule(support.ErrorSev, validateYamlExtension(linter, fileName)) linter.RunLinterRule(support.ErrorSev, validateAllowedExtension(fileName))
if !yamlFile { // We only apply the following lint rules to yaml files
return if filepath.Ext(fileName) != ".yaml" {
continue
} }
// Check that all the templates have a matching value // Check that all the templates have a matching value
linter.RunLinterRule(support.WarningSev, validateNonMissingValues(fileName, chartValues, preExecutedTemplate)) linter.RunLinterRule(support.WarningSev, validateNonMissingValues(fileName, templatesPath, chartValues, preExecutedTemplate))
linter.RunLinterRule(support.WarningSev, validateQuotes(fileName, string(preExecutedTemplate))) linter.RunLinterRule(support.WarningSev, validateQuotes(fileName, string(preExecutedTemplate)))
@ -100,7 +101,7 @@ func Templates(linter *support.Linter) {
validYaml := linter.RunLinterRule(support.ErrorSev, validateYamlContent(fileName, err)) validYaml := linter.RunLinterRule(support.ErrorSev, validateYamlContent(fileName, err))
if !validYaml { if !validYaml {
return continue
} }
linter.RunLinterRule(support.ErrorSev, validateNoNamespace(fileName, yamlStruct)) linter.RunLinterRule(support.ErrorSev, validateNoNamespace(fileName, yamlStruct))
@ -108,7 +109,7 @@ func Templates(linter *support.Linter) {
} }
// Validation functions // Validation functions
func validateTemplatesDir(linter *support.Linter, templatesPath string) (lintError support.LintError) { func validateTemplatesDir(templatesPath string) (lintError support.LintError) {
if fi, err := os.Stat(templatesPath); err != nil { if fi, err := os.Stat(templatesPath); err != nil {
lintError = fmt.Errorf("Templates directory not found") lintError = fmt.Errorf("Templates directory not found")
} else if err == nil && !fi.IsDir() { } else if err == nil && !fi.IsDir() {
@ -145,22 +146,42 @@ func validateQuotes(templateName string, templateContent string) (lintError supp
return return
} }
func validateYamlExtension(linter *support.Linter, fileName string) (lintError support.LintError) { func validateAllowedExtension(fileName string) (lintError support.LintError) {
if filepath.Ext(fileName) != ".yaml" { ext := filepath.Ext(fileName)
lintError = fmt.Errorf("templates: \"%s\" needs to use the .yaml extension", fileName) validExtensions := []string{".yaml", ".tpl"}
for _, b := range validExtensions {
if b == ext {
return
}
} }
lintError = fmt.Errorf("templates: \"%s\" needs to use .yaml or .tpl extensions", fileName)
return return
} }
// validateNonMissingValues checks that all the {{}} functions returns a non empty value (<no value> or "") // validateNonMissingValues checks that all the {{}} functions returns a non empty value (<no value> or "")
// and return an error otherwise. // and return an error otherwise.
func validateNonMissingValues(fileName string, chartValues chartutil.Values, templateContent []byte) (lintError support.LintError) { func validateNonMissingValues(fileName string, templatesPath string, chartValues chartutil.Values, templateContent []byte) (lintError support.LintError) {
// 1 - Load Main and associated templates
// Main template that we will parse dynamically
tmpl := template.New("tpl").Funcs(sprig.TxtFuncMap()) tmpl := template.New("tpl").Funcs(sprig.TxtFuncMap())
// If the templatesPath includes any *.tpl files we will parse and import them as associated templates
associatedTemplates, err := filepath.Glob(filepath.Join(templatesPath, "*.tpl"))
if len(associatedTemplates) > 0 {
tmpl, err = tmpl.ParseFiles(associatedTemplates...)
if err != nil {
return err
}
}
var buf bytes.Buffer var buf bytes.Buffer
var emptyValues []string var emptyValues []string
// 2 - Extract every function and execute them agains the loaded values
// Supported {{ .Chart.Name }}, {{ .Chart.Name | quote }} // Supported {{ .Chart.Name }}, {{ .Chart.Name | quote }}
r, _ := regexp.Compile(`{{([\w]|\.*|\s|\|)+}}`) r, _ := regexp.Compile(`{{[\w|\.|\s|\|\"|\']+}}`)
functions := r.FindAllString(string(templateContent), -1) functions := r.FindAllString(string(templateContent), -1)
// Iterate over the {{ FOO }} templates, executing them against the chartValues // Iterate over the {{ FOO }} templates, executing them against the chartValues
@ -172,7 +193,12 @@ func validateNonMissingValues(fileName string, chartValues chartutil.Values, tem
return return
} }
err = newtmpl.Execute(&buf, chartValues) err = newtmpl.ExecuteTemplate(&buf, "tpl", chartValues)
if err != nil {
return err
}
renderedValue := buf.String() renderedValue := buf.String()
if renderedValue == "<no value>" || renderedValue == "" { if renderedValue == "<no value>" || renderedValue == "" {
@ -182,7 +208,7 @@ func validateNonMissingValues(fileName string, chartValues chartutil.Values, tem
} }
if len(emptyValues) > 0 { if len(emptyValues) > 0 {
lintError = fmt.Errorf("templates: %s: The following functions are not returning eny value %v", fileName, emptyValues) lintError = fmt.Errorf("templates: %s: The following functions are not returning any value %v", fileName, emptyValues)
} }
return return
} }

@ -24,6 +24,23 @@ import (
const templateTestBasedir = "./testdata/albatross" const templateTestBasedir = "./testdata/albatross"
func TestValidateAllowedExtension(t *testing.T) {
var failTest = []string{"/foo", "/test.yml", "/test.toml", "test.yml"}
for _, test := range failTest {
err := validateAllowedExtension(test)
if err == nil || !strings.Contains(err.Error(), "needs to use .yaml or .tpl extension") {
t.Errorf("validateAllowedExtension('%s') to return \"needs to use .yaml or .tpl extension\", got no error", test)
}
}
var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml"}
for _, test := range successTest {
err := validateAllowedExtension(test)
if err != nil {
t.Errorf("validateAllowedExtension('%s') to return no error but got \"%s\"", test, err.Error())
}
}
}
func TestValidateQuotes(t *testing.T) { func TestValidateQuotes(t *testing.T) {
// add `| quote` lint error // add `| quote` lint error
var failTest = []string{"foo: {{.Release.Service }}", "foo: {{.Release.Service }}", "- {{.Release.Service }}", "foo: {{default 'Never' .restart_policy}}", "- {{.Release.Service }} "} var failTest = []string{"foo: {{.Release.Service }}", "foo: {{.Release.Service }}", "- {{.Release.Service }}", "foo: {{default 'Never' .restart_policy}}", "- {{.Release.Service }} "}

@ -19,8 +19,6 @@ package support
import "fmt" import "fmt"
// Severity indicatest the severity of a Message. // Severity indicatest the severity of a Message.
type Severity int
const ( const (
// UnknownSev indicates that the severity of the error is unknown, and should not stop processing. // UnknownSev indicates that the severity of the error is unknown, and should not stop processing.
UnknownSev = iota UnknownSev = iota
@ -38,7 +36,7 @@ var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"}
// Message is a linting output message // Message is a linting output message
type Message struct { type Message struct {
// Severity is one of the *Sev constants // Severity is one of the *Sev constants
Severity Severity Severity int
// Text contains the message text // Text contains the message text
Text string Text string
} }
@ -60,9 +58,9 @@ func (m Message) String() string {
} }
// Returns true if the validation passed // Returns true if the validation passed
func (l *Linter) RunLinterRule(severity Severity, lintError LintError) bool { func (l *Linter) RunLinterRule(severity int, lintError LintError) bool {
// severity is out of bound // severity is out of bound
if severity < 0 || int(severity) >= len(sev) { if severity < 0 || severity >= len(sev) {
return false return false
} }

@ -26,7 +26,7 @@ var lintError LintError = fmt.Errorf("Foobar")
func TestRunLinterRule(t *testing.T) { func TestRunLinterRule(t *testing.T) {
var tests = []struct { var tests = []struct {
Severity Severity Severity int
LintError error LintError error
ExpectedMessages int ExpectedMessages int
ExpectedReturn bool ExpectedReturn bool

Loading…
Cancel
Save