mirror of https://github.com/helm/helm
Fixed tests Adding chart name linter Add lint error Moving to blocks Moving to method Moved lint rules to functions Semantic version validation Linting engine Adding sources and home validations Sharing file loading Sharing file loading Rolling back readme Rewriting other linters Fixing tests Typo Using chart.Metadata Fixing format Adding UNKNOWN in Engine Adding tabs Fixing tabspull/833/head
parent
7a227440f7
commit
c2459c06bf
@ -1,46 +0,0 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
)
|
||||
|
||||
// Chartfile checks the Chart.yaml file for errors and warnings.
|
||||
func Chartfile(basepath string) (m []Message) {
|
||||
m = []Message{}
|
||||
|
||||
path := filepath.Join(basepath, "Chart.yaml")
|
||||
if fi, err := os.Stat(path); err != nil {
|
||||
m = append(m, Message{Severity: ErrorSev, Text: "Chart.yaml file: " + path + " does not exist"})
|
||||
return
|
||||
} else if fi.IsDir() {
|
||||
m = append(m, Message{Severity: ErrorSev, Text: "Chart.yaml is a directory."})
|
||||
return
|
||||
}
|
||||
|
||||
cf, err := chartutil.LoadChartfile(path)
|
||||
if err != nil {
|
||||
m = append(m, Message{
|
||||
Severity: ErrorSev,
|
||||
Text: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if cf.Name == "" {
|
||||
m = append(m, Message{
|
||||
Severity: ErrorSev,
|
||||
Text: "Chart.yaml: 'name' is required",
|
||||
})
|
||||
}
|
||||
|
||||
if cf.Version == "" || cf.Version == "0.0.0" {
|
||||
m = append(m, Message{
|
||||
Severity: ErrorSev,
|
||||
Text: "Chart.yaml: 'version' is required, and must be greater than 0.0.0",
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const badchartfile = "testdata/badchartfile"
|
||||
|
||||
func TestChartfile(t *testing.T) {
|
||||
msgs := Chartfile(badchartfile)
|
||||
if len(msgs) != 2 {
|
||||
t.Errorf("Expected 2 errors, got %d", len(msgs))
|
||||
}
|
||||
|
||||
if msgs[0].Text != "Chart.yaml: 'name' is required" {
|
||||
t.Errorf("Unexpected message 0: %s", msgs[0].Text)
|
||||
}
|
||||
|
||||
if msgs[1].Text != "Chart.yaml: 'version' is required, and must be greater than 0.0.0" {
|
||||
t.Errorf("Unexpected message 1: %s", msgs[1].Text)
|
||||
}
|
||||
}
|
@ -1,9 +1,21 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"k8s.io/helm/pkg/lint/rules"
|
||||
"k8s.io/helm/pkg/lint/support"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// All runs all of the available linters on the given base directory.
|
||||
func All(basedir string) []Message {
|
||||
out := Chartfile(basedir)
|
||||
out = append(out, Templates(basedir)...)
|
||||
out = append(out, Values(basedir)...)
|
||||
return out
|
||||
func All(basedir string) []support.Message {
|
||||
// Using abs path to get directory context
|
||||
current, _ := os.Getwd()
|
||||
chartDir := filepath.Join(current, basedir)
|
||||
|
||||
linter := support.Linter{ChartDir: chartDir}
|
||||
rules.Chartfile(&linter)
|
||||
rules.Values(&linter)
|
||||
rules.Templates(&linter)
|
||||
return linter.Messages
|
||||
}
|
||||
|
@ -0,0 +1,153 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/asaskevich/govalidator"
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/lint/support"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
)
|
||||
|
||||
// Chartfile runs a set of linter rules related to Chart.yaml file
|
||||
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))
|
||||
|
||||
chartFile, err := chartutil.LoadChartfile(chartPath)
|
||||
validChartFile := linter.RunLinterRule(support.ErrorSev, validateChartYamlFormat(linter, 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))
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// Auxiliar validation methods
|
||||
func validateChartYamlFileExistence(linter *support.Linter, chartPath string) (lintError support.LintError) {
|
||||
_, err := os.Stat(chartPath)
|
||||
if err != nil {
|
||||
lintError = fmt.Errorf("Chart.yaml file does not exists")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateChartYamlNotDirectory(linter *support.Linter, chartPath string) (lintError support.LintError) {
|
||||
fi, err := os.Stat(chartPath)
|
||||
|
||||
if err == nil && fi.IsDir() {
|
||||
lintError = fmt.Errorf("Chart.yaml is a directory")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateChartYamlFormat(linter *support.Linter, 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) {
|
||||
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) {
|
||||
lintError = fmt.Errorf("Chart.yaml: 'name' and directory do not match")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateChartVersion(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
|
||||
if cf.Version == "" {
|
||||
lintError = fmt.Errorf("Chart.yaml: 'version' value is required")
|
||||
return
|
||||
}
|
||||
|
||||
version, err := semver.NewVersion(cf.Version)
|
||||
|
||||
if err != nil {
|
||||
lintError = fmt.Errorf("Chart.yaml: version '%s' is not a valid SemVer", cf.Version)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint("> 0")
|
||||
valid, msg := c.Validate(version)
|
||||
|
||||
if !valid && len(msg) > 0 {
|
||||
lintError = fmt.Errorf("Chart.yaml: 'version' %v", msg[0])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func validateChartEngine(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
|
||||
if cf.Engine == "" {
|
||||
return
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(chart.Metadata_Engine_value))
|
||||
for engine := range chart.Metadata_Engine_value {
|
||||
str := strings.ToLower(engine)
|
||||
|
||||
if str == "unknown" {
|
||||
continue
|
||||
}
|
||||
|
||||
if str == cf.Engine {
|
||||
return
|
||||
}
|
||||
|
||||
keys = append(keys, str)
|
||||
}
|
||||
|
||||
lintError = fmt.Errorf("Chart.yaml: 'engine %v not valid. Valid options are %v", cf.Engine, keys)
|
||||
return
|
||||
}
|
||||
|
||||
func validateChartMaintainer(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
|
||||
for _, maintainer := range cf.Maintainers {
|
||||
if maintainer.Name == "" {
|
||||
lintError = fmt.Errorf("Chart.yaml: maintainer requires a name")
|
||||
} else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) {
|
||||
lintError = fmt.Errorf("Chart.yaml: maintainer invalid email")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateChartSources(linter *support.Linter, 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)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateChartHome(linter *support.Linter, cf *chart.Metadata) (lintError support.LintError) {
|
||||
if cf.Home != "" && !govalidator.IsRequestURL(cf.Home) {
|
||||
lintError = fmt.Errorf("Chart.yaml: 'home' invalid URL %s", cf.Home)
|
||||
}
|
||||
return
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"k8s.io/helm/pkg/lint/support"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const badchartfile = "testdata/badchartfile"
|
||||
|
||||
func TestChartfile(t *testing.T) {
|
||||
linter := support.Linter{ChartDir: badchartfile}
|
||||
Chartfile(&linter)
|
||||
msgs := linter.Messages
|
||||
|
||||
if len(msgs) != 3 {
|
||||
t.Errorf("Expected 3 errors, got %d", len(msgs))
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[0].Text, "'name' is required") {
|
||||
t.Errorf("Unexpected message 0: %s", msgs[0].Text)
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[1].Text, "'name' and directory do not match") {
|
||||
t.Errorf("Unexpected message 1: %s", msgs[1].Text)
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[2].Text, "'version' 0.0.0 is less than or equal to 0") {
|
||||
t.Errorf("Unexpected message 2: %s", msgs[2].Text)
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Masterminds/sprig"
|
||||
"io/ioutil"
|
||||
"k8s.io/helm/pkg/lint/support"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Templates lints a chart's templates.
|
||||
func Templates(linter *support.Linter) {
|
||||
templatespath := filepath.Join(linter.ChartDir, "templates")
|
||||
|
||||
templatesExist := linter.RunLinterRule(support.WarningSev, validateTemplatesExistence(linter, templatespath))
|
||||
|
||||
// Templates directory is optional for now
|
||||
if !templatesExist {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, validateTemplatesDir(linter, templatespath))
|
||||
linter.RunLinterRule(support.ErrorSev, validateTemplatesParseable(linter, templatespath))
|
||||
}
|
||||
|
||||
func validateTemplatesExistence(linter *support.Linter, templatesPath string) (lintError support.LintError) {
|
||||
if _, err := os.Stat(templatesPath); err != nil {
|
||||
lintError = fmt.Errorf("Templates directory not found")
|
||||
}
|
||||
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")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateTemplatesParseable(linter *support.Linter, templatesPath string) (lintError support.LintError) {
|
||||
tpl := template.New("tpl").Funcs(sprig.TxtFuncMap())
|
||||
|
||||
lintError = filepath.Walk(templatesPath, func(name string, fi os.FileInfo, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
return
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/lint/support"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Values lints a chart's values.yaml file.
|
||||
func Values(linter *support.Linter) {
|
||||
vf := filepath.Join(linter.ChartDir, "values.yaml")
|
||||
fileExists := linter.RunLinterRule(support.InfoSev, validateValuesFileExistence(linter, vf))
|
||||
|
||||
if !fileExists {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, validateValuesFile(linter, vf))
|
||||
}
|
||||
|
||||
func validateValuesFileExistence(linter *support.Linter, valuesPath string) (lintError support.LintError) {
|
||||
_, err := os.Stat(valuesPath)
|
||||
if err != nil {
|
||||
lintError = fmt.Errorf("values.yaml file does not exists")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func validateValuesFile(linter *support.Linter, valuesPath string) (lintError support.LintError) {
|
||||
_, err := chartutil.ReadValuesFile(valuesPath)
|
||||
if err != nil {
|
||||
lintError = fmt.Errorf("values.yaml is malformed: %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package lint
|
||||
package support
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,64 +0,0 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
)
|
||||
|
||||
// Templates lints a chart's templates.
|
||||
func Templates(basepath string) (messages []Message) {
|
||||
messages = []Message{}
|
||||
path := filepath.Join(basepath, "templates")
|
||||
if fi, err := os.Stat(path); err != nil {
|
||||
messages = append(messages, Message{Severity: WarningSev, Text: "No templates"})
|
||||
return
|
||||
} else if !fi.IsDir() {
|
||||
messages = append(messages, Message{Severity: ErrorSev, Text: "'templates' is not a directory"})
|
||||
return
|
||||
}
|
||||
|
||||
tpl := template.New("tpl").Funcs(sprig.TxtFuncMap())
|
||||
|
||||
err := filepath.Walk(basepath, func(name string, fi os.FileInfo, e error) error {
|
||||
// If an error is returned, we fail. Non-fatal errors should just be
|
||||
// added directly to messages.
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
messages = append(messages, Message{
|
||||
Severity: ErrorSev,
|
||||
Text: fmt.Sprintf("cannot read %s: %s", name, err),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// An error rendering a file should emit a warning.
|
||||
newtpl, err := tpl.Parse(string(data))
|
||||
if err != nil {
|
||||
messages = append(messages, Message{
|
||||
Severity: ErrorSev,
|
||||
Text: fmt.Sprintf("error processing %s: %s", name, err),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
tpl = newtpl
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
messages = append(messages, Message{Severity: ErrorSev, Text: err.Error()})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
)
|
||||
|
||||
// Values lints a chart's values.yaml file.
|
||||
func Values(basepath string) (messages []Message) {
|
||||
vf := filepath.Join(basepath, "values.yaml")
|
||||
messages = []Message{}
|
||||
if _, err := os.Stat(vf); err != nil {
|
||||
messages = append(messages, Message{Severity: InfoSev, Text: "No values.yaml file"})
|
||||
return
|
||||
}
|
||||
_, err := chartutil.ReadValuesFile(vf)
|
||||
if err != nil {
|
||||
messages = append(messages, Message{Severity: ErrorSev, Text: err.Error()})
|
||||
}
|
||||
return messages
|
||||
}
|
Loading…
Reference in new issue