Repository: grafana/tanka
Pull request #839 (2023-04-25 10:32:39)

Bump github.com/google/go-cmp from 0.5.8 to 0.5.9
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.8 to 0.5.9.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/google/go-cmp/releases">github.com/google/go-cmp's releases</a>.</em></p>
<blockquote>
<h2>v0.5.9</h2>
<p>Reporter changes:</p>
<ul>
<li>(<a href="https://redirect.github.com/google/go-cmp/issues/299">#299</a>) Adjust heuristic for line-based versus byte-based diffing</li>
<li>(<a href="https://redirect.github.com/google/go-cmp/issues/306">#306</a>) Use <code>value.TypeString</code> in <code>PathStep.String</code></li>
</ul>
<p>Code cleanup changes:</p>
<ul>
<li>(<a href="https://redirect.github.com/google/go-cmp/issues/297">#297</a>) Use <code>reflect.Value.IsZero</code></li>
<li>(<a href="https://redirect.github.com/google/go-cmp/issues/304">#304</a>) Format with Go 1.19 formatter</li>
<li>(<a href="https://redirect.github.com/google/go-cmp/issues/300">#300</a> )Fix typo in Result documentation</li>
<li>(<a href="https://redirect.github.com/google/go-cmp/issues/302">#302</a>) Pre-declare global type variables</li>
<li>(<a href="https://redirect.github.com/google/go-cmp/issues/309">#309</a>) Run tests on Go 1.19</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/google/go-cmp/commit/a97318bf6562f2ed2632c5f985db51b1bc5bdcd0"><code>a97318b</code></a> Adjust heuristic for line-based versus byte-based diffing (<a href="https://redirect.github.com/google/go-cmp/issues/299">#299</a>)</li>
<li><a href="https://github.com/google/go-cmp/commit/377d28384c85781079e04aab3937170479da8cd6"><code>377d283</code></a> Run tests on Go 1.19 (<a href="https://redirect.github.com/google/go-cmp/issues/309">#309</a>)</li>
<li><a href="https://github.com/google/go-cmp/commit/6606d4d51e3239f038565f525940ac6043aff53e"><code>6606d4d</code></a> Use value.TypeString in PathStep.String (<a href="https://redirect.github.com/google/go-cmp/issues/306">#306</a>)</li>
<li><a href="https://github.com/google/go-cmp/commit/f36a68d19a9bca43e070954ab9170a8305662d15"><code>f36a68d</code></a> Pre-declare global type variables (<a href="https://redirect.github.com/google/go-cmp/issues/302">#302</a>)</li>
<li><a href="https://github.com/google/go-cmp/commit/5dac6aa44b75666a956f67df1b5bd4e2e044e1f8"><code>5dac6aa</code></a> Fix typo in Result documentation (<a href="https://redirect.github.com/google/go-cmp/issues/300">#300</a>)</li>
<li><a href="https://github.com/google/go-cmp/commit/14ad8a02f30ba66e7e19f9814e69daab44219cb8"><code>14ad8a0</code></a> Format with Go 1.19 formatter (<a href="https://redirect.github.com/google/go-cmp/issues/304">#304</a>)</li>
<li><a href="https://github.com/google/go-cmp/commit/a53d7e09b000ee6e0ca9f2676820299b5de8e89f"><code>a53d7e0</code></a> Use reflect.Value.IsZero (<a href="https://redirect.github.com/google/go-cmp/issues/297">#297</a>)</li>
<li>See full diff in <a href="https://github.com/google/go-cmp/compare/v0.5.8...v0.5.9">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/google/go-cmp&package-manager=go_modules&previous-version=0.5.8&new-version=0.5.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.drone/drone.jsonnet b/.drone/drone.jsonnet
index e736a777..736be1fd 100644
--- a/.drone/drone.jsonnet
+++ b/.drone/drone.jsonnet
@@ -1,6 +1,6 @@
 local vault = import 'vault.libsonnet';
 
-local golang = 'golang:1.19';
+local golang = 'golang:1.20';
 
 local volumes = [{ name: 'gopath', temp: {} }];
 local mounts = [{ name: 'gopath', path: '/go' }];
@@ -76,6 +76,23 @@ local docker(arch, depends_on=[]) =
     ],
   } + constraints.pullRequest + constraints.mainPush,
 
+  pipeline('benchmark against main') {
+    node: {
+      // To ensure that benchmarks are accurate, run this build on a node that doesn't do parallel builds.
+      type: 'no-parallel',
+    },
+    steps: [
+      go('benchmark', [
+        'go test -bench=. -benchmem -count=6 -run=^$ ./... | tee bench-pr.txt',
+        'git fetch origin main',
+        'git checkout main',
+        'go test -bench=. -benchmem -count=6 -run=^$ ./... | tee bench-main.txt',
+        'go install golang.org/x/perf/cmd/...@latest',
+        'benchstat bench-main.txt bench-pr.txt',
+      ]),
+    ],
+  } + constraints.pullRequest,
+
   pipeline('release') {
     steps: [
       go('fetch-tags', ['git fetch origin --tags']),
@@ -105,7 +122,7 @@ local docker(arch, depends_on=[]) =
       ]),
       {
         name: 'manifest',
-        image: 'plugins/manifest',
+        image: 'plugins/manifest:1.4.0',
         settings: {
           ignore_missing: true,
           spec: '.drone/docker-manifest.tmpl',
@@ -124,7 +141,7 @@ local docker(arch, depends_on=[]) =
   pipeline('manifest') {
     steps: [{
       name: 'manifest',
-      image: 'plugins/manifest',
+      image: 'plugins/manifest:1.4.0',
       settings: {
         auto_tag: true,
         ignore_missing: true,
diff --git a/.drone/drone.yml b/.drone/drone.yml
index bb0b0e73..c086a325 100644
--- a/.drone/drone.yml
+++ b/.drone/drone.yml
@@ -4,7 +4,7 @@ name: check
 steps:
 - commands:
   - go mod download
-  image: golang:1.19
+  image: golang:1.20
   name: download
   volumes:
   - name: gopath
@@ -17,7 +17,7 @@ steps:
   - fi
   - cp linux-amd64/helm /usr/local/bin/helm
   - make lint
-  image: golang:1.19
+  image: golang:1.20
   name: lint
   volumes:
   - name: gopath
@@ -30,7 +30,7 @@ steps:
   - fi
   - cp linux-amd64/helm /usr/local/bin/helm
   - make test
-  image: golang:1.19
+  image: golang:1.20
   name: test
   volumes:
   - name: gopath
@@ -43,7 +43,7 @@ steps:
   - fi
   - cp linux-amd64/helm /usr/local/bin/helm
   - make cross
-  image: golang:1.19
+  image: golang:1.20
   name: build
   volumes:
   - name: gopath
@@ -56,6 +56,30 @@ volumes:
 - name: gopath
   temp: {}
 ---
+kind: pipeline
+name: benchmark against main
+node:
+  type: no-parallel
+steps:
+- commands:
+  - go test -bench=. -benchmem -count=6 -run=^$ ./... | tee bench-pr.txt
+  - git fetch origin main
+  - git checkout main
+  - go test -bench=. -benchmem -count=6 -run=^$ ./... | tee bench-main.txt
+  - go install golang.org/x/perf/cmd/...@latest
+  - benchstat bench-main.txt bench-pr.txt
+  image: golang:1.20
+  name: benchmark
+  volumes:
+  - name: gopath
+    path: /go
+trigger:
+  ref:
+  - refs/pull/*/head
+volumes:
+- name: gopath
+  temp: {}
+---
 depends_on:
 - check
 kind: pipeline
@@ -63,7 +87,7 @@ name: release
 steps:
 - commands:
   - git fetch origin --tags
-  image: golang:1.19
+  image: golang:1.20
   name: fetch-tags
   volumes:
   - name: gopath
@@ -76,7 +100,7 @@ steps:
   - fi
   - cp linux-amd64/helm /usr/local/bin/helm
   - make cross
-  image: golang:1.19
+  image: golang:1.20
   name: cross
   volumes:
   - name: gopath
@@ -127,7 +151,7 @@ platform:
 steps:
 - commands:
   - git fetch origin --tags
-  image: golang:1.19
+  image: golang:1.20
   name: fetch-tags
   volumes:
   - name: gopath
@@ -140,7 +164,7 @@ steps:
   - fi
   - cp linux-amd64/helm /usr/local/bin/helm
   - make static
-  image: golang:1.19
+  image: golang:1.20
   name: static
   volumes:
   - name: gopath
@@ -173,7 +197,7 @@ platform:
 steps:
 - commands:
   - git fetch origin --tags
-  image: golang:1.19
+  image: golang:1.20
   name: fetch-tags
   volumes:
   - name: gopath
@@ -186,7 +210,7 @@ steps:
   - fi
   - cp linux-amd64/helm /usr/local/bin/helm
   - make static
-  image: golang:1.19
+  image: golang:1.20
   name: static
   volumes:
   - name: gopath
@@ -218,12 +242,12 @@ steps:
 - commands:
   - git fetch origin --tags
   - echo "main-$(git describe --tags)" > .tags
-  image: golang:1.19
+  image: golang:1.20
   name: fetch-tags
   volumes:
   - name: gopath
     path: /go
-- image: plugins/manifest
+- image: plugins/manifest:1.4.0
   name: manifest
   settings:
     ignore_missing: true
@@ -245,7 +269,7 @@ depends_on:
 kind: pipeline
 name: manifest
 steps:
-- image: plugins/manifest
+- image: plugins/manifest:1.4.0
   name: manifest
   settings:
     auto_tag: true
@@ -282,6 +306,6 @@ kind: secret
 name: dockerhub_password
 ---
 kind: signature
-hmac: adbd6c28d92efbf2968766102c5abe0bbad5a4af1c76e84ada59b2c5cf9c958d
+hmac: 060bcec4c9fbea042e7b70478a9ea0255de0f1e245f3dd031b02d7ca83ef5067
 
 ...
diff --git a/Dockerfile b/Dockerfile
index 63138b4a..0653f4a0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 # download kubectl
-FROM golang:1.20.3-alpine as kubectl
+FROM golang:1.20.4-alpine as kubectl
 RUN apk add --no-cache curl
 RUN export VERSION=$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt) &&\
     export OS=$(go env GOOS) && \
@@ -8,7 +8,7 @@ RUN export VERSION=$(curl -s https://storage.googleapis.com/kubernetes-release/r
     chmod +x /usr/local/bin/kubectl
 
 # build jsonnet-bundler
-FROM golang:1.20.3-alpine as jb
+FROM golang:1.20.4-alpine as jb
 WORKDIR /tmp
 RUN apk add --no-cache git make bash &&\
     git clone https://github.com/jsonnet-bundler/jsonnet-bundler &&\
@@ -17,7 +17,7 @@ RUN apk add --no-cache git make bash &&\
     make static &&\
     mv _output/jb /usr/local/bin/jb
 
-FROM golang:1.20.3-alpine as helm
+FROM golang:1.20.4-alpine as helm
 WORKDIR /tmp/helm
 RUN apk add --no-cache jq curl
 RUN export TAG=$(curl --silent "https://api.github.com/repos/helm/helm/releases/latest" | jq -r .tag_name) &&\
@@ -26,7 +26,7 @@ RUN export TAG=$(curl --silent "https://api.github.com/repos/helm/helm/releases/
     curl -SL "https://get.helm.sh/helm-${TAG}-${OS}-${ARCH}.tar.gz" > helm.tgz && \
     tar -xvf helm.tgz --strip-components=1
 
-FROM golang:1.20.3-alpine as kustomize
+FROM golang:1.20.4-alpine as kustomize
 WORKDIR /tmp/kustomize
 RUN apk add --no-cache jq curl
 # Get the latest version of kustomize
diff --git a/Makefile b/Makefile
index 09888112..4dc47767 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ GOX := $(BIN_DIR)/gox
 GOLINTER := $(GOPATH)/bin/golangci-lint
 
 $(GOLINTER):
-	go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.49.0
+	go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2
 
 lint: $(GOLINTER)
 	$(GOLINTER) run
diff --git a/cmd/tk/env.go b/cmd/tk/env.go
index 8264a153..dba9aba9 100644
--- a/cmd/tk/env.go
+++ b/cmd/tk/env.go
@@ -114,11 +114,7 @@ func envSetCmd() *cli.Command {
 			return err
 		}
 
-		if err := writeJSON(cfg, filepath.Join(path, "spec.json")); err != nil {
-			return err
-		}
-
-		return nil
+		return writeJSON(cfg, filepath.Join(path, "spec.json"))
 	}
 	return cmd
 }
@@ -149,11 +145,7 @@ func envAddCmd() *cli.Command {
 			}
 		}
 
-		if err := addEnv(args[0], cfg, *inline); err != nil {
-			return err
-		}
-
-		return nil
+		return addEnv(args[0], cfg, *inline)
 	}
 	return cmd
 }
diff --git a/cmd/tk/jsonnet.go b/cmd/tk/jsonnet.go
index 5079416d..e4d2d4fb 100644
--- a/cmd/tk/jsonnet.go
+++ b/cmd/tk/jsonnet.go
@@ -38,11 +38,7 @@ func evalCmd() *cli.Command {
 			return err
 		}
 
-		if err := pageln(string(out)); err != nil {
-			return err
-		}
-
-		return nil
+		return pageln(string(out))
 	}
 
 	return cmd
diff --git a/cmd/tk/util.go b/cmd/tk/util.go
index 53cb1451..d56f919b 100644
--- a/cmd/tk/util.go
+++ b/cmd/tk/util.go
@@ -33,9 +33,7 @@ func fPageln(r io.Reader) error {
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
 
-		if err := cmd.Run(); err != nil {
-			// Fallthrough on failure so that the contents of the reader are copied to stdout.
-		} else {
+		if err := cmd.Run(); err == nil {
 			return nil
 		}
 	}
diff --git a/docs/docs/helm.mdx b/docs/docs/helm.mdx
index 20546564..1881de3a 100644
--- a/docs/docs/helm.mdx
+++ b/docs/docs/helm.mdx
@@ -79,8 +79,6 @@ The following options control how the command is invoked:
     values: {
       persistence: { enabled: true }
     },
-    // Equivalent to: --include-crds; only relevant for Helm v3+.
-    includeCrds: true,
     // Equivalent to: --api-versions v1 --api-versions apps/v1
     apiVersions: ['v1', 'apps/v1']
     // Equivalent to: --kube-version v1.20.0
@@ -90,6 +88,17 @@ The following options control how the command is invoked:
 }
 ```
 
+Tanka will install Custom Resource Definitions (CRDs) automatically, if the
+Helm Chart requires them and ships them in `crds/`. This is equivalent to `helm
+template --include-crds`. This can be disabled using `includeCrds: false`:
+
+```jsonnet
+{
+  grafana: helm.template("grafana", "./charts/grafana", {
+    includeCrds: false
+  })
+}
+```
 
 ## Vendoring Helm Charts
 
diff --git a/go.mod b/go.mod
index df3592f4..27d702a3 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
 	github.com/fatih/structs v1.1.0
 	github.com/go-clix/cli v0.2.0
 	github.com/gobwas/glob v0.2.3
-	github.com/google/go-cmp v0.5.8
+	github.com/google/go-cmp v0.5.9
 	github.com/google/go-jsonnet v0.20.0
 	github.com/karrick/godirwalk v1.16.1
 	github.com/pkg/errors v0.9.1
@@ -19,7 +19,7 @@ require (
 	github.com/stretchr/objx v0.3.0
 	github.com/stretchr/testify v1.7.1
 	github.com/thoas/go-funk v0.9.2
-	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
+	golang.org/x/term v0.8.0
 	golang.org/x/text v0.3.7
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
@@ -46,6 +46,6 @@ require (
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/spf13/cast v1.4.1 // indirect
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
-	golang.org/x/sys v0.1.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
 	k8s.io/klog/v2 v2.40.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 406627be..3c3ad059 100644
--- a/go.sum
+++ b/go.sum
@@ -66,8 +66,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
 github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -225,11 +225,11 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
diff --git a/pkg/helm/charts.go b/pkg/helm/charts.go
index 18453cb1..da8f015a 100644
--- a/pkg/helm/charts.go
+++ b/pkg/helm/charts.go
@@ -140,11 +140,11 @@ func (c Charts) Vendor(prune bool) error {
 			if chartYAML.Version.String() == r.Version.String() {
 				log.Info().Msgf("%s exists", r)
 				continue
-			} else {
-				log.Info().Msgf("Removing %s", r)
-				if err := os.RemoveAll(chartPath); err != nil {
-					return err
-				}
+			}
+
+			log.Info().Msgf("Removing %s", r)
+			if err := os.RemoveAll(chartPath); err != nil {
+				return err
 			}
 		} else if chartDirExists {
 			// If the chart dir exists but the manifest doesn't, we'll clear it out and re-download the chart
diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go
index b68f8a38..edaa7631 100644
--- a/pkg/helm/helm.go
+++ b/pkg/helm/helm.go
@@ -21,6 +21,9 @@ type Helm interface {
 
 	// Template returns the individual resources of a Helm Chart
 	Template(name, chart string, opts TemplateOpts) (manifest.List, error)
+
+	// ChartExists checks if a chart exists in the provided calledFromPath
+	ChartExists(chart string, opts *JsonnetOpts) (string, error)
 }
 
 // PullOpts are additional, non-required options for Helm.Pull
@@ -103,6 +106,17 @@ func (e ExecHelm) RepoUpdate(opts Opts) error {
 	return nil
 }
 
+func (e ExecHelm) ChartExists(chart string, opts *JsonnetOpts) (string, error) {
+	// resolve the Chart relative to the caller
+	callerDir := filepath.Dir(opts.CalledFrom)
+	chart = filepath.Join(callerDir, chart)
+	if _, err := os.Stat(chart); err != nil {
+		return "", fmt.Errorf("helmTemplate: Failed to find a chart at '%s': %s. See https://tanka.dev/helm#failed-to-find-chart", chart, err)
+	}
+
+	return chart, nil
+}
+
 // cmd returns a prepared exec.Cmd to use the `helm` binary
 func (e ExecHelm) cmd(action string, args ...string) *exec.Cmd {
 	argv := []string{action}
diff --git a/pkg/helm/jsonnet.go b/pkg/helm/jsonnet.go
index e0d7111e..f7cfe156 100644
--- a/pkg/helm/jsonnet.go
+++ b/pkg/helm/jsonnet.go
@@ -3,8 +3,6 @@ package helm
 import (
 	"encoding/json"
 	"fmt"
-	"os"
-	"path/filepath"
 
 	"github.com/google/go-jsonnet"
 	"github.com/google/go-jsonnet/ast"
@@ -54,10 +52,8 @@ func NativeFunc(h Helm) *jsonnet.NativeFunction {
 				return "", err
 			}
 
-			// resolve the Chart relative to the caller
-			callerDir := filepath.Dir(opts.CalledFrom)
-			chart := filepath.Join(callerDir, chartpath)
-			if _, err := os.Stat(chart); err != nil {
+			chart, err := h.ChartExists(chartpath, opts)
+			if err != nil {
 				return nil, fmt.Errorf("helmTemplate: Failed to find a chart at '%s': %s. See https://tanka.dev/helm#failed-to-find-chart", chart, err)
 			}
 
@@ -83,7 +79,16 @@ func parseOpts(data interface{}) (*JsonnetOpts, error) {
 	if err != nil {
 		return nil, err
 	}
-	var opts JsonnetOpts
+
+	// default IncludeCRDs to true, as this is the default in the `helm install`
+	// command. Needs to be specified here because the zero value of bool is
+	// false.
+	opts := JsonnetOpts{
+		TemplateOpts: TemplateOpts{
+			IncludeCRDs: true,
+		},
+	}
+
 	if err := json.Unmarshal(c, &opts); err != nil {
 		return nil, err
 	}
diff --git a/pkg/helm/template.go b/pkg/helm/template.go
index 7aa70439..f395da0e 100644
--- a/pkg/helm/template.go
+++ b/pkg/helm/template.go
@@ -10,13 +10,18 @@ import (
 	yaml "gopkg.in/yaml.v3"
 )
 
-// Template expands a Helm Chart into a regular manifest.List using the `helm
-// template` command
-func (e ExecHelm) Template(name, chart string, opts TemplateOpts) (manifest.List, error) {
+func (e ExecHelm) templateCommandArgs(name, chart string, opts TemplateOpts) []string {
 	args := []string{name, chart,
 		"--values", "-", // values from stdin
 	}
 	args = append(args, opts.Flags()...)
+	return args
+}
+
+// Template expands a Helm Chart into a regular manifest.List using the `helm
+// template` command
+func (e ExecHelm) Template(name, chart string, opts TemplateOpts) (manifest.List, error) {
+	args := e.templateCommandArgs(name, chart, opts)
 
 	cmd := e.cmd("template", args...)
 	var buf bytes.Buffer
diff --git a/pkg/jsonnet/importer.go b/pkg/jsonnet/importer.go
index 54e370ec..c8b45113 100644
--- a/pkg/jsonnet/importer.go
+++ b/pkg/jsonnet/importer.go
@@ -68,7 +68,7 @@ func (i *ExtendedImporter) Import(importedFrom, importedPath string) (contents j
 }
 
 // tkLoader provides `tk.libsonnet` from memory (builtin)
-func tkLoader(importedFrom, importedPath string) (contents *jsonnet.Contents, foundAt string, err error) {
+func tkLoader(_, importedPath string) (contents *jsonnet.Contents, foundAt string, err error) {
 	if importedPath != "tk" {
 		return nil, "", nil
 	}
diff --git a/pkg/kubernetes/client/apply.go b/pkg/kubernetes/client/apply.go
index 40825efd..c36d46d2 100644
--- a/pkg/kubernetes/client/apply.go
+++ b/pkg/kubernetes/client/apply.go
@@ -12,7 +12,7 @@ import (
 )
 
 // Test-ability: isolate applyCtl to build and return exec.Cmd from ApplyOpts
-func (k Kubectl) applyCtl(data manifest.List, opts ApplyOpts) *exec.Cmd {
+func (k Kubectl) applyCtl(_ manifest.List, opts ApplyOpts) *exec.Cmd {
 	argv := []string{"-f", "-"}
 	serverSide := (opts.ApplyStrategy == "server")
 	if serverSide {
diff --git a/pkg/process/filter.go b/pkg/process/filter.go
index 8d359592..eaa5f1ee 100644
--- a/pkg/process/filter.go
+++ b/pkg/process/filter.go
@@ -116,7 +116,7 @@ type NegMatcher struct {
 	exp Matcher
 }
 
-func (n NegMatcher) MatchString(s string) bool {
+func (n NegMatcher) MatchString(_ string) bool {
 	return true
 }
 
diff --git a/pkg/tanka/inline.go b/pkg/tanka/inline.go
index e7f6a97b..71f5182a 100644
--- a/pkg/tanka/inline.go
+++ b/pkg/tanka/inline.go
@@ -37,12 +37,12 @@ func (i *InlineLoader) Load(path string, opts LoaderOpts) (*v1alpha1.Environment
 		names := make([]string, 0, len(envs))
 		for _, e := range envs {
 			// If there's a full match on the given name, use this environment
-			if name := e.Metadata().Name(); name == opts.Name {
+			name := e.Metadata().Name()
+			if name == opts.Name {
 				envs = manifest.List{e}
 				break
-			} else {
-				names = append(names, name)
 			}
+			names = append(names, name)
 		}
 		if len(envs) > 1 {
 			sort.Strings(names)
diff --git a/pkg/tanka/prune.go b/pkg/tanka/prune.go
index 5cf18f6a..a377f0a8 100644
--- a/pkg/tanka/prune.go
+++ b/pkg/tanka/prune.go
@@ -66,10 +66,10 @@ func Prune(baseDir string, opts PruneOpts) error {
 	}
 
 	// prompt for confirm
-	if opts.AutoApprove == AutoApproveAlways {
-		// Skip approval
-	} else if err := confirmPrompt("Pruning from", p.Env.Spec.Namespace, kube.Info()); err != nil {
-		return err
+	if opts.AutoApprove != AutoApproveAlways {
+		if err := confirmPrompt("Pruning from", p.Env.Spec.Namespace, kube.Info()); err != nil {
+			return err
+		}
 	}
 
 	// delete resources
diff --git a/pkg/tanka/static.go b/pkg/tanka/static.go
index f076f943..68c677e9 100644
--- a/pkg/tanka/static.go
+++ b/pkg/tanka/static.go
@@ -27,7 +27,7 @@ func (s StaticLoader) Load(path string, opts LoaderOpts) (*v1alpha1.Environment,
 	return config, nil
 }
 
-func (s StaticLoader) Peek(path string, opts LoaderOpts) (*v1alpha1.Environment, error) {
+func (s StaticLoader) Peek(path string, _ LoaderOpts) (*v1alpha1.Environment, error) {
 	config, err := parseStaticSpec(path)
 	if err != nil {
 		return nil, err
diff --git a/pkg/tanka/workflow.go b/pkg/tanka/workflow.go
index adbc8c33..b965e2b8 100644
--- a/pkg/tanka/workflow.go
+++ b/pkg/tanka/workflow.go
@@ -127,10 +127,10 @@ func Apply(baseDir string, opts ApplyOpts) error {
 	}
 
 	// prompt for confirmation
-	if opts.AutoApprove == AutoApproveAlways || (noChanges && opts.AutoApprove == AutoApproveNoChanges) || opts.DryRun != "" {
-		// Skip approval
-	} else if err := confirmPrompt("Applying to", l.Env.Spec.Namespace, kube.Info()); err != nil {
-		return err
+	if opts.AutoApprove != AutoApproveAlways && !(noChanges && opts.AutoApprove == AutoApproveNoChanges) && opts.DryRun == "" {
+		if err := confirmPrompt("Applying to", l.Env.Spec.Namespace, kube.Info()); err != nil {
+			return err
+		}
 	}
 
 	return kube.Apply(l.Resources, kubernetes.ApplyOpts{
@@ -230,10 +230,10 @@ func Delete(baseDir string, opts DeleteOpts) error {
 	}
 
 	// prompt for confirmation
-	if opts.AutoApprove == AutoApproveAlways || opts.DryRun != "" {
-		// Skip approval
-	} else if err := confirmPrompt("Deleting from", l.Env.Spec.Namespace, kube.Info()); err != nil {
-		return err
+	if opts.AutoApprove != AutoApproveAlways && opts.DryRun == "" {
+		if err := confirmPrompt("Deleting from", l.Env.Spec.Namespace, kube.Info()); err != nil {
+			return err
+		}
 	}
 
 	return kube.Delete(l.Resources, kubernetes.DeleteOpts{


Test patch:
diff --git a/pkg/helm/jsonnet_test.go b/pkg/helm/jsonnet_test.go
new file mode 100644
index 00000000..7607e836
--- /dev/null
+++ b/pkg/helm/jsonnet_test.go
@@ -0,0 +1,142 @@
+package helm
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+
+	"github.com/grafana/tanka/pkg/kubernetes/manifest"
+)
+
+const calledFrom = "/my/path/here"
+
+type MockHelm struct {
+	mock.Mock
+}
+
+// fulfill the Helm interface
+func (m *MockHelm) Pull(chart, version string, opts PullOpts) error {
+	args := m.Called(chart, version, opts)
+	return args.Error(0)
+}
+
+func (m *MockHelm) RepoUpdate(opts Opts) error {
+	args := m.Called(opts)
+	return args.Error(0)
+}
+
+func (m *MockHelm) Template(name, chart string, opts TemplateOpts) (manifest.List, error) {
+	args := m.Called(name, chart, opts)
+
+	// figure out what arguments `helm template` would be called with and save
+	// them
+	execHelm := &ExecHelm{}
+	cmdArgs := execHelm.templateCommandArgs(name, chart, opts)
+	m.TestData().Set("templateCommandArgs", cmdArgs)
+
+	return args.Get(0).(manifest.List), args.Error(1)
+}
+
+func (m *MockHelm) ChartExists(chart string, opts *JsonnetOpts) (string, error) {
+	args := m.Called(chart, opts)
+	return args.String(0), args.Error(1)
+}
+
+func callNativeFunction(t *testing.T, expectedHelmTemplateOptions TemplateOpts, inputOptionsFromJsonnet map[string]interface{}) []string {
+	t.Helper()
+
+	helmMock := &MockHelm{}
+
+	helmMock.On(
+		"ChartExists",
+		"exampleChartPath",
+		mock.AnythingOfType("*helm.JsonnetOpts")).
+		Return("/full/chart/path", nil).
+		Once()
+
+	// this verifies that the helmMock.Template() method is called with the
+	// correct arguments, i.e. includeCrds: true is set by default
+	helmMock.On("Template", "exampleChartName", "/full/chart/path", expectedHelmTemplateOptions).
+		Return(manifest.List{}, nil).
+		Once()
+
+	nf := NativeFunc(helmMock)
+	require.NotNil(t, nf)
+
+	// the mandatory parameters to helm.template() in Jsonnet
+	params := []string{
+		"exampleChartName",
+		"exampleChartPath",
+	}
+
+	// mandatory parameters + the k-v pairs from the Jsonnet input
+	paramsInterface := make([]interface{}, 3)
+	paramsInterface[0] = params[0]
+	paramsInterface[1] = params[1]
+	paramsInterface[2] = inputOptionsFromJsonnet
+
+	_, err := nf.Func(paramsInterface)
+
+	require.NoError(t, err)
+
+	helmMock.AssertExpectations(t)
+
+	return helmMock.TestData().Get("templateCommandArgs").StringSlice()
+}
+
+// TestDefaultCommandineFlagsIncludeCrds tests that the includeCrds flag is set
+// to true by default
+func TestDefaultCommandLineFlagsIncludeCrds(t *testing.T) {
+	kubeVersion := "1.18.0"
+
+	// we will check that the template function is called with these options,
+	// i.e. that includeCrds got set to true. This is not us passing an input,
+	// we are asserting here that the template function is called with these
+	// options.
+	expectedHelmTemplateOptions := TemplateOpts{
+		KubeVersion: kubeVersion,
+		IncludeCRDs: true,
+	}
+
+	// the options to helm.template(), which are a Jsonnet object, turned into a
+	// go map[string]interface{}. we do not set includeCrds here, so it should
+	// be true by default
+	inputOptionsFromJsonnet := make(map[string]interface{})
+	inputOptionsFromJsonnet["calledFrom"] = calledFrom
+	inputOptionsFromJsonnet["kubeVersion"] = kubeVersion
+
+	args := callNativeFunction(t, expectedHelmTemplateOptions, inputOptionsFromJsonnet)
+
+	// finally check that the actual command line arguments we will pass to
+	// `helm template` contain the --include-crds flag
+	require.Contains(t, args, "--include-crds")
+}
+
+// TestIncludeCrdsFalse tests that the includeCrds flag is can be set to false,
+// and this makes it to the helm.Template() method call
+func TestIncludeCrdsFalse(t *testing.T) {
+	kubeVersion := "1.18.0"
+
+	// we will check that the template function is called with these options,
+	// i.e. that includeCrds got set to false. This is not us passing an input,
+	// we are asserting here that the template function is called with these
+	// options.
+	expectedHelmTemplateOptions := TemplateOpts{
+		KubeVersion: kubeVersion,
+		IncludeCRDs: false,
+	}
+
+	// the options to helm.template(), which are a Jsonnet object, turned into a
+	// go map[string]interface{}. we explicitly set includeCrds to false here
+	inputOptionsFromJsonnet := make(map[string]interface{})
+	inputOptionsFromJsonnet["calledFrom"] = calledFrom
+	inputOptionsFromJsonnet["kubeVersion"] = kubeVersion
+	inputOptionsFromJsonnet["includeCrds"] = false
+
+	args := callNativeFunction(t, expectedHelmTemplateOptions, inputOptionsFromJsonnet)
+
+	// finally check that the actual command line arguments we will pass to
+	// `helm template` don't contain the --include-crds flag
+	require.NotContains(t, args, "--include-crds")
+}
diff --git a/pkg/jsonnet/find_importers_test.go b/pkg/jsonnet/find_importers_test.go
index d295f821..58080276 100644
--- a/pkg/jsonnet/find_importers_test.go
+++ b/pkg/jsonnet/find_importers_test.go
@@ -8,24 +8,28 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestFindImportersForFiles(t *testing.T) {
-	// Make sure the main files all eval correctly
-	// We want to make sure that the importers command works correctly,
-	// but there's no point in testing on invalid jsonnet files
-	files, err := FindFiles("testdata", nil)
-	require.NoError(t, err)
-	require.NotEmpty(t, files)
-	for _, file := range files {
-		_, err := EvaluateFile(file, Opts{})
-		require.NoError(t, err, "failed to eval %s", file)
+type findImportersTestCase struct {
+	name              string
+	files             []string
+	expectedImporters []string
+	expectedErr       error
+}
+
+func (tc findImportersTestCase) run(t testing.TB) {
+	importers, err := FindImporterForFiles("testdata/findImporters", tc.files)
+
+	if tc.expectedErr != nil {
+		require.EqualError(t, err, tc.expectedErr.Error())
+	} else {
+		require.NoError(t, err)
+		require.Equal(t, tc.expectedImporters, importers)
 	}
+}
 
-	cases := []struct {
-		name              string
-		files             []string
-		expectedImporters []string
-		expectedErr       error
-	}{
+func findImportersTestCases(t testing.TB) []findImportersTestCase {
+	t.Helper()
+
+	return []findImportersTestCase{
 		{
 			name:  "no files",
 			files: []string{},
@@ -115,17 +119,23 @@ func TestFindImportersForFiles(t *testing.T) {
 			},
 		},
 	}
+}
 
-	for _, c := range cases {
-		t.Run(c.name, func(t *testing.T) {
-			importers, err := FindImporterForFiles("testdata/findImporters", c.files)
+func TestFindImportersForFiles(t *testing.T) {
+	// Make sure the main files all eval correctly
+	// We want to make sure that the importers command works correctly,
+	// but there's no point in testing on invalid jsonnet files
+	files, err := FindFiles("testdata", nil)
+	require.NoError(t, err)
+	require.NotEmpty(t, files)
+	for _, file := range files {
+		_, err := EvaluateFile(file, Opts{})
+		require.NoError(t, err, "failed to eval %s", file)
+	}
 
-			if c.expectedErr != nil {
-				require.EqualError(t, err, c.expectedErr.Error())
-			} else {
-				require.NoError(t, err)
-				require.Equal(t, c.expectedImporters, importers)
-			}
+	for _, c := range findImportersTestCases(t) {
+		t.Run(c.name, func(t *testing.T) {
+			c.run(t)
 		})
 	}
 }
@@ -149,7 +159,17 @@ func BenchmarkFindImporters(b *testing.B) {
 	}
 }
 
-func absPath(t *testing.T, path string) string {
+func BenchmarkFindImporters_StaticCases(b *testing.B) {
+	for _, c := range findImportersTestCases(b) {
+		b.Run(c.name, func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				c.run(b)
+			}
+		})
+	}
+}
+
+func absPath(t testing.TB, path string) string {
 	t.Helper()
 
 	abs, err := filepath.Abs(path)