diff --git a/.circleci/config.yml b/.circleci/config.yml index 14b84dfe..af59bd48 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 b96488a2..0a16563d 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 e4d94d9f..fad94f7e 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 03e62821..5b8a0d79 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 18555adf..a641056d 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 f6da796f..8a832dde 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 42cba135..8b0e30ab 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 84d19b1a..23480090 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/e2e-kind.sh b/e2e-kind.sh index 873d3727..086dc391 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/go.sum b/go.sum index ad7147ae..05a54c68 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 04f1ce09..7380350e 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/chart/chart_test.go b/pkg/chart/chart_test.go index 8d6847f7..13a1196b 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 163774ba..46d05646 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", diff --git a/pkg/tool/helm.go b/pkg/tool/helm.go index f76a0a70..bcb01d6c 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 609d347f..13fa70d6 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) {