preflight

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Index

Constants

View Source
const (
	FindingIDConnectionFailed     = "connection_failed"
	FindingIDConnectionPingFailed = "connection_ping_failed"

	FindingIDWALLevelNotLogical               = "wal_level_not_logical"
	FindingIDWAL2JSONUnavailable              = "wal2json_unavailable"
	FindingIDReplicationSlotHeadroomExhausted = "replication_slot_headroom_exhausted"
	FindingIDReplicationRoleAttributeMissing  = "replication_role_attribute_missing"

	FindingIDReplicaIdentityNoPrimaryKey  = "replica_identity_no_primary_key"
	FindingIDReplicaIdentityNothing       = "replica_identity_nothing"
	FindingIDReplicaIdentityIndexUnusable = "replica_identity_index_unusable"
	FindingIDReplicaIdentityUnknown       = "replica_identity_unknown"

	FindingIDSourceTableSelectPrivilegeMissing    = "source_table_select_privilege_missing"
	FindingIDSourceSequenceSelectPrivilegeMissing = "source_sequence_select_privilege_missing"
	FindingIDTargetCreateDBPrivilegeMissing       = "target_createdb_privilege_missing"
	FindingIDTargetCreateRolePrivilegeMissing     = "target_createrole_privilege_missing"

	FindingIDUnsupportedColumnType  = "unsupported_column_type"
	FindingIDUnsupportedRangeType   = "unsupported_range_type"
	FindingIDTargetExtensionMissing = "target_extension_missing"

	FindingIDSnapshotConnectionHeadroomInsufficient = "snapshot_connection_headroom_insufficient"
	FindingIDSourceMultipleInstances                = "source_multiple_instances"

	FindingIDTargetVersionOlderThanSource = "target_version_older_than_source"
)

Finding ids name the kind of problem a check reports, never the instance of it. An id is stable: once released it does not change, because consumers key metrics and their own copy on it. An id contains lowercase letters, digits and underscores only, so it can never carry a value read from the database under test.

Variables

Builders is the registry of category builders. Adding a new category = one Builder entry here + one flag declaration on checkCmd.

View Source
var ErrNilSnapshotData = errors.New("snapshot data configuration must not be nil")

ErrNilSnapshotData is returned by BuildSourceChecks when WithSnapshotData is given a nil configuration.

Functions

func BuildAccessChecks

func BuildAccessChecks(cfg *stream.Config, opts ...ConnOption) ([]Check, CleanupFunc)

BuildAccessChecks returns the access-preflight checks applicable to cfg, plus a cleanup function that closes the shared source connection.

func BuildChecks

func BuildChecks(cfg *stream.Config, selected []Category, opts ...ConnOption) ([]Check, CleanupFunc)

BuildChecks returns the concrete checks for the selected categories, preserving the registration order in Builders, plus a single cleanup function that releases every category's resources. The returned cleanup is always non-nil; callers can defer it unconditionally. An empty selection runs every registered category. The connection options apply to every connection the resulting checks open.

func BuildConnectivityChecks

func BuildConnectivityChecks(cfg *stream.Config, opts ...ConnOption) ([]Check, CleanupFunc)

BuildConnectivityChecks returns the connectivity checks applicable to cfg. A source check is added when a source postgres URL is configured; a target check is added when a postgres target is configured. Each check opens its own conn (to its own URL), so no shared cleanup is needed.

func BuildReplicationChecks

func BuildReplicationChecks(cfg *stream.Config, opts ...ConnOption) ([]Check, CleanupFunc)

BuildReplicationChecks returns the replication-preflight checks applicable to cfg, plus a cleanup function that closes the shared source connection. Replication checks only apply when the source is configured with a replication slot.

func BuildResourcesChecks

func BuildResourcesChecks(cfg *stream.Config, opts ...ConnOption) ([]Check, CleanupFunc)

BuildResourcesChecks returns the resource-capacity preflight checks that apply to cfg, plus a cleanup function that closes the shared source connection. The database-size report applies to any configured source. The snapshot connection-headroom check is added only when a data snapshot is configured, because it sizes snapshot_workers x table_workers against the source's max_connections.

