Skip to content

Commit

Permalink
Lint dependencies (helm#7970)
Browse files Browse the repository at this point in the history
* feat: add dependency tests

Signed-off-by: Matt Butcher <[email protected]>

* replaced on-disk fixtures with in-memory fixtures

Signed-off-by: Matt Butcher <[email protected]>
  • Loading branch information
technosophos authored Jul 9, 2020
1 parent bd09f1f commit 2750e4d
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 12 deletions.
2 changes: 1 addition & 1 deletion cmd/helm/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) {
name: "lint good chart with bad subcharts",
cmd: fmt.Sprintf("lint %s", testChart),
golden: "output/lint-chart-with-bad-subcharts.txt",
wantError: false,
wantError: true,
}, {
name: "lint good chart with bad subcharts using --with-subcharts flag",
cmd: fmt.Sprintf("lint --with-subcharts %s", testChart),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[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
[ERROR] Chart.yaml: name is required
[ERROR] Chart.yaml: apiVersion is required. The value must be either "v1" or "v2"
[ERROR] Chart.yaml: version is required
[INFO] Chart.yaml: icon is recommended
[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
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found

Error: 3 chart(s) linted, 1 chart(s) failed
Error: 3 chart(s) linted, 2 chart(s) failed
4 changes: 3 additions & 1 deletion cmd/helm/testdata/output/lint-chart-with-bad-subcharts.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[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
Original file line number Diff line number Diff line change
@@ -1 +1 @@
description: Bad subchart
description: Bad subchart
6 changes: 2 additions & 4 deletions pkg/chartutil/save_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ import (
"testing"
"time"

"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
)

func TestSave(t *testing.T) {
tmp, err := ioutil.TempDir("", "helm-")
if err != nil {
t.Fatal(err)
}
tmp := ensure.TempDir(t)
defer os.RemoveAll(tmp)

for _, dest := range []string{tmp, path.Join(tmp, "newdir")} {
Expand Down
1 change: 1 addition & 0 deletions pkg/lint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ func All(basedir string, values map[string]interface{}, namespace string, strict
rules.Chartfile(&linter)
rules.ValuesWithOverrides(&linter, values)
rules.Templates(&linter, values, namespace, strict)
rules.Dependencies(&linter)
return linter
}
12 changes: 8 additions & 4 deletions pkg/lint/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ const goodChartDir = "rules/testdata/goodone"

func TestBadChart(t *testing.T) {
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("All didn't fail with expected errors, got %#v", m)
}
// There should be one INFO, 2 WARNINGs and one ERROR messages, check for them
var i, w, e, e2, e3, e4, e5 bool
// There should be one INFO, 2 WARNINGs and 2 ERROR messages, check for them
var i, w, e, e2, e3, e4, e5, e6 bool
for _, msg := range m {
if msg.Severity == support.InfoSev {
if strings.Contains(msg.Err.Error(), "icon is recommended") {
Expand Down Expand Up @@ -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") {
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)
}
}
Expand Down
82 changes: 82 additions & 0 deletions pkg/lint/rules/dependencies.go
Original file line number Diff line number Diff line change
@@ -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
}
99 changes: 99 additions & 0 deletions pkg/lint/rules/dependencies_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit 2750e4d

Please sign in to comment.