Merge pull request #857 from migmartri/689-template-rules

Templates directory lint rules
pull/883/head
Michelle Noorali 8 years ago committed by GitHub
commit 1dc95be105

@ -1,19 +1,19 @@
apiVersion: v1
kind: Pod
metadata:
name: {{.Release.Name}}-{{.Chart.Name}}
name: "{{.Release.Name}}-{{.Chart.Name}}"
labels:
# 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
# is responsible for.
heritage: {{.Release.Service}}
heritage: {{.Release.Service | quote }}
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
release: {{.Release.Name}}
release: {{.Release.Name | quote }}
# This makes it easy to audit chart usage.
chart: {{.Chart.Name}}-{{.Chart.Version}}
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
annotations:
"helm.sh/created": "{{.Release.Time.Seconds}}"
"helm.sh/created": {{.Release.Time.Seconds | quote }}
spec:
# This shows how to use a simple value. This will look for a passed-in value
# called restartPolicy. If it is not found, it will use the default value.

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

@ -9,18 +9,18 @@ metadata:
# 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
# is responsible for.
heritage: {{.Release.Service}}
heritage: {{ .Release.Service | quote }}
# 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.
chart: {{.Chart.Name}}-{{.Chart.Version}}
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
spec:
replicas: {{default 1 .replicaCount}}
replicas: {{ default 1 .replicaCount | quote }}
template:
metadata:
labels:
app: {{template "fullname" .}}
release: {{.Release.Name}}
release: {{.Release.Name | quote }}
spec:
containers:
- name: {{template "fullname" .}}

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