func BuildSchemaChecks

func BuildSchemaChecks(cfg *stream.Config, opts ...ConnOption) ([]Check, CleanupFunc)

BuildSchemaChecks returns the schema-preflight checks applicable to cfg, plus a cleanup function that closes the shared source (and, when the target is Postgres, target) connection. Schema checks cover every table pgstream reads (snapshot and replication), so they use the combined access table selection. The version check runs whenever a source is configured — reporting the source version alone (so it survives --source) and additionally comparing against the target when a Postgres target URL is configured. The range-type check is added when the target is Postgres; the extension check additionally needs the target URL to query the target.

func BuildSourceChecks added in v1.5.0

func BuildSourceChecks(sourceURL string, opts ...SourceOption) ([]Check, CleanupFunc, error)

BuildSourceChecks returns every preflight check that only needs a connection to the source Postgres, plus a cleanup function releasing the connections those checks share. It is the entry point for callers that want to validate a source without assembling a full stream.Config: connectivity, replication readiness, source read privileges, schema compatibility and snapshot capacity.

Checks that compare the source against a target (extension compatibility, range-type support, the target privilege checks) are excluded by construction, and postgres_version reports the source version alone.

The returned cleanup is always non-nil, including on error, so callers can defer it unconditionally.

Types

type Builder

type Builder struct {
	Category Category
	Flag     string
	Build    func(*stream.Config, ...ConnOption) ([]Check, CleanupFunc)
}

Builder turns a stream.Config into the concrete checks for a category, plus an optional cleanup function that releases resources the checks share (e.g. a Postgres connection). The connection options apply to every connection those checks open. Each new category adds an entry to Builders and a matching CLI flag in cmd/root_cmd.go.

type Category

type Category string

Category groups checks of the same concern so callers can opt in by category via CLI flags. New categories are added as new check sets land — see docs/migration_preflight_issue.md for the planned ones.

const (
	CategoryConnectivity Category = "connectivity"
	CategoryReplication  Category = "replication"
	CategoryAccess       Category = "access"
	CategorySchema       Category = "schema"
	CategoryResources    Category = "resources"
)

type Check

type Check interface {
	Name() string
	Run(ctx context.Context) ([]Finding, error)
}

Check is the minimal contract every preflight check must satisfy. Run returns the findings the check produced; a non-nil error means the check itself could not complete (distinct from finding a problem with the system under test).

type CheckResult

type CheckResult struct {
	Name     string         `json:"name"`
	Status   CheckStatus    `json:"-"`
	Reason   StatusReason   `json:"-"`
	Findings []Finding      `json:"findings"`
	Err      error          `json:"-"`
	Details  map[string]any `json:"-"`
	Summary  string         `json:"-"`
}

CheckResult bundles a check's name with whatever it produced. Status and Reason are derived by the engine: Status says which of the four outcomes the check reached, and Reason explains every status other than StatusOK.

func (CheckResult) MarshalJSON

func (r CheckResult) MarshalJSON() ([]byte, error)

MarshalJSON renders Err as a string so the report is consumable from a non-Go process (the default error marshaling drops the message).

type CheckStatus added in v1.5.0

type CheckStatus string

CheckStatus is the outcome the engine derived for a check. The engine always derives it; a check never sets its own status.

const (
	StatusOK       CheckStatus = "ok"       // ran, found nothing wrong
	StatusFindings CheckStatus = "findings" // ran, reported at least one finding
	StatusError    CheckStatus = "error"    // ran, could not complete
	StatusNotRun   CheckStatus = "not_run"  // never started, or cut off before it produced a result
)

type CleanupFunc

type CleanupFunc func(context.Context) error

CleanupFunc releases any resources a builder set up (e.g. a shared Postgres connection). Builders return nil when there's nothing to clean up.

type ConnOption added in v1.5.0

type ConnOption func(*connOptions)

ConnOption configures every connection the checks a builder returns open, including the connections the exported snapshot probe opens. Supplying no option leaves the connections exactly as pgstream configures them.

func WithDialFunc added in v1.5.0

func WithDialFunc(dial DialFunc) ConnOption

