From 98310a915fced2683922a97dda65cc0939288ab4 Mon Sep 17 00:00:00 2001 From: Steve Wilkerson Date: Fri, 10 Feb 2017 13:16:44 -0600 Subject: [PATCH] fix(helm): add warnings for missing chart dependencies When 'helm install', 'helm package', and 'helm upgrade' are run, Helm will not issue any warnings if any dependencies listed in a chart's requirements.yaml file are missing. This change includes warnings when a chart is found in requirements.yaml but isn't in charts/. Closes #1567 --- cmd/helm/install.go | 25 +++++++++++++++++++ cmd/helm/install_test.go | 6 +++++ cmd/helm/package.go | 4 +++ cmd/helm/package_test.go | 6 +++++ .../testcharts/chart-missing-deps/.helmignore | 21 ++++++++++++++++ .../testcharts/chart-missing-deps/Chart.yaml | 3 +++ .../charts/reqsubchart/.helmignore | 21 ++++++++++++++++ .../charts/reqsubchart/Chart.yaml | 3 +++ .../charts/reqsubchart/values.yaml | 4 +++ .../chart-missing-deps/requirements.yaml | 7 ++++++ .../testcharts/chart-missing-deps/values.yaml | 4 +++ cmd/helm/upgrade.go | 8 ++++++ cmd/helm/upgrade_test.go | 15 +++++++++++ 13 files changed, 127 insertions(+) create mode 100644 cmd/helm/testdata/testcharts/chart-missing-deps/.helmignore create mode 100644 cmd/helm/testdata/testcharts/chart-missing-deps/Chart.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore create mode 100644 cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-missing-deps/requirements.yaml create mode 100644 cmd/helm/testdata/testcharts/chart-missing-deps/values.yaml diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 6917a6e6a..557a45d43 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -35,8 +35,10 @@ import ( "k8s.io/helm/cmd/helm/downloader" "k8s.io/helm/cmd/helm/helmpath" "k8s.io/helm/cmd/helm/strvals" + "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" ) @@ -199,6 +201,13 @@ func (i *installCmd) run() error { fmt.Printf("FINAL NAME: %s\n", i.name) } + // Check chart requirements to make sure all dependencies are present in /charts + if c, err := chartutil.Load(i.chartPath); err == nil { + if req, err := chartutil.LoadRequirements(c); err == nil { + checkDependencies(c, req, i.out) + } + } + res, err := i.client.InstallRelease( i.chartPath, i.namespace, @@ -388,3 +397,19 @@ func defaultNamespace() string { } return "default" } + +func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements, out io.Writer) { + deps := ch.GetDependencies() + for _, r := range reqs.Dependencies { + found := false + for _, d := range deps { + if d.Metadata.Name == r.Name { + found = true + break + } + } + if !found { + fmt.Fprintf(out, "Warning: %s is in requirements.yaml but not in the charts/ directory!\n", r.Name) + } + } +} diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index 3204a2437..ee0ffe0e8 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -132,6 +132,12 @@ func TestInstall(t *testing.T) { args: []string{"testdata/testcharts/signtest-0.1.0.tgz"}, flags: strings.Split("--verify --keyring testdata/helm-test-key.pub", " "), }, + // Install, chart with missing dependencies in /charts + { + name: "install chart with missing dependencies", + args: []string{"testdata/testcharts/chart-missing-deps"}, + expected: "Warning: reqsubchart2 is in requirements.yaml but not in the charts/ directory!", + }, } runReleaseCases(t, tests, func(c *fakeReleaseClient, out io.Writer) *cobra.Command { diff --git a/cmd/helm/package.go b/cmd/helm/package.go index c85fd3d40..d88fcf214 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -125,6 +125,10 @@ func (p *packageCmd) run(cmd *cobra.Command, args []string) error { return fmt.Errorf("directory name (%s) and Chart.yaml name (%s) must match", filepath.Base(path), ch.Metadata.Name) } + if reqs, err := chartutil.LoadRequirements(ch); err == nil { + checkDependencies(ch, reqs, p.out) + } + // Save to the current working directory. cwd, err := os.Getwd() if err != nil { diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index 1655bf23c..f72b1d25c 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -101,6 +101,12 @@ func TestPackage(t *testing.T) { expect: "", hasfile: "alpine-0.1.0.tgz", }, + { + name: "package testdata/testcharts/chart-missing-deps", + args: []string{"testdata/testcharts/chart-missing-deps"}, + expect: "Warning: reqsubchart2 is in requirements.yaml but not in the charts/ directory!\n", + hasfile: "chart-missing-deps-0.1.0.tgz", + }, } // Because these tests are destructive, we run them in a tempdir. diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/.helmignore b/cmd/helm/testdata/testcharts/chart-missing-deps/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-deps/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/Chart.yaml b/cmd/helm/testdata/testcharts/chart-missing-deps/Chart.yaml new file mode 100644 index 000000000..02be4c013 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-deps/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: chart-missing-deps +version: 0.1.0 diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore b/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml b/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml new file mode 100644 index 000000000..c3813bc8c --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: reqsubchart +version: 0.1.0 diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml b/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml new file mode 100644 index 000000000..0f0b63f2a --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqsubchart. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/requirements.yaml b/cmd/helm/testdata/testcharts/chart-missing-deps/requirements.yaml new file mode 100644 index 000000000..4b0b8c2db --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-deps/requirements.yaml @@ -0,0 +1,7 @@ +dependencies: + - name: reqsubchart + version: 0.1.0 + repository: "https://example.com/charts" + - name: reqsubchart2 + version: 0.2.0 + repository: "https://example.com/charts" diff --git a/cmd/helm/testdata/testcharts/chart-missing-deps/values.yaml b/cmd/helm/testdata/testcharts/chart-missing-deps/values.yaml new file mode 100644 index 000000000..d57f76b07 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-deps/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqtest. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 5f6d36142..d96993297 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/cmd/helm/strvals" + "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/storage/driver" ) @@ -160,6 +161,13 @@ func (u *upgradeCmd) run() error { return err } + // Check chart requirements to make sure all dependencies are present in /charts + if ch, err := chartutil.Load(chartPath); err == nil { + if req, err := chartutil.LoadRequirements(ch); err == nil { + checkDependencies(ch, req, u.out) + } + } + resp, err := u.client.UpdateRelease( u.release, chartPath, diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index dadf3fb80..2ae4abb25 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -20,6 +20,7 @@ import ( "io" "io/ioutil" "os" + "path/filepath" "testing" "github.com/spf13/cobra" @@ -79,6 +80,14 @@ func TestUpgradeCmd(t *testing.T) { t.Errorf("Error loading updated chart: %v", err) } + originalDepsPath := filepath.Join("testdata/testcharts/reqtest") + missingDepsPath := filepath.Join("testdata/testcharts/chart-missing-deps") + var ch3 *chart.Chart + ch3, err = chartutil.Load(originalDepsPath) + if err != nil { + t.Errorf("Error loading chart with missing dependencies: %v", err) + } + tests := []releaseCase{ { name: "upgrade a release", @@ -121,6 +130,12 @@ func TestUpgradeCmd(t *testing.T) { resp: releaseMock(&releaseOptions{name: "crazy-bunny", version: 2, chart: ch2}), expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n", }, + { + name: "upgrade a release with missing dependencies", + args: []string{"bonkers-bunny", missingDepsPath}, + resp: releaseMock(&releaseOptions{name: "bonkers-bunny", version: 1, chart: ch3}), + expected: "Warning: reqsubchart2 is in requirements.yaml but not in the charts/ directory!", + }, } cmd := func(c *fakeReleaseClient, out io.Writer) *cobra.Command {