mirror of https://github.com/helm/helm
Merge pull request #833 from migmartri/689-linter-errors
Linter structure changes + extra chartfile rulespull/861/head
commit
a6f89d7ac4
@ -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
|
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.
|
// All runs all of the available linters on the given base directory.
|
||||||
func All(basedir string) []Message {
|
func All(basedir string) []support.Message {
|
||||||
out := Chartfile(basedir)
|
// Using abs path to get directory context
|
||||||
out = append(out, Templates(basedir)...)
|
current, _ := os.Getwd()
|
||||||
out = append(out, Values(basedir)...)
|
chartDir := filepath.Join(current, basedir)
|
||||||
return out
|
|
||||||
|
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 (
|
import (
|
||||||
"fmt"
|
"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