mirror of https://github.com/helm/helm.git
Templates Lint rules
Template rules Adding chart errors Added function that checks the existence of all the values in the templates Adding chartfile unit tests Testing runLinterRule Fixing out of range Fixing out of range Improving quote detector
This commit is contained in:
parent
69f66629c4
commit
03d27779d3
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,200 @@ 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(linter, 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)
|
||||
|
||||
func validateTemplatesExistence(linter *support.Linter, templatesPath string) (lintError support.LintError) {
|
||||
if _, err := os.Stat(templatesPath); err != nil {
|
||||
lintError = fmt.Errorf("Templates directory not found")
|
||||
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
|
||||
|
||||
yamlFile := linter.RunLinterRule(support.ErrorSev, validateYamlExtension(linter, fileName))
|
||||
|
||||
if !yamlFile {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that all the templates have a matching value
|
||||
linter.RunLinterRule(support.WarningSev, validateNonMissingValues(fileName, 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 {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, validateNoNamespace(fileName, yamlStruct))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validation functions
|
||||
func validateTemplatesDir(linter *support.Linter, templatesPath string) (lintError support.LintError) {
|
||||
fi, err := os.Stat(templatesPath)
|
||||
if err == nil && !fi.IsDir() {
|
||||
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 validateTemplatesParseable(linter *support.Linter, templatesPath string) (lintError support.LintError) {
|
||||
tpl := template.New("tpl").Funcs(sprig.TxtFuncMap())
|
||||
// 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)
|
||||
|
||||
lintError = filepath.Walk(templatesPath, func(name string, fi os.FileInfo, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
lintError = fmt.Errorf("cannot read %s: %s", name, err)
|
||||
return lintError
|
||||
}
|
||||
|
||||
newtpl, err := tpl.Parse(string(data))
|
||||
if err != nil {
|
||||
lintError = fmt.Errorf("error processing %s: %s", name, err)
|
||||
return lintError
|
||||
}
|
||||
tpl = newtpl
|
||||
return nil
|
||||
})
|
||||
// {{ .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 validateYamlExtension(linter *support.Linter, fileName string) (lintError support.LintError) {
|
||||
if filepath.Ext(fileName) != ".yaml" {
|
||||
lintError = fmt.Errorf("templates: \"%s\" needs to use the .yaml extension", 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, chartValues chartutil.Values, templateContent []byte) (lintError support.LintError) {
|
||||
tmpl := template.New("tpl").Funcs(sprig.TxtFuncMap())
|
||||
var buf bytes.Buffer
|
||||
var emptyValues []string
|
||||
|
||||
// Supported {{ .Chart.Name }}, {{ .Chart.Name | quote }}
|
||||
r, _ := regexp.Compile(`{{([\w]|\.*|\s|\|)+}}`)
|
||||
functions := r.FindAllString(string(templateContent), -1)
|
||||
|
||||
// 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("templates: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = newtmpl.Execute(&buf, chartValues)
|
||||
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 eny 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,38 @@ import (
|
|||
|
||||
const templateTestBasedir = "./testdata/albatross"
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -52,8 +52,6 @@ type LintError interface {
|
|||
error
|
||||
}
|
||||
|
||||
type ValidationFunc func(*Linter) LintError
|
||||
|
||||
// String prints a string representation of this Message.
|
||||
//
|
||||
// Implements fmt.Stringer.
|
||||
|
|
@ -63,6 +61,11 @@ func (m Message) String() string {
|
|||
|
||||
// Returns true if the validation passed
|
||||
func (l *Linter) RunLinterRule(severity Severity, lintError LintError) bool {
|
||||
// severity is out of bound
|
||||
if severity < 0 || int(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 Severity
|
||||
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…
Reference in New Issue