func WithLookupFunc added in v1.5.0

func WithLookupFunc(lookup LookupFunc) ConnOption

type ConnectivityCheck

type ConnectivityCheck struct {
	Label string
	URL   string
	// ConnOptions configure the connection the check opens. Optional. See
	// WithDialFunc and WithLookupFunc for the contract they must keep.
	ConnOptions []ConnOption
}

ConnectivityCheck verifies a Postgres URL accepts a connection and answers a ping. A connection or ping failure is reported as a finding (not a check error), since establishing connectivity is the purpose of the check.

func (*ConnectivityCheck) Name

func (c *ConnectivityCheck) Name() string

func (*ConnectivityCheck) Run

func (c *ConnectivityCheck) Run(ctx context.Context) ([]Finding, error)

type DatabaseSizeCheck added in v1.5.0

type DatabaseSizeCheck struct {
	Source postgres.AcquireFunc
	// contains filtered or unexported fields
}

DatabaseSizeCheck reports the on-disk size of the source database, so that a migration run records the size it worked against. No database size is wrong on its own, so the check is informational and never produces a finding. Note that pg_database_size covers the whole database, including indexes and bloat, while a data snapshot copies less.

func (*DatabaseSizeCheck) Details added in v1.5.0

func (c *DatabaseSizeCheck) Details() map[string]any

Details reports the size as a byte count, so that a JSON consumer can calculate with it. Summary renders that count for a reader.

func (*DatabaseSizeCheck) Name added in v1.5.0

func (c *DatabaseSizeCheck) Name() string

func (*DatabaseSizeCheck) Run added in v1.5.0

func (c *DatabaseSizeCheck) Run(ctx context.Context) ([]Finding, error)

func (*DatabaseSizeCheck) Summary added in v1.5.0

func (c *DatabaseSizeCheck) Summary() string

Summary renders the size for the human-readable report.

type Detailer

type Detailer interface {
	Details() map[string]any
}

Detailer is an optional interface. A Check implements it to attach structured, non-finding context to its result, for example the extensions it inspected. The engine calls Details after Run and puts the result in the JSON report only, under the "details" key.

type DialFunc added in v1.5.0

type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)

DialFunc opens a connection to an address that is already resolved.

type Finding

type Finding struct {
	ID      string `json:"id"`
	Title   string `json:"title"`
	Detail  string `json:"detail"`
	Message string `json:"message"`
}

Finding describes a single issue detected by a Check. Every finding is an error — a check that finds nothing wrong returns no findings at all.

ID and Title identify the kind of problem, not the instance of it. Neither carries data read from the database under test, so a consumer can count findings by ID and show Title as a heading for a kind it has never seen. Detail carries the specifics: the tables, the version, the setting value. Message is the single line the CLI prints.

type LookupFunc added in v1.5.0

type LookupFunc func(ctx context.Context, host string) ([]string, error)

LookupFunc resolves a host name to the addresses to try.

type PostgresRangeTypeCheck

type PostgresRangeTypeCheck struct {
	Source    postgres.AcquireFunc
	Selection stream.TableSelection
}

PostgresRangeTypeCheck verifies that every in-scope range/multirange column uses a range type pgstream's Postgres target writer can actually encode.

func (*PostgresRangeTypeCheck) Name

func (c *PostgresRangeTypeCheck) Name() string

func (*PostgresRangeTypeCheck) Run

type PostgresVersionCheck added in v1.2.3

type PostgresVersionCheck struct {
	Source postgres.AcquireFunc
	Target postgres.AcquireFunc
	// contains filtered or unexported fields
}

PostgresVersionCheck reports the source's PostgreSQL version and, when a target is configured, verifies the target runs a major version at least as new as the source. pgstream snapshots the source with pg_dump and restores it into the target; restoring a dump taken from a newer server into an older one is unsupported and can fail on syntax or catalog differences the older server doesn't understand. The gate is on the major version — a minor/patch difference within the same major does not block a restore.

Target is optional: when nil (e.g. source-only runs) the check is purely informational and surfaces just the source version via Details (source_version); when set it additionally surfaces target_version and reports a finding on an incompatible downgrade. Either way it is the single source of truth for version information in the report.

