From c2459c06bfaf4ee17fce334e33b4c2373ebf6b35 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Date: Thu, 9 Jun 2016 16:51:00 -0700 Subject: [PATCH] New Chart linter structure 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 tabs --- _proto/hapi/chart/metadata.proto | 4 + cmd/helm/lint.go | 5 + docs/examples/alpine/Chart.yaml | 2 - glide.lock | 2 + glide.yaml | 1 + pkg/lint/chartfile.go | 46 ------ pkg/lint/chartfile_test.go | 22 --- pkg/lint/lint.go | 22 ++- pkg/lint/lint_test.go | 27 ++-- pkg/lint/rules/chartfile.go | 153 ++++++++++++++++++ pkg/lint/rules/chartfile_test.go | 31 ++++ pkg/lint/rules/template.go | 70 ++++++++ pkg/lint/{ => rules}/template_test.go | 9 +- .../{ => rules}/testdata/albatross/Chart.yaml | 0 .../albatross/templates/albatross.yaml | 0 .../testdata/albatross/templates/fail.yaml | 0 .../testdata/albatross/values.yaml | 0 .../testdata/badchartfile/Chart.yaml | 0 .../testdata/badchartfile/values.yaml | 0 .../testdata/badvaluesfile/Chart.yaml | 0 .../templates/badvaluesfile.yaml | 0 .../testdata/badvaluesfile/values.yaml | 0 .../{ => rules}/testdata/goodone/Chart.yaml | 0 .../testdata/goodone/templates/goodone.yaml | 0 .../{ => rules}/testdata/goodone/values.yaml | 0 pkg/lint/rules/values.go | 37 +++++ pkg/lint/{ => support}/doc.go | 2 +- pkg/lint/{ => support}/message.go | 23 ++- pkg/lint/{ => support}/message_test.go | 2 +- pkg/lint/template.go | 64 -------- pkg/lint/values.go | 23 --- pkg/proto/hapi/chart/metadata.pb.go | 56 +++++-- 32 files changed, 405 insertions(+), 196 deletions(-) delete mode 100644 pkg/lint/chartfile.go delete mode 100644 pkg/lint/chartfile_test.go create mode 100644 pkg/lint/rules/chartfile.go create mode 100644 pkg/lint/rules/chartfile_test.go create mode 100644 pkg/lint/rules/template.go rename pkg/lint/{ => rules}/template_test.go (55%) rename pkg/lint/{ => rules}/testdata/albatross/Chart.yaml (100%) rename pkg/lint/{ => rules}/testdata/albatross/templates/albatross.yaml (100%) rename pkg/lint/{ => rules}/testdata/albatross/templates/fail.yaml (100%) rename pkg/lint/{ => rules}/testdata/albatross/values.yaml (100%) rename pkg/lint/{ => rules}/testdata/badchartfile/Chart.yaml (100%) rename pkg/lint/{ => rules}/testdata/badchartfile/values.yaml (100%) rename pkg/lint/{ => rules}/testdata/badvaluesfile/Chart.yaml (100%) rename pkg/lint/{ => rules}/testdata/badvaluesfile/templates/badvaluesfile.yaml (100%) rename pkg/lint/{ => rules}/testdata/badvaluesfile/values.yaml (100%) rename pkg/lint/{ => rules}/testdata/goodone/Chart.yaml (100%) rename pkg/lint/{ => rules}/testdata/goodone/templates/goodone.yaml (100%) rename pkg/lint/{ => rules}/testdata/goodone/values.yaml (100%) create mode 100644 pkg/lint/rules/values.go rename pkg/lint/{ => support}/doc.go (91%) rename pkg/lint/{ => support}/message.go (67%) rename pkg/lint/{ => support}/message_test.go (96%) delete mode 100644 pkg/lint/template.go delete mode 100644 pkg/lint/values.go diff --git a/_proto/hapi/chart/metadata.proto b/_proto/hapi/chart/metadata.proto index f64327a4a..2e848c5e2 100644 --- a/_proto/hapi/chart/metadata.proto +++ b/_proto/hapi/chart/metadata.proto @@ -17,6 +17,10 @@ message Maintainer { // // Spec: https://k8s.io/helm/blob/master/docs/design/chart_format.md#the-chart-file message Metadata { + enum Engine { + UNKNOWN = 0; + GOTPL = 1; + } // The name of the chart string name = 1; diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 35645b151..71c53aceb 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -45,6 +45,11 @@ func lintCmd(cmd *cobra.Command, args []string) error { } issues := lint.All(path) + + if len(issues) == 0 { + fmt.Println("Lint OK") + } + for _, i := range issues { fmt.Printf("%s\n", i) } diff --git a/docs/examples/alpine/Chart.yaml b/docs/examples/alpine/Chart.yaml index 2f2af3f74..bb2602087 100644 --- a/docs/examples/alpine/Chart.yaml +++ b/docs/examples/alpine/Chart.yaml @@ -4,5 +4,3 @@ version: 0.1.0 home: https://k8s.io/helm sources: - https://github.com/kubernetes/helm - - diff --git a/glide.lock b/glide.lock index a766840c6..6a4a6a13f 100644 --- a/glide.lock +++ b/glide.lock @@ -5,6 +5,8 @@ imports: version: 75cd24fc2f2c - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 +- name: github.com/asaskevich/govalidator + version: df81827fdd59d8b4fb93d8910b286ab7a3919520 - name: github.com/beorn7/perks version: b965b613227fddccbfffe13eae360ed3fa822f8d subpackages: diff --git a/glide.yaml b/glide.yaml index bd2f07a6b..5d4dd65e9 100644 --- a/glide.yaml +++ b/glide.yaml @@ -38,3 +38,4 @@ import: - package: speter.net/go/exp/math/dec/inf repo: https://github.com/go-inf/inf.git vcs: git +- package: github.com/asaskevich/govalidator diff --git a/pkg/lint/chartfile.go b/pkg/lint/chartfile.go deleted file mode 100644 index a7cbbf784..000000000 --- a/pkg/lint/chartfile.go +++ /dev/null @@ -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 -} diff --git a/pkg/lint/chartfile_test.go b/pkg/lint/chartfile_test.go deleted file mode 100644 index c8c3be911..000000000 --- a/pkg/lint/chartfile_test.go +++ /dev/null @@ -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) - } -} diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go index 3816cae63..1d7f33dd6 100644 --- a/pkg/lint/lint.go +++ b/pkg/lint/lint.go @@ -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 } diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 634b10145..4d807f5eb 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -1,39 +1,44 @@ package lint import ( + "k8s.io/helm/pkg/lint/support" "strings" "testing" ) -const badChartDir = "testdata/badchartfile" -const badValuesFileDir = "testdata/badvaluesfile" -const badYamlFileDir = "testdata/albatross" -const goodChartDir = "testdata/goodone" +const badChartDir = "rules/testdata/badchartfile" +const badValuesFileDir = "rules/testdata/badvaluesfile" +const badYamlFileDir = "rules/testdata/albatross" +const goodChartDir = "rules/testdata/goodone" func TestBadChart(t *testing.T) { m := All(badChartDir) - if len(m) != 3 { + if len(m) != 4 { + t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) } // There should be 2 WARNINGs and one ERROR messages, check for them - var w, e, e2 = false, false, false + var w, e, e2, e3 bool for _, msg := range m { - if msg.Severity == WarningSev { - if strings.Contains(msg.Text, "No templates") { + if msg.Severity == support.WarningSev { + if strings.Contains(msg.Text, "Templates directory not found") { w = true } } - if msg.Severity == ErrorSev { - if strings.Contains(msg.Text, "must be greater than 0.0.0") { + if msg.Severity == support.ErrorSev { + if strings.Contains(msg.Text, "'version' 0.0.0 is less than or equal to 0") { e = true } if strings.Contains(msg.Text, "'name' is required") { e2 = true } + if strings.Contains(msg.Text, "'name' and directory do not match") { + e3 = true + } } } - if !e || !e2 || !w { + if !e || !e2 || !e3 || !w { t.Errorf("Didn't find all the expected errors, got %#v", m) } } diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go new file mode 100644 index 000000000..e3593ee9a --- /dev/null +++ b/pkg/lint/rules/chartfile.go @@ -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 +} diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go new file mode 100644 index 000000000..95b22f536 --- /dev/null +++ b/pkg/lint/rules/chartfile_test.go @@ -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) + } +} diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go new file mode 100644 index 000000000..face9c6ff --- /dev/null +++ b/pkg/lint/rules/template.go @@ -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 +} diff --git a/pkg/lint/template_test.go b/pkg/lint/rules/template_test.go similarity index 55% rename from pkg/lint/template_test.go rename to pkg/lint/rules/template_test.go index b8c7eadd4..5f2b727ab 100644 --- a/pkg/lint/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -1,6 +1,7 @@ -package lint +package rules import ( + "k8s.io/helm/pkg/lint/support" "strings" "testing" ) @@ -8,10 +9,12 @@ import ( const templateTestBasedir = "./testdata/albatross" func TestTemplate(t *testing.T) { - res := Templates(templateTestBasedir) + linter := support.Linter{ChartDir: templateTestBasedir} + Templates(&linter) + res := linter.Messages if len(res) != 1 { - t.Fatalf("Expected one error, got %d", len(res)) + t.Fatalf("Expected one error, got %d, %v", len(res), res) } if !strings.Contains(res[0].Text, "deliberateSyntaxError") { diff --git a/pkg/lint/testdata/albatross/Chart.yaml b/pkg/lint/rules/testdata/albatross/Chart.yaml similarity index 100% rename from pkg/lint/testdata/albatross/Chart.yaml rename to pkg/lint/rules/testdata/albatross/Chart.yaml diff --git a/pkg/lint/testdata/albatross/templates/albatross.yaml b/pkg/lint/rules/testdata/albatross/templates/albatross.yaml similarity index 100% rename from pkg/lint/testdata/albatross/templates/albatross.yaml rename to pkg/lint/rules/testdata/albatross/templates/albatross.yaml diff --git a/pkg/lint/testdata/albatross/templates/fail.yaml b/pkg/lint/rules/testdata/albatross/templates/fail.yaml similarity index 100% rename from pkg/lint/testdata/albatross/templates/fail.yaml rename to pkg/lint/rules/testdata/albatross/templates/fail.yaml diff --git a/pkg/lint/testdata/albatross/values.yaml b/pkg/lint/rules/testdata/albatross/values.yaml similarity index 100% rename from pkg/lint/testdata/albatross/values.yaml rename to pkg/lint/rules/testdata/albatross/values.yaml diff --git a/pkg/lint/testdata/badchartfile/Chart.yaml b/pkg/lint/rules/testdata/badchartfile/Chart.yaml similarity index 100% rename from pkg/lint/testdata/badchartfile/Chart.yaml rename to pkg/lint/rules/testdata/badchartfile/Chart.yaml diff --git a/pkg/lint/testdata/badchartfile/values.yaml b/pkg/lint/rules/testdata/badchartfile/values.yaml similarity index 100% rename from pkg/lint/testdata/badchartfile/values.yaml rename to pkg/lint/rules/testdata/badchartfile/values.yaml diff --git a/pkg/lint/testdata/badvaluesfile/Chart.yaml b/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml similarity index 100% rename from pkg/lint/testdata/badvaluesfile/Chart.yaml rename to pkg/lint/rules/testdata/badvaluesfile/Chart.yaml diff --git a/pkg/lint/testdata/badvaluesfile/templates/badvaluesfile.yaml b/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml similarity index 100% rename from pkg/lint/testdata/badvaluesfile/templates/badvaluesfile.yaml rename to pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml diff --git a/pkg/lint/testdata/badvaluesfile/values.yaml b/pkg/lint/rules/testdata/badvaluesfile/values.yaml similarity index 100% rename from pkg/lint/testdata/badvaluesfile/values.yaml rename to pkg/lint/rules/testdata/badvaluesfile/values.yaml diff --git a/pkg/lint/testdata/goodone/Chart.yaml b/pkg/lint/rules/testdata/goodone/Chart.yaml similarity index 100% rename from pkg/lint/testdata/goodone/Chart.yaml rename to pkg/lint/rules/testdata/goodone/Chart.yaml diff --git a/pkg/lint/testdata/goodone/templates/goodone.yaml b/pkg/lint/rules/testdata/goodone/templates/goodone.yaml similarity index 100% rename from pkg/lint/testdata/goodone/templates/goodone.yaml rename to pkg/lint/rules/testdata/goodone/templates/goodone.yaml diff --git a/pkg/lint/testdata/goodone/values.yaml b/pkg/lint/rules/testdata/goodone/values.yaml similarity index 100% rename from pkg/lint/testdata/goodone/values.yaml rename to pkg/lint/rules/testdata/goodone/values.yaml diff --git a/pkg/lint/rules/values.go b/pkg/lint/rules/values.go new file mode 100644 index 000000000..56c23532b --- /dev/null +++ b/pkg/lint/rules/values.go @@ -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 +} diff --git a/pkg/lint/doc.go b/pkg/lint/support/doc.go similarity index 91% rename from pkg/lint/doc.go rename to pkg/lint/support/doc.go index f2cc19670..36bb7a248 100644 --- a/pkg/lint/doc.go +++ b/pkg/lint/support/doc.go @@ -3,4 +3,4 @@ Linting is the process of testing charts for errors or warnings regarding formatting, compilation, or standards compliance. */ -package lint +package support diff --git a/pkg/lint/message.go b/pkg/lint/support/message.go similarity index 67% rename from pkg/lint/message.go rename to pkg/lint/support/message.go index 62bf9cf58..e1efb52c2 100644 --- a/pkg/lint/message.go +++ b/pkg/lint/support/message.go @@ -1,4 +1,4 @@ -package lint +package support import "fmt" @@ -22,14 +22,33 @@ var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"} // Message is a linting output message type Message struct { // Severity is one of the *Sev constants - Severity int + Severity Severity // Text contains the message text Text string } +type Linter struct { + Messages []Message + ChartDir string +} + +type LintError interface { + error +} + +type ValidationFunc func(*Linter) LintError + // String prints a string representation of this Message. // // Implements fmt.Stringer. func (m Message) String() string { return fmt.Sprintf("[%s] %s", sev[m.Severity], m.Text) } + +// Returns true if the validation passed +func (l *Linter) RunLinterRule(severity Severity, lintError LintError) bool { + if lintError != nil { + l.Messages = append(l.Messages, Message{Text: lintError.Error(), Severity: severity}) + } + return lintError == nil +} diff --git a/pkg/lint/message_test.go b/pkg/lint/support/message_test.go similarity index 96% rename from pkg/lint/message_test.go rename to pkg/lint/support/message_test.go index 08ab12ad8..2cba98dc3 100644 --- a/pkg/lint/message_test.go +++ b/pkg/lint/support/message_test.go @@ -1,4 +1,4 @@ -package lint +package support import ( "fmt" diff --git a/pkg/lint/template.go b/pkg/lint/template.go deleted file mode 100644 index 6a5760835..000000000 --- a/pkg/lint/template.go +++ /dev/null @@ -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 -} diff --git a/pkg/lint/values.go b/pkg/lint/values.go deleted file mode 100644 index 2549f2937..000000000 --- a/pkg/lint/values.go +++ /dev/null @@ -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 -} diff --git a/pkg/proto/hapi/chart/metadata.pb.go b/pkg/proto/hapi/chart/metadata.pb.go index 1109e8b44..b753f8608 100644 --- a/pkg/proto/hapi/chart/metadata.pb.go +++ b/pkg/proto/hapi/chart/metadata.pb.go @@ -13,6 +13,27 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +type Metadata_Engine int32 + +const ( + Metadata_UNKNOWN Metadata_Engine = 0 + Metadata_GOTPL Metadata_Engine = 1 +) + +var Metadata_Engine_name = map[int32]string{ + 0: "UNKNOWN", + 1: "GOTPL", +} +var Metadata_Engine_value = map[string]int32{ + "UNKNOWN": 0, + "GOTPL": 1, +} + +func (x Metadata_Engine) String() string { + return proto.EnumName(Metadata_Engine_name, int32(x)) +} +func (Metadata_Engine) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{1, 0} } + // Maintainer describes a Chart maintainer. type Maintainer struct { // Name is a user name or organization name @@ -63,23 +84,26 @@ func (m *Metadata) GetMaintainers() []*Maintainer { func init() { proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer") proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata") + proto.RegisterEnum("hapi.chart.Metadata_Engine", Metadata_Engine_name, Metadata_Engine_value) } var fileDescriptor2 = []byte{ - // 234 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0xbd, 0x4f, 0xc3, 0x40, - 0x0c, 0xc5, 0x15, 0xda, 0x7c, 0xe0, 0x6c, 0x16, 0xaa, 0x0c, 0x53, 0xd4, 0x89, 0x29, 0x95, 0x40, - 0x42, 0xcc, 0xec, 0x5d, 0x3a, 0xb2, 0x99, 0xc4, 0x22, 0x27, 0x48, 0x2e, 0xba, 0x3b, 0x40, 0xfc, - 0xe3, 0xcc, 0x5c, 0xdc, 0xaf, 0x0c, 0x1d, 0x22, 0xbd, 0xf7, 0x7e, 0x79, 0x3e, 0xd9, 0x70, 0xdb, - 0xf1, 0x68, 0x36, 0x4d, 0xc7, 0x2e, 0x6c, 0x7a, 0x09, 0xdc, 0x72, 0xe0, 0x7a, 0x74, 0x36, 0x58, - 0x84, 0x09, 0xd5, 0x8a, 0xd6, 0x4f, 0x00, 0x5b, 0x36, 0x43, 0x88, 0x9f, 0x38, 0x44, 0x58, 0x0e, - 0xdc, 0x0b, 0x25, 0x55, 0x72, 0x7f, 0xbd, 0x53, 0x8d, 0x37, 0x90, 0x4a, 0xcf, 0xe6, 0x93, 0xae, - 0x34, 0xdc, 0x9b, 0xf5, 0x5f, 0x02, 0xc5, 0xf6, 0x30, 0xf6, 0x62, 0x2d, 0x66, 0x9d, 0x8d, 0xd9, - 0xbe, 0xa5, 0x1a, 0x09, 0x72, 0x6f, 0xbf, 0x5c, 0x23, 0x9e, 0x16, 0xd5, 0x22, 0xc6, 0x47, 0x3b, - 0x91, 0x6f, 0x71, 0xde, 0xd8, 0x81, 0x96, 0x5a, 0x38, 0x5a, 0xac, 0xa0, 0x6c, 0xc5, 0x37, 0xce, - 0x8c, 0x61, 0xa2, 0xa9, 0xd2, 0x79, 0x84, 0x77, 0x50, 0x7c, 0xc8, 0xef, 0x8f, 0x75, 0xad, 0xa7, - 0x4c, 0xc7, 0x9e, 0x3c, 0x3e, 0x43, 0xd9, 0x9f, 0xd6, 0xf3, 0x94, 0x47, 0x5c, 0x3e, 0xac, 0xea, - 0xf3, 0x01, 0xea, 0xf3, 0xf6, 0xbb, 0xf9, 0xaf, 0xb8, 0x82, 0x4c, 0x86, 0xf7, 0xa8, 0xa9, 0xd0, - 0x27, 0x0f, 0xee, 0x25, 0x7f, 0x4d, 0xb5, 0xf8, 0x96, 0xe9, 0x31, 0x1f, 0xff, 0x03, 0x00, 0x00, - 0xff, 0xff, 0x6f, 0x4a, 0x7b, 0xd0, 0x69, 0x01, 0x00, 0x00, + // 266 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0x4b, 0x4b, 0xc4, 0x40, + 0x10, 0x84, 0xdd, 0x47, 0x1e, 0xdb, 0xb9, 0x2c, 0x8d, 0x2c, 0xa3, 0xa7, 0x90, 0x93, 0xa7, 0x2c, + 0x28, 0x88, 0x67, 0x41, 0x3c, 0xe8, 0x66, 0x65, 0x51, 0x04, 0x6f, 0x63, 0xd2, 0x98, 0x41, 0x93, + 0x09, 0x33, 0xa3, 0xe2, 0x3f, 0xf1, 0xe7, 0x3a, 0xe9, 0x7d, 0x1e, 0x3c, 0x04, 0xaa, 0xea, 0x4b, + 0xd7, 0xd0, 0x0d, 0x27, 0xb5, 0xec, 0xd4, 0xbc, 0xac, 0xa5, 0x71, 0xf3, 0x86, 0x9c, 0xac, 0xa4, + 0x93, 0x79, 0x67, 0xb4, 0xd3, 0x08, 0x3d, 0xca, 0x19, 0x65, 0x97, 0x00, 0x0b, 0xa9, 0x5a, 0xe7, + 0x3f, 0x32, 0x88, 0x30, 0x6e, 0x65, 0x43, 0x62, 0x90, 0x0e, 0xce, 0x26, 0x2b, 0xd6, 0x78, 0x0c, + 0x01, 0x35, 0x52, 0x7d, 0x88, 0x21, 0x87, 0x6b, 0x93, 0xfd, 0x0e, 0x21, 0x5e, 0x6c, 0x6a, 0xff, + 0x1d, 0xf3, 0x59, 0xad, 0x7d, 0xb6, 0x9e, 0x62, 0x8d, 0x02, 0x22, 0xab, 0x3f, 0x4d, 0x49, 0x56, + 0x8c, 0xd2, 0x91, 0x8f, 0xb7, 0xb6, 0x27, 0x5f, 0x64, 0xac, 0xd2, 0xad, 0x18, 0xf3, 0xc0, 0xd6, + 0x62, 0x0a, 0x49, 0x45, 0xb6, 0x34, 0xaa, 0x73, 0x3d, 0x0d, 0x98, 0x1e, 0x46, 0x78, 0x0a, 0xf1, + 0x3b, 0xfd, 0x7c, 0x6b, 0x53, 0x59, 0x11, 0x72, 0xed, 0xce, 0xe3, 0x15, 0x24, 0xcd, 0x6e, 0x3d, + 0x2b, 0x22, 0x8f, 0x93, 0xf3, 0x59, 0xbe, 0x3f, 0x40, 0xbe, 0xdf, 0x7e, 0x75, 0xf8, 0x2b, 0xce, + 0x20, 0xa4, 0xf6, 0xcd, 0x6b, 0x11, 0xf3, 0x93, 0x1b, 0x97, 0xa5, 0x10, 0xde, 0xb0, 0xc2, 0x04, + 0xa2, 0xa7, 0xe2, 0xae, 0x58, 0x3e, 0x17, 0xd3, 0x23, 0x9c, 0x40, 0x70, 0xbb, 0x7c, 0x7c, 0xb8, + 0x9f, 0x0e, 0xae, 0xa3, 0x97, 0x80, 0xab, 0x5f, 0x43, 0x3e, 0xf7, 0xc5, 0x5f, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xe5, 0xf8, 0x57, 0xee, 0x8b, 0x01, 0x00, 0x00, }