Lint dependencies (#7970)

* feat: add dependency tests

Signed-off-by: Matt Butcher <matt.butcher@microsoft.com>

* replaced on-disk fixtures with in-memory fixtures

Signed-off-by: Matt Butcher <matt.butcher@microsoft.com>
pull/8432/head
Matt Butcher 5 years ago committed by GitHub
parent bd09f1fbec
commit 2750e4d781
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,7 +27,7 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) {
name: "lint good chart with bad subcharts", name: "lint good chart with bad subcharts",
cmd: fmt.Sprintf("lint %s", testChart), cmd: fmt.Sprintf("lint %s", testChart),
golden: "output/lint-chart-with-bad-subcharts.txt", golden: "output/lint-chart-with-bad-subcharts.txt",
wantError: false, wantError: true,
}, { }, {
name: "lint good chart with bad subcharts using --with-subcharts flag", name: "lint good chart with bad subcharts using --with-subcharts flag",
cmd: fmt.Sprintf("lint --with-subcharts %s", testChart), cmd: fmt.Sprintf("lint --with-subcharts %s", testChart),

@ -1,6 +1,8 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts ==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended [INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found [WARNING] templates/: directory not found
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart ==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/bad-subchart
[ERROR] Chart.yaml: name is required [ERROR] Chart.yaml: name is required
@ -8,9 +10,11 @@
[ERROR] Chart.yaml: version is required [ERROR] Chart.yaml: version is required
[INFO] Chart.yaml: icon is recommended [INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found [WARNING] templates/: directory not found
[ERROR] : unable to load chart
validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart ==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart
[INFO] Chart.yaml: icon is recommended [INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found [WARNING] templates/: directory not found
Error: 3 chart(s) linted, 1 chart(s) failed Error: 3 chart(s) linted, 2 chart(s) failed

@ -1,5 +1,7 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts ==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended [INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found [WARNING] templates/: directory not found
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
1 chart(s) linted, 0 chart(s) failed Error: 1 chart(s) linted, 1 chart(s) failed

@ -30,15 +30,13 @@ import (
"testing" "testing"
"time" "time"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
) )
func TestSave(t *testing.T) { func TestSave(t *testing.T) {
tmp, err := ioutil.TempDir("", "helm-") tmp := ensure.TempDir(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
for _, dest := range []string{tmp, path.Join(tmp, "newdir")} { for _, dest := range []string{tmp, path.Join(tmp, "newdir")} {

@ -32,5 +32,6 @@ func All(basedir string, values map[string]interface{}, namespace string, strict
rules.Chartfile(&linter) rules.Chartfile(&linter)
rules.ValuesWithOverrides(&linter, values) rules.ValuesWithOverrides(&linter, values)
rules.Templates(&linter, values, namespace, strict) rules.Templates(&linter, values, namespace, strict)
rules.Dependencies(&linter)
return linter return linter
} }

@ -38,12 +38,12 @@ const goodChartDir = "rules/testdata/goodone"
func TestBadChart(t *testing.T) { func TestBadChart(t *testing.T) {
m := All(badChartDir, values, namespace, strict).Messages m := All(badChartDir, values, namespace, strict).Messages
if len(m) != 7 { if len(m) != 8 {
t.Errorf("Number of errors %v", len(m)) t.Errorf("Number of errors %v", len(m))
t.Errorf("All didn't fail with expected errors, got %#v", m) t.Errorf("All didn't fail with expected errors, got %#v", m)
} }
// There should be one INFO, 2 WARNINGs and one ERROR messages, check for them // There should be one INFO, 2 WARNINGs and 2 ERROR messages, check for them
var i, w, e, e2, e3, e4, e5 bool var i, w, e, e2, e3, e4, e5, e6 bool
for _, msg := range m { for _, msg := range m {
if msg.Severity == support.InfoSev { if msg.Severity == support.InfoSev {
if strings.Contains(msg.Err.Error(), "icon is recommended") { if strings.Contains(msg.Err.Error(), "icon is recommended") {
@ -74,9 +74,13 @@ func TestBadChart(t *testing.T) {
if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") {
e5 = true e5 = true
} }
// This comes from the dependency check, which loads dependency info from the Chart.yaml
if strings.Contains(msg.Err.Error(), "unable to load chart") {
e6 = true
} }
} }
if !e || !e2 || !e3 || !e4 || !e5 || !w || !i { }
if !e || !e2 || !e3 || !e4 || !e5 || !w || !i || !e6 {
t.Errorf("Didn't find all the expected errors, got %#v", m) t.Errorf("Didn't find all the expected errors, got %#v", m)
} }
} }

@ -0,0 +1,82 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules // import "helm.sh/helm/v3/pkg/lint/rules"
import (
"fmt"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/lint/support"
)
// Dependencies runs lints against a chart's dependencies
//
// See https://github.com/helm/helm/issues/7910
func Dependencies(linter *support.Linter) {
c, err := loader.LoadDir(linter.ChartDir)
if !linter.RunLinterRule(support.ErrorSev, "", validateChartFormat(err)) {
return
}
linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c))
linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c))
}
func validateChartFormat(chartError error) error {
if chartError != nil {
return errors.Errorf("unable to load chart\n\t%s", chartError)
}
return nil
}
func validateDependencyInChartsDir(c *chart.Chart) (err error) {
dependencies := map[string]struct{}{}
missing := []string{}
for _, dep := range c.Dependencies() {
dependencies[dep.Metadata.Name] = struct{}{}
}
for _, dep := range c.Metadata.Dependencies {
if _, ok := dependencies[dep.Name]; !ok {
missing = append(missing, dep.Name)
}
}
if len(missing) > 0 {
err = fmt.Errorf("chart directory is missing these dependencies: %s", strings.Join(missing, ","))
}
return err
}
func validateDependencyInMetadata(c *chart.Chart) (err error) {
dependencies := map[string]struct{}{}
missing := []string{}
for _, dep := range c.Metadata.Dependencies {
dependencies[dep.Name] = struct{}{}
}
for _, dep := range c.Dependencies() {
if _, ok := dependencies[dep.Metadata.Name]; !ok {
missing = append(missing, dep.Metadata.Name)
}
}
if len(missing) > 0 {
err = fmt.Errorf("chart metadata is missing these dependencies: %s", strings.Join(missing, ","))
}
return err
}

@ -0,0 +1,99 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules
import (
"os"
"path/filepath"
"testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/lint/support"
)
func chartWithBadDependencies() chart.Chart {
badChartDeps := chart.Chart{
Metadata: &chart.Metadata{
Name: "badchart",
Version: "0.1.0",
APIVersion: "v2",
Dependencies: []*chart.Dependency{
{
Name: "sub2",
},
{
Name: "sub3",
},
},
},
}
badChartDeps.SetDependencies(
&chart.Chart{
Metadata: &chart.Metadata{
Name: "sub1",
Version: "0.1.0",
APIVersion: "v2",
},
},
&chart.Chart{
Metadata: &chart.Metadata{
Name: "sub2",
Version: "0.1.0",
APIVersion: "v2",
},
},
)
return badChartDeps
}
func TestValidateDependencyInChartsDir(t *testing.T) {
c := chartWithBadDependencies()
if err := validateDependencyInChartsDir(&c); err == nil {
t.Error("chart should have been flagged for missing deps in chart directory")
}
}
func TestValidateDependencyInMetadata(t *testing.T) {
c := chartWithBadDependencies()
if err := validateDependencyInMetadata(&c); err == nil {
t.Errorf("chart should have been flagged for missing deps in chart metadata")
}
}
func TestDependencies(t *testing.T) {
tmp := ensure.TempDir(t)
defer os.RemoveAll(tmp)
c := chartWithBadDependencies()
err := chartutil.SaveDir(&c, tmp)
if err != nil {
t.Fatal(err)
}
linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)}
Dependencies(&linter)
if l := len(linter.Messages); l != 2 {
t.Errorf("expected 2 linter errors for bad chart dependencies. Got %d.", l)
for i, msg := range linter.Messages {
t.Logf("Message: %d, Error: %#v", i, msg)
}
}
}
Loading…
Cancel
Save