func (*PostgresVersionCheck) Details added in v1.2.3

func (c *PostgresVersionCheck) Details() map[string]any

Details exposes the source version, and the target version too when a target was compared, so the report records what was inspected regardless of outcome.

func (*PostgresVersionCheck) Name added in v1.2.3

func (c *PostgresVersionCheck) Name() string

func (*PostgresVersionCheck) Run added in v1.2.3

func (*PostgresVersionCheck) Summary added in v1.5.0

func (c *PostgresVersionCheck) Summary() string

Summary reports the version for the human-readable report. It reports the source version alone when there is no target, and both versions otherwise.

type ProgressFunc

type ProgressFunc func(idx, total int, name string)

ProgressFunc is invoked just before each check runs. idx is 1-based. A check the engine does not start reports no progress.

type ReplicaIdentityCheck

type ReplicaIdentityCheck struct {
	Source    postgres.AcquireFunc
	Selection stream.TableSelection
}

ReplicaIdentityCheck verifies that every in-scope table has a REPLICA IDENTITY sufficient for logical replication of UPDATE/DELETE WAL events. Anything insufficient means those events would silently be skipped at run time. The check inspects only the tables that pass the user's include/exclude filter (TableSelection) so unrelated tables don't pollute the report.

func (*ReplicaIdentityCheck) Name

func (c *ReplicaIdentityCheck) Name() string

func (*ReplicaIdentityCheck) Run

type ReplicationRoleAttrCheck

type ReplicationRoleAttrCheck struct {
	Source postgres.AcquireFunc
}

ReplicationRoleAttrCheck verifies the current source role has the REPLICATION attribute, which is required to open a logical replication slot.

func (*ReplicationRoleAttrCheck) Name

func (c *ReplicationRoleAttrCheck) Name() string

func (*ReplicationRoleAttrCheck) Run

type ReplicationSlotHeadroomCheck

type ReplicationSlotHeadroomCheck struct {
	Source postgres.AcquireFunc
}

ReplicationSlotHeadroomCheck reports whether the source has at least one slot still available before max_replication_slots is reached.

func (*ReplicationSlotHeadroomCheck) Name

func (*ReplicationSlotHeadroomCheck) Run

type Report

type Report struct {
	Results []CheckResult `json:"results"`
}

Report is the outcome of running a set of checks.

func Run

func Run(ctx context.Context, checks []Check, opts ...RunOption) Report

Run executes every check in order. A check returning an error does not stop the run; subsequent checks still execute and the error is captured in the report alongside the findings. When the caller's context is done, Run starts no further checks and records each remaining one as StatusNotRun.

func (Report) HasErrors

func (r Report) HasErrors() bool

HasErrors reports whether the run was anything other than clean: a check produced findings, a check failed to complete, or a check did not run. A check that did not run counts, because a report with missing checks is no evidence that the system is ready, and the CLI exit code must not claim it is. Callers that want only the checks which looked and objected must read the statuses in the report.

type ReportPrinter

type ReportPrinter struct {
	Report Report
}

ReportPrinter renders a Report for display. It satisfies the cmd-side printer contract (PrettyPrint string + json.Marshaler), so existing print(cmd, p) helpers can drive it without change. Flag-driven rendering options (NoColor, Verbose, …) will live on this struct.

func (ReportPrinter) MarshalJSON

func (p ReportPrinter) MarshalJSON() ([]byte, error)

MarshalJSON delegates to the underlying Report so a printer marshals to the same shape as the data type it wraps.

func (ReportPrinter) PrettyPrint

func (p ReportPrinter) PrettyPrint() string

PrettyPrint renders the report as a human-readable string.

type RunOption

type RunOption func(*runOptions)

RunOption configures Run.

func WithCheckTimeout added in v1.5.0

func WithCheckTimeout(d time.Duration) RunOption

WithCheckTimeout bounds each check with a context deadline, so one slow check cannot consume the whole of the caller's budget. A check that exceeds the bound is reported as StatusNotRun, because a caller-imposed bound is not a defect in the check, and the run continues with the next check. The default is no bound, which runs every check to completion.

