---
title: Helm support
sidebar:
  order: 2
---

import { FileTree } from '@astrojs/starlight/components';

The [Helm](https://helm.sh) project is the biggest ecosystem of high quality,
well maintained application definitions for Kubernetes.

Even though Grafana Tanka uses the [Jsonnet language](./jsonnet/overview/) for
resource definition, you can still consume Helm resources, as described below.

## Consuming Helm Charts from Jsonnet

Helm support is provided using the
[`github.com/grafana/jsonnet-libs/tanka-util`](https://github.com/grafana/jsonnet-libs/tree/master/tanka-util)
library. Install it with:

```bash
jb install github.com/grafana/jsonnet-libs/tanka-util
```

The following example shows how to extract the individual resources of the
[`grafana`](https://artifacthub.io/packages/helm/grafana/grafana) Helm Chart:

```jsonnet
local tanka = import "github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet";
local helm = tanka.helm.new(std.thisFile);

{
  grafana: helm.template("grafana", "./charts/grafana", {
    namespace: "monitoring",
    values: {
      persistence: { enabled: true }
    }
  })
}
```

The Chart itself is required to be [vendored at a relative
path](./helm/#vendoring-helm-charts), in this case `./charts/grafana`.

:::caution
You **MUST** include the `.new(std.thisFile)` part in the import.
This is what tells Tanka where you actually call `helm.template()` from, so it
can find your vendored Charts.
:::

Once invoked, the `$.grafana` key holds the individual resources of Helm Chart as
a regular Jsonnet object that looks roughly like so:

```jsonnet
{
  cluster_role_binding_grafana_clusterrolebinding: {/* ... */},
  cluster_role_grafana_clusterrole: {/* ... */},
  config_map_grafana: {/* ... */},
  config_map_grafana_test: {/* ... */},
  deployment_grafana: {/* ... */},
  // ...
}
```

Above can be [manipulated](./tutorial/environments/#patching) in the same way as any other Jsonnet data.

Under the hood, this feature invokes the
[`helm template`](https://helm.sh/docs/helm/helm_template/) CLI command.
The following options control how the command is invoked:

```jsonnet
...

{
  grafana: helm.template("grafana", "./charts/grafana", {
    namespace: "monitoring",
    values: {
      persistence: { enabled: true }
    },
    // Equivalent to: --api-versions v1 --api-versions apps/v1
    apiVersions: ['v1', 'apps/v1']
    // Equivalent to: --kube-version v1.20.0
    kubeVersion: 'v1.20.0'
    // Equivalent to: --no-hooks
    noHooks: true,
}
```

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

Tanka, like Jsonnet, is hermetic. It **always yields the same
resources** when the project is
strictly self-contained.

Helm however keeps Charts and repository configuration somewhere around
`~/.config/helm`, which violates above requirement.

To comply with this requirement, Tanka expects Helm Charts to be found **inside the
bounds of a project**. This means, you MUST put your Charts **somewhere next to
the file that calls `helm.template()`**, so that it can be referred to using a
relative path.

### Vendor Location

Where to actually put them inside the project is up to you, but keep in mind you
need to refer to them using relative paths.

We recommend always writing libraries that wrap the actual Helm Chart, so the
consumer does not need to be aware of it. Whether you put these into your local `lib/` directory or
publish and vendor them into the `vendor/` directory is up to you.

A library usually looks like this:

<FileTree>

- /
  - jsonnetfile.json
  - main.libsonnet

</FileTree>

When adopting Helm inside it, we recommend vendoring at the top level, as such:

<FileTree>

- /
  - jsonnetfile.json
  - main.libsonnet
- **charts**
  - **\<someChart\>**

</FileTree>

This way, you can refer to the charts as `./charts/<someChart>` from inside
`main.libsonnet`. By keeping the chart as close to the consumer as possible, the
library is kept portable.

### Charttool

Helm does not make vendoring incredibly easy by itself. `helm pull` provides the
required plumbing, but it does not record its actions in a reproducible manner.

Therefore, Tanka ships a special utility at `tk tool charts`, which automates
`helm pull`:

```bash
# Create a chartfile.yaml in the current directory, e.g. in lib/myLibrary
tk tool charts init

# Install the MySQL chart at version 1.6.7 from the stable repository
tk tool charts add stable/mysql@1.6.7
```

#### Adding charts

To add a chart, use the following:

```bash
tk tool charts add <repo>/<name>@<version>
```

This will also call `tk tool charts vendor`, so that the `charts/` directory is updated.

#### Adding repositories

By default, the `stable` repository is automatically set up for you. If you wish
to add another repository, you can use the `add-repo` command:

```bash
# Add the official Grafana repository
tk tool charts add-repo grafana https://grafana.github.io/helm-charts
```

Another way is to modify `chartfile.yaml` directly:

```diff
// chartfile.yaml
version: 1
repositories:
  - name: stable
    url: https://charts.helm.sh/stable
+ - name: grafana
+   url: https://grafana.github.io/helm-charts
```

#### Installing multiple versions of the same chart

If you wish to install multiple versions of the same chart, you can write them to a specific directory.  
You can do so with a `:<directory>` suffix in the `add` command, or by modifying the chartfile manually.

```bash
tk tool charts add stable/mysql@1.6.7:1.6.7
tk tool charts add stable/mysql@1.6.8:1.6.8
```

The resulting chartfile will look like this:

```yaml
version: 1
directory: charts
repositories:
  - name: stable
    url: https://charts.helm.sh/stable
requires:
  - chart: stable/mysql
    directory: 1.6.7
    version: 1.6.7
  - chart: stable/mysql
    directory: 1.6.8
    version: 1.6.8
```

#### Install charts from chartfile

To install charts from an existing chartfile, use the following:

```bash
tk tool charts vendor
```

Optionally, you can also pass the `--prune` flag to remove vendored charts that are no longer in the chartfile.

#### OCI Registry Support

Tanka supports pulling charts from OCI registries. To use one, the chart name must be split into two parts: the registry and the chart name.

As example, if you wanted to pull the `oci://public.ecr.aws/karpenter/karpenter:v0.27.3` image, your chartfile would look like this:

```yaml
version: 1
directory: charts
repositories:
  - name: karpenter
    url: oci://public.ecr.aws/karpenter
requires:
  - chart: karpenter/karpenter
    directory: v0.27.3
    version: v0.27.3
```

:::note
If your registry requires login, you must run the appropriate login command before running `tk tool charts vendor`. For example, for public ECR registries:

```bash
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
tk tool charts vendor
```
:::

## Troubleshooting

### Helm executable missing

Helm support in Tanka requires the `helm` binary installed on your system and
available on the `$PATH`. If Helm is not installed, you will see this error message:

```
evaluating jsonnet: RUNTIME ERROR: Expanding Helm Chart: exec: "helm": executable file not found in $PATH
```

To solve this, you need to [install Helm](https://helm.sh/docs/intro/install/).
If you cannot install it system-wide, you can point Tanka at your executable
using [`TANKA_HELM_PATH`](./env-vars/#tanka_helm_path)

### opts.calledFrom unset

This occurs, when Tanka was not told where it `helm.template()` was invoked
from. This most likely means you didn't call `new(std.thisFile)` when importing `tanka-util`:

```jsonnet
local tanka = import "github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet";
local helm = tanka.helm.new(std.thisFile);
                       ↑ This is important
```

### Failed to find Chart

```
helmTemplate: Failed to find a Chart at 'stable/grafana': No such file or directory.
helmTemplate: Failed to find a Chart at '/home/user/stuff/tanka/environments/default/grafana': No such file or directory.
```

Tanka failed to locate your Helm chart on the filesystem. It looked at the
relative path you provided in `helm.template()`, starting from the directory of
the file you called `helm.template()` from.

Please check there is actually a valid Helm chart at this place. Referring to
charts as `<repo>/<name>` is disallowed by design.

### Two resources share the same name

To make customization easier, `helm.template()` returns the resources not as the
list it receives from Helm, but instead converts this into an object.

For the indexing key it uses `kind_name` by default. In some rare cases, this
might not be enough to distinguish between two resources, namely when the same
resource exists in two namespaces.

To handle this, pass a custom name format, e.g. to also include the namespace:

```jsonnet
custom: helm.template('foo', './charts/foo', {
  nameFormat: '{{ print .metadata.namespace "_" .kind "_" .metadata.name | snakecase }}'
})
```

The literal default format used is `{{ print .kind "_" .metadata.name | snakecase }}`

### Tanka deploys test charts

By default, Tanka will deploy charts using the default flags for `helm
template` which also includes things like test resources. If that's not what
you want, then you can set the `skipTests` option when running `helm.template`:

```jsonnet
custom: helm.template('foo', './charts/foo', {
  skipTests: true,
})
```
