Repository: helm/chart-testing
Pull request #184 (2019-11-03 17:33:43)

Add support for Helm 3

naseemkullah: bump
scottrigby: Note I have not yet had a chance to test this. If others get a chance to test it first, that would be great 😄 
naseemkullah: Is there a pre built image available with this code on quay.io/helmpack/chart-testing ?

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 14b84df..af59bd4 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -30,14 +30,18 @@ jobs:
             chmod +x ./kubectl
             sudo mv ./kubectl /usr/local/bin/kubectl
 
-            curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
+            helm_version=v3.0.0
+            curl -sSLO "https://get.helm.sh/helm-$helm_version-linux-amd64.tar.gz"
+            sudo mkdir -p "/usr/local/helm-$helm_version"
+            sudo tar -xzf "helm-$helm_version-linux-amd64.tar.gz" -C "/usr/local/helm-$helm_version"
+            sudo ln -s "/usr/local/helm-$helm_version/linux-amd64/helm" /usr/local/bin/helm
+
             curl -sSLo kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.5.1/kind-linux-amd64"
             chmod +x kind
             sudo mv kind /usr/local/bin/kind
       - run:
           name: Test
           command: |
-            go mod download
             ./e2e-kind.sh
   build:
     docker:
diff --git a/Dockerfile b/Dockerfile
index b96488a..0a16563 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,14 +19,14 @@ ARG YAMALE_VERSION=1.8.0
 RUN pip install "yamale==$YAMALE_VERSION"
 
 # Install kubectl
-ARG KUBECTL_VERSION=v1.16.0
+ARG KUBECTL_VERSION=v1.16.2
 RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl" && \
     chmod +x kubectl && \
     mv kubectl /usr/local/bin/
 
 # Install Helm
-ARG HELM_VERSION=v2.15.2
-RUN curl -LO "https://kubernetes-helm.storage.googleapis.com/helm-$HELM_VERSION-linux-amd64.tar.gz" && \
+ARG HELM_VERSION=v3.0.0
+RUN curl -LO "https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz" && \
     mkdir -p "/usr/local/helm-$HELM_VERSION" && \
     tar -xzf "helm-$HELM_VERSION-linux-amd64.tar.gz" -C "/usr/local/helm-$HELM_VERSION" && \
     ln -s "/usr/local/helm-$HELM_VERSION/linux-amd64/helm" /usr/local/bin/helm && \
diff --git a/build.sh b/build.sh
index e4d94d9..fad94f7 100755
--- a/build.sh
+++ b/build.sh
@@ -18,7 +18,8 @@ set -o errexit
 set -o nounset
 set -o pipefail
 
