@ -18,6 +18,7 @@ package rules
import (
import (
"bytes"
"bytes"
"errors"
"fmt"
"fmt"
"os"
"os"
"path/filepath"
"path/filepath"
@ -35,9 +36,10 @@ import (
// Templates lints the templates in the Linter.
// Templates lints the templates in the Linter.
func Templates ( linter * support . Linter ) {
func Templates ( linter * support . Linter ) {
templatesPath := filepath . Join ( linter . ChartDir , "templates" )
path := "templates/"
templatesPath := filepath . Join ( linter . ChartDir , path )
templatesDirExist := linter . RunLinterRule ( support . WarningSev , validateTemplatesDir ( templatesPath ) )
templatesDirExist := linter . RunLinterRule ( support . WarningSev , path , validateTemplatesDir ( templatesPath ) )
// Templates directory is optional for now
// Templates directory is optional for now
if ! templatesDirExist {
if ! templatesDirExist {
@ -47,7 +49,7 @@ func Templates(linter *support.Linter) {
// Load chart and parse templates, based on tiller/release_server
// Load chart and parse templates, based on tiller/release_server
chart , err := chartutil . Load ( linter . ChartDir )
chart , err := chartutil . Load ( linter . ChartDir )
chartLoaded := linter . RunLinterRule ( support . ErrorSev , validateNoError ( err ) )
chartLoaded := linter . RunLinterRule ( support . ErrorSev , path , err )
if ! chartLoaded {
if ! chartLoaded {
return
return
@ -63,7 +65,7 @@ func Templates(linter *support.Linter) {
}
}
renderedContentMap , err := engine . New ( ) . Render ( chart , valuesToRender )
renderedContentMap , err := engine . New ( ) . Render ( chart , valuesToRender )
renderOk := linter . RunLinterRule ( support . ErrorSev , validateNoError ( err ) )
renderOk := linter . RunLinterRule ( support . ErrorSev , path , err )
if ! renderOk {
if ! renderOk {
return
return
@ -78,8 +80,9 @@ 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
path = fileName
linter . RunLinterRule ( support . ErrorSev , validateAllowedExtension ( fileName ) )
linter . RunLinterRule ( support . ErrorSev , path , validateAllowedExtension ( fileName ) )
// We only apply the following lint rules to yaml files
// We only apply the following lint rules to yaml files
if filepath . Ext ( fileName ) != ".yaml" {
if filepath . Ext ( fileName ) != ".yaml" {
@ -87,9 +90,9 @@ func Templates(linter *support.Linter) {
}
}
// Check that all the templates have a matching value
// Check that all the templates have a matching value
linter . RunLinterRule ( support . WarningSev , validateNo n MissingValues( fileName , templatesPath , valuesToRender , preExecutedTemplate ) )
linter . RunLinterRule ( support . WarningSev , path , validateNo MissingValues( templatesPath , valuesToRender , preExecutedTemplate ) )
linter . RunLinterRule ( support . WarningSev , validateQuotes ( fileName , string ( preExecutedTemplate ) ) )
linter . RunLinterRule ( support . WarningSev , path , validateQuotes ( string ( preExecutedTemplate ) ) )
renderedContent := renderedContentMap [ fileName ]
renderedContent := renderedContentMap [ fileName ]
var yamlStruct K8sYamlStruct
var yamlStruct K8sYamlStruct
@ -97,30 +100,30 @@ func Templates(linter *support.Linter) {
// key will be raised as well
// key will be raised as well
err := yaml . Unmarshal ( [ ] byte ( renderedContent ) , & yamlStruct )
err := yaml . Unmarshal ( [ ] byte ( renderedContent ) , & yamlStruct )
validYaml := linter . RunLinterRule ( support . ErrorSev , validateYamlContent ( fileName , err ) )
validYaml := linter . RunLinterRule ( support . ErrorSev , path , validateYamlContent ( err ) )
if ! validYaml {
if ! validYaml {
continue
continue
}
}
linter . RunLinterRule ( support . ErrorSev , validateNoNamespace ( fileName , yamlStruct ) )
linter . RunLinterRule ( support . ErrorSev , path , validateNoNamespace ( yamlStruct ) )
}
}
}
}
// Validation functions
// Validation functions
func validateTemplatesDir ( templatesPath string ) ( lintError support . LintError ) {
func validateTemplatesDir ( templatesPath string ) error {
if fi , err := os . Stat ( templatesPath ) ; err != nil {
if fi , err := os . Stat ( templatesPath ) ; err != nil {
lintError = fmt . Errorf ( "Directory 'templates/' not found")
return errors . New ( "directory not found")
} else if err == nil && ! fi . IsDir ( ) {
} else if err == nil && ! fi . IsDir ( ) {
lintError = fmt . Errorf ( "'templates' is not a directory")
return errors . New ( " not a directory")
}
}
return
return nil
}
}
// Validates that go template tags include the quote pipelined function
// Validates that go template tags include the quote pipelined function
// i.e {{ .Foo.bar }} -> {{ .Foo.bar | quote }}
// i.e {{ .Foo.bar }} -> {{ .Foo.bar | quote }}
// {{ .Foo.bar }}-{{ .Foo.baz }} -> "{{ .Foo.bar }}-{{ .Foo.baz }}"
// {{ .Foo.bar }}-{{ .Foo.baz }} -> "{{ .Foo.bar }}-{{ .Foo.baz }}"
func validateQuotes ( template Name string , templateContent string ) ( lintError support . LintError ) {
func validateQuotes ( template Content string ) error {
// {{ .Foo.bar }}
// {{ .Foo.bar }}
r , _ := regexp . Compile ( ` (?m)(:|-)\s+ {{ [ \ w | \ . | \ s | \ ' ] + }} \s*$ ` )
r , _ := regexp . Compile ( ` (?m)(:|-)\s+ {{ [ \ w | \ . | \ s | \ ' ] + }} \s*$ ` )
functions := r . FindAllString ( templateContent , - 1 )
functions := r . FindAllString ( templateContent , - 1 )
@ -128,8 +131,7 @@ func validateQuotes(templateName string, templateContent string) (lintError supp
for _ , str := range functions {
for _ , str := range functions {
if match , _ := regexp . MatchString ( "quote" , str ) ; ! match {
if match , _ := regexp . MatchString ( "quote" , str ) ; ! match {
result := strings . Replace ( str , "}}" , " | quote }}" , - 1 )
result := strings . Replace ( str , "}}" , " | quote }}" , - 1 )
lintError = fmt . Errorf ( "templates: \"%s\". Wrap your substitution functions in quotes or use the sprig \"quote\" function: %s -> %s" , templateName , str , result )
return fmt . Errorf ( "wrap substitution functions in quotes or use the sprig \"quote\" function: %s -> %s" , str , result )
return
}
}
}
}
@ -139,29 +141,27 @@ func validateQuotes(templateName string, templateContent string) (lintError supp
for _ , str := range functions {
for _ , str := range functions {
result := strings . Replace ( str , str , fmt . Sprintf ( "\"%s\"" , str ) , - 1 )
result := strings . Replace ( str , str , fmt . Sprintf ( "\"%s\"" , str ) , - 1 )
lintError = fmt . Errorf ( "templates: \"%s\". Wrap your substitution functions in quotes: %s -> %s" , templateName , str , result )
return fmt . Errorf ( "wrap substitution functions in quotes: %s -> %s" , str , result )
return
}
}
return
return nil
}
}
func validateAllowedExtension ( fileName string ) ( lintError support . LintError ) {
func validateAllowedExtension ( fileName string ) error {
ext := filepath . Ext ( fileName )
ext := filepath . Ext ( fileName )
validExtensions := [ ] string { ".yaml" , ".tpl" }
validExtensions := [ ] string { ".yaml" , ".tpl" }
for _ , b := range validExtensions {
for _ , b := range validExtensions {
if b == ext {
if b == ext {
return
return nil
}
}
}
}
lintError = fmt . Errorf ( "templates: \"%s\" needs to use .yaml or .tpl extensions" , fileName )
return fmt . Errorf ( "file extension '%s' not valid. Valid extensions are .yaml or .tpl" , ext )
return
}
}
// validateNo n MissingValues checks that all the {{}} functions returns a non empty value (<no value> or "")
// validateNo MissingValues checks that all the {{}} functions returns a non empty value (<no value> or "")
// and return an error otherwise.
// and return an error otherwise.
func validateNo n MissingValues( fileName string , templatesPath string , chartValues chartutil . Values , templateContent [ ] byte ) ( lintError support . LintError ) {
func validateNo MissingValues( templatesPath string , chartValues chartutil . Values , templateContent [ ] byte ) error {
// 1 - Load Main and associated templates
// 1 - Load Main and associated templates
// Main template that we will parse dynamically
// Main template that we will parse dynamically
tmpl := template . New ( "tpl" ) . Funcs ( sprig . TxtFuncMap ( ) )
tmpl := template . New ( "tpl" ) . Funcs ( sprig . TxtFuncMap ( ) )
@ -188,8 +188,7 @@ func validateNonMissingValues(fileName string, templatesPath string, chartValues
for _ , str := range functions {
for _ , str := range functions {
newtmpl , err := tmpl . Parse ( str )
newtmpl , err := tmpl . Parse ( str )
if err != nil {
if err != nil {
lintError = fmt . Errorf ( "templates: %s" , err . Error ( ) )
return err
return
}
}
err = newtmpl . ExecuteTemplate ( & buf , "tpl" , chartValues )
err = newtmpl . ExecuteTemplate ( & buf , "tpl" , chartValues )
@ -207,30 +206,23 @@ func validateNonMissingValues(fileName string, templatesPath string, chartValues
}
}
if len ( emptyValues ) > 0 {
if len ( emptyValues ) > 0 {
lintError = fmt . Errorf ( "templates: %s: The following functions are not returning any value %v" , fileName , emptyValues )
return fmt . Errorf ( "these substitution functions are returning no value: %v" , emptyValues )
}
}
return
return nil
}
}
func validateNoError ( readError error ) ( lintError support . LintError ) {
func validateYamlContent ( err error ) error {
if readError != nil {
lintError = fmt . Errorf ( "templates: %s" , readError . Error ( ) )
}
return
}
func validateYamlContent ( filePath string , err error ) ( lintError support . LintError ) {
if err != nil {
if err != nil {
lintError = fmt . Errorf ( "templates: \"%s\". Wrong YAML content" , filePath )
return fmt . Errorf ( "unable to parse YAML\n\t%s" , err )
}
}
return
return nil
}
}
func validateNoNamespace ( filePath string , yamlStruct K8sYamlStruct ) ( lintError support . LintError ) {
func validateNoNamespace ( yamlStruct K8sYamlStruct ) error {
if yamlStruct . Metadata . Namespace != "" {
if yamlStruct . Metadata . Namespace != "" {
lintError = fmt . Errorf ( "templates: \"%s\". namespace option is currently NOT supported" , filePath )
return errors . New ( "namespace option is currently NOT supported" )
}
}
return
return nil
}
}
// K8sYamlStruct stubs a Kubernetes YAML file.
// K8sYamlStruct stubs a Kubernetes YAML file.