Skip to content

Commit c708c0d

Browse files
authored
feat(helm): add reusable chart, make targets and release workflow (#59)
Chart runs raoptimus/db-migrator as a Job: release via pre-install/pre-upgrade hook, rollback as an opt-in plain Job (works under plain Helm and werf). DSN is referenced from an existing Secret and INTERACTIVE is forced to false. Add make helm-lint/helm-template/helm-package targets and a chart-releaser workflow. Track .helmignore in git.
1 parent af2d87f commit c708c0d

14 files changed

Lines changed: 677 additions & 0 deletions

File tree

.github/workflows/helm-release.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Release Helm Chart
2+
3+
# Publishes the chart to the gh-pages branch as a Helm repository and creates a
4+
# GitHub Release tagged "db-migrator-<version>". A new release is cut only when
5+
# charts/db-migrator/Chart.yaml `version` is bumped (chart-releaser skips versions
6+
# that are already published).
7+
#
8+
# One-time setup: create an empty `gh-pages` branch and enable GitHub Pages for it
9+
# (Settings -> Pages -> Branch: gh-pages). The published repo URL will be:
10+
# https://raoptimus.github.io/db-migrator.go
11+
12+
on:
13+
push:
14+
branches: [main]
15+
paths:
16+
- 'charts/**'
17+
18+
jobs:
19+
release:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: write
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v6
26+
with:
27+
fetch-depth: 0
28+
29+
- name: Configure Git
30+
run: |
31+
git config user.name "$GITHUB_ACTOR"
32+
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
33+
34+
- name: Set up Helm
35+
uses: azure/setup-helm@v4
36+
37+
- name: Run chart-releaser
38+
uses: helm/chart-releaser-action@v1.7.0
39+
env:
40+
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.*
22
!.github
33
!.docker
4+
!.helmignore
45
*.md
56
!README.md
67
build.sh

Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ DOCKER_ID_USER = raoptimus
2121
DOCKER_PASS ?= ""
2222
DOCKER_IMAGE = "${PKG_NAME}"
2323
export GO_IMAGE_VERSION="1.26"
24+
HELM_CHART_DIR = charts/db-migrator
25+
HELM_DIST_DIR ?= ${BUILD_DIR}/charts
2426

2527
help: ## Show help message
2628
@cat $(MAKEFILE_LIST) | grep -e "^[a-zA-Z_\-]*: *.*## *" | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@@ -138,6 +140,16 @@ gen-mocks-dry-run: install-mockery ## Run mockery --dry-run=true
138140
done; \
139141
'
140142

143+
helm-lint: ## Lint the Helm chart
144+
@helm lint ${HELM_CHART_DIR} -f ${HELM_CHART_DIR}/ci/example-values.yaml
145+
146+
helm-template: ## Render the Helm chart to stdout
147+
@helm template db-migrator ${HELM_CHART_DIR} -f ${HELM_CHART_DIR}/ci/example-values.yaml
148+
149+
helm-package: helm-lint ## Package the Helm chart into ${HELM_DIST_DIR}
150+
@[ -d ${HELM_DIST_DIR} ] || mkdir -p ${HELM_DIST_DIR}
151+
@helm package ${HELM_CHART_DIR} --destination ${HELM_DIST_DIR}
152+
141153
start:
142154
@docker-compose -f "docker-compose.yml" -f "docker-compose.dev.yml" up -d
143155

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,49 @@ Migrated up successfully
280280

281281
---
282282

283+
## Running in Kubernetes (Helm)
284+
285+
A reusable Helm chart lives in [`charts/db-migrator`](charts/db-migrator). It runs the tool
286+
as a Kubernetes `Job`:
287+
288+
- **`release`** — applies pending migrations. Rendered as a Helm hook
289+
(`pre-install,pre-upgrade`), so the schema is ready before application pods roll out.
290+
- **`rollback`** — reverts the latest release batch. Opt-in plain `Job` (disabled by default),
291+
triggered explicitly, so it works the same under plain Helm and [werf](https://werf.io).
292+
293+
The DSN is referenced from an existing `Secret` (never stored in values), and `INTERACTIVE`
294+
is always forced to `false` because a Job has no TTY.
295+
296+
```bash
297+
kubectl create secret generic db-migrator-dsn \
298+
--from-literal=dsn='postgres://user:pass@postgres:5432/app?sslmode=disable'
299+
300+
helm install my-migrations ./charts/db-migrator \
301+
--set image.repository=myregistry/myapp-migrations \
302+
--set image.tag=1.4.2 \
303+
--set migrator.dsn.existingSecret=db-migrator-dsn
304+
```
305+
306+
The base image ships no migrations — build your own image with them baked in:
307+
308+
```dockerfile
309+
FROM raoptimus/db-migrator:1.7.0
310+
COPY ./migrations /migrations
311+
```
312+
313+
To roll back the latest batch, enable the rollback Job explicitly:
314+
315+
```bash
316+
helm upgrade --install my-migrations ./charts/db-migrator \
317+
--reuse-values --set rollback.enabled=true
318+
```
319+
320+
The chart can also be consumed as a subchart dependency. See
321+
[`charts/db-migrator/README.md`](charts/db-migrator/README.md) for the full values reference
322+
and usage examples. Local helpers: `make helm-lint`, `make helm-template`, `make helm-package`.
323+
324+
---
325+
283326
## Placeholders in Migrations
284327

285328
You can use placeholders in your migration SQL files that will be replaced at runtime:

charts/db-migrator/.helmignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Patterns to ignore when building packages.
2+
.DS_Store
3+
.git/
4+
.gitignore
5+
*.tmproj
6+
*.bak
7+
*.orig
8+
*.swp
9+
.idea/
10+
.vscode/

charts/db-migrator/Chart.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: v2
2+
name: db-migrator
3+
description: >-
4+
Reusable Helm chart that runs raoptimus/db-migrator as a Kubernetes Job.
5+
Applies pending migrations via the `release` command (Helm hook) and reverts
6+
the latest release batch via the `rollback` command (opt-in Job). Works both
7+
standalone and as a subchart dependency, under plain Helm and werf.
8+
type: application
9+
# Chart version (SemVer). Bump on any chart change.
10+
version: 0.1.0
11+
# Version of the db-migrator tool this chart targets; used as the default image tag.
12+
appVersion: "1.7.0"
13+
home: https://github.com/raoptimus/db-migrator.go
14+
sources:
15+
- https://github.com/raoptimus/db-migrator.go
16+
- https://hub.docker.com/r/raoptimus/db-migrator
17+
maintainers:
18+
- name: raoptimus
19+
url: https://github.com/raoptimus
20+
keywords:
21+
- migration
22+
- database
23+
- clickhouse
24+
- postgres
25+
- mysql
26+
- tarantool
27+
- iceberg

charts/db-migrator/README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# db-migrator Helm chart
2+
3+
Reusable Helm chart that runs [`raoptimus/db-migrator`](https://hub.docker.com/r/raoptimus/db-migrator)
4+
as a Kubernetes `Job`.
5+
6+
- **`release`** — applies all pending migrations atomically. Rendered as a Helm hook
7+
(`pre-install,pre-upgrade` by default), so the schema is ready **before** the application
8+
pods roll out.
9+
- **`rollback`** — reverts the latest release batch. Rendered as an opt-in plain `Job`
10+
(disabled by default), triggered explicitly. It is not a lifecycle hook, so it works the
11+
same under both plain Helm and [werf](https://werf.io) `converge`.
12+
13+
The chart works standalone (`helm install`) and as a subchart dependency of an application
14+
chart. `INTERACTIVE` is always forced to `false` because a Job has no TTY.
15+
16+
## Prerequisites
17+
18+
- Kubernetes cluster and Helm 3.
19+
- A container image that contains your migration files at `migrator.path`
20+
(default `/migrations`). The base image ships **no** migrations — see
21+
[Providing migrations](#providing-migrations).
22+
- An existing `Secret` holding the database DSN.
23+
24+
## Installation
25+
26+
```bash
27+
kubectl create secret generic db-migrator-dsn \
28+
--from-literal=dsn='postgres://user:pass@postgres:5432/app?sslmode=disable'
29+
30+
helm install my-migrations ./charts/db-migrator \
31+
--set image.repository=myregistry/myapp-migrations \
32+
--set image.tag=1.4.2 \
33+
--set migrator.dsn.existingSecret=db-migrator-dsn
34+
```
35+
36+
## Use as a subchart dependency
37+
38+
```yaml
39+
# Chart.yaml of your application chart
40+
dependencies:
41+
- name: db-migrator
42+
version: "0.1.0"
43+
repository: "oci://<registry>/charts" # or https://<repo>, or file://../charts/db-migrator
44+
```
45+
46+
```yaml
47+
# values.yaml of your application chart (values go under the "db-migrator" key)
48+
db-migrator:
49+
image:
50+
repository: myregistry/myapp-migrations
51+
tag: "1.4.2"
52+
migrator:
53+
dsn:
54+
existingSecret: myapp-db
55+
secretKey: dsn
56+
path: /migrations
57+
```
58+
59+
Then `helm dependency update && helm upgrade --install ...` — the `release` hook runs on
60+
every install/upgrade before the app pods start.
61+
62+
## Rolling back
63+
64+
`rollback` is opt-in. Enable it explicitly when you actually want to revert the latest batch:
65+
66+
```bash
67+
# werf or plain Helm
68+
helm upgrade --install my-migrations ./charts/db-migrator \
69+
--reuse-values --set rollback.enabled=true
70+
```
71+
72+
Under **plain Helm** you may instead wire rollback to `helm rollback` by turning the Job into
73+
a hook (this does not fire under werf converge):
74+
75+
```bash
76+
helm install ... --set 'rollback.hookTypes={pre-rollback}'
77+
helm rollback my-migrations
78+
```
79+
80+
## Providing migrations
81+
82+
The base image contains no migrations. Recommended: build your own image.
83+
84+
```dockerfile
85+
FROM raoptimus/db-migrator:1.7.0
86+
COPY ./migrations /migrations
87+
```
88+
89+
Alternatively, mount migrations without rebuilding the image via passthrough values, e.g. a
90+
ConfigMap:
91+
92+
```yaml
93+
extraVolumes:
94+
- name: migrations
95+
configMap:
96+
name: myapp-migrations
97+
extraVolumeMounts:
98+
- name: migrations
99+
mountPath: /migrations
100+
migrator:
101+
path: /migrations
102+
```
103+
104+
`initContainers` is also passed through (e.g. for a git-sync sidecar populating an
105+
`emptyDir`).
106+
107+
## Values
108+
109+
| Key | Default | Description |
110+
|-----|---------|-------------|
111+
| `image.repository` | `raoptimus/db-migrator` | Image containing the migrations |
112+
| `image.tag` | `""` (→ `.Chart.AppVersion`) | Image tag |
113+
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
114+
| `imagePullSecrets` | `[]` | Image pull secrets |
115+
| `migrator.dsn.existingSecret` | `""` (**required**) | Secret holding the DSN |
116+
| `migrator.dsn.secretKey` | `dsn` | Key inside the Secret |
117+
| `migrator.path` | `/migrations` | `MIGRATION_PATH` |
118+
| `migrator.table` | `migration` | `MIGRATION_TABLE` |
119+
| `migrator.clusterName` | `""` | `MIGRATION_CLUSTER_NAME` (ClickHouse) |
120+
| `migrator.replicated` | `false` | `MIGRATION_REPLICATED` (ClickHouse) |
121+
| `migrator.maxConnAttempts` | `1` | `MAX_CONN_ATTEMPTS` |
122+
| `migrator.compact` | `false` | `COMPACT` |
123+
| `migrator.dryRun` | `false` | `DRY_RUN` |
124+
| `migrator.placeholderCustom` | `""` | `PLACEHOLDER_CUSTOM` |
125+
| `migrator.extraEnv` | `[]` | Extra env entries |
126+
| `migrator.extraEnvFrom` | `[]` | Extra `envFrom` sources |
127+
| `release.enabled` | `true` | Render the release hook Job |
128+
| `release.command` | `release` | Command (`release` or `up`) |
129+
| `release.hookTypes` | `[pre-install, pre-upgrade]` | Helm hook phases |
130+
| `release.weight` | `"5"` | Hook weight |
131+
| `release.deletePolicy` | `before-hook-creation` | Hook delete policy |
132+
| `release.annotations` | `{werf.io/fail-mode: FailWholeDeployProcessImmediately}` | Extra Job annotations |
133+
| `rollback.enabled` | `false` | Render the rollback Job (opt-in) |
134+
| `rollback.command` | `rollback` | Command |
135+
| `rollback.hookTypes` | `[]` | Empty = plain Job; `[pre-rollback]` = hook |
136+
| `rollback.weight` | `"5"` | Hook weight (when hookTypes set) |
137+
| `rollback.deletePolicy` | `before-hook-creation` | Hook delete policy |
138+
| `rollback.annotations` | `{}` | Extra Job annotations |
139+
| `backoffLimit` | `0` | Job `backoffLimit` |
140+
| `activeDeadlineSeconds` | `3600` | Job `activeDeadlineSeconds` |
141+
| `ttlSecondsAfterFinished` | `30` | Job TTL after completion |
142+
| `resources` | `{}` | Container resources |
143+
| `serviceAccount.create` | `false` | Create a ServiceAccount |
144+
| `serviceAccount.name` | `""` | ServiceAccount name |
145+
| `nodeSelector` / `tolerations` / `affinity` | `{}` / `[]` / `{}` | Scheduling |
146+
| `podSecurityContext` / `securityContext` | `{}` | Security contexts |
147+
| `initContainers` / `extraVolumes` / `extraVolumeMounts` | `[]` | Migration delivery passthrough |
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Values used to render the chart in CI (`helm lint` / `helm template`).
2+
# rollback is enabled here so both Jobs are exercised during linting.
3+
image:
4+
repository: raoptimus/db-migrator
5+
tag: "1.7.0"
6+
7+
migrator:
8+
dsn:
9+
existingSecret: db-migrator-dsn
10+
secretKey: dsn
11+
path: /migrations
12+
table: migration
13+
14+
rollback:
15+
enabled: true
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
db-migrator has been deployed as release "{{ .Release.Name }}".
2+
3+
{{ if .Values.release.enabled -}}
4+
A migration Job runs the "{{ .Values.release.command }}" command as a Helm hook
5+
({{ join "," .Values.release.hookTypes }}), so it executes before the application pods roll out.
6+
7+
Watch its progress:
8+
kubectl -n {{ .Release.Namespace }} get job {{ include "db-migrator.fullname" . }}-release
9+
kubectl -n {{ .Release.Namespace }} logs job/{{ include "db-migrator.fullname" . }}-release
10+
{{- else -}}
11+
The release Job is disabled (release.enabled=false); no migrations were applied.
12+
{{- end }}
13+
14+
Rolling back the latest release batch (opt-in):
15+
# werf or plain Helm: enable the rollback Job explicitly
16+
helm upgrade --install {{ .Release.Name }} <chart> --reuse-values --set rollback.enabled=true
17+
# plain Helm alternative: wire it to `helm rollback`
18+
# --set rollback.hookTypes={pre-rollback}
19+
20+
Reminder: the image "{{ include "db-migrator.image" . }}" must contain the migration files
21+
at MIGRATION_PATH ({{ .Values.migrator.path }}). Build your own image FROM raoptimus/db-migrator
22+
with the migrations COPY'd in, or mount them via extraVolumes/extraVolumeMounts.

0 commit comments

Comments
 (0)