The bound is the deadline the check is given, not a limit the engine enforces on its own: the engine runs each check to completion, one at a time. A check honours the bound by passing the context it is given to the work it does — every check in this package passes it to the driver, which is context-aware. A check that ignores its context still runs past the bound, and blocks the run while it does.

func WithProgress

func WithProgress(fn ProgressFunc) RunOption

WithProgress installs a callback invoked before each check runs. Useful for updating a spinner or log line with "running X of N: <name>".

type SchemaExtensionCompatibilityCheck

type SchemaExtensionCompatibilityCheck struct {
	Source postgres.AcquireFunc
	Target postgres.AcquireFunc
	// contains filtered or unexported fields
}

SchemaExtensionCompatibilityCheck verifies that every extension installed on the source database is also installed on the Postgres target. pgstream replicates the schema and data of objects that depend on extensions (custom types, functions, operators, index access methods), but it never installs the extensions themselves. It reports the full set of source extensions it inspected via Details (source_extensions), regardless of the outcome.

func (*SchemaExtensionCompatibilityCheck) Details

func (c *SchemaExtensionCompatibilityCheck) Details() map[string]any

Details exposes every extension installed on the source as a string array under source_extensions, so the report records what was inspected even when nothing is missing.

func (*SchemaExtensionCompatibilityCheck) Name

func (*SchemaExtensionCompatibilityCheck) Run

type SchemaTypeCompatibilityCheck

type SchemaTypeCompatibilityCheck struct {
	Source    postgres.AcquireFunc
	Selection stream.TableSelection
}

SchemaTypeCompatibilityCheck verifies that pgstream can decode every column of every in-scope table. A column type is considered supported when either pgx's static type map natively handles it, or pgstream adds its own handling on top of pgx (pgstreamSupportedTypes).

func (*SchemaTypeCompatibilityCheck) Name

func (*SchemaTypeCompatibilityCheck) Run

type SnapshotConnectionsCheck

type SnapshotConnectionsCheck struct {
	Source postgres.AcquireFunc
	// Demand is the number of concurrent connections the snapshot will open at
	// peak (snapshot_workers × table_workers).
	Demand uint
}

SnapshotConnectionsCheck verifies the source Postgres has enough spare connection slots to serve the snapshot's peak concurrency (snapshot_workers × table_workers) on top of what is already in use, without exceeding max_connections. Non-superuser roles cannot use the slots reserved by superuser_reserved_connections, so those are excluded from the headroom.

func (*SnapshotConnectionsCheck) Name

func (c *SnapshotConnectionsCheck) Name() string

func (*SnapshotConnectionsCheck) Run

type SourceOption added in v1.5.0

type SourceOption func(*sourceOptions)

SourceOption configures BuildSourceChecks.

func WithConnOptions added in v1.5.0

func WithConnOptions(opts ...ConnOption) SourceOption

func WithSnapshotData added in v1.5.0

func WithSnapshotData(cfg *pgsnapshotgenerator.Config) SourceOption

WithSnapshotData sets the data snapshot configuration that the snapshot-gated checks size themselves against: snapshot_connection_headroom compares snapshot_workers x table_workers against the source's max_connections, and source_snapshot_single_instance derives its probe count from the same product.

func WithSourceCategories added in v1.5.0

func WithSourceCategories(categories ...Category) SourceOption

WithSourceCategories restricts the run to the given categories, in the order they are registered in Builders. Omitting it runs every category.

type SourceSequenceSelectPrivilegesCheck

type SourceSequenceSelectPrivilegesCheck struct {
	Source    postgres.AcquireFunc
	Selection stream.TableSelection
}

SourceSequenceSelectPrivilegesCheck verifies that the source Postgres role can read every in-scope sequence pgstream may need to snapshot.

func (*SourceSequenceSelectPrivilegesCheck) Name

func (*SourceSequenceSelectPrivilegesCheck) Run

type SourceSnapshotInstanceCheck added in v1.2.2

