Skip to content

Commit 869c1d2

Browse files
lint: Add --kube-version flag to set capabilities and deprecation rules
Signed-off-by: Antoine Deschênes <[email protected]>
1 parent 77d54d7 commit 869c1d2

15 files changed

+110
-15
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ test: test-unit
104104
test-unit:
105105
@echo
106106
@echo "==> Running unit tests <=="
107-
GO111MODULE=on go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
107+
GO111MODULE=on go test $(GOFLAGS) -ldflags '$(LDFLAGS)' -run $(TESTS) $(PKG) $(TESTFLAGS)
108108

109109
.PHONY: test-coverage
110110
test-coverage:

cmd/helm/lint.go

+12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/spf13/cobra"
2828

2929
"helm.sh/helm/v3/pkg/action"
30+
"helm.sh/helm/v3/pkg/chartutil"
3031
"helm.sh/helm/v3/pkg/cli/values"
3132
"helm.sh/helm/v3/pkg/getter"
3233
"helm.sh/helm/v3/pkg/lint/support"
@@ -44,6 +45,7 @@ or recommendation, it will emit [WARNING] messages.
4445
func newLintCmd(out io.Writer) *cobra.Command {
4546
client := action.NewLint()
4647
valueOpts := &values.Options{}
48+
var kubeVersion string
4749

4850
cmd := &cobra.Command{
4951
Use: "lint PATH",
@@ -54,6 +56,15 @@ func newLintCmd(out io.Writer) *cobra.Command {
5456
if len(args) > 0 {
5557
paths = args
5658
}
59+
60+
if kubeVersion != "" {
61+
parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion)
62+
if err != nil {
63+
return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err)
64+
}
65+
client.KubeVersion = parsedKubeVersion
66+
}
67+
5768
if client.WithSubcharts {
5869
for _, p := range paths {
5970
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 {
137148
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
138149
f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts")
139150
f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors")
151+
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for capabilities and deprecation checks")
140152
addValueOptionsFlags(f, valueOpts)
141153

142154
return cmd

cmd/helm/lint_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,32 @@ func TestLintCmdWithQuietFlag(t *testing.T) {
6363

6464
}
6565

66+
func TestLintCmdWithKubeVersionFlag(t *testing.T) {
67+
testChart := "testdata/testcharts/chart-with-deprecated-api"
68+
tests := []cmdTestCase{{
69+
name: "lint chart with deprecated api version using kube version flag",
70+
cmd: fmt.Sprintf("lint --kube-version 1.22.0 %s", testChart),
71+
golden: "output/lint-chart-with-deprecated-api.txt",
72+
wantError: false,
73+
}, {
74+
name: "lint chart with deprecated api version using kube version and strict flag",
75+
cmd: fmt.Sprintf("lint --kube-version 1.22.0 --strict %s", testChart),
76+
golden: "output/lint-chart-with-deprecated-api-strict.txt",
77+
wantError: true,
78+
}, {
79+
name: "lint chart with deprecated api version without kube version",
80+
cmd: fmt.Sprintf("lint %s", testChart),
81+
golden: "output/lint-chart-with-deprecated-api.txt",
82+
wantError: false,
83+
}, {
84+
name: "lint chart with deprecated api version with older kube version",
85+
cmd: fmt.Sprintf("lint --kube-version 1.21.0 --strict %s", testChart),
86+
golden: "output/lint-chart-with-deprecated-api-old-k8s.txt",
87+
wantError: false,
88+
}}
89+
runTestCmd(t, tests)
90+
}
91+
6692
func TestLintFileCompletion(t *testing.T) {
6793
checkFileCompletion(t, "lint", true)
6894
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
==> Linting testdata/testcharts/chart-with-deprecated-api
2+
[INFO] Chart.yaml: icon is recommended
3+
4+
1 chart(s) linted, 0 chart(s) failed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
==> Linting testdata/testcharts/chart-with-deprecated-api
2+
[INFO] Chart.yaml: icon is recommended
3+
[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler
4+
5+
Error: 1 chart(s) linted, 1 chart(s) failed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
==> Linting testdata/testcharts/chart-with-deprecated-api
2+
[INFO] Chart.yaml: icon is recommended
3+
[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler
4+
5+
1 chart(s) linted, 0 chart(s) failed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v2
2+
appVersion: "1.0.0"
3+
description: A Helm chart for Kubernetes
4+
name: chart-with-deprecated-api
5+
type: application
6+
version: 1.0.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: autoscaling/v2beta1
2+
kind: HorizontalPodAutoscaler
3+
metadata:
4+
name: deprecated
5+
spec:
6+
scaleTargetRef:
7+
kind: Pod
8+
name: pod
9+
maxReplicas: 3

cmd/helm/testdata/testcharts/chart-with-deprecated-api/values.yaml

Whitespace-only changes.

pkg/action/lint.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type Lint struct {
3636
Namespace string
3737
WithSubcharts bool
3838
Quiet bool
39+
KubeVersion *chartutil.KubeVersion
3940
}
4041

4142
// LintResult is the result of Lint
@@ -58,7 +59,7 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
5859
}
5960
result := &LintResult{}
6061
for _, path := range paths {
61-
linter, err := lintChart(path, vals, l.Namespace, l.Strict)
62+
linter, err := lintChart(path, vals, l.Namespace, l.KubeVersion)
6263
if err != nil {
6364
result.Errors = append(result.Errors, err)
6465
continue
@@ -85,7 +86,7 @@ func HasWarningsOrErrors(result *LintResult) bool {
8586
return len(result.Errors) > 0
8687
}
8788

88-
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
89+
func lintChart(path string, vals map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) (support.Linter, error) {
8990
var chartPath string
9091
linter := support.Linter{}
9192

@@ -124,5 +125,5 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric
124125
return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart")
125126
}
126127

127-
return lint.All(chartPath, vals, namespace, strict), nil
128+
return lint.AllWithKubeVersion(chartPath, vals, namespace, kubeVersion), nil
128129
}

pkg/action/lint_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
var (
2424
values = make(map[string]interface{})
2525
namespace = "testNamespace"
26-
strict = false
2726
chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1"
2827
chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2"
2928
corruptedTgzChart = "testdata/charts/corrupted-compressed-chart.tgz"
@@ -78,7 +77,7 @@ func TestLintChart(t *testing.T) {
7877

7978
for _, tt := range tests {
8079
t.Run(tt.name, func(t *testing.T) {
81-
_, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, strict)
80+
_, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil)
8281
switch {
8382
case err != nil && !tt.err:
8483
t.Errorf("%s", err)

pkg/lint/lint.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,25 @@ package lint // import "helm.sh/helm/v3/pkg/lint"
1919
import (
2020
"path/filepath"
2121

22+
"helm.sh/helm/v3/pkg/chartutil"
2223
"helm.sh/helm/v3/pkg/lint/rules"
2324
"helm.sh/helm/v3/pkg/lint/support"
2425
)
2526

2627
// All runs all of the available linters on the given base directory.
27-
func All(basedir string, values map[string]interface{}, namespace string, strict bool) support.Linter {
28+
func All(basedir string, values map[string]interface{}, namespace string, _ bool) support.Linter {
29+
return AllWithKubeVersion(basedir, values, namespace, nil)
30+
}
31+
32+
// AllWithKubeVersion runs all the available linters on the given base directory, allowing to specify the kubernetes version.
33+
func AllWithKubeVersion(basedir string, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) support.Linter {
2834
// Using abs path to get directory context
2935
chartDir, _ := filepath.Abs(basedir)
3036

3137
linter := support.Linter{ChartDir: chartDir}
3238
rules.Chartfile(&linter)
3339
rules.ValuesWithOverrides(&linter, values)
34-
rules.Templates(&linter, values, namespace, strict)
40+
rules.TemplatesWithKubeVersion(&linter, values, namespace, kubeVersion)
3541
rules.Dependencies(&linter)
3642
return linter
3743
}

pkg/lint/rules/deprecations.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"k8s.io/apimachinery/pkg/runtime/schema"
2525
"k8s.io/apiserver/pkg/endpoints/deprecation"
2626
kscheme "k8s.io/client-go/kubernetes/scheme"
27+
28+
"helm.sh/helm/v3/pkg/chartutil"
2729
)
2830

2931
var (
@@ -45,7 +47,7 @@ func (e deprecatedAPIError) Error() string {
4547
return msg
4648
}
4749

48-
func validateNoDeprecations(resource *K8sYamlStruct) error {
50+
func validateNoDeprecations(resource *K8sYamlStruct, kubeVersion *chartutil.KubeVersion) error {
4951
// if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation
5052
if resource.APIVersion == "" {
5153
return nil
@@ -54,6 +56,14 @@ func validateNoDeprecations(resource *K8sYamlStruct) error {
5456
return nil
5557
}
5658

59+
majorVersion := k8sVersionMajor
60+
minorVersion := k8sVersionMinor
61+
62+
if kubeVersion != nil {
63+
majorVersion = kubeVersion.Major
64+
minorVersion = kubeVersion.Minor
65+
}
66+
5767
runtimeObject, err := resourceToRuntimeObject(resource)
5868
if err != nil {
5969
// do not error for non-kubernetes resources
@@ -62,11 +72,12 @@ func validateNoDeprecations(resource *K8sYamlStruct) error {
6272
}
6373
return err
6474
}
65-
maj, err := strconv.Atoi(k8sVersionMajor)
75+
76+
maj, err := strconv.Atoi(majorVersion)
6677
if err != nil {
6778
return err
6879
}
69-
min, err := strconv.Atoi(k8sVersionMinor)
80+
min, err := strconv.Atoi(minorVersion)
7081
if err != nil {
7182
return err
7283
}

pkg/lint/rules/deprecations_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestValidateNoDeprecations(t *testing.T) {
2323
APIVersion: "extensions/v1beta1",
2424
Kind: "Deployment",
2525
}
26-
err := validateNoDeprecations(deprecated)
26+
err := validateNoDeprecations(deprecated, nil)
2727
if err == nil {
2828
t.Fatal("Expected deprecated extension to be flagged")
2929
}
@@ -35,7 +35,7 @@ func TestValidateNoDeprecations(t *testing.T) {
3535
if err := validateNoDeprecations(&K8sYamlStruct{
3636
APIVersion: "v1",
3737
Kind: "Pod",
38-
}); err != nil {
38+
}, nil); err != nil {
3939
t.Errorf("Expected a v1 Pod to not be deprecated")
4040
}
4141
}

pkg/lint/rules/template.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ var (
4646

4747
// Templates lints the templates in the Linter.
4848
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) {
49+
TemplatesWithKubeVersion(linter, values, namespace, nil)
50+
}
51+
52+
// TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version.
53+
func TemplatesWithKubeVersion(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) {
4954
fpath := "templates/"
5055
templatesPath := filepath.Join(linter.ChartDir, fpath)
5156

@@ -70,6 +75,11 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
7075
Namespace: namespace,
7176
}
7277

78+
caps := chartutil.DefaultCapabilities.Copy()
79+
if kubeVersion != nil {
80+
caps.KubeVersion = *kubeVersion
81+
}
82+
7383
// lint ignores import-values
7484
// See https://github.com/helm/helm/issues/9658
7585
if err := chartutil.ProcessDependenciesWithMerge(chart, values); err != nil {
@@ -80,7 +90,8 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
8090
if err != nil {
8191
return
8292
}
83-
valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil)
93+
94+
valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, caps)
8495
if err != nil {
8596
linter.RunLinterRule(support.ErrorSev, fpath, err)
8697
return
@@ -150,7 +161,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
150161
// NOTE: set to warnings to allow users to support out-of-date kubernetes
151162
// Refs https://github.com/helm/helm/issues/8596
152163
linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct))
153-
linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct))
164+
linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct, kubeVersion))
154165

155166
linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent))
156167
linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent))

0 commit comments

Comments
 (0)