Documentation
¶
Overview ¶
Package migrator applies the Dexaflow SQL migrations with golang-migrate over pgx v5 and turns the driver's errors into messages that name the cause, the remedy and the chart value involved (#1020).
It is what the migrate image runs (cmd/dexaflow-migrate) for the Helm chart's pre-install and pre-upgrade hook Job. Before it, the image shipped golang-migrate's own CLI, which linked lib/pq while the control plane and Lite use pgx, and printed the raw driver error: `pq: database "x" does not exist` on a fresh install, with nothing saying that database.url is a chart value or that Dexaflow does not create the database. ADR 0090 builds the installation id and the opt-in self-migration on top of this package.
Exit codes split deterministic failures from transient ones, so the chart's podFailurePolicy can fail the Job at once on a missing database or a wrong password instead of retrying it three times (see DeterministicExitCodes).
Index ¶
- Constants
- func DeterministicExitCodes() []int
- func DriverURL(dsn string) string
- func Main(ctx context.Context, args []string, stdout, stderr io.Writer, version string) int
- func PreflightCA(t Target) error
- func Up(ctx context.Context, dsn string, log io.Writer) error
- type Failure
- type Kind
- type Target
Constants ¶
const ( // ExitFailed is an unrecognized failure, retried by the Job. ExitFailed = 1 // ExitUsage is a command line the wrapper cannot run. ExitUsage = 2 // ExitBadDSN is a database.url that is not a Postgres URL. ExitBadDSN = 3 // ExitMissingDatabase is a database that does not exist on the server. ExitMissingDatabase = 4 // ExitAuth is a refused user or password. ExitAuth = 5 // ExitMissingCA is a missing sslrootcert file. ExitMissingCA = 6 // ExitDirty is a dirty schema, which needs a person. ExitDirty = 7 // ExitReplaced is a database that is not this release's own, which needs // a person to restore the right one or accept this one. ExitReplaced = 8 // ExitTransient is a failure worth retrying: a server not reachable yet, // not accepting connections, or another migration holding the lock. It is // EX_TEMPFAIL from sysexits.h. ExitTransient = 75 )
Exit codes. The deterministic ones (see DeterministicExitCodes) are the failures a retry cannot fix; the chart fails the Job on them at once.
const InstallationIDKey = "installationId"
InstallationIDKey is the ConfigMap key that holds the installation id. The chart's Deployments read it into LEOFLOW_DATABASE_INSTALLATION_ID (database.installation_id).
Variables ¶
This section is empty.
Functions ¶
func DeterministicExitCodes ¶
func DeterministicExitCodes() []int
DeterministicExitCodes are the exit codes a retry cannot change. The chart's migration Job lists the same set in its podFailurePolicy (a test keeps the two equal).
func DriverURL ¶
DriverURL rewrites a postgres:// or postgresql:// DSN to golang-migrate's pgx5:// scheme, so the migration runs on pgx like the control plane does and a DSN that starts the server also migrates (sslrootcert=system is pgx-only, and the lib/pq CLI failed on it).
func Main ¶
Main runs the command line the migrate image takes and returns the process exit code. Failures are printed to stderr in two lines: what happened and what to do, then the driver's own error, which the docs quote.
func PreflightCA ¶
PreflightCA checks, before any dial, that the sslrootcert file the DSN names is present. Without it pgx fails with `unable to read CA file`, which says nothing about the ConfigMap the chart mounts there. A Helm-side check cannot do this: lookup returns nothing under helm template, --dry-run and Argo CD.
func Up ¶
Up applies the migrations embedded in this binary to the database at dsn, as the image's `up` does, and returns nil or a *Failure. It is what the server runs in-process for migrations.onBoot (ADR 0090 decision 4), so self-migration and the hook Job share one code path, one set of failure messages and golang-migrate's advisory lock, which serializes them. It neither reads nor writes the installation id: only the Job, which owns the ConfigMap, records it.
Types ¶
type Failure ¶
type Failure struct {
// Kind is the class of the failure.
Kind Kind
// Code is the exit code the process ends with.
Code int
// Summary names the cause and the remedy.
Summary string
// Cause is the underlying error, nil when the wrapper produced it.
Cause error
}
Failure is a classified migration error: its kind, the process exit code, and a message in the chart's terms. Cause is the driver error it came from.
func Classify ¶
Classify maps an error from opening the database or running a command to a Failure. A Failure already in the chain is returned as is. Classify(nil) is nil.
type Kind ¶
type Kind int
Kind names the class of a migration failure.
const ( // KindOther is an error the wrapper does not recognize; it is retried. KindOther Kind = iota // KindUsage is a command line the wrapper cannot run. KindUsage // KindBadDSN is a database.url that is not a Postgres URL. KindBadDSN // KindMissingDatabase is a server without the database the DSN names. KindMissingDatabase // KindAuth is a server that refused the DSN's user or password. KindAuth // KindMissingCA is an sslrootcert path with no file behind it. KindMissingCA // KindDirty is a schema_migrations row left dirty by an interrupted file. KindDirty // KindUnreachable is a server that could not be reached or is not // accepting connections yet. KindUnreachable // KindLocked is another migration holding golang-migrate's lock. KindLocked // KindReplaced is a database that is not the one this release was // installed on (ADR 0090 decision 3): emptied, restored from another // installation, or repointed. KindReplaced )
The failure kinds. Each maps to one exit code.
type Target ¶
type Target struct {
// Host is the host name or address of the Postgres server.
Host string
// Port is the TCP port, 5432 when the DSN names none.
Port string
// Database is the database name from the DSN's path.
Database string
// User is the role the DSN connects as.
User string
// SSLRootCert is the sslrootcert parameter, empty when absent.
SSLRootCert string
// contains filtered or unexported fields
}
Target is what the messages say about the database the DSN points at. It is parsed from the DSN once, before any connection, and never carries the password into a message.
func ParseTarget ¶
ParseTarget reads the parts of a DSN the messages need. A DSN that is not a Postgres URL with a database name is a KindBadDSN failure, whose text never quotes the DSN, since it holds the password.