295 lines
9.5 KiB
Markdown
295 lines
9.5 KiB
Markdown
# 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
|
|
|