# Hooks

## Hooks

A Helmfile hook is a per-release extension point that is composed of:

* `events`
* `command`
* `args`
* `showlogs`
* `kubectlApply` (alternative to `command`/`args`)

Helmfile triggers various `events` while it is running.
Once `events` are triggered, associated `hooks` are executed, by running the `command` with `args`. The standard output of the `command` will be displayed if `showlogs` is set and it's value is `true`.

Hooks exec order follows the order of definition in the helmfile state.

Currently supported `events` are:

* `prepare`
* `preapply`
* `presync`
* `preuninstall`
* `postuninstall`
* `postsync`
* `cleanup`

Hooks associated to `prepare` events are triggered after each release in your helmfile is loaded from YAML, before execution.
`prepare` hooks are triggered on the release as long as it is not excluded by the helmfile selector(e.g. `helmfile -l key=value`).

Hooks associated to `presync` events are triggered before each release is synced (installed or upgraded) on the cluster.
This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.

`preapply` hooks are triggered before a release is uninstalled, installed, or upgraded as part of `helmfile apply`.
This is the ideal event to hook into when you are going to use `helmfile apply` for every kind of change. Note that preapply hooks will only run if at least one release has changes to apply. Be sure to make each `preapply` hook command idempotent. Otherwise, rerunning `helmfile apply` on a transient failure may end up either breaking your cluster, or the hook that runs for the second time will never succeed.

`preuninstall` hooks are triggered immediately before a release is uninstalled as part of `helmfile apply`, `helmfile sync`, `helmfile delete`, and `helmfile destroy`.

`postuninstall` hooks are triggered immediately after successful uninstall of a release while running `helmfile apply`, `helmfile sync`, `helmfile delete`, `helmfile destroy`.

`postsync` hooks are triggered after each release is synced (installed or upgraded) on the cluster, regardless if the sync was successful or not.
This is the ideal place to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.

`cleanup` hooks are triggered after each release is processed.
This is the counterpart to `prepare`, as any release on which `prepare` has been triggered gets `cleanup` triggered as well.

The following is an example hook that just prints the contextual information provided to hook:

```yaml
releases:
- name: myapp
  chart: mychart
  # *snip*
  hooks:
  - events: ["prepare", "cleanup"]
    showlogs: true
    command: "echo"
    args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
"]
```

Let's say you ran `helmfile --environment prod sync`, the above hook results in executing:

```
echo {{Environment.Name}} {{.Release.Name}} {{.HelmfileCommand}}
```

Whereas the template expressions are executed thus the command becomes:

```
echo prod myapp sync
```

Now, replace `echo` with any command you like, and rewrite `args` that actually conforms to the command, so that you can integrate any command that does:

* templating
* linting
* testing

Hooks expose additional template expressions:

`.Event.Name` is the name of the hook event.

`.Event.Error` is the error generated by a failed release, exposed for `postsync` hooks only when a release fails, otherwise its value is `nil`.

You can use the hooks event expressions to send notifications to platforms such as `Slack`, `MS Teams`, etc.

The following example passes arguments to a script which sends a notification:

```yaml
releases:
- name: myapp
  chart: mychart
  # *snip*
  hooks:
  - events:
    - presync
    - postsync
    showlogs: true
    command: notify.sh
    args:
    - --event
    - '{{`{{ .Event.Name }}`}}'
    - --status
    - '{{`{{ if .Event.Error }}failure{{ else }}success{{ end }}`}}'
    - --environment
    - '{{`{{ .Environment.Name }}`}}'
    - --namespace
    - '{{`{{ .Release.Namespace }}`}}'
    - --release
    - '{{`{{ .Release.Name }}`}}'
```

For templating, imagine that you created a hook that generates a helm chart on-the-fly by running an external tool like ksonnet, kustomize, or your own template engine.
It will allow you to write your helm releases with any language you like, while still leveraging goodies provided by helm.

### Hooks, Kubectl and Environments

Hooks can also be used in combination with small tasks using `kubectl` directly,
e.g.: in order to install a custom storage class.

In the following example, a specific release depends on a custom storage class.
Further, all enviroments have a default kube context configured where releases are deployed into.
The `.Environment.KubeContext` is used in order to apply / remove the YAML to the correct context depending on the environment.

`environments.yaml`:

```yaml
environments:
  dev:
    values:
    - ../values/default.yaml
    - ../values/dev.yaml
    kubeContext: dev-cluster
  prod:
    values:
    - ../values/default.yaml
    - ../values/prod.yaml
    kubeContext: prod-cluster
```

