feat(helm): support namespace and additional values in lint (#2972)

* Add "--namespace" to helm lint

Keep lint syntax as close as possible to "helm install" resp. "helm
upgrade", so that one only needs to change the command.

See #2036

* Align lintCmd struct

* Add "--set" and "--values" to helm lint

Keep lint syntax as close as possible to "helm install" resp. "helm
upgrade", so that one only needs to change the command.

Closes #2495,#2036

* Reuse strict parameter, when rendering during lint

We want to see the rendering fail, if we missed a value, so we reuse
"--strict".

See #2495,#2036

* Fix lint unit test

See #2495,#2036

* Update docs
pull/3257/head
Julian 7 years ago committed by Matthew Fisher
parent 6707fe0468
commit ca415ab8db

@ -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,9 +44,12 @@ or recommendation, it will emit [WARNING] messages.
`
type lintCmd struct {
strict bool
paths []string
out io.Writer
valueFiles valueFiles
values []string
namespace string
strict bool
paths []string
out io.Writer
}
func newLintCmd(out io.Writer) *cobra.Command {
@ -64,6 +69,9 @@ 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")
return cmd
@ -79,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); err != nil {
if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil {
fmt.Println("==> Skipping", path)
fmt.Println(err)
} else {
@ -114,7 +128,7 @@ func (l *lintCmd) run() error {
return nil
}
func lintChart(path string) (support.Linter, error) {
func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) {
var chartPath string
linter := support.Linter{}
@ -146,5 +160,33 @@ func lintChart(path string) (support.Linter, error) {
return linter, errLintNoChart
}
return lint.All(chartPath), nil
return lint.All(chartPath, vals, namespace, strict), 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, &currentMap); 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)
}

@ -21,16 +21,19 @@ import (
)
var (
values = []byte{}
namespace = "testNamespace"
strict = false
archivedChartPath = "testdata/testcharts/compressedchart-0.1.0.tgz"
chartDirPath = "testdata/testcharts/decompressedchart/"
)
func TestLintChart(t *testing.T) {
if _, err := lintChart(chartDirPath); err != nil {
if _, err := lintChart(chartDirPath, values, namespace, strict); err != nil {
t.Errorf("%s", err)
}
if _, err := lintChart(archivedChartPath); err != nil {
if _, err := lintChart(archivedChartPath, values, namespace, strict); err != nil {
t.Errorf("%s", err)
}

@ -21,7 +21,10 @@ helm lint [flags] PATH
### Options
```
--strict fail on lint warnings
--namespace string namespace to install the release into (only used if --install is set) (default "default")
--set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
--strict fail on lint warnings
-f, --values valueFiles specify values in a YAML file (can specify multiple) (default [])
```
### Options inherited from parent commands

@ -24,13 +24,13 @@ import (
)
// All runs all of the available linters on the given base directory.
func All(basedir string) support.Linter {
func All(basedir string, values []byte, namespace string, strict bool) 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)
rules.Templates(&linter, values, namespace, strict)
return linter
}

@ -24,13 +24,18 @@ import (
"testing"
)
var values = []byte{}
const namespace = "testNamespace"
const strict = false
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).Messages
m := All(badChartDir, values, namespace, strict).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)
@ -66,7 +71,7 @@ func TestBadChart(t *testing.T) {
}
func TestInvalidYaml(t *testing.T) {
m := All(badYamlFileDir).Messages
m := All(badYamlFileDir, values, namespace, strict).Messages
if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
}
@ -76,7 +81,7 @@ func TestInvalidYaml(t *testing.T) {
}
func TestBadValues(t *testing.T) {
m := All(badValuesFileDir).Messages
m := All(badValuesFileDir, values, namespace, strict).Messages
if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
}
@ -86,7 +91,7 @@ func TestBadValues(t *testing.T) {
}
func TestGoodChart(t *testing.T) {
m := All(goodChartDir).Messages
m := All(goodChartDir, values, namespace, strict).Messages
if len(m) != 0 {
t.Errorf("All failed but shouldn't have: %#v", m)
}

@ -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) {
func Templates(linter *support.Linter, values []byte, namespace string, strict bool) {
path := "templates/"
templatesPath := filepath.Join(linter.ChartDir, path)
@ -51,20 +52,34 @@ func Templates(linter *support.Linter) {
return
}
options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: "testNamespace"}
options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: namespace}
caps := &chartutil.Capabilities{
APIVersions: chartutil.DefaultVersionSet,
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.
//linter.RunLinterRule(support.ErrorSev, err)
return
}
renderedContentMap, err := engine.New().Render(chart, valuesToRender)
e := engine.New()
if strict {
e.Strict = true
}
renderedContentMap, err := e.Render(chart, valuesToRender)
renderOk := linter.RunLinterRule(support.ErrorSev, path, err)

@ -44,9 +44,14 @@ func TestValidateAllowedExtension(t *testing.T) {
}
}
var values = []byte("nameOverride: ''\nhttpPort: 80")
const namespace = "testNamespace"
const strict = false
func TestTemplateParsing(t *testing.T) {
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter)
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 1 {
@ -69,7 +74,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) {
defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter)
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 0 {

@ -1,2 +1,2 @@
metadata:
name: {{.name | default "foo" | title}}
name: {{.Values.name | default "foo" | title}}

Loading…
Cancel
Save