Documentation
¶
Overview ¶
Package migration provides integration with Flyway for database migrations
Index ¶
- Constants
- Variables
- func PGRoleProvisioningSQL(spec *PGRoleSpec) ([]string, error)
- func ProvisionPGRoles(ctx context.Context, db *sql.DB, spec *PGRoleSpec) error
- type Action
- type AuditContext
- type AuditEvent
- type AuditEventType
- type AuditOutcome
- type AuditRecorder
- type Config
- type Emitter
- type ErrorClass
- type FlywayMigrator
- func (fm *FlywayMigrator) Close(ctx context.Context) error
- func (fm *FlywayMigrator) DefaultMigrationConfig() *Config
- func (fm *FlywayMigrator) DefaultMigrationConfigForVendor(vendor string) *Config
- func (fm *FlywayMigrator) Info(ctx context.Context, cfg *Config) error
- func (fm *FlywayMigrator) InfoFor(ctx context.Context, db *config.DatabaseConfig, cfg *Config) error
- func (fm *FlywayMigrator) Migrate(ctx context.Context, cfg *Config) (Result, error)
- func (fm *FlywayMigrator) MigrateFor(ctx context.Context, db *config.DatabaseConfig, cfg *Config) (Result, error)
- func (fm *FlywayMigrator) RunMigrationsAtStartup(ctx context.Context) error
- func (fm *FlywayMigrator) Validate(ctx context.Context, cfg *Config) error
- func (fm *FlywayMigrator) ValidateFor(ctx context.Context, db *config.DatabaseConfig, cfg *Config) error
- func (fm *FlywayMigrator) WithAuditRecorder(sink AuditRecorder) *FlywayMigrator
- type MemoryQuiesceController
- func (c *MemoryQuiesceController) Clear(ctx context.Context, by string) (*QuiesceStatus, error)
- func (c *MemoryQuiesceController) CreateTable(_ context.Context) error
- func (c *MemoryQuiesceController) IsSet(_ context.Context) (bool, error)
- func (c *MemoryQuiesceController) Query(_ context.Context) (*QuiesceStatus, error)
- func (c *MemoryQuiesceController) Set(ctx context.Context, opts QuiesceSetOptions) (*QuiesceStatus, error)
- func (c *MemoryQuiesceController) WithAudit(em Emitter) *MemoryQuiesceController
- func (c *MemoryQuiesceController) WithClock(now func() time.Time) *MemoryQuiesceController
- type MigrateAllOptions
- type MigrateAllResult
- type PGRoleSpec
- type PostgresQuiesceController
- func (c *PostgresQuiesceController) Clear(ctx context.Context, by string) (*QuiesceStatus, error)
- func (c *PostgresQuiesceController) CreateTable(ctx context.Context) error
- func (c *PostgresQuiesceController) IsSet(ctx context.Context) (bool, error)
- func (c *PostgresQuiesceController) Query(ctx context.Context) (*QuiesceStatus, error)
- func (c *PostgresQuiesceController) Set(ctx context.Context, opts QuiesceSetOptions) (*QuiesceStatus, error)
- func (c *PostgresQuiesceController) WithAudit(em Emitter) *PostgresQuiesceController
- func (c *PostgresQuiesceController) WithClock(now func() time.Time) *PostgresQuiesceController
- type QuiesceController
- type QuiesceGate
- type QuiesceSetOptions
- type QuiesceStatus
- type Result
- type SecretFetcher
- type SecretsProvider
- type TenantLister
- type TenantResult
Constants ¶
const ( // DefaultQuiesceScope is the single scope key used in v1 (single-region). // The scope column exists so per-region/per-pool scoping is additive. DefaultQuiesceScope = "global" // DefaultQuiesceTTL is applied when QuiesceSetOptions.TTL is zero. The TTL // is the auto-release horizon: a migration job that crashes after Set can // never block provisioning beyond this without an explicit renew. DefaultQuiesceTTL = 30 * time.Minute // MaxQuiesceTTL caps QuiesceSetOptions.TTL so a fat-fingered multi-day TTL // cannot brick provisioning. Values above the ceiling are clamped. MaxQuiesceTTL = 2 * time.Hour // DefaultQuiesceTable is the control-plane table name used when an empty // table name is supplied to NewPostgresQuiesceController. DefaultQuiesceTable = "quiesce_flags" )
const DefaultSecretsPrefix = "gobricks/migrate/"
DefaultSecretsPrefix is the default name prefix used to look up tenant database credentials in a secret store. The full secret name is DefaultSecretsPrefix + tenantID.
const PostgresQuiesceTableDDL = `` /* 324-byte string literal not displayed */
PostgresQuiesceTableDDL is the CREATE TABLE statement used by CreateTable. Exported so operators managing schema externally can run it via their own tooling. The %s placeholder is replaced with the validated, quoted table identifier. One row per scope (id); v1 uses the single "global" scope.
expires_at is the TTL hard stop: IsSet treats now >= expires_at as released even when cleared_at is NULL, so a migration job that crashes after Set can never block provisioning beyond the TTL (read-side auto-release, no sweeper).
const PrincipalUnspecified = "<unspecified>"
PrincipalUnspecified is the sentinel emitted when an operator does not supply AppliedByPrincipal. The audit event still fires (so the gap is itself auditable) and the emitter logs a warning. Operators MUST pass an explicit principal in well-behaved callers; the framework refuses to invent one from IAM/OS context per ADR-019.
Variables ¶
var ErrEmptyTenantID = errors.New("migration: tenantID is empty")
ErrEmptyTenantID is returned when DBConfig is invoked with a blank tenant ID.
var ErrEnvFieldHasControlChar = errors.New("migration: env field contains forbidden control character (CR/LF/NUL)")
ErrEnvFieldHasControlChar is returned when a DatabaseConfig field destined for the Flyway subprocess environment contains a forbidden control character (CR, LF, or NUL). Go's exec.Cmd.Env passes strings to execve(2) verbatim and does not split on newlines, so this isn't a known injection path — but rejecting at the boundary prevents a compromised secret writer from propagating multi-line surprises into downstream logs or env-parsing tools.
var ErrInvalidPGIdentifier = errors.New("migration: PostgreSQL identifier rejected")
ErrInvalidPGIdentifier is returned by Validate when a role or schema name fails the safe-identifier check enforced by ProvisionPGRoles.
var ErrInvalidPrefix = errors.New("migration: invalid secrets prefix (must end with '/')")
ErrInvalidPrefix indicates the configured prefix is unusable.
var ErrInvalidQuiesceTTL = errors.New("migration: quiesce TTL must not be negative")
ErrInvalidQuiesceTTL is returned by Set when QuiesceSetOptions.TTL is negative. Zero is valid (means DefaultQuiesceTTL); negative is rejected rather than silently defaulted, so a bad operator input fails loudly instead of pausing provisioning for an unintended duration.
var ErrInvalidQuiesceTable = errors.New("migration: invalid quiesce table name")
ErrInvalidQuiesceTable is returned by NewPostgresQuiesceController when the supplied table name fails the safe-identifier check.
var ErrInvalidTenantID = errors.New("migration: tenantID contains characters outside [A-Za-z0-9_-] or exceeds 128 characters")
ErrInvalidTenantID is returned when DBConfig is invoked with a tenant ID that contains characters outside the [A-Za-z0-9_-] allowlist or exceeds the 128-character length bound.
var ErrNoConfigProvider = errors.New("migration: database.DBConfigProvider is nil")
ErrNoConfigProvider is returned when MigrateAll is called without a DBConfigProvider.
var ErrNoFetcher = errors.New("migration: SecretsProvider.Fetch is nil")
ErrNoFetcher indicates the SecretsProvider was constructed without a Fetch function.
var ErrNoLister = errors.New("migration: TenantLister is nil")
ErrNoLister is returned when MigrateAll is called without a TenantLister.
var ErrQuiesceBlocked = errors.New("migration: paused by deployment quiesce flag")
ErrQuiesceBlocked is returned by MigrateAll when the deployment quiesce flag is set: dispatch of not-yet-started tenants stops and the partial result is returned. Distinguish a paused run from a failed one via errors.Is.
var ErrQuiesceNotSet = errors.New("migration: no active quiesce flag")
ErrQuiesceNotSet is returned by Clear when no active flag exists.
var ErrSecretMalformed = errors.New("migration: secret payload malformed")
ErrSecretMalformed indicates the secret payload could not be parsed into a usable DatabaseConfig in either canonical or RDS-rotation form.
Functions ¶
func PGRoleProvisioningSQL ¶ added in v0.32.0
func PGRoleProvisioningSQL(spec *PGRoleSpec) ([]string, error)
PGRoleProvisioningSQL returns the SQL statements that ProvisionPGRoles would execute for spec, in order. Use this when operators want to inspect or apply the provisioning manually via psql, or feed it into their own migration runner (Flyway, Liquibase) rather than the Go helper.
Returns ErrInvalidPGIdentifier when spec fails Validate. The returned slice does not include trailing semicolons; callers concatenating them into a single script should add separators themselves.
SECURITY: when spec.MigratorPassword or spec.RuntimePassword is non-empty, the returned statements include the password as an in-clear SQL literal (`ALTER ROLE "..." PASSWORD '<secret>'`). Treat the returned slice as a sensitive value: do not echo it to logs, CI build artifacts, or anywhere the original credential wouldn't be acceptable. Callers preparing scripts for review should redact the literal before persisting to disk.
func ProvisionPGRoles ¶ added in v0.32.0
ProvisionPGRoles applies the role-pair + schema described by spec to the PostgreSQL instance reachable via db. All statements are idempotent: a rerun against an already-provisioned tenant is a no-op, except that MigratorPassword / RuntimePassword (when non-empty) are reapplied on every call to support secret rotation.
db MUST be authenticated as a role with CREATEROLE plus the right to CREATE SCHEMA AUTHORIZATION <other> — typically the instance bootstrap superuser or a dedicated provisioner role granted those capabilities. The migrator and runtime roles created here cannot self-provision: they are denied SUPERUSER, CREATEDB, CREATEROLE, BYPASSRLS, and REPLICATION per the deliverables of #378.
PostgreSQL is not fully transactional across role + schema boundaries (CREATE ROLE in particular is not transactional), so a partial-progress failure can leak intermediate state. Callers should rerun the same spec to converge; the idempotent template makes that safe.
Types ¶
type Action ¶ added in v0.31.0
type Action int
Action selects which Flyway operation MigrateAll runs against each tenant.
type AuditContext ¶ added in v0.32.0
type AuditContext struct {
// Principal identifies who triggered the migration (operator username,
// service account name, pipeline identifier). Empty values emit with
// PrincipalUnspecified + a warning so the gap is itself auditable.
Principal string
// GitCommitSHA records the source-tree commit the migration was built
// from. Useful for correlating an audit event to a specific deployment.
GitCommitSHA string
// PipelineRunID is an opaque CI/CD run identifier (e.g. a GitHub
// Actions run ID, a Jenkins build number). Lets compliance reporting
// trace an audit event back to a pipeline run.
PipelineRunID string
// Target overrides the audit event's Target field. Defaults to the
// database name (db.Database) when empty. Useful for multi-tenant runs
// where the tenant ID is more informative for compliance correlation
// than the per-tenant schema name.
Target string
}
AuditContext groups the per-call audit fields that flow into every migration.applied event. Operators MUST supply Principal explicitly per ADR-019; GitCommitSHA, PipelineRunID, and Target are optional but strongly recommended for deployment-time runs.
type AuditEvent ¶ added in v0.32.0
type AuditEvent struct {
Type AuditEventType
Target string
AppliedByPrincipal string
StartedAt time.Time
CompletedAt time.Time
Outcome AuditOutcome
// Version is the Flyway version applied. Set on migration.applied.
Version string
// FromState / ToState describe a provisioning-state-machine transition.
// Set on state.transitioned.
FromState string
ToState string
// ErrorClass is set when Outcome == failed; one of the published
// constants above. Empty for success/skipped outcomes.
ErrorClass ErrorClass
// GitCommitSHA and PipelineRunID are optional but strongly recommended
// for deployment-time runs; sourced from explicit caller input.
GitCommitSHA string
PipelineRunID string
// Attributes is a free-form extension point for callsite-specific
// metadata. Keys SHOULD use dotted lowercase (e.g. "migration.vendor").
Attributes map[string]string
}
AuditEvent is the canonical payload that flows into both the OpenTelemetry emission path and the optional AuditRecorder. The two paths share this struct so schemas cannot drift. Backwards-compatible additions follow Go's struct-additive rules; removing a field is a breaking change.
Target is an opaque schema/database identifier (tenant ID or schema name) and MUST NOT be a DSN — credentials never appear in audit events.
type AuditEventType ¶ added in v0.32.0
type AuditEventType string
AuditEventType enumerates the four migration audit-event types defined by ADR-019. Engine-layer emission covers migration.applied; orchestrator-layer emission covers state.transitioned (provisioning.Executor); quiesce.* events land with the deployment quiesce gate (#380).
const ( // AuditEventTypeMigrationApplied marks a Flyway migration application // (successful or failed) against a target. AuditEventTypeMigrationApplied AuditEventType = "migration.applied" // AuditEventTypeStateTransitioned marks a provisioning-state-machine // transition. Emitted by provisioning.Executor for every persisted edge. AuditEventTypeStateTransitioned AuditEventType = "state.transitioned" // AuditEventTypeQuiesceSet marks an operator setting the deployment // quiesce flag. Emitted by a QuiesceController wired with WithAudit. AuditEventTypeQuiesceSet AuditEventType = "quiesce.set" // AuditEventTypeQuiesceCleared marks an operator clearing the deployment // quiesce flag. Emitted by a QuiesceController wired with WithAudit. AuditEventTypeQuiesceCleared AuditEventType = "quiesce.cleared" )
type AuditOutcome ¶ added in v0.32.0
type AuditOutcome string
AuditOutcome is the terminal outcome of the audited operation.
const ( AuditOutcomeSuccess AuditOutcome = "success" AuditOutcomeFailed AuditOutcome = "failed" AuditOutcomeSkipped AuditOutcome = "skipped" )
type AuditRecorder ¶ added in v0.32.0
type AuditRecorder interface {
Record(ctx context.Context, event *AuditEvent) error
}
AuditRecorder is the opt-in delivery path described in ADR-019. When wired (typically via FlywayMigrator.WithAuditRecorder), every AuditEvent fires to Record after the OTel emission, on a separate goroutine with a bounded send queue.
Record receives a non-nil *AuditEvent — the pointer matches the framework convention for medium-sized event payloads (see outbox.OutboxPublisher). Implementations SHOULD treat the event as read-only; the framework does not synchronize concurrent reads if a sink decides to mutate.
The framework calls Record with a fresh background context that may be cancelled by FlywayMigrator.Close. Implementations SHOULD respect ctx.Done() for prompt cancellation, but the framework does not retry on the sink's behalf — sink owners requiring zero-loss audit must back their implementation with a durable buffer (Kafka commit-log, S3 staging, etc.).
Errors returned from Record are logged as warnings and increment the migration.audit.sink_failures counter; they do NOT abort the migration. This is a deliberate trade-off per ADR-019: audit must not block business work.
type Config ¶
type Config struct {
FlywayPath string // Path to the Flyway executable
ConfigPath string // Path to the configuration file
MigrationPath string // Path to migration scripts
Timeout time.Duration // Timeout for migration operations
Environment string // Environment (development, testing, production)
DryRun bool // Only validate, do not execute
// Audit carries the per-call audit-event context required by ADR-019.
// Populated by operators (CLI flags) or pipelines (env vars or library
// call argument). The framework will NOT infer Principal from IAM/OS
// context — empty values flow through with a warning log.
Audit AuditContext
}
Config configuration for migrations
type Emitter ¶ added in v0.39.0
type Emitter interface {
// Emit dispatches a single AuditEvent through the always-on OpenTelemetry
// path and the optional AuditRecorder fan-out. Safe for concurrent use;
// never blocks on the sink.
Emit(ctx context.Context, ev *AuditEvent)
// Close drains the optional AuditRecorder queue and tears down the
// background consumer. Safe to call when no sink is configured (no-op).
Close(ctx context.Context) error
}
Emitter is the public emission seam for migration audit events. Both the engine layer (FlywayMigrator) and the orchestrator layer (provisioning.Executor) route AuditEvents through an Emitter so the OTel span + structured-log + optional-sink schema can never drift between migration.applied and state.transitioned events.
Construct one with NewEmitter. The zero value is not usable.
func NewEmitter ¶ added in v0.39.0
func NewEmitter(log logger.Logger, sink AuditRecorder) Emitter
NewEmitter constructs the public Emitter backing both audit layers. log must be non-nil; sink may be nil for OTel-only emission (span + structured log). When sink is non-nil it receives every event after the OTel emission, on a bounded-queue background goroutine per ADR-019 — call Close to drain it.
type ErrorClass ¶ added in v0.32.0
type ErrorClass string
ErrorClass is a stable string from a published taxonomy that downstream alerting can pin on. ADR-019 publishes seven values; the list is additive (new classes are non-breaking; removing one is breaking). Set only when Outcome == failed; otherwise leave empty.
const ( // ErrorClassChecksumMismatch — Flyway detected an applied script was // modified after the fact. ErrorClassChecksumMismatch ErrorClass = "checksum_mismatch" // ErrorClassLockTimeout — could not acquire the advisory / DBMS_LOCK // within the configured timeout. ErrorClassLockTimeout ErrorClass = "lock_timeout" // ErrorClassSchemaHistoryCorrupt — flyway_schema_history is in an // inconsistent state. ErrorClassSchemaHistoryCorrupt ErrorClass = "schema_history_corrupt" // ErrorClassTargetNotReady — the state-machine target is not in a state // that allows migration. Set by the orchestrator (#379), not the engine. ErrorClassTargetNotReady ErrorClass = "target_not_ready" // ErrorClassTargetUnreachable — the target database refused, timed out, // or DNS-failed. ErrorClassTargetUnreachable ErrorClass = "target_unreachable" // ErrorClassQuiesceBlocked — the quiesce flag was set; the run aborted // before any Flyway work. Set by the orchestrator (#380), not the engine. ErrorClassQuiesceBlocked ErrorClass = "quiesce_blocked" // ErrorClassInternal is the catch-all for unclassified panics and // unexpected errors. ErrorClassInternal ErrorClass = "internal_error" )
type FlywayMigrator ¶
type FlywayMigrator struct {
// contains filtered or unexported fields
}
FlywayMigrator handles database migrations using Flyway
func NewFlywayMigrator ¶
func NewFlywayMigrator(cfg *config.Config, log logger.Logger) *FlywayMigrator
NewFlywayMigrator creates a new instance of the migrator with the always-on OpenTelemetry audit-emission path wired up. Call WithAuditRecorder to add an optional compliance-grade durable delivery path per ADR-019.
func (*FlywayMigrator) Close ¶ added in v0.32.0
func (fm *FlywayMigrator) Close(ctx context.Context) error
Close drains the optional AuditRecorder queue and tears down the audit consumer goroutine. Safe to call when no sink is configured. Honors ctx for shutdown deadline; events still in flight when ctx expires are silently dropped (their OTel emission already succeeded).
func (*FlywayMigrator) DefaultMigrationConfig ¶ added in v0.19.0
func (fm *FlywayMigrator) DefaultMigrationConfig() *Config
DefaultMigrationConfig returns the default configuration for migrations
func (*FlywayMigrator) DefaultMigrationConfigForVendor ¶ added in v0.31.0
func (fm *FlywayMigrator) DefaultMigrationConfigForVendor(vendor string) *Config
DefaultMigrationConfigForVendor returns the default migration config for the given database vendor (e.g. "postgresql", "oracle"). Used by multi-tenant migrations where each tenant may run a different vendor than the migrator's own cfg.Database.Type. Unknown vendors fall back to the migrator's configured Database.Type so the vendor string never reaches filesystem path interpolation unvalidated; if even that is unknown, an "unknown" segment is used so callers see an obvious error rather than a path-traversal artifact.
func (*FlywayMigrator) Info ¶
func (fm *FlywayMigrator) Info(ctx context.Context, cfg *Config) error
Info shows information about the status of migrations against the migrator's database.
func (*FlywayMigrator) InfoFor ¶ added in v0.31.0
func (fm *FlywayMigrator) InfoFor(ctx context.Context, db *config.DatabaseConfig, cfg *Config) error
InfoFor shows migration status for the supplied database.
func (*FlywayMigrator) Migrate ¶
Migrate executes pending migrations against the migrator's configured database. The Result carries the parsed per-target outcome; on parse failure it is zero-valued and the error return is authoritative.
func (*FlywayMigrator) MigrateFor ¶ added in v0.31.0
func (fm *FlywayMigrator) MigrateFor(ctx context.Context, db *config.DatabaseConfig, cfg *Config) (Result, error)
MigrateFor executes pending migrations against the supplied database. Used by multi-tenant migrations to target a tenant-specific DatabaseConfig. See Migrate for the Result contract.
func (*FlywayMigrator) RunMigrationsAtStartup ¶
func (fm *FlywayMigrator) RunMigrationsAtStartup(ctx context.Context) error
RunMigrationsAtStartup executes migrations automatically at application startup. The structured Result is discarded here; downstream consumers pick it up via the migration.applied audit event.
func (*FlywayMigrator) Validate ¶
func (fm *FlywayMigrator) Validate(ctx context.Context, cfg *Config) error
Validate validates migrations without executing them against the migrator's database.
func (*FlywayMigrator) ValidateFor ¶ added in v0.31.0
func (fm *FlywayMigrator) ValidateFor(ctx context.Context, db *config.DatabaseConfig, cfg *Config) error
ValidateFor validates migrations for the supplied database.
func (*FlywayMigrator) WithAuditRecorder ¶ added in v0.32.0
func (fm *FlywayMigrator) WithAuditRecorder(sink AuditRecorder) *FlywayMigrator
WithAuditRecorder registers an optional AuditRecorder for compliance-grade durable delivery alongside the always-on OpenTelemetry emission. Replaces any previously-configured sink. Returns the receiver for chaining.
Intended to be called once at startup. The sink runs on its own goroutine with a bounded send queue per ADR-019 — slow sinks cannot stall migrations, and sink errors are logged but do not abort the migration. Call Close to drain the queue on shutdown.
type MemoryQuiesceController ¶ added in v0.39.0
type MemoryQuiesceController struct {
// contains filtered or unexported fields
}
MemoryQuiesceController is an in-memory QuiesceController for unit tests and single-process use. The Set/Clear/IsSet/Query operations are safe for concurrent use (mu guards the rec). WithClock/WithAudit are builder methods — configure them before concurrent use, mirroring MemoryStore. Construct with NewMemoryQuiesceController.
func NewMemoryQuiesceController ¶ added in v0.39.0
func NewMemoryQuiesceController() *MemoryQuiesceController
NewMemoryQuiesceController returns an empty in-memory controller (not quiesced).
func (*MemoryQuiesceController) Clear ¶ added in v0.39.0
func (c *MemoryQuiesceController) Clear(ctx context.Context, by string) (*QuiesceStatus, error)
Clear deactivates an uncleared flag (active OR auto-released by TTL) — the unconditional operator override. Returns ErrQuiesceNotSet only when nothing has ever been set or the row was already explicitly cleared; an expired-but- uncleared row is still clearable so operators can tidy a crashed deploy's flag and emit the quiesce.cleared audit trail.
func (*MemoryQuiesceController) CreateTable ¶ added in v0.39.0
func (c *MemoryQuiesceController) CreateTable(_ context.Context) error
CreateTable is a no-op for the in-memory controller.
func (*MemoryQuiesceController) IsSet ¶ added in v0.39.0
func (c *MemoryQuiesceController) IsSet(_ context.Context) (bool, error)
IsSet reports whether the flag is currently active (uncleared and unexpired).
func (*MemoryQuiesceController) Query ¶ added in v0.39.0
func (c *MemoryQuiesceController) Query(_ context.Context) (*QuiesceStatus, error)
Query returns the full status snapshot.
func (*MemoryQuiesceController) Set ¶ added in v0.39.0
func (c *MemoryQuiesceController) Set(ctx context.Context, opts QuiesceSetOptions) (*QuiesceStatus, error)
Set activates (or renews) the flag.
func (*MemoryQuiesceController) WithAudit ¶ added in v0.39.0
func (c *MemoryQuiesceController) WithAudit(em Emitter) *MemoryQuiesceController
WithAudit enables quiesce.set / quiesce.cleared audit emission through the shared migration.Emitter. The audited principal is the operator who performed the action (QuiesceSetOptions.By / the Clear `by` argument), never inferred.
func (*MemoryQuiesceController) WithClock ¶ added in v0.39.0
func (c *MemoryQuiesceController) WithClock(now func() time.Time) *MemoryQuiesceController
WithClock installs a deterministic clock (tests). Passing nil restores time.Now. Builder method — call before concurrent use. Returns the controller for chaining.
type MigrateAllOptions ¶ added in v0.31.0
type MigrateAllOptions struct {
// BaseConfig supplies Flyway timeout / paths. ConfigPath and
// MigrationPath are auto-resolved per vendor when zero.
BaseConfig *Config
// ContinueOnError keeps iterating after the first per-tenant failure.
// Default false (fail-fast).
ContinueOnError bool
// Parallelism caps concurrent tenant migrations. 0 or 1 = sequential.
// Implementation caps the value to a reasonable maximum to avoid
// connection storms.
Parallelism int
// Logger receives progress updates. May be nil.
Logger logger.Logger
// Hook is invoked after each tenant completes (success or failure).
// Useful for streaming progress to the CLI / CI logs. May be nil.
Hook func(TenantResult)
// Quiesce, when set, gates tenant dispatch on the deployment quiesce flag:
// once the flag is observed set, no further tenants are dispatched (in-flight
// tenants drain) and MigrateAll returns ErrQuiesceBlocked with the partial
// result. Nil disables the check (fully opt-in). Check errors fail open.
Quiesce QuiesceGate
}
MigrateAllOptions tunes per-tenant execution.
type MigrateAllResult ¶ added in v0.31.0
type MigrateAllResult struct {
Action Action
Results []TenantResult
}
MigrateAllResult aggregates per-tenant results from a MigrateAll run.
func MigrateAll ¶ added in v0.31.0
func MigrateAll( ctx context.Context, migrator *FlywayMigrator, lister TenantLister, configs database.DBConfigProvider, action Action, opts MigrateAllOptions, ) (*MigrateAllResult, error)
MigrateAll lists tenants via lister, resolves each tenant's database config via configs (the existing database.DBConfigProvider abstraction), and runs the chosen Flyway action against every one. Sequential fail-fast unless opts say otherwise.
func (*MigrateAllResult) Failed ¶ added in v0.31.0
func (r *MigrateAllResult) Failed() []TenantResult
Failed returns only the tenant results whose Err is non-nil.
type PGRoleSpec ¶ added in v0.32.0
type PGRoleSpec struct {
// Schema is the per-tenant schema name (e.g. "tenant_a"). Owned by
// MigratorRole after provisioning.
Schema string
// MigratorRole owns Schema and is used exclusively by the migration runner.
// Must differ from RuntimeRole.
MigratorRole string
// MigratorPassword is optionally assigned to MigratorRole via ALTER ROLE
// PASSWORD on every call. Useful for the one-time bootstrap and for
// secret rotation. Leave empty when credentials are managed externally
// (e.g., the role is created out-of-band and password set via a
// privileged migration pipeline).
MigratorPassword string
// RuntimeRole is the per-tenant DML-only role consumed by the running
// service. Must differ from MigratorRole.
RuntimeRole string
// RuntimePassword is optionally assigned to RuntimeRole. Same semantics
// as MigratorPassword — passing it on every call makes secret rotation a
// no-op rerun.
RuntimePassword string
}
PGRoleSpec describes a PostgreSQL role-pair plus per-tenant schema for the migrator-vs-runtime role-separation model defined in issue #378.
Migrator role: owns the per-tenant schema, holds DDL privileges, used exclusively by the migration runner. Created with NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS NOREPLICATION so even a compromised migrator credential cannot escalate itself.
Runtime role: per-tenant LOGIN role granted only DML on the tenant schema. Does not own the schema, so PostgreSQL's default ownership model rejects ALTER/CREATE/DROP statements from this role without any explicit REVOKE. Granted SELECT/INSERT/UPDATE/DELETE on existing AND future tables via ALTER DEFAULT PRIVILEGES so subsequent migrations don't need per-script grants.
func (*PGRoleSpec) Validate ¶ added in v0.32.0
func (s *PGRoleSpec) Validate() error
Validate reports whether the spec's identifiers pass the safe-identifier check and the two roles differ. Returns ErrInvalidPGIdentifier wrapped with the offending field name (and value) on failure.
type PostgresQuiesceController ¶ added in v0.39.0
type PostgresQuiesceController struct {
// contains filtered or unexported fields
}
PostgresQuiesceController is a PostgreSQL-backed QuiesceController. It stores the flag in a control-plane table (zero new dependencies) so it survives a process restart and is queryable by operators / the CLI. Construct with NewPostgresQuiesceController.
func NewPostgresQuiesceController ¶ added in v0.39.0
func NewPostgresQuiesceController(db *sql.DB, tableName string) (*PostgresQuiesceController, error)
NewPostgresQuiesceController returns a controller backed by db. An empty tableName resolves to DefaultQuiesceTable. The name may be schema-qualified ("schema.table"). Returns ErrInvalidQuiesceTable if it fails the safe- identifier check.
func (*PostgresQuiesceController) Clear ¶ added in v0.39.0
func (c *PostgresQuiesceController) Clear(ctx context.Context, by string) (*QuiesceStatus, error)
Clear deactivates an uncleared flag (active OR auto-released by TTL) — the unconditional operator override, keyed on scope, not the setter's session. Returns ErrQuiesceNotSet only when no row exists or it was already cleared; an expired-but-uncleared row is still clearable so operators can tidy a crashed deploy's flag and produce the quiesce.cleared audit trail.
func (*PostgresQuiesceController) CreateTable ¶ added in v0.39.0
func (c *PostgresQuiesceController) CreateTable(ctx context.Context) error
CreateTable provisions the quiesce table idempotently.
func (*PostgresQuiesceController) IsSet ¶ added in v0.39.0
func (c *PostgresQuiesceController) IsSet(ctx context.Context) (bool, error)
IsSet reports whether the flag is currently active.
func (*PostgresQuiesceController) Query ¶ added in v0.39.0
func (c *PostgresQuiesceController) Query(ctx context.Context) (*QuiesceStatus, error)
Query returns the full status snapshot. A missing row is reported as the zero status (never quiesced).
func (*PostgresQuiesceController) Set ¶ added in v0.39.0
func (c *PostgresQuiesceController) Set(ctx context.Context, opts QuiesceSetOptions) (*QuiesceStatus, error)
Set activates (or renews) the flag. Idempotent on the scope id: an existing row is updated (renewing expires_at and un-clearing it).
func (*PostgresQuiesceController) WithAudit ¶ added in v0.39.0
func (c *PostgresQuiesceController) WithAudit(em Emitter) *PostgresQuiesceController
WithAudit enables quiesce.set / quiesce.cleared audit emission. The audited principal is the operator who performed the action (Set's By / Clear's by), never inferred. Returns the controller for chaining.
func (*PostgresQuiesceController) WithClock ¶ added in v0.39.0
func (c *PostgresQuiesceController) WithClock(now func() time.Time) *PostgresQuiesceController
WithClock installs a deterministic clock (tests). Passing nil restores time.Now. Builder method — call before concurrent use. Returns the controller for chaining.
type QuiesceController ¶ added in v0.39.0
type QuiesceController interface {
QuiesceGate
// Set activates quiesce for opts.TTL (defaulted + ceiling-clamped).
// Idempotent: calling Set on an already-active flag renews ExpiresAt
// (a heartbeat for long deploys). Records By/Reason for visibility + audit.
Set(ctx context.Context, opts QuiesceSetOptions) (*QuiesceStatus, error)
// Clear deactivates the active flag (unconditional operator override; not
// keyed on the setter's session, so it works even if the setter died).
// Returns ErrQuiesceNotSet when nothing is active.
Clear(ctx context.Context, by string) (*QuiesceStatus, error)
// CreateTable provisions backing storage idempotently (no-op for the
// in-memory controller).
CreateTable(ctx context.Context) error
}
QuiesceController is the write side used by the deployment job and the CLI. It composes QuiesceGate so a single implementation serves both reads and writes.
type QuiesceGate ¶ added in v0.39.0
type QuiesceGate interface {
// IsSet reports whether provisioning is currently quiesced. Returns false
// for an expired (auto-released) or cleared flag.
IsSet(ctx context.Context) (bool, error)
// Query returns the full status for operator visibility.
Query(ctx context.Context) (*QuiesceStatus, error)
}
QuiesceGate is the read side consumed by provisioning workers (provisioning.Executor) and the deployment fan-out (MigrateAll). Workers depend on this narrow interface (Interface Segregation); a nil gate means "never quiesced" so the feature is fully opt-in.
type QuiesceSetOptions ¶ added in v0.39.0
QuiesceSetOptions parameterizes Set. By is the principal (explicit, never inferred — ADR-019); empty surfaces the PrincipalUnspecified sentinel on the audit path. TTL of zero defaults to DefaultQuiesceTTL and is clamped to MaxQuiesceTTL.
type QuiesceStatus ¶ added in v0.39.0
type QuiesceStatus struct {
Active bool // cleared_at IS NULL AND now < expires_at
SetAt time.Time // when the active/last flag was set
SetBy string // principal that set it (visibility + audit)
Reason string // operator-supplied "why" (deploy id, ticket)
ExpiresAt time.Time // TTL hard stop
ClearedAt *time.Time // nil while uncleared; set when explicitly cleared
Expired bool // cleared_at IS NULL AND now >= expires_at (stale, auto-released)
}
QuiesceStatus is the operator-facing snapshot of the quiesce flag returned by Query. Active reports whether provisioning is currently paused; Expired reports that an uncleared flag has passed its TTL and was auto-released (read-side, no sweeper) — a distinct signal from a deliberately-cleared flag.
Active and Expired are mutually-exclusive conveniences derived from ClearedAt + ExpiresAt + the current time; the controller keeps them consistent. Treat the struct as read-only.
type Result ¶ added in v0.32.0
type Result struct {
// Operation is the Flyway verb. Empty on the error envelope.
Operation string
// Success is false whenever Flyway emitted an error envelope, even if
// the JSON parsed cleanly.
Success bool
// AppliedVersions enumerates the migration versions Flyway applied in
// this run, in the order Flyway reported them. Empty on no-op reruns.
AppliedVersions []string
// StartingVersion is the schema version before this run (Flyway's
// initialSchemaVersion). Empty when Flyway reported it as null —
// typically on the first migrate against a fresh schema.
StartingVersion string
// EndingVersion is the schema version after this run. Flyway reports
// targetSchemaVersion as null on no-op runs; the parser falls back to
// StartingVersion in that case so callers always see a usable terminus.
EndingVersion string
// DurationMillis is Flyway's totalMigrationTime in milliseconds.
DurationMillis int64
// FlywayVersion is the engine version that produced this result.
FlywayVersion string
// DatabaseType is Flyway's databaseType field ("PostgreSQL", "Oracle").
DatabaseType string
// ErrorCode is Flyway's errorCode on the failure envelope (e.g.
// "VALIDATE_ERROR" for a checksum mismatch). Empty when Success is true.
ErrorCode string
// ErrorMessage is the human-readable error message from Flyway when
// Success is false. May contain embedded newlines from Flyway.
ErrorMessage string
}
Result captures the structured outcome of a single Flyway migrate invocation, populated from the engine's -outputType=json output. Fields are best-effort: an empty Result is returned when the subprocess crashed before emitting JSON or the payload was malformed. Callers that need an authoritative pass/fail signal should still consult the error returned alongside the Result.
type SecretFetcher ¶ added in v0.31.0
SecretFetcher resolves an opaque secret name to its raw payload bytes. The framework stays decoupled from any specific cloud SDK; callers wire AWS Secrets Manager, HashiCorp Vault, or another store behind this seam.
type SecretsProvider ¶ added in v0.31.0
type SecretsProvider struct {
// Prefix is prepended to each tenant ID when composing the secret name.
// Empty defaults to DefaultSecretsPrefix at lookup time.
Prefix string
// Fetch resolves a secret name to its payload. Required.
Fetch SecretFetcher
// contains filtered or unexported fields
}
SecretsProvider implements database.DBConfigProvider on top of a SecretFetcher. It composes the secret name as Prefix + tenantID, fetches the bytes, and parses them as either the canonical go-bricks DatabaseConfig shape or the AWS-managed RDS rotation shape.
func (*SecretsProvider) DBConfig ¶ added in v0.31.0
func (p *SecretsProvider) DBConfig(ctx context.Context, tenantID string) (*config.DatabaseConfig, error)
DBConfig satisfies database.DBConfigProvider. Looks up the tenant's secret, parses the payload, and returns the resulting DatabaseConfig.
func (*SecretsProvider) SecretName ¶ added in v0.31.0
func (p *SecretsProvider) SecretName(tenantID string) string
SecretName composes the full secret name for the given tenant ID using the provider's prefix (or DefaultSecretsPrefix when unset).
func (*SecretsProvider) Validate ¶ added in v0.31.0
func (p *SecretsProvider) Validate() error
Validate checks that the provider is wired correctly. Callers may invoke it eagerly at startup; DBConfig also calls it lazily on first lookup so library callers who skip the explicit check still get a clear error before any tenant fetch.
type TenantLister ¶ added in v0.31.0
TenantLister enumerates the tenant IDs that should receive migrations. Implementations include the HTTP source (for control-plane APIs) and a static source backed by config.TenantStore.
type TenantResult ¶ added in v0.31.0
type TenantResult struct {
TenantID string
Vendor string
Err error
Duration time.Duration
// Result is the parsed Flyway outcome for ActionMigrate. Zero-valued
// for Validate / Info, or when Flyway crashed before emitting JSON.
Result Result
}
TenantResult captures the outcome of running an Action against one tenant.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package provisioning implements a durable, crash-recoverable state machine for dynamic per-tenant provisioning under the multi-tenant migration model defined in issue #379.
|
Package provisioning implements a durable, crash-recoverable state machine for dynamic per-tenant provisioning under the multi-tenant migration model defined in issue #379. |
|
testing
Package testing provides test utilities for the provisioning state machine.
|
Package testing provides test utilities for the provisioning state machine. |
|
source
|
|
|
http
Package http provides a TenantLister that pulls tenant IDs from a control-plane API conforming to the go-bricks pre-defined contract.
|
Package http provides a TenantLister that pulls tenant IDs from a control-plane API conforming to the go-bricks pre-defined contract. |
|
static
Package static provides a TenantLister that enumerates tenant IDs from a config-backed source (typically the YAML-driven multitenant.tenants block).
|
Package static provides a TenantLister that enumerates tenant IDs from a config-backed source (typically the YAML-driven multitenant.tenants block). |