`helmfile.yaml`:

```yaml
bases:
  - ./environments.yaml

---
releases:
  - name: myService
    namespace: my-ns
    installed: true
    chart: mychart
    version: "1.2.3"
    values:
      - ../services/my-service/values.yaml.gotmpl
    hooks:
      - events: ["presync"]
        showlogs: true
        command: "kubectl"
        args:
          - "apply"
          - "-f"
          - "./custom-storage-class.yaml"
          - "--context"
          - "{{`{{.Environment.KubeContext}}`}}"
      - events: ["postuninstall"]
        showlogs: true
        command: "kubectl"
        args:
          - "delete"
          - "-f"
          - "./custom-storage-class.yaml"
          - "--context"
          - "{{`{{.Environment.KubeContext}}`}}"
```

### Global Hooks

In contrast to the per release hooks mentioned above these are run only once at the very beginning and end of the execution of a helmfile command and only the `prepare` and `cleanup` hooks are available respectively.

They use the same syntax as per release hooks, but at the top level of your helmfile:

```yaml
hooks:
- events: ["prepare", "cleanup"]
  showlogs: true
  command: "echo"
  args: ["{{`{{.Environment.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
"]
```

### Helmfile + Kustomize

Do you prefer `kustomize` to write and organize your Kubernetes apps, but still want to leverage helm's useful features
like rollback, history, and so on? This section is for you!

The combination of `hooks` and [helmify-kustomize](https://gist.github.com/mumoshu/f9d0bd98e0eb77f636f79fc2fb130690)
enables you to integrate [kustomize](https://github.com/kubernetes-sigs/kustomize) into Helmfile.

That is, you can use `kustomize` to build a local helm chart from a kustomize overlay.

Let's assume you have a kustomize project named `foo-kustomize` like this:

```
foo-kustomize/
├── base
│   ├── configMap.yaml
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    ├── default
    │   ├── kustomization.yaml
    │   └── map.yaml
    ├── production
    │   ├── deployment.yaml
    │   └── kustomization.yaml
    └── staging
        ├── kustomization.yaml
        └── map.yaml

5 directories, 10 files
```

Write `helmfile.yaml`:

```yaml
- name: kustomize
  chart: ./foo
  hooks:
  - events: ["prepare", "cleanup"]
    command: "../helmify"
    args: ["{{`{{if eq .Event.Name \"prepare\"}}build{{else}}clean{{end}}`}}", "{{`{{.Release.Ch\
art}}`}}", "{{`{{.Environment.Name}}`}}"]
```

Run `helmfile --environment staging sync` and see it results in helmfile running `kustomize build foo-kustomize/overlays/staging > foo/templates/all.yaml`.

Voilà! You can mix helm releases that are backed by remote charts, local charts, and even kustomize overlays.

### kubectlApply Hook

Instead of specifying `command` and `args`, you can use the `kubectlApply` field to run `kubectl apply` directly:

```yaml
releases:
- name: myapp
  chart: mychart
  hooks:
  - events: ["presync"]
    showlogs: true
    kubectlApply:
      filename: manifests/custom-resource.yaml
```

Or apply a kustomize overlay:

```yaml
  hooks:
  - events: ["presync"]
    showlogs: true
    kubectlApply:
      kustomize: overlays/default/
```

The `kubectlApply` field accepts either:
* `filename:` - runs `kubectl apply -f <value>`
* `kustomize:` - runs `kubectl apply -k <value>`

**Note:** `filename` and `kustomize` cannot be used together. When `kubectlApply` is set, the `command` field is ignored with a warning.

### Hook Template Data

Hooks have access to the following template data:

Per-release hooks:
* `{{ .Environment.Name }}` - the environment name
* `{{ .Environment.KubeContext }}` - the environment kube context
* `{{ .Release.Name }}` - the release name
* `{{ .Release.Namespace }}` - the release namespace
* `{{ .Release.Labels }}` - the release labels
* `{{ .Release.Chart }}` - the release chart
* `{{ .Values }}` - state values
* `{{ .HelmfileCommand }}` - the helmfile command name (e.g., `sync`, `apply`)
* `{{ .Event.Name }}` - the hook event name
* `{{ .Event.Error }}` - the error (available in `postsync` hooks when a release fails)

Global hooks:
* `{{ .Environment.Name }}` - the environment name
* `{{ .HelmfileCommand }}` - the helmfile command name
* `{{ .Event.Name }}` - the hook event name
* `{{ .Event.Error }}` - the error

