mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
lint: Add --kube-version flag to set capabilities and deprecation rules
Signed-off-by: Antoine Deschênes <antoine@antoinedeschenes.com>
This commit is contained in:
parent
77d54d7dbe
commit
869c1d2560
15 changed files with 110 additions and 15 deletions
2
Makefile
2
Makefile
|
|
@ -104,7 +104,7 @@ test: test-unit
|
|||
test-unit:
|
||||
@echo
|
||||
@echo "==> Running unit tests <=="
|
||||
GO111MODULE=on go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
|
||||
GO111MODULE=on go test $(GOFLAGS) -ldflags '$(LDFLAGS)' -run $(TESTS) $(PKG) $(TESTFLAGS)
|
||||
|
||||
.PHONY: test-coverage
|
||||
test-coverage:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/cli/values"
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
"helm.sh/helm/v3/pkg/lint/support"
|
||||
|
|
@ -44,6 +45,7 @@ or recommendation, it will emit [WARNING] messages.
|
|||
func newLintCmd(out io.Writer) *cobra.Command {
|
||||
client := action.NewLint()
|
||||
valueOpts := &values.Options{}
|
||||
var kubeVersion string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "lint PATH",
|
||||
|
|
@ -54,6 +56,15 @@ func newLintCmd(out io.Writer) *cobra.Command {
|
|||
if len(args) > 0 {
|
||||
paths = args
|
||||
}
|
||||
|
||||
if kubeVersion != "" {
|
||||
parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err)
|
||||
}
|
||||
client.KubeVersion = parsedKubeVersion
|
||||
}
|
||||
|
||||
if client.WithSubcharts {
|
||||
for _, p := range paths {
|
||||
filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, err error) error {
|
||||
|
|
@ -137,6 +148,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
|
|||
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
|
||||
f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts")
|
||||
f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors")
|
||||
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for capabilities and deprecation checks")
|
||||
addValueOptionsFlags(f, valueOpts)
|
||||
|
||||
return cmd
|
||||
|
|
|
|||
|
|
@ -63,6 +63,32 @@ func TestLintCmdWithQuietFlag(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestLintCmdWithKubeVersionFlag(t *testing.T) {
|
||||
testChart := "testdata/testcharts/chart-with-deprecated-api"
|
||||
tests := []cmdTestCase{{
|
||||
name: "lint chart with deprecated api version using kube version flag",
|
||||
cmd: fmt.Sprintf("lint --kube-version 1.22.0 %s", testChart),
|
||||
golden: "output/lint-chart-with-deprecated-api.txt",
|
||||
wantError: false,
|
||||
}, {
|
||||
name: "lint chart with deprecated api version using kube version and strict flag",
|
||||
cmd: fmt.Sprintf("lint --kube-version 1.22.0 --strict %s", testChart),
|
||||
golden: "output/lint-chart-with-deprecated-api-strict.txt",
|
||||
wantError: true,
|
||||
}, {
|
||||
name: "lint chart with deprecated api version without kube version",
|
||||
cmd: fmt.Sprintf("lint %s", testChart),
|
||||
golden: "output/lint-chart-with-deprecated-api.txt",
|
||||
wantError: false,
|
||||
}, {
|
||||
name: "lint chart with deprecated api version with older kube version",
|
||||
cmd: fmt.Sprintf("lint --kube-version 1.21.0 --strict %s", testChart),
|
||||
golden: "output/lint-chart-with-deprecated-api-old-k8s.txt",
|
||||
wantError: false,
|
||||
}}
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
func TestLintFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "lint", true)
|
||||
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given
|
||||
|
|
|
|||
4
cmd/helm/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt
vendored
Normal file
4
cmd/helm/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
==> Linting testdata/testcharts/chart-with-deprecated-api
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
|
||||
1 chart(s) linted, 0 chart(s) failed
|
||||
5
cmd/helm/testdata/output/lint-chart-with-deprecated-api-strict.txt
vendored
Normal file
5
cmd/helm/testdata/output/lint-chart-with-deprecated-api-strict.txt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
==> Linting testdata/testcharts/chart-with-deprecated-api
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler
|
||||
|
||||
Error: 1 chart(s) linted, 1 chart(s) failed
|
||||
5
cmd/helm/testdata/output/lint-chart-with-deprecated-api.txt
vendored
Normal file
5
cmd/helm/testdata/output/lint-chart-with-deprecated-api.txt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
==> Linting testdata/testcharts/chart-with-deprecated-api
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler
|
||||
|
||||
1 chart(s) linted, 0 chart(s) failed
|
||||
6
cmd/helm/testdata/testcharts/chart-with-deprecated-api/Chart.yaml
vendored
Normal file
6
cmd/helm/testdata/testcharts/chart-with-deprecated-api/Chart.yaml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
appVersion: "1.0.0"
|
||||
description: A Helm chart for Kubernetes
|
||||
name: chart-with-deprecated-api
|
||||
type: application
|
||||
version: 1.0.0
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: deprecated
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
kind: Pod
|
||||
name: pod
|
||||
maxReplicas: 3
|
||||
0
cmd/helm/testdata/testcharts/chart-with-deprecated-api/values.yaml
vendored
Normal file
0
cmd/helm/testdata/testcharts/chart-with-deprecated-api/values.yaml
vendored
Normal file
|
|
@ -36,6 +36,7 @@ type Lint struct {
|
|||
Namespace string
|
||||
WithSubcharts bool
|
||||
Quiet bool
|
||||
KubeVersion *chartutil.KubeVersion
|
||||
}
|
||||
|
||||
// LintResult is the result of Lint
|
||||
|
|
@ -58,7 +59,7 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
|
|||
}
|
||||
result := &LintResult{}
|
||||
for _, path := range paths {
|
||||
linter, err := lintChart(path, vals, l.Namespace, l.Strict)
|
||||
linter, err := lintChart(path, vals, l.Namespace, l.KubeVersion)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err)
|
||||
continue
|
||||
|
|
@ -85,7 +86,7 @@ func HasWarningsOrErrors(result *LintResult) bool {
|
|||
return len(result.Errors) > 0
|
||||
}
|
||||
|
||||
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
|
||||
func lintChart(path string, vals map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) (support.Linter, error) {
|
||||
var chartPath string
|
||||
linter := support.Linter{}
|
||||
|
||||
|
|
@ -124,5 +125,5 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric
|
|||
return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart")
|
||||
}
|
||||
|
||||
return lint.All(chartPath, vals, namespace, strict), nil
|
||||
return lint.AllWithKubeVersion(chartPath, vals, namespace, kubeVersion), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
var (
|
||||
values = make(map[string]interface{})
|
||||
namespace = "testNamespace"
|
||||
strict = false
|
||||
chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1"
|
||||
chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2"
|
||||
corruptedTgzChart = "testdata/charts/corrupted-compressed-chart.tgz"
|
||||
|
|
@ -78,7 +77,7 @@ func TestLintChart(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, strict)
|
||||
_, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil)
|
||||
switch {
|
||||
case err != nil && !tt.err:
|
||||
t.Errorf("%s", err)
|
||||
|
|
|
|||
|
|
@ -19,19 +19,25 @@ package lint // import "helm.sh/helm/v3/pkg/lint"
|
|||
import (
|
||||
"path/filepath"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/lint/rules"
|
||||
"helm.sh/helm/v3/pkg/lint/support"
|
||||
)
|
||||
|
||||
// All runs all of the available linters on the given base directory.
|
||||
func All(basedir string, values map[string]interface{}, namespace string, strict bool) support.Linter {
|
||||
func All(basedir string, values map[string]interface{}, namespace string, _ bool) support.Linter {
|
||||
return AllWithKubeVersion(basedir, values, namespace, nil)
|
||||
}
|
||||
|
||||
// AllWithKubeVersion runs all the available linters on the given base directory, allowing to specify the kubernetes version.
|
||||
func AllWithKubeVersion(basedir string, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) support.Linter {
|
||||
// Using abs path to get directory context
|
||||
chartDir, _ := filepath.Abs(basedir)
|
||||
|
||||
linter := support.Linter{ChartDir: chartDir}
|
||||
rules.Chartfile(&linter)
|
||||
rules.ValuesWithOverrides(&linter, values)
|
||||
rules.Templates(&linter, values, namespace, strict)
|
||||
rules.TemplatesWithKubeVersion(&linter, values, namespace, kubeVersion)
|
||||
rules.Dependencies(&linter)
|
||||
return linter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/deprecation"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -45,7 +47,7 @@ func (e deprecatedAPIError) Error() string {
|
|||
return msg
|
||||
}
|
||||
|
||||
func validateNoDeprecations(resource *K8sYamlStruct) error {
|
||||
func validateNoDeprecations(resource *K8sYamlStruct, kubeVersion *chartutil.KubeVersion) error {
|
||||
// if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation
|
||||
if resource.APIVersion == "" {
|
||||
return nil
|
||||
|
|
@ -54,6 +56,14 @@ func validateNoDeprecations(resource *K8sYamlStruct) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
majorVersion := k8sVersionMajor
|
||||
minorVersion := k8sVersionMinor
|
||||
|
||||
if kubeVersion != nil {
|
||||
majorVersion = kubeVersion.Major
|
||||
minorVersion = kubeVersion.Minor
|
||||
}
|
||||
|
||||
runtimeObject, err := resourceToRuntimeObject(resource)
|
||||
if err != nil {
|
||||
// do not error for non-kubernetes resources
|
||||
|
|
@ -62,11 +72,12 @@ func validateNoDeprecations(resource *K8sYamlStruct) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
maj, err := strconv.Atoi(k8sVersionMajor)
|
||||
|
||||
maj, err := strconv.Atoi(majorVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
min, err := strconv.Atoi(k8sVersionMinor)
|
||||
min, err := strconv.Atoi(minorVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func TestValidateNoDeprecations(t *testing.T) {
|
|||
APIVersion: "extensions/v1beta1",
|
||||
Kind: "Deployment",
|
||||
}
|
||||
err := validateNoDeprecations(deprecated)
|
||||
err := validateNoDeprecations(deprecated, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected deprecated extension to be flagged")
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ func TestValidateNoDeprecations(t *testing.T) {
|
|||
if err := validateNoDeprecations(&K8sYamlStruct{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
}); err != nil {
|
||||
}, nil); err != nil {
|
||||
t.Errorf("Expected a v1 Pod to not be deprecated")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ var (
|
|||
|
||||
// Templates lints the templates in the Linter.
|
||||
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) {
|
||||
TemplatesWithKubeVersion(linter, values, namespace, nil)
|
||||
}
|
||||
|
||||
// TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version.
|
||||
func TemplatesWithKubeVersion(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) {
|
||||
fpath := "templates/"
|
||||
templatesPath := filepath.Join(linter.ChartDir, fpath)
|
||||
|
||||
|
|
@ -70,6 +75,11 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
|
|||
Namespace: namespace,
|
||||
}
|
||||
|
||||
caps := chartutil.DefaultCapabilities.Copy()
|
||||
if kubeVersion != nil {
|
||||
caps.KubeVersion = *kubeVersion
|
||||
}
|
||||
|
||||
// lint ignores import-values
|
||||
// See https://github.com/helm/helm/issues/9658
|
||||
if err := chartutil.ProcessDependenciesWithMerge(chart, values); err != nil {
|
||||
|
|
@ -80,7 +90,8 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil)
|
||||
|
||||
valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, caps)
|
||||
if err != nil {
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
return
|
||||
|
|
@ -150,7 +161,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
|
|||
// NOTE: set to warnings to allow users to support out-of-date kubernetes
|
||||
// Refs https://github.com/helm/helm/issues/8596
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct))
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct))
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct, kubeVersion))
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent))
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent))
|
||||
|
|
|
|||
Loading…
Reference in a new issue