How to build a Helm plugin in minutes
Tags: kubernetes, helm, devops
Published: 2021-06-16 13:39:58

## Introduction

[Helm](https://helm.sh/) is a great addition to the Kubernetes ecosystem; it simplifies complex Kubernetes manifests by separating them into charts and values. 

Sharing charts has never been easier especially since all the customizable parameters are located separately (values.yaml). The downside of this is that there’s no single place to see the resulting manifest, as it’s usually compiled and installed in a single step - `helm install`. 

Helm alleviates this with a plugin system that allows you to seamlessly integrate with the Helm flow and easily run custom code that’s not part of the Helm core code which, as we’ll see in the next section, can be written in any language, even Bash.

## A simple example of how to create a Helm plugin

Let's build a simple plugin that prints out some useful information and environment variables that Helm provides.

A helm plugin consists of a single required file - `plugin.yaml`

That’s the entrypoint for Helm. By using this, Helm knows the settings for your plugin, the command it executes when the plugin is run and hooks for customizing the plugin lifecycle (more on that later).

This is the only required file for a complete plugin. You can set it up in 3 steps:
1. Create a plugin.yaml with the following content
{% gist https://gist.github.com/romanlab/8467763343839a41020b7c743beb3d32 %}
2. Run the installation command from the directory of the file
    ```
    helm plugin install .
    ```
3. Execute the plugin
    ```
    helm myplugin
    ```

## A complete example of a Helm plugin

The simple plugin example covers the most basic use cases, goals that can be achieved even with an alias. More complex cases require more points of integration with Helm and greater customizability of the plugin’s lifecycle. The following parts will give you all the necessary tools to execute any logic in the Helm flow.

### Lifecycle customization - Install, update and delete hooks

Helm provides under-documented (the closest thing I found to documentation was [here](https://github.com/helm/helm/blob/bf486a25cdc12017c7dac74d1582a8a16acd37ea/pkg/plugin/hooks.go)) capability for hooking into the plugin’s [install, update or uninstall commands](https://helm.sh/docs/helm/helm_plugin/).

{% gist https://gist.github.com/romanlab/6a01ca90765177c89d5387b8ca8f73f8 file=plugin-hooks.yaml %}

Each hook corresponds to a command and will execute the provided script when invoked. 

Some plugins may require a more complex installation flow: Downloading a binary based on OS architecture, building or compiling code or simply installing system dependencies. The install script is the place to do it. A useful example can be found [here](https://github.com/datreeio/helm-datree/blob/6e498e5e966f36a38f67e986022c74781da865b1/scripts/install.sh).

### Execution integration
There are two ways to specify what will be executed and when:

**command**
{% gist https://gist.github.com/romanlab/6a01ca90765177c89d5387b8ca8f73f8 file=plugin-command.yaml %}

**platformCommand**
{% gist https://gist.github.com/romanlab/6a01ca90765177c89d5387b8ca8f73f8 file=plugin-platformCommand.yaml %}

It's important to note that Helm has a hierarchy for choosing the correct command:

1. `platformCommand(os+arch match)`
2. `platformCommand(os only match)`
3. `command`

Helm plugins aren’t executed in a shell, so complex commands must be part of a script that will be executed every time the plugin is invoked. 

A script can be used to run complex logic, handle parameter parsing, etc. 

```
command: "$HELM_PLUGIN_DIR/scripts/run.sh"
```

A comprehensive example of a run script can be found [here](https://github.com/datreeio/helm-datree/blob/6e498e5e966f36a38f67e986022c74781da865b1/scripts/run.sh). A very useful thing that we can do is to render the chart and then execute logic on the resulting Kubernetes yaml file:
```
helm template "${HELM_OPTIONS[@]}" > ${TEMP_MANIFEST_NAME}
 
$HELM_PLUGIN_DIR/bin/myplugin ${TEMP_MANIFEST_NAME} "${MYPLUGIN_OPTIONS[@]}"
```

A very important flag in `plugin.yaml` is:
```
ignoreFlags: false
```

This flag specifies whether or not the command line params are passed to the plugin or not. If the plugin accepts parameters, this should be set to false.

## Tips and caveats
* Helm exposes many [environment variables](https://helm.sh/docs/topics/plugins/#environment-variables) that can simplify a lot of the complex logic and provides much of the necessary information and context for the plugin’s execution. 
* The `useTunnel` flag in `config.yaml` is deprecated in Helm V3 and is no longer needed
* To easily test the plugin during development, you can install the plugin from the dev directory by running `helm plugin install PATH_TO_DIR`
* The plugin can then be uninstalled with `helm plugin uninstall PLUGIN_NAME`

## Conclusion
Helm plugin writing is easy to learn but difficult to master. Lack of documentation makes writing complex plugins an arduous task. 

This article aims to expose some of the lesser known abilities of the Helm plugin system and to provide tools and scaffolding that remove the limitations of the plugin system and allow execution of as complex a business logic as is necessary to extend Helm’s behavior.