type SourceSnapshotInstanceCheck struct {
	Probe  func(ctx context.Context, probes int) (missing int, err error)
	Probes int
}

SourceSnapshotInstanceCheck verifies the source URL resolves to a single Postgres instance, which parallel data snapshotting requires. The data snapshot generator exports a transaction snapshot on one connection and imports it (SET TRANSACTION SNAPSHOT) on other connections; an exported snapshot is instance-local, so a load-balanced source (an Aurora/RDS reader endpoint, or a pooler spanning instances) that routes some connections to a different instance fails with `snapshot "…" does not exist`.

The probe is probabilistic (see ProbeExportedSnapshotVisibility): a run that happens to route every probe connection to the exporting instance reports no finding.

func (*SourceSnapshotInstanceCheck) Name added in v1.2.2

func (*SourceSnapshotInstanceCheck) Run added in v1.2.2

type SourceTableSelectPrivilegesCheck

type SourceTableSelectPrivilegesCheck struct {
	Source    postgres.AcquireFunc
	Selection stream.TableSelection
}

SourceTableSelectPrivilegesCheck verifies that the source Postgres role can read every table pgstream may need to snapshot or replicate.

func (*SourceTableSelectPrivilegesCheck) Name

func (*SourceTableSelectPrivilegesCheck) Run

type StatusReason added in v1.5.0

type StatusReason string

StatusReason is a short machine-readable explanation of a status other than StatusOK.

const (
	ReasonFindingsReported      StatusReason = "findings_reported"       // accompanies StatusFindings
	ReasonCheckError            StatusReason = "check_error"             // accompanies StatusError; the message is in Err
	ReasonCheckDeadlineExceeded StatusReason = "check_deadline_exceeded" // exceeded the bound set with WithCheckTimeout
	ReasonRunDeadlineExceeded   StatusReason = "run_deadline_exceeded"   // the caller's context expired
	ReasonRunCanceled           StatusReason = "run_canceled"            // the caller cancelled the context
)

type Summarizer added in v1.5.0

type Summarizer interface {
	Summary() string
}

Summarizer is an optional interface. A Check implements it to report one short line about what it observed, such as a size or a version. The engine calls Summary after Run and prints the result next to the check name in the human-readable report only.

type TargetCreateDBPrivilegeCheck added in v1.3.1

type TargetCreateDBPrivilegeCheck struct {
	Target postgres.AcquireFunc
}

func (*TargetCreateDBPrivilegeCheck) Name added in v1.3.1

func (*TargetCreateDBPrivilegeCheck) Run added in v1.3.1

type TargetCreateRolePrivilegeCheck added in v1.4.0

type TargetCreateRolePrivilegeCheck struct {
	Target postgres.AcquireFunc
}

func (*TargetCreateRolePrivilegeCheck) Name added in v1.4.0

func (*TargetCreateRolePrivilegeCheck) Run added in v1.4.0

type WAL2JSONCheck

type WAL2JSONCheck struct {
	Source postgres.AcquireFunc
}

WAL2JSONCheck verifies that the wal2json output plugin is installed and loadable on the source. pgstream decodes WAL through wal2json.

wal2json is a logical-decoding output plugin, not a SQL extension, so it never appears in pg_available_extensions and there is no catalog that lists installed output plugins. The only way to detect it with pgstream's privileges (a non-superuser REPLICATION role) is to probe actual behaviour: create a temporary logical replication slot with the plugin and inspect the outcome. The temporary slot is released automatically at session end, and is dropped explicitly on success so it never counts against slot headroom.

func (*WAL2JSONCheck) Name

func (c *WAL2JSONCheck) Name() string

func (*WAL2JSONCheck) Run

func (c *WAL2JSONCheck) Run(ctx context.Context) ([]Finding, error)

type WALLevelCheck

type WALLevelCheck struct {
	Source postgres.AcquireFunc
}

WALLevelCheck verifies the source Postgres has `wal_level=logical`, which pgstream's replication path requires.

func (*WALLevelCheck) Name

func (c *WALLevelCheck) Name() string

func (*WALLevelCheck) Run

func (c *WALLevelCheck) Run(ctx context.Context) ([]Finding, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL