From 41b659ac89bf0a5fa05d4154ebe04f3537e951c8 Mon Sep 17 00:00:00 2001 From: John Gerassimou Date: Wed, 26 Aug 2026 12:24:22 -0400 Subject: [PATCH] Add Argo CD database migration mode --- .circleci/config.yml | 7 +++ .helmignore | 3 +- CHANGELOG.md | 6 +++ Chart.yaml | 2 +- README.md | 6 +++ README.md.gotmpl | 6 +++ templates/_helpers.tpl | 9 ++++ templates/dua-server-deployment.yaml | 5 ++ templates/hook-install-job.yaml | 2 + templates/hook-upgrade-job.yaml | 2 + templates/migration-job.yaml | 76 ++++++++++++++++++++++++++++ templates/scheduler-deployment.yaml | 5 ++ templates/server-deployment.yaml | 5 ++ templates/tests/test-connection.yaml | 2 + templates/worker-deployment.yaml | 5 ++ tests/test-migration-modes.sh | 72 ++++++++++++++++++++++++++ values.yaml | 8 +++ 17 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 templates/migration-job.yaml create mode 100755 tests/test-migration-modes.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 6912f7f..0b5fd0f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -103,6 +103,13 @@ jobs: helm repo add bitnami "https://charts.bitnami.com/bitnami" helm dependency build . + - run: + name: Verify migration modes + command: | + if [[ $HELM_VERSION =~ ^v3\. ]]; then + bash ./tests/test-migration-modes.sh + fi + - run: name: Generate test configuration command: | diff --git a/.helmignore b/.helmignore index 3f9f754..e663d77 100644 --- a/.helmignore +++ b/.helmignore @@ -24,8 +24,9 @@ # Ignore development oriented files .circleci/ scripts/ +tests/test-migration-modes.sh *.gotmpl .restyled.yaml .pre-commit-config.yaml -release/ \ No newline at end of file +release/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d67981..b352c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 3.1.0 + +- Add an ordinary, wave-ordered database migration Job for Argo CD releases. +- Preserve the existing install and upgrade hooks for Helm releases. +- Allow Helm test Pods to be disabled for Argo CD rendering. + ## 3.0.16 - Use `Recreate` for the scheduler Deployment. diff --git a/Chart.yaml b/Chart.yaml index e0e6895..bf5dae8 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v1 name: redash -version: 3.0.16 +version: 3.1.0 appVersion: 11.0.0-next description: Redash is an open source tool built for teams to query, visualize and collaborate. keywords: diff --git a/README.md b/README.md index c56ede7..522ee9f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,12 @@ $ helm delete my-release The command removes all the Kubernetes components associated with the chart and deletes the release. +## Database migrations + +The default `helm-hooks` mode preserves existing Helm behavior. For an Argo +CD-managed release, use `argocd-job` to run one ordinary migration Job before +the Deployments. Retry a failed migration by syncing the application again. + ## Requirements | Repository | Name | Version | diff --git a/README.md.gotmpl b/README.md.gotmpl index 2c95f4c..a0539df 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -61,6 +61,12 @@ $ helm delete my-release The command removes all the Kubernetes components associated with the chart and deletes the release. +## Database migrations + +The default `helm-hooks` mode preserves existing Helm behavior. For an Argo +CD-managed release, use `argocd-job` to run one ordinary migration Job before +the Deployments. Retry a failed migration by syncing the application again. + {{ template "chart.requirementsSection" . }} ## Configuration diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index c56399c..01fa152 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -13,6 +13,15 @@ Create chart name and version as used by the chart label. {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} +{{/* Validate and return the database migration mode. */}} +{{- define "redash.migrationMode" -}} +{{- $mode := default "helm-hooks" .Values.migration.mode -}} +{{- if not (has $mode (list "helm-hooks" "argocd-job")) -}} +{{- fail "migration.mode must be one of helm-hooks or argocd-job" -}} +{{- end -}} +{{- $mode -}} +{{- end -}} + {{/* Create a default fully qualified app name. We truncate at 43 chars because some Kubernetes name fields are limited to 64 (by the DNS naming spec), diff --git a/templates/dua-server-deployment.yaml b/templates/dua-server-deployment.yaml index b8387af..98d2672 100644 --- a/templates/dua-server-deployment.yaml +++ b/templates/dua-server-deployment.yaml @@ -1,4 +1,5 @@ {{- if .Values.duaServer.enabled }} +{{- $migrationMode := include "redash.migrationMode" . }} # Dedicated DUA server for long-lived /api/dua/* requests. # Keep shared application settings aligned with server-deployment.yaml. apiVersion: apps/v1 @@ -8,6 +9,10 @@ metadata: labels: {{- include "redash.labels" . | nindent 4 }} app.kubernetes.io/component: dua-server + {{- if eq $migrationMode "argocd-job" }} + annotations: + argocd.argoproj.io/sync-wave: "2" + {{- end }} spec: replicas: {{ .Values.duaServer.replicaCount }} strategy: diff --git a/templates/hook-install-job.yaml b/templates/hook-install-job.yaml index 73514d1..fa074d9 100644 --- a/templates/hook-install-job.yaml +++ b/templates/hook-install-job.yaml @@ -1,3 +1,4 @@ +{{- if eq (include "redash.migrationMode" .) "helm-hooks" }} apiVersion: batch/v1 kind: Job metadata: @@ -69,3 +70,4 @@ spec: tolerations: {{ toYaml . | indent 8 }} {{- end }} +{{- end }} diff --git a/templates/hook-upgrade-job.yaml b/templates/hook-upgrade-job.yaml index 1502798..ebd92d5 100644 --- a/templates/hook-upgrade-job.yaml +++ b/templates/hook-upgrade-job.yaml @@ -1,3 +1,4 @@ +{{- if eq (include "redash.migrationMode" .) "helm-hooks" }} apiVersion: batch/v1 kind: Job metadata: @@ -69,3 +70,4 @@ spec: tolerations: {{ toYaml . | indent 8 }} {{- end }} +{{- end }} diff --git a/templates/migration-job.yaml b/templates/migration-job.yaml new file mode 100644 index 0000000..360ea8e --- /dev/null +++ b/templates/migration-job.yaml @@ -0,0 +1,76 @@ +{{- if eq (include "redash.migrationMode" .) "argocd-job" }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ printf "%s-db-migration" (include "redash.fullname" .) }} + labels: + {{- include "redash.labels" . | nindent 4 }} + app.kubernetes.io/component: database-migration + annotations: + argocd.argoproj.io/sync-wave: "1" + argocd.argoproj.io/sync-options: Force=true,Replace=true +spec: + backoffLimit: 0 + template: + metadata: + labels: + {{- include "redash.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: database-migration + {{- with .Values.commonLabels }} + {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} + annotations: + sidecar.istio.io/inject: "false" + spec: + automountServiceAccountToken: false + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "redash.serviceAccountName" . }} + restartPolicy: Never + securityContext: + {{- toYaml .Values.hookUpgradeJob.podSecurityContext | nindent 8 }} + containers: + - name: database-migration + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.hookUpgradeJob.securityContext | nindent 12 }} + command: + - /bin/bash + - -c + args: + - '. /config/dynamicenv.sh && exec /app/manage.py db upgrade' + volumeMounts: + - name: config + mountPath: /config + env: + {{- include "redash.env" . | nindent 12 }} + {{- range $key, $value := .Values.server.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + envFrom: + {{- include "redash.envFrom" . | nindent 12 }} + {{- with .Values.hookUpgradeJob.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "redash.fullname" . }} + {{- with .Values.hookUpgradeJob.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hookUpgradeJob.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hookUpgradeJob.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/templates/scheduler-deployment.yaml b/templates/scheduler-deployment.yaml index cc541f3..7253bda 100644 --- a/templates/scheduler-deployment.yaml +++ b/templates/scheduler-deployment.yaml @@ -1,4 +1,5 @@ {{- if not .Values.skipSchedulerDeployment }} +{{- $migrationMode := include "redash.migrationMode" . }} apiVersion: apps/v1 kind: Deployment metadata: @@ -6,6 +7,10 @@ metadata: labels: {{- include "redash.labels" . | nindent 4 }} app.kubernetes.io/component: scheduler + {{- if eq $migrationMode "argocd-job" }} + annotations: + argocd.argoproj.io/sync-wave: "2" + {{- end }} spec: replicas: {{ .Values.scheduler.replicaCount }} strategy: diff --git a/templates/server-deployment.yaml b/templates/server-deployment.yaml index 14cf850..12339a2 100644 --- a/templates/server-deployment.yaml +++ b/templates/server-deployment.yaml @@ -1,3 +1,4 @@ +{{- $migrationMode := include "redash.migrationMode" . }} apiVersion: apps/v1 kind: Deployment metadata: @@ -5,6 +6,10 @@ metadata: labels: {{- include "redash.labels" . | nindent 4 }} app.kubernetes.io/component: server + {{- if eq $migrationMode "argocd-job" }} + annotations: + argocd.argoproj.io/sync-wave: "2" + {{- end }} spec: replicas: {{ .Values.server.replicaCount }} strategy: diff --git a/templates/tests/test-connection.yaml b/templates/tests/test-connection.yaml index e6176ab..2e2a4dd 100644 --- a/templates/tests/test-connection.yaml +++ b/templates/tests/test-connection.yaml @@ -1,3 +1,4 @@ +{{- if .Values.tests.enabled }} apiVersion: v1 kind: Pod metadata: @@ -14,3 +15,4 @@ spec: command: ['sh'] args: ['-c', 'curl --silent --show-error -L --max-redirs 3 --retry 3 --retry-connrefused --retry-delay 10 --max-time 30 "http://{{ include "redash.fullname" . }}:{{ .Values.service.port }}" | fgrep "Welcome to Redash"'] restartPolicy: Never +{{- end }} diff --git a/templates/worker-deployment.yaml b/templates/worker-deployment.yaml index 8df5818..a0bf580 100644 --- a/templates/worker-deployment.yaml +++ b/templates/worker-deployment.yaml @@ -1,4 +1,5 @@ {{ range $key, $worker := .Values.workers }} +{{- $migrationMode := include "redash.migrationMode" $ }} apiVersion: apps/v1 kind: Deployment metadata: @@ -6,6 +7,10 @@ metadata: labels: {{- include "redash.labels" $ | nindent 4 }} app.kubernetes.io/component: {{ $worker.name }} + {{- if eq $migrationMode "argocd-job" }} + annotations: + argocd.argoproj.io/sync-wave: "2" + {{- end }} spec: replicas: {{ $worker.replicaCount }} selector: diff --git a/tests/test-migration-modes.sh b/tests/test-migration-modes.sh new file mode 100755 index 0000000..71c0579 --- /dev/null +++ b/tests/test-migration-modes.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/redash-chart-migrations.XXXXXX")" +trap 'rm -rf -- "$tmp_dir"' EXIT + +base_args=( + --namespace default + --set postgresql.enabled=false + --set redis.enabled=false + --set-string externalPostgreSQL=postgresql://redash:password@postgresql:5432/redash + --set-string externalRedis=redis://redis:6379/0 + --set redash.secretKey=test + --set redash.cookieSecret=test +) + +default_render="$tmp_dir/default.yaml" +helm template redash "$repo_root" "${base_args[@]}" >"$default_render" +[[ "$(grep -c '^kind: Job$' "$default_render")" -eq 2 ]] +[[ "$(grep -c 'helm.sh/hook: post-' "$default_render")" -eq 2 ]] + +legacy_render="$tmp_dir/legacy.yaml" +helm template redash "$repo_root/release/redash-3.0.16.tgz" \ + "${base_args[@]}" >"$legacy_render" +sed -E 's/helm.sh\/chart: redash-[^[:space:]]+/helm.sh\/chart: redash-VERSION/' \ + "$default_render" >"$tmp_dir/default-normalized.yaml" +sed -E 's/helm.sh\/chart: redash-[^[:space:]]+/helm.sh\/chart: redash-VERSION/' \ + "$legacy_render" >"$tmp_dir/legacy-normalized.yaml" +diff -u "$tmp_dir/legacy-normalized.yaml" "$tmp_dir/default-normalized.yaml" + +argo_render="$tmp_dir/argocd.yaml" +helm template redash "$repo_root" "${base_args[@]}" \ + --set migration.mode=argocd-job \ + --set tests.enabled=false >"$argo_render" + +[[ "$(grep -c '^kind: Job$' "$argo_render")" -eq 1 ]] +grep -Fq 'name: redash-db-migration' "$argo_render" +grep -Fq 'argocd.argoproj.io/sync-wave: "1"' "$argo_render" +grep -Fq 'argocd.argoproj.io/sync-options: Force=true,Replace=true' "$argo_render" +grep -Fq '. /config/dynamicenv.sh && exec /app/manage.py db upgrade' "$argo_render" +grep -Fq 'sidecar.istio.io/inject: "false"' "$argo_render" +grep -Fq 'backoffLimit: 0' "$argo_render" + +deployment_count="$(grep -c '^kind: Deployment$' "$argo_render")" +[[ "$deployment_count" -gt 0 ]] +[[ "$deployment_count" -eq "$(grep -c 'argocd.argoproj.io/sync-wave: \"2\"' "$argo_render")" ]] + +if grep -Eq 'helm.sh/hook|argocd.argoproj.io/hook|^kind: Pod$|release-database|database-head|sha256:' "$argo_render"; then + echo "Argo CD migration mode rendered unsupported migration machinery" >&2 + exit 1 +fi + +if helm template redash "$repo_root" "${base_args[@]}" \ + --set migration.mode=disabled >"$tmp_dir/invalid.out" 2>"$tmp_dir/invalid.err"; then + echo "invalid migration mode unexpectedly rendered" >&2 + exit 1 +fi +grep -Fq 'migration.mode must be one of' "$tmp_dir/invalid.err" + +builtin_postgresql_render="$tmp_dir/builtin-postgresql.yaml" +helm template redash "$repo_root" \ + --namespace default \ + --set redash.secretKey=test \ + --set redash.cookieSecret=test \ + --set-string postgresql.postgresqlPassword=test \ + --set migration.mode=argocd-job \ + --set tests.enabled=false >"$builtin_postgresql_render" +grep -Fq 'name: REDASH_DATABASE_HOSTNAME' "$builtin_postgresql_render" +grep -Fq '/app/manage.py db upgrade' "$builtin_postgresql_render" + +echo "migration mode render tests passed" diff --git a/values.yaml b/values.yaml index 9d5880f..f0b7bde 100644 --- a/values.yaml +++ b/values.yaml @@ -15,6 +15,14 @@ imagePullSecrets: [] nameOverride: "" fullnameOverride: "" +# migration.mode -- Database migration integration. Use helm-hooks for Helm releases or argocd-job for Argo CD. +migration: + mode: helm-hooks + +# tests.enabled -- Render the Helm test Pod. +tests: + enabled: true + # commonLabels -- Labels applied to all chart resources and pod templates. Values are rendered as templates. commonLabels: {}