@ -211,6 +211,7 @@ func LoadDir(dir string) (*chart.Chart, error) {
files := []*afile{}
topdir += string(filepath.Separator)
err = filepath.Walk(topdir, func(name string, fi os.FileInfo, err error) error {
n := strings.TrimPrefix(name, topdir)
if err != nil {

@ -33,30 +33,30 @@ import (
func Chartfile(linter *support.Linter) {
chartPath := filepath.Join(linter.ChartDir, "Chart.yaml")
linter.RunLinterRule(support.ErrorSev, validateChartYamlFileExistence(linter, chartPath))
linter.RunLinterRule(support.ErrorSev, validateChartYamlNotDirectory(linter, chartPath))
linter.RunLinterRule(support.ErrorSev, validateChartYamlFileExistence(chartPath))
linter.RunLinterRule(support.ErrorSev, validateChartYamlNotDirectory(chartPath))
chartFile, err := chartutil.LoadChartfile(chartPath)
validChartFile := linter.RunLinterRule(support.ErrorSev, validateChartYamlFormat(linter, err))
validChartFile := linter.RunLinterRule(support.ErrorSev, validateChartYamlFormat(err))
// Guard clause. Following linter rules require a parseable ChartFile
if !validChartFile {
return
}
linter.RunLinterRule(support.ErrorSev, validateChartName(linter, chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartNameDirMatch(linter, chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartName(chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartNameDirMatch(linter.ChartDir, chartFile))
// Chart metadata
linter.RunLinterRule(support.ErrorSev, validateChartVersion(linter, chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartEngine(linter, chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartMaintainer(linter, chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartSources(linter, chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartHome(linter, chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartVersion(chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartEngine(chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartMaintainer(chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartSources(chartFile))
linter.RunLinterRule(support.ErrorSev, validateChartHome(chartFile))
}
// Auxiliar validation methods
func validateChartYamlFileExistence(linter *support.Linter, chartPath string) (lintError support.LintError) {
func validateChartYamlFileExistence(chartPath string) (lintError support.LintError) {
_, err := os.Stat(chartPath)
if err != nil {
lintError = fmt.Errorf("Chart.yaml file does not exists")
@ -64,7 +64,7 @@ func validateChartYamlFileExistence(linter *support.Linter, chartPath string) (l
return
}
func validateChartYamlNotDirectory(linter *support.Linter, chartPath string) (lintError support.LintError) {
func validateChartYamlNotDirectory(chartPath string) (lintError support.LintError) {
fi, err := os.Stat(chartPath)
if err == nil && fi.IsDir() {
@ -73,28 +73,28 @@ func validateChartYamlNotDirectory(linter *support.Linter, chartPath string) (li
return
}
func validateChartYamlFormat(linter *support.Linter, chartFileError error) (lintError support.LintError) {
func validateChartYamlFormat(chartFileError error) (lintError support.LintError) {
if chartFileError != nil {
lintError = fmt.Errorf("Chart.yaml is malformed: %s", chartFileError.Error())
}
return
}
func validateChartName(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
func validateChartName(cf *chart.Metadata) (lintError support.LintError) {
if cf.Name == "" {
lintError = fmt.Errorf("Chart.yaml: 'name' is required")
}
return
}
func validateChartNameDirMatch(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
if cf.Name != filepath.Base(linter.ChartDir) {
func validateChartNameDirMatch(chartDir string, cf *chart.Metadata) (lintError support.LintError) {
if cf.Name != filepath.Base(chartDir) {
lintError = fmt.Errorf("Chart.yaml: 'name' and directory do not match")
}
return
}
func validateChartVersion(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
func validateChartVersion(cf *chart.Metadata) (lintError support.LintError) {
if cf.Version == "" {
lintError = fmt.Errorf("Chart.yaml: 'version' value is required")
return
@ -117,7 +117,7 @@ func validateChartVersion(linter *support.Linter, cf *chart.Metadata) (lintError
return
}
func validateChartEngine(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
func validateChartEngine(cf *chart.Metadata) (lintError support.LintError) {
if cf.Engine == "" {
return
}
@ -141,7 +141,7 @@ func validateChartEngine(linter *support.Linter, cf *chart.Metadata) (lintError
return
}
func validateChartMaintainer(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
func validateChartMaintainer(cf *chart.Metadata) (lintError support.LintError) {
for _, maintainer := range cf.Maintainers {
if maintainer.Name == "" {
lintError = fmt.Errorf("Chart.yaml: maintainer requires a name")
@ -152,7 +152,7 @@ func validateChartMaintainer(linter *support.Linter, cf *chart.Metadata) (lintEr
return
}
func validateChartSources(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
func validateChartSources(cf *chart.Metadata) (lintError support.LintError) {
for _, source := range cf.Sources {
if source == "" || !govalidator.IsRequestURL(source) {
lintError = fmt.Errorf("Chart.yaml: 'source' invalid URL %s", source)
@ -161,7 +161,7 @@ func validateChartSources(linter *support.Linter, cf *chart.Metadata) (lintError
return
}
func validateChartHome(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
func validateChartHome(cf *chart.Metadata) (lintError support.LintError) {
if cf.Home != "" && !govalidator.IsRequestURL(cf.Home) {
lintError = fmt.Errorf("Chart.yaml: 'home' invalid URL %s", cf.Home)
}

@ -17,15 +17,214 @@ limitations under the License.
package rules
import (
"k8s.io/helm/pkg/lint/support"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/lint/support"
"k8s.io/helm/pkg/proto/hapi/chart"
)
const badchartfile = "testdata/badchartfile"
const badChartDir = "testdata/badchartfile"
const goodChartDir = "testdata/goodone"
var badChartFilePath string = filepath.Join(badChartDir, "Chart.yaml")
var goodChartFilePath string = filepath.Join(goodChartDir, "Chart.yaml")
var nonExistingChartFilePath string = filepath.Join(os.TempDir(), "Chart.yaml")
var badChart, chatLoadRrr = chartutil.LoadChartfile(badChartFilePath)
var goodChart, _ = chartutil.LoadChartfile(goodChartFilePath)
// Validation functions Test
func TestValidateChartYamlFileExistence(t *testing.T) {
err := validateChartYamlFileExistence(nonExistingChartFilePath)
if err == nil {
t.Errorf("validateChartYamlFileExistence to return a linter error, got no error")
}
err = validateChartYamlFileExistence(badChartFilePath)
if err != nil {
t.Errorf("validateChartYamlFileExistence to return no error, got a linter error")
}
}
func TestValidateChartYamlNotDirectory(t *testing.T) {
_ = os.Mkdir(nonExistingChartFilePath, os.ModePerm)
defer os.Remove(nonExistingChartFilePath)
err := validateChartYamlNotDirectory(nonExistingChartFilePath)
if err == nil {
t.Errorf("validateChartYamlNotDirectory to return a linter error, got no error")
}
}
func TestValidateChartYamlFormat(t *testing.T) {
err := validateChartYamlFormat(errors.New("Read error"))
if err == nil {
t.Errorf("validateChartYamlFormat to return a linter error, got no error")
}
err = validateChartYamlFormat(nil)
if err != nil {
t.Errorf("validateChartYamlFormat to return no error, got a linter error")
}
}
func TestValidateChartName(t *testing.T) {
err := validateChartName(badChart)
if err == nil {
t.Errorf("validateChartName to return a linter error, got no error")
}
}
func TestValidateChartNameDirMatch(t *testing.T) {
err := validateChartNameDirMatch(goodChartDir, goodChart)
if err != nil {
t.Errorf("validateChartNameDirMatch to return no error, gor a linter error")
}
// It has not name
err = validateChartNameDirMatch(badChartDir, badChart)
if err == nil {
t.Errorf("validatechartnamedirmatch to return a linter error, got no error")
}
// Wrong path
err = validateChartNameDirMatch(badChartDir, goodChart)
if err == nil {
t.Errorf("validatechartnamedirmatch to return a linter error, got no error")
}
}
func TestValidateChartVersion(t *testing.T) {
var failTest = []struct {
Version string
ErrorMsg string
}{
{"", "'version' value is required"},
{"0", "0 is less than or equal to 0"},
{"waps", "is not a valid SemVer"},
{"-3", "is not a valid SemVer"},
}
var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"}
for _, test := range failTest {
badChart.Version = test.Version
err := validateChartVersion(badChart)
if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) {
t.Errorf("validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg)
}
}
for _, version := range successTest {
badChart.Version = version
err := validateChartVersion(badChart)
if err != nil {
t.Errorf("validateChartVersion(%s) to return no error, got a linter error", version)
}
}
}
func TestValidateChartEngine(t *testing.T) {
var successTest = []string{"", "gotpl"}
for _, engine := range successTest {
badChart.Engine = engine
err := validateChartEngine(badChart)
if err != nil {
t.Errorf("validateChartEngine(%s) to return no error, got a linter error %s", engine, err.Error())
}
}
badChart.Engine = "foobar"
err := validateChartEngine(badChart)
if err == nil || !strings.Contains(err.Error(), "not valid. Valid options are [gotpl") {
t.Errorf("validateChartEngine(%s) to return an error, got no error", badChart.Engine)
}
}
func TestValidateChartMaintainer(t *testing.T) {
var failTest = []struct {
Name string
Email string
ErrorMsg string
}{
{"", "", "maintainer requires a name"},
{"", "test@test.com", "maintainer requires a name"},
{"John Snow", "wrongFormatEmail.com", "maintainer invalid email"},
}
var successTest = []struct {
Name string
Email string
}{
{"John Snow", ""},
{"John Snow", "john@winterfell.com"},
}
for _, test := range failTest {
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(badChart)
if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) {
t.Errorf("validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg)
}
}
for _, test := range successTest {
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(badChart)
if err != nil {
t.Errorf("validateChartMaintainer(%s, %s) to return no error, got %s", test.Name, test.Email, err.Error())
}
}
}
func TestValidateChartSources(t *testing.T) {
var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"}
for _, test := range failTest {
badChart.Sources = []string{test}
err := validateChartSources(badChart)
if err == nil || !strings.Contains(err.Error(), "invalid URL") {
t.Errorf("validateChartSources(%s) to return \"invalid URL\", got no error", test)
}
}
for _, test := range successTest {
badChart.Sources = []string{test}
err := validateChartSources(badChart)
if err != nil {
t.Errorf("validateChartSources(%s) to return no error, got %s", test, err.Error())
}
}
}
func TestValidateChartHome(t *testing.T) {
var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"", "http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"}
for _, test := range failTest {
badChart.Home = test
err := validateChartHome(badChart)
if err == nil || !strings.Contains(err.Error(), "invalid URL") {
t.Errorf("validateChartHome(%s) to return \"invalid URL\", got no error", test)
}
}
for _, test := range successTest {
badChart.Home = test
err := validateChartHome(badChart)
if err != nil {
t.Errorf("validateChartHome(%s) to return no error, got %s", test, err.Error())
}
}
}
func TestChartfile(t *testing.T) {
linter := support.Linter{ChartDir: badchartfile}
linter := support.Linter{ChartDir: badChartDir}
Chartfile(&linter)
msgs := linter.Messages

@ -17,70 +17,226 @@ limitations under the License.
package rules
import (
"bytes"
"fmt"
"github.com/Masterminds/sprig"
"io/ioutil"
"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"
)
// Templates lints a chart's templates.
func Templates(linter *support.Linter) {
templatespath := filepath.Join(linter.ChartDir, "templates")
templatesPath := filepath.Join(linter.ChartDir, "templates")
templatesExist := linter.RunLinterRule(support.WarningSev, validateTemplatesExistence(linter, templatespath))
templatesDirExist := linter.RunLinterRule(support.WarningSev, validateTemplatesDir(templatesPath))
// Templates directory is optional for now
if !templatesExist {
if !templatesDirExist {
return
}
linter.RunLinterRule(support.ErrorSev, validateTemplatesDir(linter, templatespath))
linter.RunLinterRule(support.ErrorSev, validateTemplatesParseable(linter, templatespath))
// Load chart and parse templates, based on tiller/release_server
chart, err := chartutil.Load(linter.ChartDir)
chartLoaded := linter.RunLinterRule(support.ErrorSev, validateNoError(err))
if !chartLoaded {
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)
renderOk := linter.RunLinterRule(support.ErrorSev, validateNoError(err))
if !renderOk {
return
}
/* Iterate over all the templates to check:
- It is a .yaml file
- All the values in the template file is defined
- {{}} include | quote
- Generated content is a valid Yaml file
- Metadata.Namespace is not set
*/
for _, template := range chart.Templates {
fileName, preExecutedTemplate := template.Name, template.Data
linter.RunLinterRule(support.ErrorSev, validateAllowedExtension(fileName))
// We only apply the following lint rules to yaml files
if filepath.Ext(fileName) != ".yaml" {
continue
}
// Check that all the templates have a matching value
linter.RunLinterRule(support.WarningSev, validateNonMissingValues(fileName, templatesPath, chartValues, preExecutedTemplate))
linter.RunLinterRule(support.WarningSev, validateQuotes(fileName, string(preExecutedTemplate)))
renderedContent := renderedContentMap[fileName]
var yamlStruct K8sYamlStruct
// Even though K8sYamlStruct only defines Metadata namespace, an error in any other
// key will be raised as well
err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct)
validYaml := linter.RunLinterRule(support.ErrorSev, validateYamlContent(fileName, err))
if !validYaml {
continue
}
linter.RunLinterRule(support.ErrorSev, validateNoNamespace(fileName, yamlStruct))
}
}
func validateTemplatesExistence(linter *support.Linter, templatesPath string) (lintError support.LintError) {
if _, err := os.Stat(templatesPath); err != nil {
// Validation functions
func validateTemplatesDir(templatesPath string) (lintError support.LintError) {
if fi, err := os.Stat(templatesPath); err != nil {
lintError = fmt.Errorf("Templates directory not found")
} else if err == nil && !fi.IsDir() {
lintError = fmt.Errorf("'templates' is not a directory")
}
return
}
func validateTemplatesDir(linter *support.Linter, templatesPath string) (lintError support.LintError) {
fi, err := os.Stat(templatesPath)
if err == nil && !fi.IsDir() {
lintError = fmt.Errorf("'templates' is not a directory")
// Validates that go template tags include the quote pipelined function
// i.e {{ .Foo.bar }} -> {{ .Foo.bar | quote }}
// {{ .Foo.bar }}-{{ .Foo.baz }} -> "{{ .Foo.bar }}-{{ .Foo.baz }}"
func validateQuotes(templateName string, templateContent string) (lintError support.LintError) {
// {{ .Foo.bar }}
r, _ := regexp.Compile(`(?m)(:|-)\s+{{[\w|\.|\s|\']+}}\s*$`)
functions := r.FindAllString(templateContent, -1)
for _, str := range functions {
if match, _ := regexp.MatchString("quote", str); !match {
result := strings.Replace(str, "}}", " | quote }}", -1)
lintError = fmt.Errorf("templates: \"%s\". add \"| quote\" to your substitution functions: %s -> %s", templateName, str, result)
return
}
}
// {{ .Foo.bar }}-{{ .Foo.baz }} -> "{{ .Foo.bar }}-{{ .Foo.baz }}"
r, _ = regexp.Compile(`(?m)({{(\w|\.|\s|\')+}}(\s|-)*)+\s*$`)
functions = r.FindAllString(templateContent, -1)
for _, str := range functions {
result := strings.Replace(str, str, fmt.Sprintf("\"%s\"", str), -1)
lintError = fmt.Errorf("templates: \"%s\". wrap your substitution functions in double quotes: %s -> %s", templateName, str, result)
return
}
return
}
func validateTemplatesParseable(linter *support.Linter, templatesPath string) (lintError support.LintError) {
tpl := template.New("tpl").Funcs(sprig.TxtFuncMap())
func validateAllowedExtension(fileName string) (lintError support.LintError) {
ext := filepath.Ext(fileName)
validExtensions := []string{".yaml", ".tpl"}
lintError = filepath.Walk(templatesPath, func(name string, fi os.FileInfo, e error) error {
if e != nil {
return e
for _, b := range validExtensions {
if b == ext {
return
}
if fi.IsDir() {
return nil
}
lintError = fmt.Errorf("templates: \"%s\" needs to use .yaml or .tpl extensions", fileName)
return
}
// validateNonMissingValues checks that all the {{}} functions returns a non empty value (<no value> or "")
// and return an error otherwise.
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())
// 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 emptyValues []string
// 2 - Extract every function and execute them agains the loaded values
// Supported {{ .Chart.Name }}, {{ .Chart.Name | quote }}
r, _ := regexp.Compile(`{{[\w|\.|\s|\|\"|\']+}}`)
functions := r.FindAllString(string(templateContent), -1)
data, err := ioutil.ReadFile(name)
// Iterate over the {{ FOO }} templates, executing them against the chartValues
// We do individual templates parsing so we keep the reference for the key (str) that we want it to be interpolated.
for _, str := range functions {
newtmpl, err := tmpl.Parse(str)
if err != nil {
lintError = fmt.Errorf("cannot read %s: %s", name, err)
return lintError
lintError = fmt.Errorf("templates: %s", err.Error())
return
}
newtpl, err := tpl.Parse(string(data))
err = newtmpl.ExecuteTemplate(&buf, "tpl", chartValues)
if err != nil {
lintError = fmt.Errorf("error processing %s: %s", name, err)
return lintError
return err
}
tpl = newtpl
return nil
})
renderedValue := buf.String()
if renderedValue == "<no value>" || renderedValue == "" {
emptyValues = append(emptyValues, str)
}
buf.Reset()
}
if len(emptyValues) > 0 {
lintError = fmt.Errorf("templates: %s: The following functions are not returning any value %v", fileName, emptyValues)
}
return
}
func validateNoError(readError error) (lintError support.LintError) {
if readError != nil {
lintError = fmt.Errorf("templates: %s", readError.Error())
}
return
}
func validateYamlContent(filePath string, err error) (lintError support.LintError) {
if err != nil {
lintError = fmt.Errorf("templates: \"%s\". Wrong YAML content.", filePath)
}
return
}
func validateNoNamespace(filePath string, yamlStruct K8sYamlStruct) (lintError support.LintError) {
if yamlStruct.Metadata.Namespace != "" {
lintError = fmt.Errorf("templates: \"%s\". namespace option is currently NOT supported.", filePath)
}
return
}
// Need to access for now to Namespace only
type K8sYamlStruct struct {
Metadata struct {
Namespace string
}
}

@ -24,6 +24,55 @@ import (
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) {
// add `| quote` lint error
var failTest = []string{"foo: {{.Release.Service }}", "foo: {{.Release.Service }}", "- {{.Release.Service }}", "foo: {{default 'Never' .restart_policy}}", "- {{.Release.Service }} "}
for _, test := range failTest {
err := validateQuotes("testTemplate.yaml", test)
if err == nil || !strings.Contains(err.Error(), "add \"| quote\" to your substitution functions") {
t.Errorf("validateQuotes('%s') to return \"add | quote error\", got no error", test)
}
}
var successTest = []string{"foo: {{.Release.Service | quote }}", "foo: {{.Release.Service | quote }}", "- {{.Release.Service | quote }}", "foo: {{default 'Never' .restart_policy | quote }}", "foo: \"{{ .Release.Service }}\"", "foo: \"{{ .Release.Service }} {{ .Foo.Bar }}\"", "foo: \"{{ default 'Never' .Release.Service }} {{ .Foo.Bar }}\""}
for _, test := range successTest {
err := validateQuotes("testTemplate.yaml", test)
if err != nil {
t.Errorf("validateQuotes('%s') to return not error and got \"%s\"", test, err.Error())
}
}
// Surrounding quotes
failTest = []string{"foo: {{.Release.Service }}-{{ .Release.Bar }}", "foo: {{.Release.Service }} {{ .Release.Bar }}", "- {{.Release.Service }}-{{ .Release.Bar }}", "- {{.Release.Service }}-{{ .Release.Bar }} {{ .Release.Baz }}", "foo: {{.Release.Service | default }}-{{ .Release.Bar }}"}
for _, test := range failTest {
err := validateQuotes("testTemplate.yaml", test)
if err == nil || !strings.Contains(err.Error(), "wrap your substitution functions in double quotes") {
t.Errorf("validateQuotes('%s') to return \"wrap your substitution functions in double quotes\", got no error %s", test, err.Error())
}
}
}
func TestTemplate(t *testing.T) {
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter)

@ -19,8 +19,6 @@ package support
import "fmt"
// Severity indicatest the severity of a Message.
type Severity int
const (
// UnknownSev indicates that the severity of the error is unknown, and should not stop processing.
UnknownSev = iota
@ -38,7 +36,7 @@ var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"}
// Message is a linting output message
type Message struct {
// Severity is one of the *Sev constants
Severity Severity
Severity int
// Text contains the message text
Text string
}
@ -52,8 +50,6 @@ type LintError interface {
error
}
type ValidationFunc func(*Linter) LintError
// String prints a string representation of this Message.
//
// Implements fmt.Stringer.
@ -62,7 +58,12 @@ func (m Message) String() string {
}
// 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
if severity < 0 || severity >= len(sev) {
return false
}
if lintError != nil {
l.Messages = append(l.Messages, Message{Text: lintError.Error(), Severity: severity})
}

@ -21,7 +21,38 @@ import (
"testing"
)
var _ fmt.Stringer = Message{}
var linter Linter = Linter{}
var lintError LintError = fmt.Errorf("Foobar")
func TestRunLinterRule(t *testing.T) {
var tests = []struct {
Severity int
LintError error
ExpectedMessages int
ExpectedReturn bool
}{
{ErrorSev, lintError, 1, false},
{WarningSev, lintError, 2, false},
{InfoSev, lintError, 3, false},
// No error so it returns true
{ErrorSev, nil, 3, true},
// Invalid severity values
{4, lintError, 3, false},
{22, lintError, 3, false},
{-1, lintError, 3, false},
}
for _, test := range tests {
isValid := linter.RunLinterRule(test.Severity, test.LintError)
if len(linter.Messages) != test.ExpectedMessages {
t.Errorf("RunLinterRule(%d, %v), linter.Messages should have now %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages))
}
if isValid != test.ExpectedReturn {
t.Errorf("RunLinterRule(%d, %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid)
}
}
}
func TestMessage(t *testing.T) {
m := Message{ErrorSev, "Foo"}

Loading…
Cancel
Save