diff --git a/.agents/architecture.md b/.agents/architecture.md index 44f11e2..da3b7c6 100644 --- a/.agents/architecture.md +++ b/.agents/architecture.md @@ -38,17 +38,79 @@ editing templates or values. name + instance + component. `commonLabels` / `podLabels` must never leak into a selector. `app.kubernetes.io/component` separates the two services' Services within one release. -9. **Component fullnames truncate the base BEFORE suffixing** - (`trunc 52` then `-server` / `-ui` / engine suffix), so long release names - cannot collapse every resource onto one identical name. -10. **The migration Job shares the deployment's env by construction.** - `authup.server.configEnv` (map) and `authup.server.secretEnv` (list) are - the single sources consumed by both `server/deployment.yaml` and +9. **Component fullnames truncate the base BEFORE suffixing, on a budget + derived from the suffix.** `authup.component.fullname` + (`dict "context" $ "suffix" "server"`) is the single implementation; every + component name and the migration Job go through it. Truncating first is what + keeps names DISTINCT (a 63-char fullname would otherwise collapse every + component onto one name); deriving the budget is what keeps them LEGAL. + + The ceiling is 63, not the 253 a ConfigMap allows, wherever a name becomes a + DNS-1035 label (Service) or a label value (a Job name is copied into the + `job-name` pod labels). The old flat `trunc 52` ignored that: `-admin-console` + rendered a 66-char Service, so any release name from ~43 characters up could + not install at all, and appending `-migration` to the `-server` name reached + 69. Both are now `min 52 (63 - len(suffix) - 1)`. + + `min 52` is the load-bearing half. The derived budget is WIDER than 52 for + short suffixes, and widening RENAMES resources on releases whose fullname + lands between 53 and 55 characters. A renamed Secret carrying + `helm.sh/resource-policy: keep` orphans the old one and generates a new admin + password: a silent credential rotation on upgrade. **The budget may only ever + tighten**, which by construction touches only names too long to exist. Assert + that when changing it (see testing.md), do not assume it. +10. **The migration Job shares the deployment's env by construction, minus + what a hook cannot see.** `authup.server.configEnv` (map), + `authup.server.secretEnv` (list) and the two volume helpers are the single + sources consumed by both `server/deployment.yaml` and `server/migration-job.yaml`; the Job INLINES the config map (a pre-upgrade hook would otherwise run against the previous release's ConfigMap). The Job is pre-upgrade ONLY (never pre-install: hooks run before backing services exist; authup migrates at boot on fresh installs). With - `useHelmHooks=false` it renders ArgoCD `PreSync` hook annotations instead. + `useHelmHooks=false` it renders ArgoCD `PreSync` hook annotations instead, + which is an ArgoCD-only mode: see rule 19. + + Helm applies a pre-upgrade hook BEFORE the release manifest, so every + NON-HOOK resource the Job references must already exist from the PREVIOUS + release. A hook resource at a lower weight is the one exception: it is + created earlier in the same hook phase, which is exactly what the config + copy below relies on. Four + helpers take a `hook` flag (`secretEnv`, the two volume helpers and + `configurationConfigMapName`; `configEnv` does not, it is inlined instead) + and drop what `migration run` does not read. That flag is the ONE mechanism + for this: the theme volume used to be a pair of deployment-only defines + carved out for the same reason, and two conventions in one `volumeMounts:` + block is how the next mount ends up on the wrong side. `themeEnv` stays + separate because it splits along a different axis. Dropped: + `REDIS`, `SMTP` (their Secrets are release resources, and the migration + builds no cache or mail module) and the provisioning mount (`ProvisionerModule` + is registered by the start command only). What stays, stays for a reason: + the writable directory, because under the image's `NODE_ENV=production` the + logger opens `/http.log` before the first query and an uncreatable + path is a hard ENOENT; and the config file, because `migration run` loads + `authup.server.core.conf` unconditionally and its file-only db keys (`ssl`, + `socketPath`, `replication`, `extensions`) decide how the migration connects. + The Job reads that file from a hook-scoped COPY + (`server/configmap-migration-configuration.yaml`, weight -5) for the same + reason it inlines the env: the release ConfigMap is either absent or one + release stale when the hook runs. `USER_ADMIN_PASSWORD` and + `CLIENT_SYSTEM_SECRET` go the same way: no identity or provisioning module + on the migration path, and the auth Secret they read is itself a release + resource. `SECRETS_ENCRYPTION_KEY` deliberately does NOT, even though its + key is conditional too and the migration does not read it today: rule 6's + fail-closed posture outranks the one-off break, so a write-once KEK gets its + own upgrade. + + What the flag cannot reach, i.e. the residuals to keep in mind when adding + anything to the Job: `DB_PASSWORD` (the Secret behind it changes on an engine + switch, on adopting a built-in engine after `externalDatabase`, and on a + first inline `externalDatabase.password`, since `secret-db.yaml` is a release + resource too); the `serviceAccountName`, whose ServiceAccount renders only + under `serviceAccount.create`, so flipping that on fails pod ADMISSION with + no container status to read; and the `extraEnvVarsCM` / `extraEnvVarsSecret` + / `extraVolumes` passthroughs, whose targets are operator-owned unless the + operator ships them through `extraDeploy`, which renders them into the + release manifest and therefore after the hook. 11. **Checksum annotations roll pods on config or secret changes.** The server deployment checksums the env map plus every chart-managed secret it consumes (auth, external-db, redis, smtp, provisioning, configuration), @@ -114,6 +176,19 @@ editing templates or values. `validations.yaml` fails that combination; `route.matches` / `route.filters` are the raw passthroughs that express it (authup always serves at `/`, so the prefix must be matched AND rewritten away). +19. **`useHelmHooks=false` is an ArgoCD-only mode.** ArgoCD renders with + `helm template` and never executes Helm hooks, so it needs its own + `argocd.argoproj.io/hook` annotations. Flux is the opposite: helm-controller + runs a real `helm upgrade` and honours Helm hooks natively. Turning them off + there applies the migration Job as an ordinary release resource, and + `Job.spec.template` is immutable, so the next upgrade that touches the pod + template (image tag, `appVersion` label, a new env) fails to patch it. A + content-hashed Job name would make that apply-able but not correct: helm + orders a plain Job AFTER the Deployment and does not wait for it, which is + the ordering the Job exists to provide. So the value stays doc-scoped to + ArgoCD and NOTES warns when it is set. ArgoCD also maps Helm hooks onto its + own sync phases, so `true` works there as well; the flag only chooses which + annotation family drives the Job. ## Values conventions diff --git a/.agents/references/authup.md b/.agents/references/authup.md index 59b5118..6c4110c 100644 --- a/.agents/references/authup.md +++ b/.agents/references/authup.md @@ -65,6 +65,27 @@ unsupported per `.agents/architecture.md` in the monorepo), - server-core auto-runs migrations + provisioning at boot (`app/modules/database/module.ts`; no off-switch) -> generous startupProbe; optional pre-upgrade migration Job for multi-replica DDL serialization. +- `migration run` (`cli/commands/migration.ts`, `defineCLIMigrationCommand`) + builds only three modules: config, logger, database. No http, cache, mail, + identity or provisioning module. It therefore ignores `REDIS` / `SMTP`, and + never scans `/provisioning` (`ProvisionerModule` is registered by + `createApplication()`, i.e. the `start` command only) -> the chart drops all + three from the migration Job. +- `migration run` DOES read the config file, unconditionally: `createCLIConfigModule` + passes `fs: {}` (truthy) into `readConfig`, so `readConfigRawFromFS` runs + (`config/read/fs.ts`). Env wins per key, but the db keys typeorm-extension's + env reader does not name survive: `ssl`, `socketPath`, `replication`, + `poolSize`, `charset`, `extensions` (postgres `CREATE EXTENSION` during + `initialize()`). So the config file decides how the migration connects and + what it creates -> the chart MUST mount it on the Job. (`entities` and + `subscribers` are NOT in that set: `DB_ENTITIES` / `DB_SUBSCRIBERS` exist. + Dump the real list with + `grep -rhoE "DB_[A-Z_]+" node_modules/typeorm-extension/dist | sort -u`.) +- Under `NODE_ENV=production` (baked into the image) `migration run` needs the + writable directory before it touches the database: the logger adds winston + File transports for `/http.log` and `/error.log`, and the + transport does `mkdirSync` + open eagerly. An unwritable path is a hard ENOENT + failure of the command, not a degradation -> the Job keeps the writable mount. - Replicas > 1 without redis: per-process MemoryCache breaks auth codes, revocations, MFA challenges (`app/modules/cache/module.ts`) -> hard validation in the chart. diff --git a/.agents/testing.md b/.agents/testing.md index 8ffaf33..9d4cd59 100644 --- a/.agents/testing.md +++ b/.agents/testing.md @@ -12,7 +12,7 @@ matrix. | Render matrix | `make template` | template errors across every `ci/*-values.yaml` | | Values coverage | `make lint-values-coverage` | `.Values.*` paths missing from values.yaml (strict-schema dead features) | | Drift gates (CI) | `make docs` / `make schema` + `git status --porcelain` | uncommitted regenerations of README.md / values.schema.json | -| ct install (CI) | kind cluster, one install per `ci/*-values.yaml` | real boot: DB provisioning, probes, migrations | +| ct install (CI) | kind cluster, per `ci/*-values.yaml`: install, plus two upgrades | real boot: DB provisioning, probes, migrations, and pre-upgrade hooks | `make test` runs lint + template + coverage locally. @@ -49,6 +49,7 @@ helm template t charts/authup --set server.config.PUBLIC_URL=http://x # f helm template t charts/authup --set server.config.WRITABLE_DIRECTORY_PATH=/x # ditto; the chart pins this one to the path it mounts helm template t charts/authup --set 'server.route.enabled=yes' # flag that is neither true nor false helm template t charts/authup --set adminConsole.enabled=false --set adminConsole.route.enabled=yes # ditto: validated even with the component off +helm template t charts/authup --set 'server.configuration=logger: true' --set server.existingConfigmap=cm # both config carriers helm template t charts/authup --set server.theme.enabled=true # theme with no carrier helm template t charts/authup --set server.theme.enabled=true --set server.theme.title=X --set server.theme.existingConfigMap=cm # manifest + existing CM helm template t charts/authup --set server.theme.enabled=true --set server.theme.logo=logo.svg # asset outside assets/ @@ -83,6 +84,70 @@ flag, so all six read sites convert together: leave one raw and an umbrella-driv route renders unguarded. `ci/default-values.yaml` carries the false direction as the in-repo regression guard. +The pre-upgrade migration Job must stay narrower than the Deployment. Helm +applies a hook before the release manifest, so anything the Job references has +to exist from the previous release: + +```bash +helm template t charts/authup --set server.migration.enabled=true \ + --set valkey.enabled=true --set smtp.connectionString=smtp://u:p@mail:25 \ + --set auth.systemClientEnabled=true \ + --set server.provisioning.enabled=true --set 'server.provisioning.files.realms\.json=[]' \ + --set 'server.configuration=db: {ssl: true}' \ + -s templates/server/migration-job.yaml +``` + +The Job's only secret-backed env must be `DB_PASSWORD` (plus +`SECRETS_ENCRYPTION_KEY` when the KEK is set): no `REDIS`, no `SMTP`, no +`USER_ADMIN_PASSWORD`, no `CLIENT_SYSTEM_SECRET`. Volumes `writable` / `tmp` / +`configuration` but NO `provisioning`; and the configuration volume must name +`-server-migration-configuration` +(the hook-scoped copy at weight -5), never `-server-configuration`. The +server Deployment in the same render must still carry all of them. Dropping the +config file from the Job is NOT a valid simplification: `migration run` reads it +and its file-only db keys (`ssl`, `socketPath`, `extensions`) govern the +connection, so a missing mount migrates over a plaintext connection instead of +failing. + +Names have two ceilings, not one (rule 9). 63 applies to a Service (DNS-1035 +label) and to a Job (its name becomes a `job-name` label value); 253 applies to +ConfigMaps and Secrets. Audit every rendered name at the longest release name +helm accepts: + +```bash +helm template $(python3 -c "print('n'*53)") charts/authup \ + --set valkey.enabled=true --set server.migration.enabled=true | python3 -c " +import sys, yaml +for d in yaml.safe_load_all(sys.stdin): + if d and d['kind'] in ('Service','Job') and len(d['metadata']['name']) > 63: + print('OVER 63:', d['kind'], d['metadata']['name']) +" +``` + +Must print nothing. The stronger property, and the one to assert whenever the +budget in `authup.component.fullname` changes, is that **no name changes for a +release that could already install**: render every release-name length 3..53 on +both `origin/master` and the branch, and check that the two name sets differ only +at lengths where master already emitted an over-63 Service or Job. Widening the +budget silently renames resources, and a renamed `resource-policy: keep` Secret +regenerates the admin password. + +`useHelmHooks=false` must print the Flux/plain-helm warning in NOTES.txt, and +must not print it with hooks on. NOTES is not reachable through `helm template`, +and `.Files.Get "templates/NOTES.txt"` does NOT work either (helm excludes +`templates/` from `.Files`, so the wrapper renders empty and BOTH directions +"pass"). Inline the raw template text into a generated template instead: + +```bash +cp -r charts/authup /tmp/nc +{ printf 'apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: notes\ndata:\n notes: |\n'; \ + sed 's/^/ /' /tmp/nc/templates/NOTES.txt; } > /tmp/nc/templates/zz-notes.yaml +helm template t /tmp/nc --set server.migration.enabled=true --set useHelmHooks=false \ + -s templates/zz-notes.yaml | grep -c 'useHelmHooks=false' # must be >0 +helm template t /tmp/nc --set server.migration.enabled=true \ + -s templates/zz-notes.yaml | grep -c 'useHelmHooks=false' # must be 0 +``` + Umbrella use is part of the contract: `global` must stay open. Render a throwaway parent chart with authup in `charts/` and an unrelated global (`global.myOrgKey`) whenever the schema generation changes; `ci/default-values.yaml` carries a stray @@ -110,6 +175,16 @@ The generated `values.schema.json` must keep catching typos (the external-db scenario's throwaway postgres + secrets live there). - The kind job only runs when `ct list-changed` reports chart changes, so docs-only PRs stay fast. -- `--timeout 600s` accounts for first-pull of the authup image plus boot-time - migrations; server-core's startupProbe budget (60 x 5s) covers create-db + +- `upgrade: true` (in `.github/configs/ct.yaml`) is what puts the pre-upgrade + migration Job on a real cluster at all: a plain `helm install` skips + `pre-upgrade` hooks entirely, so without it the Job and its hook-scoped + ConfigMap are render-tested only. Per values file ct then runs the chart on + `master` and upgrades to this revision, then installs this revision and + upgrades it to itself. The first leg is skipped once a release bumps the + middle digit, because ct reads that as a breaking change for a 0.x chart + (`~0.x.y` constraint); the self-upgrade leg always runs. Budget roughly 3x + the install-only runtime. +- `--timeout 600s` is passed to install AND upgrade (ct hands `helm-extra-args` + to both), so it also has to cover hook execution. It accounts for first-pull + of the authup image plus boot-time migrations; server-core's startupProbe budget (60 x 5s) covers create-db + migrate + provision on first boot. diff --git a/.github/configs/ct.yaml b/.github/configs/ct.yaml index 8cb618c..b0c5757 100644 --- a/.github/configs/ct.yaml +++ b/.github/configs/ct.yaml @@ -3,6 +3,12 @@ target-branch: master chart-dirs: - charts helm-extra-args: --timeout 600s +# Runs two real `helm upgrade`s per ci values file: master's chart -> this +# revision, then this revision -> itself. Without it no pre-upgrade hook is ever +# created on a cluster, so the migration Job and its hook-scoped ConfigMap are +# render-tested only. ct skips the first leg once a release bumps the middle +# digit (0.x treats that as breaking); the self-upgrade leg always runs. +upgrade: true check-version-increment: false validate-maintainers: false lint-conf: .github/configs/lintconf.yaml diff --git a/DESIGN.md b/DESIGN.md index 0632940..38cbee1 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -321,7 +321,15 @@ first-class templating, in v1. Job serializes DDL before new pods roll — recommended (and referenced by the replicas>1 validation) for multi-replica deployments, since MySQL DDL is non-transactional and concurrent boot migrations can race. -- `useHelmHooks: false` support (ArgoCD/Flux users get a plain Job). +- `useHelmHooks: false` support: ArgoCD only. ArgoCD renders with + `helm template` and never runs Helm hooks, so it gets `argocd.argoproj.io` + annotations instead. Flux runs a real `helm upgrade` and honours Helm hooks, + so a plain Job there hits the immutable `spec.template` on the next upgrade. +- The hook Job sees only the PREVIOUS release's ConfigMaps and Secrets, so it + carries a narrowed env/mount set (DB_PASSWORD and the encryption key only, no + provisioning mount) plus a + hook-scoped copy of `authup.server.core.conf`, which `migration run` does + read. - Value reshuffles get authentik-style tripwires: a `deprecations.yaml` template fails loudly naming the moved key. BREAKING.md tracks migrations; chart versioning is independent SemVer (0.major.minor pre-1.0), `appVersion` tracks diff --git a/charts/authup/BREAKING.md b/charts/authup/BREAKING.md index 3bd34a2..180d316 100644 --- a/charts/authup/BREAKING.md +++ b/charts/authup/BREAKING.md @@ -5,6 +5,23 @@ land on the middle digit. Every entry lists the value migrations required. ## Next release (unreleased) +- Component resource names are truncated on a budget derived from their suffix + (`min 52 (63 - len(suffix) - 1)`) rather than a flat `trunc 52`, so the + 63-character limit that applies to a Service name and to a Job name is + respected. Only names that were already too long to exist change: with a + release name from roughly 43 characters up, the admin-console Service was 66 + characters and the API server rejected it, so the release could not install at + all; the migration Job reached 69. Nothing to migrate, since no cluster can + hold a release in that range. Verified by rendering every release-name length + from 3 to 53 against the previous revision: the name sets differ at no length + where the old chart was installable. +- Setting BOTH `server.configuration` and `server.existingConfigmap` now fails + the render. It never worked: the existing ConfigMap is the one that gets + mounted, so the inline content was silently dropped, and that content is + typically where `db.ssl` / `socketPath` / `replication` live, i.e. how the + server pods and the pre-upgrade migration hook connect to the database. Move + the inline content into the referenced ConfigMap, or drop + `server.existingConfigmap`. - The writable directory moves from `/usr/src/app/writable` to `/var/lib/authup`, following the image (authup/authup#3474, shipped in v1.0.0-beta.63). The chart mounts an emptyDir there, so nothing persists across the change; only a diff --git a/charts/authup/Chart.yaml b/charts/authup/Chart.yaml index f37db84..cb90326 100644 --- a/charts/authup/Chart.yaml +++ b/charts/authup/Chart.yaml @@ -37,3 +37,13 @@ annotations: description: Track authup v1.0.0-beta.63, whose image writes to /var/lib/authup - kind: added description: server.route.enabled / adminConsole.route.enabled accept a tpl-rendered string, so an umbrella chart can drive them from a global + - kind: fixed + description: The pre-upgrade migration Job no longer references release resources that do not exist when the hook runs; it drops the provisioning mount and every secret env migration run does not read (REDIS, SMTP, USER_ADMIN_PASSWORD, CLIENT_SYSTEM_SECRET), keeping only DB_PASSWORD and the encryption key + - kind: fixed + description: The migration Job reads authup.server.core.conf from a hook-scoped copy, so it no longer runs against a missing or one-release-stale config file + - kind: changed + description: useHelmHooks=false is documented as ArgoCD-only; under Flux or plain helm it makes the Job a plain resource whose immutable pod template fails the next upgrade + - kind: fixed + description: Component names now derive their truncation budget from the suffix, so the 63-character limit that applies to a Service name and to a Job name is respected; release names from about 43 characters up previously rendered an admin-console Service the API server rejects, and the migration Job reached 69 characters. Only names that were already too long to exist change + - kind: changed + description: Setting both server.configuration and server.existingConfigmap now fails the render instead of silently dropping the inline content, which is usually where db.ssl and socketPath live diff --git a/charts/authup/README.md b/charts/authup/README.md index d5e6879..1319eb6 100644 --- a/charts/authup/README.md +++ b/charts/authup/README.md @@ -484,7 +484,7 @@ Kubernetes: `>=1.25.0-0` | smtp.connectionString | string | `""` | SMTP connection string (smtp(s)://user:pass@host:port); stored in a chart-managed secret | | smtp.existingSecret | string | `""` | Existing secret holding the SMTP connection string (tpl-rendered) | | smtp.existingSecretKey | string | `"smtp-connection-string"` | Key inside smtp.existingSecret holding the connection string | -| useHelmHooks | bool | `true` | Render Job hook annotations (set false for ArgoCD / Flux) | +| useHelmHooks | bool | `true` | Render Helm hook annotations on the migration Job. Set false only for ArgoCD, which reads its own PreSync annotations instead (it also understands Helm hooks, so true works there too). Flux and plain helm need true: a plain Job's pod template is immutable, so the next upgrade cannot patch it. | | valkey.affinity | object | `{}` | Valkey affinity | | valkey.auth.password | string | `""` | Valkey password ("" = generate once, keep across upgrades) | | valkey.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"runAsGroup":999,"runAsNonRoot":true,"runAsUser":999,"seccompProfile":{"type":"RuntimeDefault"}}` | Valkey container security context | diff --git a/charts/authup/ci/valkey-values.yaml b/charts/authup/ci/valkey-values.yaml index ccfa0a0..b46d4e0 100644 --- a/charts/authup/ci/valkey-values.yaml +++ b/charts/authup/ci/valkey-values.yaml @@ -8,6 +8,13 @@ server: replicaCount: 2 migration: enabled: true + # The only ci scenario setting server.configuration, which with ct's + # `upgrade: true` makes the master-to-branch leg the upgrade that FIRST + # introduces the config file while the migration hook is on: the exact case + # the hook-scoped copy exists for. Deliberately a no-op option: a db block + # here would fight the connection this scenario installs. + configuration: | + logger: true resources: requests: cpu: 50m diff --git a/charts/authup/templates/NOTES.txt b/charts/authup/templates/NOTES.txt index 1ee7dd2..6dc0add 100644 --- a/charts/authup/templates/NOTES.txt +++ b/charts/authup/templates/NOTES.txt @@ -84,6 +84,17 @@ schema migrations are serialized in a pre-upgrade Job instead of racing at pod boot. {{- end }} +{{- if and .Values.server.enabled .Values.server.migration.enabled (not .Values.useHelmHooks) }} + +WARNING: useHelmHooks=false renders the migration Job as a plain resource +carrying ArgoCD PreSync annotations, which only ArgoCD reads. Under Flux or +plain helm the Job is applied like any other resource, and a Job pod template +is immutable, so the next upgrade that changes it (new image tag, new env) +fails with "spec.template: field is immutable". Use useHelmHooks=true there. +ArgoCD maps Helm hook annotations onto its own sync phases, so true works for +ArgoCD too; false only changes which annotation family drives the Job. +{{- end }} + {{- if .Values.server.provisioning.enabled }} NOTE: "admin-console" and "account-console" are reserved client names: authup diff --git a/charts/authup/templates/_helpers.tpl b/charts/authup/templates/_helpers.tpl index ad125b4..dd774e3 100644 --- a/charts/authup/templates/_helpers.tpl +++ b/charts/authup/templates/_helpers.tpl @@ -23,27 +23,50 @@ Release-scoped fully qualified name. Every resource name derives from this. {{/* Per-component names. The base is truncated BEFORE suffixing so the component -suffix always survives — otherwise a 63-char fullname would collapse every +suffix always survives: otherwise a 63-char fullname would collapse every component onto one identical name. + +The budget is DERIVED from the suffix, not hardcoded, because the ceiling that +actually bites is 63 and not the 253 a ConfigMap allows: a Service name is a +DNS-1035 label, and a Job name is copied into the job-name pod labels where a +label value stops at 63. A flat `trunc 52` let the 13-character +`-admin-console` suffix render a 66-character Service that the API server +rejects outright, so a long release name could not install at all. + +`min 52` is load-bearing, not decoration. The derived budget is WIDER than 52 +for short suffixes, and widening would RENAME resources on releases whose +fullname lands between 53 and 55 characters. A renamed Secret carrying +`helm.sh/resource-policy: keep` means the old one is orphaned and a new +admin password and system-client secret are generated: a silent credential +rotation on upgrade. The budget may therefore only ever tighten, which by +construction touches only names that are already too long to exist. + +Usage: {{ include "authup.component.fullname" (dict "context" $ "suffix" "server") }} */}} +{{- define "authup.component.fullname" -}} +{{- $suffix := .suffix -}} +{{- $budget := min 52 (sub 63 (add1 (len $suffix))) | int -}} +{{- printf "%s-%s" (include "authup.fullname" .context | trunc $budget | trimSuffix "-") $suffix -}} +{{- end -}} + {{- define "authup.server.fullname" -}} -{{- printf "%s-server" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} +{{- include "authup.component.fullname" (dict "context" . "suffix" "server") -}} {{- end -}} {{- define "authup.adminConsole.fullname" -}} -{{- printf "%s-admin-console" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} +{{- include "authup.component.fullname" (dict "context" . "suffix" "admin-console") -}} {{- end -}} {{- define "authup.postgresql.fullname" -}} -{{- printf "%s-postgresql" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} +{{- include "authup.component.fullname" (dict "context" . "suffix" "postgresql") -}} {{- end -}} {{- define "authup.mysql.fullname" -}} -{{- printf "%s-mysql" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} +{{- include "authup.component.fullname" (dict "context" . "suffix" "mysql") -}} {{- end -}} {{- define "authup.valkey.fullname" -}} -{{- printf "%s-valkey" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} +{{- include "authup.component.fullname" (dict "context" . "suffix" "valkey") -}} {{- end -}} {{/* diff --git a/charts/authup/templates/_server-env.tpl b/charts/authup/templates/_server-env.tpl index 615628f..eeb9fb3 100644 --- a/charts/authup/templates/_server-env.tpl +++ b/charts/authup/templates/_server-env.tpl @@ -55,47 +55,70 @@ WRITABLE_DIRECTORY_PATH: "/var/lib/authup" {{/* Secret-backed server-core env entries (valueFrom.secretKeyRef list). Shared by the Deployment and the migration Job. +Usage: {{ include "authup.server.secretEnv" (dict "context" $ "hook" true) }} + +"hook" marks the pre-upgrade migration Job and drops REDIS and SMTP. Not +tidiness: both Secrets are ordinary release resources, and helm applies a +pre-upgrade hook BEFORE the release manifest, so the upgrade that first enables +valkey or SMTP would schedule a hook pod whose secretKeyRef target does not +exist yet (CreateContainerConfigError until the hook times out). `migration run` +builds config + logger + database only, no cache and no mail module, so neither +value is read there. USER_ADMIN_PASSWORD and CLIENT_SYSTEM_SECRET go too: the +migration builds no identity or provisioning module either, and the auth Secret +they read is itself a release resource (an upgrade dropping auth.existingSecret +for a chart-managed one creates it only AFTER the hook), on top of +CLIENT_SYSTEM_SECRET's key being conditional. The hook keeps exactly two: +DB_PASSWORD, without which the migration cannot run, and the KEK (see below). */}} {{- define "authup.server.secretEnv" -}} +{{- $ctx := required "authup.server.secretEnv: call it as (dict \"context\" $ \"hook\" bool)" .context -}} - name: DB_PASSWORD valueFrom: secretKeyRef: - name: {{ include "authup.database.secretName" . }} - key: {{ include "authup.database.passwordKey" . }} -{{- if include "authup.redis.enabled" . }} + name: {{ include "authup.database.secretName" $ctx }} + key: {{ include "authup.database.passwordKey" $ctx }} +{{- if and (not .hook) (include "authup.redis.enabled" $ctx) }} - name: REDIS valueFrom: secretKeyRef: - name: {{ include "authup.redis.secretName" . }} - key: {{ include "authup.redis.secretKey" . }} + name: {{ include "authup.redis.secretName" $ctx }} + key: {{ include "authup.redis.secretKey" $ctx }} {{- end }} -{{- if include "authup.smtp.enabled" . }} +{{- if and (not .hook) (include "authup.smtp.enabled" $ctx) }} - name: SMTP valueFrom: secretKeyRef: - name: {{ include "authup.smtp.secretName" . }} - key: {{ include "authup.smtp.secretKey" . }} + name: {{ include "authup.smtp.secretName" $ctx }} + key: {{ include "authup.smtp.secretKey" $ctx }} {{- end }} +{{- if not .hook }} - name: USER_ADMIN_PASSWORD valueFrom: secretKeyRef: - name: {{ include "authup.auth.secretName" . }} - key: {{ .Values.auth.secretKeys.adminPasswordKey }} -{{- if .Values.auth.systemClientEnabled }} + name: {{ include "authup.auth.secretName" $ctx }} + key: {{ $ctx.Values.auth.secretKeys.adminPasswordKey }} +{{- end }} +{{- if and (not .hook) $ctx.Values.auth.systemClientEnabled }} - name: CLIENT_SYSTEM_SECRET valueFrom: secretKeyRef: - name: {{ include "authup.auth.secretName" . }} - key: {{ .Values.auth.secretKeys.systemClientSecretKey }} + name: {{ include "authup.auth.secretName" $ctx }} + key: {{ $ctx.Values.auth.secretKeys.systemClientSecretKey }} {{- end }} -{{- if include "authup.auth.hasSecretsEncryptionKey" . }} +{{- if include "authup.auth.hasSecretsEncryptionKey" $ctx }} {{- /* Never optional: a silently missing KEK would boot authup into - plaintext-at-rest and defer unrecoverable decrypt failures. */}} + plaintext-at-rest and defer unrecoverable decrypt failures. Kept on the + hook for the same reason, even though its key is conditional and + `migration run` does not read it today: a migration that ever touches a + wrapped column must fail closed, not run without the key. The cost is + that enabling auth.secretsEncryptionKey and server.migration.enabled in + ONE upgrade schedules a hook pod referencing a key the release has not + written yet. Enable a write-once KEK on its own upgrade. */}} - name: SECRETS_ENCRYPTION_KEY valueFrom: secretKeyRef: - name: {{ include "authup.auth.secretName" . }} - key: {{ .Values.auth.secretKeys.secretsEncryptionKeyKey }} + name: {{ include "authup.auth.secretName" $ctx }} + key: {{ $ctx.Values.auth.secretKeys.secretsEncryptionKeyKey }} {{- end }} - name: npm_config_cache value: /tmp/.npm-cache @@ -104,54 +127,102 @@ Shared by the Deployment and the migration Job. {{/* Shared volumes / volumeMounts for the server container (writable dir, tmp, provisioning files, config file). +Usage: {{ include "authup.server.volumeMounts" (dict "context" $ "hook" true) }} +The `required` on .context is load-bearing: helm renders with missingkey=zero, so +a call site that passed a bare `.` would leave every guard below reading false and +emit writable+tmp only, silently dropping the config file. Failing the render is +the chart's posture everywhere else. + +"hook" marks the pre-upgrade migration Job. It drops the provisioning mount, +whose ConfigMap/Secret is an ordinary release resource that helm applies AFTER +the hook: the upgrade that first sets server.provisioning.files would leave the +hook pod in ContainerCreating on a "configmap not found" until it times out, and +`migration run` never reads those files anyway (ProvisionerModule is registered +by the start command only). The writable directory stays for BOTH: under the +image's NODE_ENV=production the logger opens /http.log and +/error.log before the migration touches the database, and an +uncreatable path is a hard ENOENT failure. + +The config file stays for both as well, and mounting it is not optional: +`migration run` loads authup.server.core.conf unconditionally, and the db keys +that only the file can carry (ssl, socketPath, replication, extensions, poolSize) +decide how the migration connects and what it creates. Dropping it would silently +migrate over a plaintext connection. The Job reads it from a hook-scoped copy +instead: see authup.server.configurationConfigMapName. + +The theme volume rides the same flag. It used to be a pair of deployment-only +defines carved out for exactly this hook reason, which left two calling +conventions in deployment.yaml two lines apart; one mechanism is one thing to +get right. authup.server.themeEnv stays separate: it splits along a different +axis (configEnv is one define shared by the env ConfigMap and the Job's inlined +env, and THEME_* must stay in its reserved-key list either way). */}} {{- define "authup.server.volumeMounts" -}} +{{- $ctx := required "authup.server.volumeMounts: call it as (dict \"context\" $ \"hook\" bool)" .context -}} - name: writable mountPath: /var/lib/authup - name: tmp mountPath: /tmp -{{- if and .Values.server.provisioning.enabled (or .Values.server.provisioning.files .Values.server.provisioning.existingConfigMap .Values.server.provisioning.existingSecret) }} +{{- if and (not .hook) $ctx.Values.server.provisioning.enabled (or $ctx.Values.server.provisioning.files $ctx.Values.server.provisioning.existingConfigMap $ctx.Values.server.provisioning.existingSecret) }} - name: provisioning mountPath: /var/lib/authup/provisioning readOnly: true {{- end }} -{{- if or .Values.server.configuration .Values.server.existingConfigmap }} +{{- if or $ctx.Values.server.configuration $ctx.Values.server.existingConfigmap }} - name: configuration mountPath: /usr/src/app/authup.server.core.conf subPath: authup.server.core.conf readOnly: true {{- end }} +{{- if and (not .hook) (include "authup.server.themeMounted" $ctx) }} +- name: theme + mountPath: {{ include "authup.server.themeMountPath" $ctx }} + readOnly: true +{{- end }} {{- end -}} {{- define "authup.server.volumes" -}} +{{- $ctx := required "authup.server.volumes: call it as (dict \"context\" $ \"hook\" bool)" .context -}} - name: writable emptyDir: {} - name: tmp emptyDir: {} -{{- if and .Values.server.provisioning.enabled (or .Values.server.provisioning.files .Values.server.provisioning.existingConfigMap .Values.server.provisioning.existingSecret) }} +{{- if and (not .hook) $ctx.Values.server.provisioning.enabled (or $ctx.Values.server.provisioning.files $ctx.Values.server.provisioning.existingConfigMap $ctx.Values.server.provisioning.existingSecret) }} - name: provisioning - {{- if .Values.server.provisioning.existingSecret }} + {{- if $ctx.Values.server.provisioning.existingSecret }} secret: - secretName: {{ include "authup.tplvalues.render" (dict "value" .Values.server.provisioning.existingSecret "context" $) }} + secretName: {{ include "authup.tplvalues.render" (dict "value" $ctx.Values.server.provisioning.existingSecret "context" $ctx) }} {{- else }} configMap: - name: {{ include "authup.server.provisioningConfigMapName" . }} + name: {{ include "authup.server.provisioningConfigMapName" $ctx }} {{- end }} {{- end }} -{{- if or .Values.server.configuration .Values.server.existingConfigmap }} +{{- if or $ctx.Values.server.configuration $ctx.Values.server.existingConfigmap }} - name: configuration configMap: - name: {{ include "authup.server.configurationConfigMapName" . }} + name: {{ include "authup.server.configurationConfigMapName" (dict "context" $ctx "hook" .hook) }} +{{- end }} +{{- if and (not .hook) (include "authup.server.themeMounted" $ctx) }} +- name: theme + configMap: + name: {{ include "authup.server.themeConfigMapName" $ctx }} + {{- /* Whole-volume projection on purpose: a subPath mount is frozen + until the pod restarts, which would destroy authup's live theme + reload. */}} + {{- if $ctx.Values.server.theme.existingConfigMap }} + {{- with $ctx.Values.server.theme.existingConfigMapItems }} + items: {{- include "authup.tplvalues.render" (dict "value" . "context" $ctx) | nindent 6 }} + {{- end }} + {{- else }} + items: + {{- range $path := splitList "\n" (include "authup.server.themePaths" $ctx) }} + - key: {{ include "authup.server.themeConfigMapKey" $path }} + path: {{ $path }} + {{- end }} + {{- end }} {{- end }} {{- end -}} -{{/* -Theme volume / volumeMount, deliberately NOT part of the shared server -helpers: the migration Job is a pre-upgrade HOOK, and hooks precede regular -resources, so on the upgrade that first enables theming it would reference a -ConfigMap that does not exist yet and hang. A migration run has no use for -the theme either way. -*/}} {{/* Theme environment, kept OUT of authup.server.configEnv for the same reason as the volume: the migration Job inlines configEnv, and pointing @@ -170,36 +241,6 @@ THEME_FRAGMENTS_ENABLED: {{ .Values.server.theme.fragmentsEnabled | toString | q {{- end }} {{- end -}} -{{- define "authup.server.themeVolumeMounts" -}} -{{- if include "authup.server.themeMounted" . }} -- name: theme - mountPath: {{ include "authup.server.themeMountPath" . }} - readOnly: true -{{- end }} -{{- end -}} - -{{- define "authup.server.themeVolumes" -}} -{{- if include "authup.server.themeMounted" . }} -- name: theme - configMap: - name: {{ include "authup.server.themeConfigMapName" . }} - {{- /* Whole-volume projection on purpose: a subPath mount is frozen - until the pod restarts, which would destroy authup's live theme - reload. */}} - {{- if .Values.server.theme.existingConfigMap }} - {{- with .Values.server.theme.existingConfigMapItems }} - items: {{- include "authup.tplvalues.render" (dict "value" . "context" $) | nindent 6 }} - {{- end }} - {{- else }} - items: - {{- range $path := splitList "\n" (include "authup.server.themePaths" $) }} - - key: {{ include "authup.server.themeConfigMapKey" $path }} - path: {{ $path }} - {{- end }} - {{- end }} -{{- end }} -{{- end -}} - {{/* Absolute path the theme volume is mounted at, and the value of THEME_DIRECTORY_PATH. A constant: the chart owns both ends. @@ -409,10 +450,35 @@ looks exactly like an un-themed page. {{- end -}} {{- end -}} +{{/* +ConfigMap carrying authup.server.core.conf for one consumer. +Usage: {{ include "authup.server.configurationConfigMapName" (dict "context" $ "hook" true) }} + +"hook" resolves to the migration Job's own copy (templates/server/configmap- +migration-configuration.yaml), which is itself a pre-upgrade hook and is +therefore created before the Job. Two reasons the Job cannot share the release +ConfigMap: on the upgrade that first sets server.configuration it does not exist +yet, and on every later upgrade it still holds the PREVIOUS release's content +when the hook runs. Same reasoning that makes the Job inline configEnv. +An operator-supplied existingConfigmap is not the chart's to copy: it lives +outside the release and already exists when the hook runs. +*/}} {{- define "authup.server.configurationConfigMapName" -}} -{{- if .Values.server.existingConfigmap -}} -{{- include "authup.tplvalues.render" (dict "value" .Values.server.existingConfigmap "context" $) -}} +{{- $ctx := required "authup.server.configurationConfigMapName: call it as (dict \"context\" $ \"hook\" bool)" .context -}} +{{- if $ctx.Values.server.existingConfigmap -}} +{{- include "authup.tplvalues.render" (dict "value" $ctx.Values.server.existingConfigmap "context" $ctx) -}} +{{- else if .hook -}} +{{- printf "%s-migration-configuration" (include "authup.server.fullname" $ctx) -}} {{- else -}} -{{- printf "%s-configuration" (include "authup.server.fullname" .) -}} +{{- printf "%s-configuration" (include "authup.server.fullname" $ctx) -}} +{{- end -}} {{- end -}} + +{{/* +Rendered content of authup.server.core.conf. One source for the release +ConfigMap and the hook copy, so the migration cannot run against a config file +that differs from the one the server pods get. +*/}} +{{- define "authup.server.configurationContent" -}} +{{- include "authup.tplvalues.render" (dict "value" .Values.server.configuration "context" $) -}} {{- end -}} diff --git a/charts/authup/templates/server/configmap-configuration.yaml b/charts/authup/templates/server/configmap-configuration.yaml index cd6c48c..ad31b31 100644 --- a/charts/authup/templates/server/configmap-configuration.yaml +++ b/charts/authup/templates/server/configmap-configuration.yaml @@ -8,5 +8,5 @@ metadata: annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} data: authup.server.core.conf: |- - {{- include "authup.tplvalues.render" (dict "value" .Values.server.configuration "context" $) | nindent 4 }} + {{- include "authup.server.configurationContent" . | nindent 4 }} {{- end }} diff --git a/charts/authup/templates/server/configmap-migration-configuration.yaml b/charts/authup/templates/server/configmap-migration-configuration.yaml new file mode 100644 index 0000000..ffd99de --- /dev/null +++ b/charts/authup/templates/server/configmap-migration-configuration.yaml @@ -0,0 +1,39 @@ +{{- if and .Values.server.enabled .Values.server.migration.enabled .Values.server.configuration (not .Values.server.existingConfigmap) }} +{{/* +The migration Job's own copy of authup.server.core.conf. + +`migration run` loads the config file unconditionally, and the db options only +the file can carry (ssl, socketPath, replication, extensions, poolSize) decide +how it connects and what it creates, so the Job has to mount it. It cannot mount +the release ConfigMap: a pre-upgrade hook runs BEFORE the release manifest is +applied, so that object either does not exist yet (the upgrade that first sets +server.configuration) or still holds the previous release's content. Same reason +the Job inlines the env ConfigMap instead of mounting it. + +Rendered from authup.server.configurationContent, the single source the release +ConfigMap uses too, so migration and server pods can never read different files. +Hook weight -5 puts it ahead of the Job's 0 (helm applies the hook-succeeded +delete policy only after every hook in the event has run, so it outlives the +Job); the ArgoCD branch mirrors that with sync-wave -5. +*/}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "authup.server.configurationConfigMapName" (dict "context" $ "hook" true) }} + namespace: {{ include "authup.namespace" . | quote }} + labels: {{- include "authup.labels" (dict "context" $ "component" "migration") | nindent 4 }} + annotations: + {{- include "authup.annotations" (dict "context" $) | nindent 4 }} + {{- if .Values.useHelmHooks }} + helm.sh/hook: pre-upgrade + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "-5" + {{- else }} + argocd.argoproj.io/hook: PreSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + argocd.argoproj.io/sync-wave: "-5" + {{- end }} +data: + authup.server.core.conf: |- + {{- include "authup.server.configurationContent" . | nindent 4 }} +{{- end }} diff --git a/charts/authup/templates/server/deployment.yaml b/charts/authup/templates/server/deployment.yaml index ee53043..5c91523 100644 --- a/charts/authup/templates/server/deployment.yaml +++ b/charts/authup/templates/server/deployment.yaml @@ -121,7 +121,7 @@ spec: - secretRef: name: {{ include "authup.tplvalues.render" (dict "value" .Values.server.extraEnvVarsSecret "context" $) }} {{- end }} - env: {{- include "authup.server.secretEnv" . | nindent 12 }} + env: {{- include "authup.server.secretEnv" (dict "context" $) | nindent 12 }} {{- if .Values.server.extraEnvVars }} {{- include "authup.tplvalues.render" (dict "value" .Values.server.extraEnvVars "context" $) | nindent 12 }} {{- end }} @@ -164,20 +164,14 @@ spec: {{- if .Values.server.lifecycleHooks }} lifecycle: {{- include "authup.tplvalues.render" (dict "value" .Values.server.lifecycleHooks "context" $) | nindent 12 }} {{- end }} - volumeMounts: {{- include "authup.server.volumeMounts" . | nindent 12 }} - {{- if include "authup.server.themeMounted" . }} - {{- include "authup.server.themeVolumeMounts" . | nindent 12 }} - {{- end }} + volumeMounts: {{- include "authup.server.volumeMounts" (dict "context" $) | nindent 12 }} {{- if .Values.server.extraVolumeMounts }} {{- include "authup.tplvalues.render" (dict "value" .Values.server.extraVolumeMounts "context" $) | nindent 12 }} {{- end }} {{- if .Values.server.sidecars }} {{- include "authup.tplvalues.render" (dict "value" .Values.server.sidecars "context" $) | nindent 8 }} {{- end }} - volumes: {{- include "authup.server.volumes" . | nindent 8 }} - {{- if include "authup.server.themeMounted" . }} - {{- include "authup.server.themeVolumes" . | nindent 8 }} - {{- end }} + volumes: {{- include "authup.server.volumes" (dict "context" $) | nindent 8 }} {{- if .Values.server.extraVolumes }} {{- include "authup.tplvalues.render" (dict "value" .Values.server.extraVolumes "context" $) | nindent 8 }} {{- end }} diff --git a/charts/authup/templates/server/migration-job.yaml b/charts/authup/templates/server/migration-job.yaml index 06f4f46..5b94308 100644 --- a/charts/authup/templates/server/migration-job.yaml +++ b/charts/authup/templates/server/migration-job.yaml @@ -10,7 +10,15 @@ against the previous release's ConfigMap. apiVersion: batch/v1 kind: Job metadata: - name: {{ printf "%s-migration" (include "authup.server.fullname" .) }} + {{- /* Built from the component helper with the WHOLE "server-migration" + suffix rather than by appending to authup.server.fullname, so the + 63-character budget is derived once instead of truncating a name that + was already assembled. It matters here because a Job name is copied + into the job-name / batch.kubernetes.io/job-name pod labels with no + hand-written spec.selector, and a label VALUE stops at 63: appending + to the -server name reaches 69 and the hook becomes unschedulable, + hanging the upgrade. Identical output for any normal release name. */}} + name: {{ include "authup.component.fullname" (dict "context" . "suffix" "server-migration") }} namespace: {{ include "authup.namespace" . | quote }} labels: {{- include "authup.labels" (dict "context" $ "component" "migration") | nindent 4 }} annotations: @@ -69,7 +77,7 @@ spec: - name: {{ $key }} value: {{ $value | quote }} {{- end }} - {{- include "authup.server.secretEnv" . | nindent 12 }} + {{- include "authup.server.secretEnv" (dict "context" $ "hook" true) | nindent 12 }} {{- if .Values.server.extraEnvVars }} {{- include "authup.tplvalues.render" (dict "value" .Values.server.extraEnvVars "context" $) | nindent 12 }} {{- end }} @@ -77,11 +85,11 @@ spec: {{- if $resources }} resources: {{- toYaml $resources | nindent 12 }} {{- end }} - volumeMounts: {{- include "authup.server.volumeMounts" . | nindent 12 }} + volumeMounts: {{- include "authup.server.volumeMounts" (dict "context" $ "hook" true) | nindent 12 }} {{- if .Values.server.extraVolumeMounts }} {{- include "authup.tplvalues.render" (dict "value" .Values.server.extraVolumeMounts "context" $) | nindent 12 }} {{- end }} - volumes: {{- include "authup.server.volumes" . | nindent 8 }} + volumes: {{- include "authup.server.volumes" (dict "context" $ "hook" true) | nindent 8 }} {{- if .Values.server.extraVolumes }} {{- include "authup.tplvalues.render" (dict "value" .Values.server.extraVolumes "context" $) | nindent 8 }} {{- end }} diff --git a/charts/authup/templates/validations.yaml b/charts/authup/templates/validations.yaml index 7e8a499..65cf34d 100644 --- a/charts/authup/templates/validations.yaml +++ b/charts/authup/templates/validations.yaml @@ -34,6 +34,17 @@ store, token blocklist and MFA challenges fall back to a per-process memory cach {{- fail "authup: auth.existingSecret and inline auth values are mutually exclusive — when an existing secret is referenced, the inline adminPassword / systemClientSecret / secretsEncryptionKey values are unused; remove them to avoid a false sense of configuration." }} {{- end }} +{{/* +Same shape as the auth pair above, and worth its own guard because the value it +drops is usually load-bearing: server.configuration is where db.ssl / socketPath +/ replication live, and those decide how BOTH the server pods and the pre-upgrade +migration hook connect. Existing-wins silently would mean believing TLS to the +database is configured while the mounted file is the operator's. +*/}} +{{- if and .Values.server.enabled .Values.server.configuration .Values.server.existingConfigmap }} +{{- fail "authup: server.configuration and server.existingConfigmap are mutually exclusive. The existing ConfigMap is the one that gets mounted, so the inline content would be silently dropped: move it into that ConfigMap, or drop server.existingConfigmap." }} +{{- end }} + {{- if and .Values.adminConsole.enabled (not .Values.server.enabled) (not .Values.adminConsole.apiUrl) }} {{- fail "authup: adminConsole.enabled without server.enabled requires adminConsole.apiUrl (the browser-reachable URL of an external authup server-core)." }} {{- end }} diff --git a/charts/authup/values.schema.json b/charts/authup/values.schema.json index 7324034..51aaf40 100644 --- a/charts/authup/values.schema.json +++ b/charts/authup/values.schema.json @@ -3641,7 +3641,7 @@ }, "useHelmHooks": { "default": true, - "description": "Render Job hook annotations (set false for ArgoCD / Flux)", + "description": "Render Helm hook annotations on the migration Job. Set false only for\nArgoCD, which reads its own PreSync annotations instead (it also understands\nHelm hooks, so true works there too). Flux and plain helm need true: a plain\nJob's pod template is immutable, so the next upgrade cannot patch it.", "required": [], "title": "useHelmHooks", "type": "boolean" diff --git a/charts/authup/values.yaml b/charts/authup/values.yaml index f205f23..83e654d 100644 --- a/charts/authup/values.yaml +++ b/charts/authup/values.yaml @@ -34,7 +34,10 @@ commonLabels: {} commonAnnotations: {} # -- Extra objects to deploy (rendered through tpl; list of manifests or strings) extraDeploy: [] -# -- Render Job hook annotations (set false for ArgoCD / Flux) +# -- Render Helm hook annotations on the migration Job. Set false only for +# ArgoCD, which reads its own PreSync annotations instead (it also understands +# Helm hooks, so true works there too). Flux and plain helm need true: a plain +# Job's pod template is immutable, so the next upgrade cannot patch it. useHelmHooks: true diagnosticMode: # -- Start every container with a sleep command and disable probes (debugging)