feat(liquibase): add native Liquibase migration Job with wait-for-job support - #9
Merged
Merged
Conversation
Pulls the pod annotations/labels, CA-bundle init container, VPA-aware resources, appSettings/ca-bundle volumes and mounts, and scheduling blocks out of _job.yaml into _job-partials.tpl so a second Job template can reuse them without duplication. Rendered output of the test template chart is byte-identical before and after across five value combinations (default, CA-bundle, VPA, no-resources, scheduling).
…ns ConfigMaps Declares the liquibase block in values.yaml and renders the changelog and migration ConfigMaps from the consuming chart's own files via .Files, keyed by basename. Both land together because the values-contract guard is bidirectional.
Adds per-engine port and URL-template defaults with urlTemplate and url overrides, the LIQUIBASE_* env list, and the credentials Secret. The URL is a plain env value; only username and password are Secret-sourced, and the Secret is skipped when database.existingSecret is used.
Adds _liquibase.tpl, a pre-install/pre-upgrade hook Job at weight -10 that mounts the changelog by subPath and the migrations directory, and reuses the shared Job pod-spec partials for CA-bundle init, VPA-aware resources, appSettings mounting, and scheduling.
Teaches waitFor.active and the Deployment's wait list about the Liquibase Job, so the existing kubectl-wait init container, ServiceAccount automounting, and wait-for-jobs RBAC all apply with no liquibase-specific code. waitForIt defaults to true, so the check treats an absent key as enabled.
Validates addressability, changelog presence, engine support, credential ambiguity, and name collisions. Notably a changelog path or migrations glob that resolves to nothing now fails instead of shipping an empty ConfigMap and a migration that silently does nothing.
Asserts the migration Job is waited on, its RBAC exists, its ConfigMaps and Secret are hook-weighted ahead of it, and that existingSecret suppresses the generated Secret. Regenerates the chart README and plugin skill so the values reference includes the liquibase block; the engine comment no longer uses raw pipe characters, which broke the generated markdown table.
piotrlaczykowski
force-pushed
the
feat/liquibase-native-support
branch
from
August 28, 2026 06:38
c9633b4 to
61bf4ad
Compare
lwasak
reviewed
Aug 28, 2026
…onfig The engine enum (sqlserver/postgresql/mysql/oracle) selected a default port and a built-in JDBC URL template. Both are removed: the framework now ships no knowledge of any driver. Raised in review on #9, and since liquibase support is unreleased this changes no published API. A closed enum in a library chart's public API is a liability — every unlisted driver needs an escape hatch, and adding or correcting an entry later silently changes behaviour for charts pinned to it. Baked-in templates also hid the connection string from chart authors, so vendor-specific parameters (encrypt, trustServerCertificate, oracle.net.ssl_server_dn_match, pool and timeout options) could not be expressed and failed at migration time rather than render time. Configuration is now explicitly one of two, neither with a default: database.url literal JDBC URL, tpl-rendered; wins when both set database.urlTemplate printf with exactly three %s: host, port, name Validation replaces the two engine rules with three sharper ones: one of url/urlTemplate is required; urlTemplate requires host, port and name, naming whichever are missing; and urlTemplate must contain exactly three %s verbs, since printf would otherwise emit %!(EXTRA) or %!s(MISSING) into the URL and fail confusingly at connect time.
piotrlaczykowski
enabled auto-merge
August 28, 2026 07:50
lwasak
reviewed
Aug 28, 2026
urlTemplate took three printf %s verbs filled with host, port, name in that order. Raised in review on #9: verb count is checkable, verb meaning is not. A template written `jdbc:custom:%s@%s:%s` intending name/host/port received host/port/name and produced a syntactically valid URL that rendered cleanly, passed validation, and failed only when Liquibase tried to connect. urlTemplate now uses named placeholders instead: jdbc:sqlserver://{host}:{port};database={name}; They cannot be mis-ordered, and each is independently optional, so a driver URL needing only host and port simply omits {name}. Substitution is `replace` rather than `printf`. Validation covers the ways a template can be wrong: a printf verb is rejected outright with the rewrite spelled out (charts migrating from a hand-rolled printf connectionStringTemplate hit this first); an unknown placeholder such as {database} is rejected listing the supported set; a template with no placeholders is rejected as a constant that belongs in database.url; and every placeholder actually used must have its value set, named in the message. The legal placeholder names live in one helper read by both the URL composer and the validation, so the two cannot disagree.
lwasak
approved these changes
Aug 28, 2026
piotrlaczykowski
disabled auto-merge
August 28, 2026 08:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds native Liquibase migration support. A chart declares a
liquibase:block and gets a complete pre-start database migration:<fullname>-liquibase-changelog-20<fullname>-liquibase-migrations-20<fullname>-liquibase-env-20<fullname>-liquibase-10waitForItdefaults totrue, so the Deployment's pods get the existingkubectl waitinit container and will not start until the migration completes. No new RBAC code —waitFor.activegained a second trigger, which transitively brings ServiceAccount creation, token automounting, and the<fullname>-wait-for-jobsRole/RoleBinding.Minimal usage:
Why
Charts needing a migration before pod start currently hand-roll all of it: their own singular
job:block, their own pod-updater RBAC, their ownwaitForEnabledflag, two ConfigMap templates, and a connection Secret template. The framework already had the hard part (jobs[].waitForItand its init container + RBAC); what was missing was Liquibase-shaped — JDBC URL composition, credential plumbing, and ConfigMaps for the changelog and SQL. There was no ConfigMap template in the framework at all.Notable decisions
Changelog and migration SQL come from the consuming chart's own files. Because a consuming chart passes its own root context into the library
include,.Files.Get/.Files.Globinside a framework template resolve against that chart's directory. Verified with a throwaway two-chart probe before designing around it. SQL stays as real files with real diffs;changelog.content/migrations.filesare inline escape hatches.The framework ships no knowledge of any JDBC driver. An earlier revision had a
database.engineenum selecting a default port and URL template; it was dropped during review. A closed enum in a library chart's public API is a liability — every unlisted driver needs an escape hatch, and correcting an entry later silently changes behaviour for charts pinned to it. Baked-in templates also hid the connection string, so vendor-specific parameters (encrypt,trustServerCertificate,oracle.net.ssl_server_dn_match, pool/timeout options) couldn't be expressed and failed at migration time rather than render time.Configuration is explicitly one of two, neither with a default:
database.url(a literal JDBC URL,tpl-rendered, never parsed by the framework) ordatabase.urlTemplate(named placeholders{host},{port},{name}). Common templates are documented as examples invalues.yaml, not implemented as code.The JDBC URL is a plain env var; only username/password are Secret-sourced. A connection target is not a credential, and keeping it in the pod spec makes
kubectl describe jobdiagnostic.database.existingSecretsuppresses the generated Secret entirely — the supported path for production credentials via ExternalSecret._job.yamlwas refactored into seven shared partials (_job-partials.tpl) rather than duplicating ~120 lines of pod spec into the new Job. Rendered output of the test template chart is byte-identical before and after across five value combinations: default, CA-bundle enabled, VPA enabled, no-resources, and scheduling (nodeSelector/tolerations/podAnnotations/podLabels).Validation
Eight fail-fast rules in
_values-validation.tpl, each verified to fire. Three are worth calling out:changelog.filepath or amigrations.pathsglob that resolves to nothing now fails rather than shipping an empty ConfigMap and a migration that silently does nothing. Both messages name.helmignoreas the non-obvious cause.urlTemplateplaceholders are named, not positional. Positional printf verbs were dropped during review: verb count is checkable, verb meaning is not, sojdbc:custom:%s@%s:%swritten intending name/host/port silently produced a valid-looking wrong URL. A printf verb is now rejected outright with the rewrite shown; so are unknown placeholders ({database}) and templates with no placeholders at all.Testing
liquibase/changelog.xmlandliquibase/migrations/001_init.sqlfixtures, so existinghelm lint/ct lint/ kubeconform CI covers the new templates.existingSecretmust suppress the generated Secret.npm testpasses (40/40) — the bidirectional values-contract guard required thevalues.yamldeclaration and the first.Values.liquibaseread to land in one commit.enginecomment's raw|characters broke the generated markdown table.Not verified locally:
kubeconform—curlis blocked in my environment. All 29 rendered documents were checked to parse as YAML with structural spot-checks (everyvolumeMounthas a backing volume, env sources correct, ConfigMap keys correct). CI runs the real schema validation.Not breaking
New opt-in key,
enabled: falseby default. The_job.yamlrefactor is provably output-identical.Design spec and implementation plan are included under
docs/superpowers/.