-readonly SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
+SCRIPT_DIR=$(dirname -- "$(readlink -e "${BASH_SOURCE[0]}" || realpath "${BASH_SOURCE[0]}")")
+readonly SCRIPT_DIR
 
 show_help() {
     cat << EOF
@@ -71,7 +72,6 @@ main() {
 
     pushd "$SCRIPT_DIR" > /dev/null
 
-    go mod download
     go test ./...
     goreleaser "${goreleaser_args[@]}"
 
diff --git a/ct/cmd/docGen.go b/ct/cmd/docGen.go
index 03e6282..5b8a0d7 100644
--- a/ct/cmd/docGen.go
+++ b/ct/cmd/docGen.go
@@ -16,8 +16,6 @@ package cmd
 
 import (
 	"fmt"
-	"os"
-
 	"github.com/MakeNowJust/heredoc"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra/doc"
@@ -31,18 +29,18 @@ func newGenerateDocsCmd() *cobra.Command {
 			Generate documentation for all commands
 			to the 'docs' directory.`),
 		Hidden: true,
-		Run:    generateDocs,
+		RunE:   generateDocs,
 	}
 }
 
-func generateDocs(cmd *cobra.Command, args []string) {
+func generateDocs(cmd *cobra.Command, args []string) error {
 	fmt.Println("Generating docs...")
 
 	err := doc.GenMarkdownTree(NewRootCmd(), "doc")
 	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
+		return err
 	}
 
 	fmt.Println("Done.")
+	return nil
 }
diff --git a/ct/cmd/install.go b/ct/cmd/install.go
index 18555ad..a641056 100644
--- a/ct/cmd/install.go
+++ b/ct/cmd/install.go
@@ -16,8 +16,6 @@ package cmd
 
 import (
 	"fmt"
-	"os"
-
 	"github.com/MakeNowJust/heredoc"
 	"github.com/helm/chart-testing/v3/pkg/chart"
 	"github.com/helm/chart-testing/v3/pkg/config"
@@ -48,7 +46,7 @@ func newInstallCmd() *cobra.Command {
 			directory. The chart is installed and tested for each of these files.
 			If no custom values file is present, the chart is installed and
 			tested with defaults.`),
-		Run: install,
+		RunE: install,
 	}
 
 	flags := cmd.Flags()
@@ -80,26 +78,24 @@ func addInstallFlags(flags *flag.FlagSet) {
 		This is only used if namespace is specified`))
 }
 
-func install(cmd *cobra.Command, args []string) {
+func install(cmd *cobra.Command, args []string) error {
 	fmt.Println("Installing charts...")
 
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, true)
 	if err != nil {
-		fmt.Printf("Error loading configuration: %s\n", err)
-		os.Exit(1)
+		return fmt.Errorf("Error loading configuration: %s", err)
 	}
 
-	testing := chart.NewTesting(*configuration)
+	testing, err := chart.NewTesting(*configuration)
+	if err != nil {
+		fmt.Println(err)
+	}
 	results, err := testing.InstallCharts()
 	if err != nil {
-		fmt.Printf("Error installing charts: %s\n", err)
-	} else {
-		fmt.Println("All charts installed successfully")
+		return fmt.Errorf("Error installing charts: %s", err)
 	}
 
+	fmt.Println("All charts installed successfully")
 	testing.PrintResults(results)
-
-	if err != nil {
-		os.Exit(1)
-	}
+	return nil
 }
diff --git a/ct/cmd/lint.go b/ct/cmd/lint.go
index f6da796..8a832dd 100644
--- a/ct/cmd/lint.go
+++ b/ct/cmd/lint.go
@@ -16,8 +16,6 @@ package cmd
 
 import (
 	"fmt"
-	"os"
-
 	"github.com/MakeNowJust/heredoc"
 	"github.com/helm/chart-testing/v3/pkg/chart"
 	"github.com/helm/chart-testing/v3/pkg/config"
@@ -44,7 +42,7 @@ func newLintCmd() *cobra.Command {
 			'*-values.yaml' in a directory named 'ci' in the root of the chart's
 			directory. The chart is linted for each of these files. If no custom
 			values file is present, the chart is linted with defaults.`),
-		Run: lint,
+		RunE: lint,
 	}
 
 	flags := cmd.Flags()
@@ -72,26 +70,24 @@ func addLintFlags(flags *flag.FlagSet) {
 			Enable linting of 'Chart.yaml' and values files (default: true)`))
 }
 
-func lint(cmd *cobra.Command, args []string) {
+func lint(cmd *cobra.Command, args []string) error {
 	fmt.Println("Linting charts...")
 
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, true)
 	if err != nil {
-		fmt.Printf("Error loading configuration: %s\n", err)
-		os.Exit(1)
+		return fmt.Errorf("Error loading configuration: %s", err)
 	}
 
-	testing := chart.NewTesting(*configuration)
+	testing, err := chart.NewTesting(*configuration)
+	if err != nil {
+		return err
+	}
 	results, err := testing.LintCharts()
 	if err != nil {
-		fmt.Printf("Error linting charts: %s\n", err)
-	} else {
-		fmt.Println("All charts linted successfully")
+		return fmt.Errorf("Error linting charts: %s", err)
 	}
 
+	fmt.Println("All charts linted successfully")
 	testing.PrintResults(results)
-
-	if err != nil {
-		os.Exit(1)
-	}
+	return nil
 }
diff --git a/ct/cmd/lintAndInstall.go b/ct/cmd/lintAndInstall.go
index 42cba13..8b0e30a 100644
--- a/ct/cmd/lintAndInstall.go
+++ b/ct/cmd/lintAndInstall.go
@@ -16,8 +16,6 @@ package cmd
 
 import (
 	"fmt"
-	"os"
-
 	"github.com/helm/chart-testing/v3/pkg/chart"
 	"github.com/helm/chart-testing/v3/pkg/config"
 
@@ -30,7 +28,7 @@ func newLintAndInstallCmd() *cobra.Command {
 		Aliases: []string{"li"},
 		Short:   "Lint, install, and test a chart",
 		Long:    "Combines 'lint' and 'install' commands.",
-		Run:     lintAndInstall,
+		RunE:    lintAndInstall,
 	}
 
 	flags := cmd.Flags()
@@ -40,26 +38,24 @@ func newLintAndInstallCmd() *cobra.Command {
 	return cmd
 }
 
-func lintAndInstall(cmd *cobra.Command, args []string) {
+func lintAndInstall(cmd *cobra.Command, args []string) error {
 	fmt.Println("Linting and installing charts...")
 
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, true)
 	if err != nil {
-		fmt.Printf("Error loading configuration: %s\n", err)
-		os.Exit(1)
+		return fmt.Errorf("Error loading configuration: %s", err)
 	}
 
-	testing := chart.NewTesting(*configuration)
+	testing, err := chart.NewTesting(*configuration)
+	if err != nil {
+		return err
+	}
 	results, err := testing.LintAndInstallCharts()
 	if err != nil {
-		fmt.Printf("Error linting and installing charts: %s\n", err)
-	} else {
-		fmt.Println("All charts linted and installed successfully")
+		return fmt.Errorf("Error linting and installing charts: %s", err)
 	}
 
+	fmt.Println("All charts linted and installed successfully")
 	testing.PrintResults(results)
-
-	if err != nil {
-		os.Exit(1)
-	}
+	return nil
 }
diff --git a/ct/cmd/listChanged.go b/ct/cmd/listChanged.go
index 84d19b1..2348009 100644
--- a/ct/cmd/listChanged.go
+++ b/ct/cmd/listChanged.go
@@ -16,8 +16,6 @@ package cmd
 
 import (
 	"fmt"
-	"os"
-
 	"github.com/MakeNowJust/heredoc"
 
 	"github.com/helm/chart-testing/v3/pkg/chart"
@@ -33,7 +31,7 @@ func newListChangedCmd() *cobra.Command {
 		Long: heredoc.Doc(`
 			"List changed charts based on configured charts directories,
 			"remote, and target branch`),
-		Run: listChanged,
+		RunE: listChanged,
 	}
 
 	flags := cmd.Flags()
@@ -41,20 +39,23 @@ func newListChangedCmd() *cobra.Command {
 	return cmd
 }
 
-func listChanged(cmd *cobra.Command, args []string) {
+func listChanged(cmd *cobra.Command, args []string) error {
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, false)
 	if err != nil {
-		fmt.Printf("Error loading configuration: %s\n", err)
-		os.Exit(1)
+		return fmt.Errorf("Error loading configuration: %s", err)
 	}
 
-	testing := chart.NewTesting(*configuration)
+	testing, err := chart.NewTesting(*configuration)
+	if err != nil {
+		return err
+	}
 	chartDirs, err := testing.ComputeChangedChartDirectories()
 	if err != nil {
-		os.Exit(1)
+		return err
 	}
 
 	for _, dir := range chartDirs {
 		fmt.Println(dir)
 	}
+	return nil
 }
diff --git a/go.sum b/go.sum
index ad7147a..05a54c6 100644
--- a/go.sum
+++ b/go.sum
@@ -59,6 +59,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -66,4 +67,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go
index 04f1ce0..7380350 100644
--- a/pkg/chart/chart.go
+++ b/pkg/chart/chart.go
@@ -16,6 +16,7 @@ package chart
 
 import (
 	"fmt"
+	"github.com/Masterminds/semver"
 	"io/ioutil"
 	"path/filepath"
 	"strings"
@@ -79,14 +80,14 @@ type Git interface {
 //
 // DeleteRelease purges the specified Helm release.
 type Helm interface {
-	Init() error
 	AddRepo(name string, url string, extraArgs []string) error
 	BuildDependencies(chart string) error
 	LintWithValues(chart string, valuesFile string) error
 	InstallWithValues(chart string, valuesFile string, namespace string, release string) error
-	Upgrade(chart string, release string) error
-	Test(release string, cleanup bool) error
-	DeleteRelease(release string)
+	Upgrade(chart string, namespace string, release string) error
+	Test(namespace string, release string) error
+	DeleteRelease(namespace string, release string)
+	Version() (string, error)
 }
 
 // Kubectl is the interface that wraps kubectl operations
@@ -109,6 +110,7 @@ type Helm interface {
 //
 // GetContainers gets all containers of pod
 type Kubectl interface {
+	CreateNamespace(namespace string) error
 	DeleteNamespace(namespace string)
 	WaitForDeployments(namespace string, selector string) error
 	GetPodsforDeployment(namespace string, deployment string) ([]string, error)
@@ -242,10 +244,11 @@ type TestResult struct {
 }
 
 // NewTesting creates a new Testing struct with the given config.
-func NewTesting(config config.Configuration) Testing {
+func NewTesting(config config.Configuration) (Testing, error) {
 	procExec := exec.NewProcessExecutor(config.Debug)
 	extraArgs := strings.Fields(config.HelmExtraArgs)
-	return Testing{
+
+	testing := Testing{
 		config:           config,
 		helm:             tool.NewHelm(procExec, extraArgs),
 		git:              tool.NewGit(procExec),
@@ -255,6 +258,21 @@ func NewTesting(config config.Configuration) Testing {
 		directoryLister:  util.DirectoryLister{},
 		chartUtils:       util.ChartUtils{},
 	}
+
+	versionString, err := testing.helm.Version()
+	if err != nil {
+		return testing, err
+	}
+
+	version, err := semver.NewVersion(versionString)
+	if err != nil {
+		return testing, err
+	}
+
+	if version.Major() < 3 {
+		return testing, fmt.Errorf("minimum required Helm version is v3.0.0; found: %s", version)
+	}
+	return testing, nil
 }
 
 // computePreviousRevisionPath converts any file or directory path to the same path in the
@@ -291,10 +309,6 @@ func (t *Testing) processCharts(action func(chart *Chart) TestResult) ([]TestRes
 	util.PrintDelimiterLine("-")
 	fmt.Println()
 
-	if err := t.helm.Init(); err != nil {
-		return nil, errors.Wrap(err, "Error initializing Helm")
-	}
-
 	repoArgs := map[string][]string{}
 
 	for _, repo := range t.config.HelmRepoExtraArgs {
@@ -529,10 +543,13 @@ func (t *Testing) doInstall(chart *Chart) error {
 			namespace, release, releaseSelector, cleanup := t.generateInstallConfig(chart)
 			defer cleanup()
 
+			if err := t.kubectl.CreateNamespace(namespace); err != nil {
+				return err
+			}
 			if err := t.helm.InstallWithValues(chart.Path(), valuesFile, namespace, release); err != nil {
 				return err
 			}
-			return t.testRelease(release, namespace, releaseSelector, false)
+			return t.testRelease(namespace, release, releaseSelector)
 		}
 
 		if err := fun(); err != nil {
@@ -564,6 +581,9 @@ func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) er
 			namespace, release, releaseSelector, cleanup := t.generateInstallConfig(oldChart)
 			defer cleanup()
 
+			if err := t.kubectl.CreateNamespace(namespace); err != nil {
+				return err
+			}
 			// Install previous version of chart. If installation fails, ignore this release.
 			if err := t.helm.InstallWithValues(oldChart.Path(), valuesFile, namespace, release); err != nil {
 				if oldChartMustPass {
@@ -572,7 +592,7 @@ func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) er
 				fmt.Println(errors.Wrap(err, fmt.Sprintf("Upgrade testing for release '%s' skipped because of previous revision installation error", release)))
 				return nil
 			}
-			if err := t.testRelease(release, namespace, releaseSelector, true); err != nil {
+			if err := t.testRelease(namespace, release, releaseSelector); err != nil {
 				if oldChartMustPass {
 					return err
 				}
@@ -580,11 +600,11 @@ func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) er
 				return nil
 			}
 
-			if err := t.helm.Upgrade(oldChart.Path(), release); err != nil {
+			if err := t.helm.Upgrade(oldChart.Path(), namespace, release); err != nil {
 				return err
 			}
 
-			return t.testRelease(release, namespace, releaseSelector, false)
+			return t.testRelease(namespace, release, releaseSelector)
 		}
 
 		if err := fun(); err != nil {
@@ -595,11 +615,11 @@ func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) er
 	return nil
 }
 
-func (t *Testing) testRelease(release, namespace, releaseSelector string, cleanupHelmTests bool) error {
+func (t *Testing) testRelease(namespace, release, releaseSelector string) error {
 	if err := t.kubectl.WaitForDeployments(namespace, releaseSelector); err != nil {
 		return err
 	}
-	if err := t.helm.Test(release, cleanupHelmTests); err != nil {
+	if err := t.helm.Test(namespace, release); err != nil {
 		return err
 	}
 	return nil
@@ -612,13 +632,13 @@ func (t *Testing) generateInstallConfig(chart *Chart) (namespace, release, relea
 		releaseSelector = fmt.Sprintf("%s=%s", t.config.ReleaseLabel, release)
 		cleanup = func() {
 			t.PrintEventsPodDetailsAndLogs(namespace, releaseSelector)
-			t.helm.DeleteRelease(release)
+			t.helm.DeleteRelease(namespace, release)
 		}
 	} else {
 		release, namespace = chart.CreateInstallParams(t.config.BuildId)
 		cleanup = func() {
 			t.PrintEventsPodDetailsAndLogs(namespace, releaseSelector)
-			t.helm.DeleteRelease(release)
+			t.helm.DeleteRelease(namespace, release)
 			t.kubectl.DeleteNamespace(namespace)
 		}
 	}
diff --git a/pkg/tool/helm.go b/pkg/tool/helm.go
index f76a0a7..bcb01d6 100644
--- a/pkg/tool/helm.go
+++ b/pkg/tool/helm.go
@@ -32,10 +32,6 @@ func NewHelm(exec exec.ProcessExecutor, extraArgs []string) Helm {
 	}
 }
 
-func (h Helm) Init() error {
-	return h.exec.RunProcess("helm", "init", "--client-only")
-}
-
 func (h Helm) AddRepo(name string, url string, extraArgs []string) error {
 	return h.exec.RunProcess("helm", "repo", "add", name, url, extraArgs)
 }
@@ -59,7 +55,7 @@ func (h Helm) InstallWithValues(chart string, valuesFile string, namespace strin
 		values = []string{"--values", valuesFile}
 	}
 
-	if err := h.exec.RunProcess("helm", "install", chart, "--name", release, "--namespace", namespace,
+	if err := h.exec.RunProcess("helm", "install", release, chart, "--namespace", namespace,
 		"--wait", values, h.extraArgs); err != nil {
 		return err
 	}
@@ -67,22 +63,26 @@ func (h Helm) InstallWithValues(chart string, valuesFile string, namespace strin
 	return nil
 }
 
-func (h Helm) Upgrade(chart string, release string) error {
-	if err := h.exec.RunProcess("helm", "upgrade", release, chart, "--reuse-values",
-		"--wait", h.extraArgs); err != nil {
+func (h Helm) Upgrade(chart string, namespace string, release string) error {
+	if err := h.exec.RunProcess("helm", "upgrade", release, chart, "--namespace", namespace,
+		"--reuse-values", "--wait", h.extraArgs); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-func (h Helm) Test(release string, cleanup bool) error {
-	return h.exec.RunProcess("helm", "test", release, h.extraArgs, fmt.Sprintf("--cleanup=%t", cleanup))
+func (h Helm) Test(namespace string, release string) error {
+	return h.exec.RunProcess("helm", "test", release, "--namespace", namespace, h.extraArgs)
 }
 
-func (h Helm) DeleteRelease(release string) {
+func (h Helm) DeleteRelease(namespace string, release string) {
 	fmt.Printf("Deleting release '%s'...\n", release)
-	if err := h.exec.RunProcess("helm", "delete", "--purge", release, h.extraArgs); err != nil {
+	if err := h.exec.RunProcess("helm", "uninstall", release, "--namespace", namespace, h.extraArgs); err != nil {
 		fmt.Println("Error deleting Helm release:", err)
 	}
 }
+
+func (h Helm) Version() (string, error) {
+	return h.exec.RunProcessAndCaptureOutput("helm", "version", "--short")
+}
diff --git a/pkg/tool/kubectl.go b/pkg/tool/kubectl.go
index 609d347..13fa70d 100644
--- a/pkg/tool/kubectl.go
+++ b/pkg/tool/kubectl.go
@@ -23,6 +23,12 @@ func NewKubectl(exec exec.ProcessExecutor) Kubectl {
 	}
 }
 
+// CreateNamespace creates a new namespace with the given name.
+func (k Kubectl) CreateNamespace(namespace string) error {
+	fmt.Printf("Creating namespace '%s'...\n", namespace)
+	return k.exec.RunProcess("kubectl", "create", "namespace", namespace)
+}
+
 // DeleteNamespace deletes the specified namespace. If the namespace does not terminate within 120s, pods running in the
 // namespace and, eventually, the namespace itself are force-deleted.
 func (k Kubectl) DeleteNamespace(namespace string) {


Test patch:
diff --git a/e2e-kind.sh b/e2e-kind.sh
index 873d372..086dc39 100755
--- a/e2e-kind.sh
+++ b/e2e-kind.sh
@@ -4,8 +4,11 @@ set -o errexit
 set -o nounset
 set -o pipefail
 
-readonly CLUSTER_NAME=chart-testing
-readonly K8S_VERSION=v1.15.3
+CLUSTER_NAME=chart-testing
+readonly CLUSTER_NAME
+
+K8S_VERSION=v1.15.3
+readonly K8S_VERSION
 
 create_kind_cluster() {
     kind create cluster --name "$CLUSTER_NAME" --image "kindest/node:$K8S_VERSION" --wait 60s
@@ -22,14 +25,6 @@ create_kind_cluster() {
     echo
 }
 
-install_tiller() {
-    echo 'Installing Tiller...'
-    kubectl --namespace kube-system --output yaml create serviceaccount tiller --dry-run | kubectl apply -f -
-    kubectl create --output yaml clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller --dry-run | kubectl apply -f -
-    helm init --service-account tiller --upgrade --wait
-    echo
-}
-
 install_local-path-provisioner() {
     # kind doesn't support Dynamic PVC provisioning yet, this is one way to get it working
     # https://github.com/rancher/local-path-provisioner
@@ -57,7 +52,6 @@ main() {
 
     create_kind_cluster
     install_local-path-provisioner
-    install_tiller
     test_e2e
 }
 
diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go
index 8d6847f..13a1196 100644
--- a/pkg/chart/chart_test.go
+++ b/pkg/chart/chart_test.go
@@ -91,20 +91,23 @@ func (l *fakeLinter) Yamale(yamlFile, schemaFile string) error {
 
 type fakeHelm struct{}
 
-func (h fakeHelm) Init() error                                          { return nil }
 func (h fakeHelm) AddRepo(name, url string, extraArgs []string) error   { return nil }
 func (h fakeHelm) BuildDependencies(chart string) error                 { return nil }
 func (h fakeHelm) LintWithValues(chart string, valuesFile string) error { return nil }
 func (h fakeHelm) InstallWithValues(chart string, valuesFile string, namespace string, release string) error {
 	return nil
 }
-func (h fakeHelm) Upgrade(chart string, release string) error {
+func (h fakeHelm) Upgrade(chart string, namespace string, release string) error {
 	return nil
 }
-func (h fakeHelm) Test(release string, cleanup bool) error {
+func (h fakeHelm) Test(namespace string, release string) error {
 	return nil
 }
-func (h fakeHelm) DeleteRelease(release string) {}
+func (h fakeHelm) DeleteRelease(namespace string, release string) {}
+
+func (h fakeHelm) Version() (string, error) {
+	return "v3.0.0", nil
+}
 
 var ct Testing
 
diff --git a/pkg/chart/integration_test.go b/pkg/chart/integration_test.go
index 163774b..46d0564 100644
--- a/pkg/chart/integration_test.go
+++ b/pkg/chart/integration_test.go
@@ -57,7 +57,7 @@ func TestInstallChart(t *testing.T) {
 			"install only in custom namespace",
 			config.Configuration{
 				Debug:        true,
-				Namespace:    "default",
+				Namespace:    "foobar",
 				ReleaseLabel: "app.kubernetes.io/instance",
 			},
 			"test_charts/must-pass-upgrade-install",