diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 7ad37984e..855fe24b4 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -25,11 +25,13 @@ import ( "path/filepath" "strings" + "github.com/ghodss/yaml" "github.com/spf13/cobra" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/lint" "k8s.io/helm/pkg/lint/support" + "k8s.io/helm/pkg/strvals" ) var longLintHelp = ` @@ -42,6 +44,8 @@ or recommendation, it will emit [WARNING] messages. ` type lintCmd struct { + valueFiles valueFiles + values []string namespace string strict bool paths []string @@ -65,6 +69,8 @@ func newLintCmd(out io.Writer) *cobra.Command { }, } + cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") + cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)") cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings") @@ -81,10 +87,16 @@ func (l *lintCmd) run() error { lowestTolerance = support.ErrorSev } + // Get the raw values + rvals, err := l.vals() + if err != nil { + return err + } + var total int var failures int for _, path := range l.paths { - if linter, err := lintChart(path, l.namespace); err != nil { + if linter, err := lintChart(path, rvals, l.namespace); err != nil { fmt.Println("==> Skipping", path) fmt.Println(err) } else { @@ -116,7 +128,7 @@ func (l *lintCmd) run() error { return nil } -func lintChart(path string, namespace string) (support.Linter, error) { +func lintChart(path string, vals []byte, namespace string) (support.Linter, error) { var chartPath string linter := support.Linter{} @@ -148,5 +160,33 @@ func lintChart(path string, namespace string) (support.Linter, error) { return linter, errLintNoChart } - return lint.All(chartPath, namespace), nil + return lint.All(chartPath, vals, namespace), nil +} + +func (l *lintCmd) vals() ([]byte, error) { + base := map[string]interface{}{} + + // User specified a values files via -f/--values + for _, filePath := range l.valueFiles { + currentMap := map[string]interface{}{} + bytes, err := ioutil.ReadFile(filePath) + if err != nil { + return []byte{}, err + } + + if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) + } + // Merge with the previous map + base = mergeValues(base, currentMap) + } + + // User specified a value via --set + for _, value := range l.values { + if err := strvals.ParseInto(value, base); err != nil { + return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) + } + } + + return yaml.Marshal(base) } diff --git a/cmd/helm/lint_test.go b/cmd/helm/lint_test.go index acf408eaa..3ef1e4b71 100644 --- a/cmd/helm/lint_test.go +++ b/cmd/helm/lint_test.go @@ -21,17 +21,18 @@ import ( ) var ( + values = []byte{} namespace = "testNamespace" archivedChartPath = "testdata/testcharts/compressedchart-0.1.0.tgz" chartDirPath = "testdata/testcharts/decompressedchart/" ) func TestLintChart(t *testing.T) { - if _, err := lintChart(chartDirPath, namespace); err != nil { + if _, err := lintChart(chartDirPath, values, namespace); err != nil { t.Errorf("%s", err) } - if _, err := lintChart(archivedChartPath, namespace); err != nil { + if _, err := lintChart(archivedChartPath, values, namespace); err != nil { t.Errorf("%s", err) } diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go index 3debfd926..58a570a7b 100644 --- a/pkg/lint/lint.go +++ b/pkg/lint/lint.go @@ -24,13 +24,13 @@ import ( ) // All runs all of the available linters on the given base directory. -func All(basedir string, namespace string) support.Linter { +func All(basedir string, values []byte, namespace string) support.Linter { // Using abs path to get directory context chartDir, _ := filepath.Abs(basedir) linter := support.Linter{ChartDir: chartDir} rules.Chartfile(&linter) rules.Values(&linter) - rules.Templates(&linter, namespace) + rules.Templates(&linter, values, namespace) return linter } diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index da3edc8db..9c7da821e 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -24,6 +24,8 @@ import ( "testing" ) +var values = []byte{} + const namespace = "testNamespace" const badChartDir = "rules/testdata/badchartfile" @@ -32,7 +34,7 @@ const badYamlFileDir = "rules/testdata/albatross" const goodChartDir = "rules/testdata/goodone" func TestBadChart(t *testing.T) { - m := All(badChartDir, namespace).Messages + m := All(badChartDir, values, namespace).Messages if len(m) != 5 { t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) @@ -68,7 +70,7 @@ func TestBadChart(t *testing.T) { } func TestInvalidYaml(t *testing.T) { - m := All(badYamlFileDir, namespace).Messages + m := All(badYamlFileDir, values, namespace).Messages if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -78,7 +80,7 @@ func TestInvalidYaml(t *testing.T) { } func TestBadValues(t *testing.T) { - m := All(badValuesFileDir, namespace).Messages + m := All(badValuesFileDir, values, namespace).Messages if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -88,7 +90,7 @@ func TestBadValues(t *testing.T) { } func TestGoodChart(t *testing.T) { - m := All(goodChartDir, namespace).Messages + m := All(goodChartDir, values, namespace).Messages if len(m) != 0 { t.Errorf("All failed but shouldn't have: %#v", m) } diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index c4264e493..5bf786295 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -26,12 +26,13 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/lint/support" + cpb "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/timeconv" tversion "k8s.io/helm/pkg/version" ) // Templates lints the templates in the Linter. -func Templates(linter *support.Linter, namespace string) { +func Templates(linter *support.Linter, values []byte, namespace string) { path := "templates/" templatesPath := filepath.Join(linter.ChartDir, path) @@ -57,7 +58,17 @@ func Templates(linter *support.Linter, namespace string) { KubeVersion: chartutil.DefaultKubeVersion, TillerVersion: tversion.GetVersionProto(), } - valuesToRender, err := chartutil.ToRenderValuesCaps(chart, chart.Values, options, caps) + cvals, err := chartutil.CoalesceValues(chart, &cpb.Config{Raw: string(values)}) + if err != nil { + return + } + // convert our values back into config + yvals, err := cvals.YAML() + if err != nil { + return + } + cc := &cpb.Config{Raw: yvals} + valuesToRender, err := chartutil.ToRenderValuesCaps(chart, cc, options, caps) if err != nil { // FIXME: This seems to generate a duplicate, but I can't find where the first // error is coming from. diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index 686dc6a98..47f6cbdc1 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -44,11 +44,13 @@ func TestValidateAllowedExtension(t *testing.T) { } } +var values = []byte("nameOverride: ''\nhttpPort: 80") + const namespace = "testNamespace" func TestTemplateParsing(t *testing.T) { linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, namespace) + Templates(&linter, values, namespace) res := linter.Messages if len(res) != 1 { @@ -71,7 +73,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { defer os.Rename(ignoredTemplatePath, wrongTemplatePath) linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, namespace) + Templates(&linter, values, namespace) res := linter.Messages if len(res) != 0 {