Documentation
¶
Overview ¶
Package cf_postgres provides the caerus-framework PostgreSQL component. It wraps a pgx/v5 connection pool, verifies connectivity with a fail-fast ping at Init, and exposes the pool to dependent components via Pool().
Index ¶
- Constants
- func OverlayDSN(cfg *PostgresConfig, dsn string) error
- type CFPostgres
- func (c *CFPostgres) GetDependencies() []string
- func (c *CFPostgres) GetInitOrderStage() cf.Stage
- func (c *CFPostgres) Health(ctx context.Context) error
- func (c *CFPostgres) Init(ctx context.Context, fw *cf.CaerusFramework) error
- func (c *CFPostgres) Metrics() []cf_observability.Metric
- func (c *CFPostgres) Migrate(ctx context.Context) error
- func (c *CFPostgres) Name() string
- func (c *CFPostgres) OnConfigReload(source string, cfg any)
- func (c *CFPostgres) Pool() *pgxpool.Pool
- func (c *CFPostgres) RegisterConfigSources(conf any) error
- func (c *CFPostgres) RunJob(ctx context.Context, task string) error
- func (c *CFPostgres) Shutdown(ctx context.Context) error
- func (c *CFPostgres) WithinTx(ctx context.Context, fn func(pgx.Tx) error) error
- type MigrationOption
- type Option
- func WithApplicationName(name string) Option
- func WithConfig(cfg PostgresConfig) Option
- func WithConfigSource(name, path string, opts ...SourceOption) Option
- func WithConnString(dsn string) Option
- func WithConnectTimeout(d time.Duration) Option
- func WithDatabase(db string) Option
- func WithDegradedMode(enabled bool) Option
- func WithEmbeddedMigrations(fsys embed.FS, dir string, opts ...MigrationOption) Option
- func WithHealthCheckPeriod(d time.Duration) Option
- func WithHealthWhenDegraded(policy string) Option
- func WithHost(host string) Option
- func WithLockTimeout(d time.Duration) Option
- func WithLogger(logger *slog.Logger) Option
- func WithMaxConnIdleTime(d time.Duration) Option
- func WithMaxConnLifetime(d time.Duration) Option
- func WithMaxConns(n int32) Option
- func WithMigrateOnInit() Option
- func WithMigrations(fsys fs.FS, opts ...MigrationOption) Option
- func WithMinConns(n int32) Option
- func WithName(name string) Option
- func WithPassword(password string) Option
- func WithPingTimeout(d time.Duration) Option
- func WithPoolConfig(cfg *pgxpool.Config) Option
- func WithPort(port int) Option
- func WithQueryTracer(t pgx.QueryTracer) Option
- func WithSSLMode(mode string) Option
- func WithStatementTimeout(d time.Duration) Option
- func WithTLSClientCertFile(certPath, keyPath string) Option
- func WithTLSRootCAFile(path string) Option
- func WithUser(user string) Option
- type PostgresConfig
- type SourceOption
Constants ¶
const ( // ComponentName is the framework component name for the postgresql // component. It is the identifier other components use in GetDependencies // to require postgres. ComponentName = "postgresql" // ComponentStage is the stage data-layer components initialize in. It is // not a built-in bootstrap stage; AddComponent registers it automatically // the first time a component declares it. ComponentStage = cf.Stage("data") )
Variables ¶
This section is empty.
Functions ¶
func OverlayDSN ¶
func OverlayDSN(cfg *PostgresConfig, dsn string) error
OverlayDSN merges connection fields from dsn into cfg. DSN-derived fields win over existing values (file/env). Pool sizing from the DSN is applied only when the parsed pool sets a non-zero value.
Types ¶
type CFPostgres ¶
type CFPostgres struct {
// contains filtered or unexported fields
}
CFPostgres is the caerus-framework-postgresql component. It wraps a pgx connection pool, verifies connectivity at Init, and closes it at Shutdown.
func New ¶
func New(opts ...Option) *CFPostgres
New creates a postgresql component. The pool is created and pinged at Init, not here. Invalid user-facing options (bad DSN, unknown ssl_mode) are deferred to Init rather than panicking; only an unparseable built-in default connection string panics (internal invariant).
func (*CFPostgres) GetDependencies ¶
func (c *CFPostgres) GetDependencies() []string
GetDependencies implements cf.Dependencies. The component logs through the framework logs component, and depends on configuration when WithConfigSource is set.
func (*CFPostgres) GetInitOrderStage ¶
func (c *CFPostgres) GetInitOrderStage() cf.Stage
GetInitOrderStage implements cf.CaerusComponent.
func (*CFPostgres) Health ¶
func (c *CFPostgres) Health(ctx context.Context) error
Health implements cf.HealthProvider. It pings the pool, so the observability component's readiness endpoint reflects real database connectivity. A nil pool (before Init or after Shutdown) is unhealthy. After DegradedMode with a failed ping (or nil pool from a failed create), behaviour follows health_when_degraded (default not_ready → still unhealthy for /readyz).
func (*CFPostgres) Init ¶
func (c *CFPostgres) Init(ctx context.Context, fw *cf.CaerusFramework) error
Init implements cf.CaerusComponent. It creates the pgx connection pool and verifies connectivity with a ping. By default a broken database fails startup (fail-fast). With DegradedMode, a failed ping keeps the pool (or a failed create leaves Pool nil) and lets Initialize continue (metrics/logs scream; Health stays honest unless health_when_degraded=ready).
func (*CFPostgres) Metrics ¶
func (c *CFPostgres) Metrics() []cf_observability.Metric
Metrics implements cf_observability.MetricsProvider. Before Init or after Shutdown it returns nil. After Init (including DegradedMode without a live ping) it always returns samples so degrade/unreachable state is visible.
Pool gauges come from pgxpool.Stat on every scrape when the pool is non-nil; *_total counters are cumulative for the pool's lifetime and reset only when the pool is rebuilt (config reload / reconnect). The "component" label carries Name() so named primary/replica instances are distinguishable on /metrics.
func (*CFPostgres) Migrate ¶
func (c *CFPostgres) Migrate(ctx context.Context) error
Migrate applies pending up migrations using the live pool and the filesystem configured via WithMigrations. Requires a successful Init. "Already up to date" (migrate.ErrNoChange) is success. Concurrent callers are serialized by golang-migrate's advisory lock.
Production: prefer the framework job flag --postgresql.job=migrate from the same binary; omit WithMigrateOnInit on serving Deployments so Init does not migrate.
func (*CFPostgres) Name ¶
func (c *CFPostgres) Name() string
Name implements cf.CaerusComponent. Name implements cf.CaerusComponent. Returns the custom name set via WithName, or the default ComponentName ("postgresql") if no custom name was set.
func (*CFPostgres) OnConfigReload ¶
func (c *CFPostgres) OnConfigReload(source string, cfg any)
OnConfigReload implements cf.ConfigReloader. It rebuilds the pool from the bound configuration source (file → env → DSN already applied by configuration). The fresh value is delivered as cfg but the pool is rebuilt from the source so the translation stays in one place. On failure the previous pool is kept (last-good).
func (*CFPostgres) Pool ¶
func (c *CFPostgres) Pool() *pgxpool.Pool
Pool returns the pgx connection pool. It is non-nil after a successful Init and nil before Init or after Shutdown.
func (*CFPostgres) RegisterConfigSources ¶
func (c *CFPostgres) RegisterConfigSources(conf any) error
RegisterConfigSources implements cf.ConfigSourceRegistrar. The framework calls it during argv absorption; it registers this component's configuration source (name, path, env prefix, format, Owner and the POSTGRES_DSN AfterLoad overlay) with the configuration component. No-op when no source is bound.
func (*CFPostgres) RunJob ¶
func (c *CFPostgres) RunJob(ctx context.Context, task string) error
RunJob implements cf.JobRunner. The only supported task is "migrate", which it dispatches to CFPostgres.Migrate; any other task is an error (the framework validates the task against the source's declared set before this runs, so this is the last line of defense).
func (*CFPostgres) Shutdown ¶
func (c *CFPostgres) Shutdown(ctx context.Context) error
Shutdown implements cf.CaerusComponent. It closes the pgx pool; further use of Pool() after shutdown returns nil.
func (*CFPostgres) WithinTx ¶
WithinTx runs fn inside a single transaction, committing when it returns nil and rolling back otherwise (including on an error from fn or a canceled context). Use it for multi-statement operations that must be atomic — the framework keeps the pool lifecycle; this helper keeps the commit/rollback boilerplate out of every caller. Requires a successful Init.
type MigrationOption ¶
type MigrationOption func(*migrationConfig)
MigrationOption configures how WithMigrations applies schema migrations.
func WithMigrationsTable ¶
func WithMigrationsTable(name string) MigrationOption
WithMigrationsTable sets the table golang-migrate uses to track applied versions (default "schema_migrations"). Pick a distinct name when several services share one database.
type Option ¶
type Option func(*options)
Option configures the postgresql component at construction time.
func WithApplicationName ¶
WithApplicationName sets the application_name runtime parameter, identifying this process's connections to the server (default: unset, so the server-side default applies).
func WithConfig ¶
func WithConfig(cfg PostgresConfig) Option
WithConfig sets a static connection configuration snapshot. Non-zero fields of cfg override the values set by the convenience options. Prefer WithConfigSource when using caerus-framework-configuration with hot-reload.
cfg, err := cf_configuration.Lookup[cf_postgres.PostgresConfig](conf, "postgresql") p := cf_postgres.New(cf_postgres.WithConfig(*cfg))
func WithConfigSource ¶
func WithConfigSource(name, path string, opts ...SourceOption) Option
WithConfigSource binds this component to a named configuration source and registers that source with the configuration component (via the framework's ConfigSourceRegistrar pass during argv absorption). The module owns the Source: the config type, default EnvPrefix, the POSTGRES_DSN AfterLoad overlay and its Owner (Name(), so named instances reload correctly). main only points the instance at where the config lives.
cf_postgres.New(cf_postgres.WithConfigSource("postgresql", "config/postgresql.json"))
cf_postgres.New(cf_postgres.WithConfigSource("orders", "/etc/app/orders.yaml",
cf_postgres.WithSourceFormat(cf_configuration.FormatYAML)))
A path of "" registers an env-only (fileless) source when the EnvPrefix is non-empty. The path CLI override stays --<source-name> (ParseFlags). Declares a dependency on "configuration".
func WithConnString ¶
WithConnString sets the full connection string (DSN or URL), parsed via pgxpool.ParseConfig. A bad DSN is recorded and returned from Init (New does not panic). Later convenience setters override individual fields.
func WithConnectTimeout ¶
WithConnectTimeout sets how long a single connection attempt may take (default 0 = no timeout, the pgx/libpq default).
func WithDatabase ¶
WithDatabase sets the database name (default: the user name).
func WithDegradedMode ¶ added in v0.0.2
WithDegradedMode allows Init to succeed when the connectivity ping (or pool create) fails. Default is hard-fail. Degraded mode screams in logs/metrics; Health still fails ping unless HealthWhenDegraded is "ready".
func WithEmbeddedMigrations ¶
func WithEmbeddedMigrations(fsys embed.FS, dir string, opts ...MigrationOption) Option
WithEmbeddedMigrations is WithMigrations for go:embed: it takes the embedded filesystem plus the directory holding the golang-migrate files and resolves the sub-filesystem internally. embed guarantees the directory exists at build time, so the only failure mode is a mismatched dir string — a programmer error that panics loudly:
//go:embed migrations var migrations embed.FS p := cf_postgres.New( cf_postgres.WithEmbeddedMigrations(migrations, "migrations"), )
func WithHealthCheckPeriod ¶
WithHealthCheckPeriod sets how often the pool health-checks idle connections (default 1m).
func WithHealthWhenDegraded ¶ added in v0.0.2
WithHealthWhenDegraded sets Health() behaviour while unreachable after DegradedMode: "not_ready" (default) or "ready" (break-glass LB traffic).
func WithLockTimeout ¶
WithLockTimeout sets the per-connection lock_timeout (0 = unset). The value is applied as a runtime parameter in milliseconds on every connection, so a blocked row lock aborts instead of waiting forever.
func WithLogger ¶
WithLogger overrides the logger used for component diagnostics. By default the component logs through the framework logs component (declared in GetDependencies); WithLogger is an explicit override for tests and embedded use and wins over the framework logger. slog.Default() remains the fallback only when neither is available.
func WithMaxConnIdleTime ¶
WithMaxConnIdleTime sets how long an idle connection may live before being closed (default 30m).
func WithMaxConnLifetime ¶
WithMaxConnLifetime sets how long a connection may live before being closed and replaced (default 1h).
func WithMaxConns ¶
WithMaxConns sets the pool's maximum number of connections (default: max(4, runtime.GOMAXPROCS)).
func WithMigrateOnInit ¶
func WithMigrateOnInit() Option
WithMigrateOnInit makes Init call CFPostgres.Migrate after the connectivity ping (fail-fast). Use for local/single-replica only. Production should keep WithMigrations (so the framework job flag works) but omit WithMigrateOnInit on the serving Deployment, and run the Job with --postgresql.job=migrate instead.
func WithMigrations ¶
func WithMigrations(fsys fs.FS, opts ...MigrationOption) Option
WithMigrations configures the migration filesystem (and optional tracking table) used by CFPostgres.Migrate. It does not migrate by itself at Init; combine with WithMigrateOnInit for local single-process apps, or use the framework job flag --postgresql.job=migrate so the same binary can run as a K8s Job without a separate cmd/migrate.
fsys must be rooted at the directory containing golang-migrate files (<version>_<name>.up.sql / .down.sql). For go:embed use WithEmbeddedMigrations, which takes the embedded filesystem and directory and resolves the sub-filesystem for you.
func WithMinConns ¶
WithMinConns sets the pool's minimum number of connections (default 0).
func WithName ¶
WithName sets a custom component name, allowing multiple postgres instances in the same process. The default name is "postgresql" (ComponentName). Use this when you need multiple postgres clients (e.g., primary and replica) in one binary. Retrieve named instances with GetByName[*CFPostgres](fw, "primary").
func WithPassword ¶
WithPassword sets the authentication password.
func WithPingTimeout ¶
WithPingTimeout sets how long Init waits for the connectivity ping before failing (default 5s).
func WithPoolConfig ¶
WithPoolConfig sets the full pgxpool.Config; the config is deep-copied, so later mutation by the caller does not affect the component and instances never share state. Convenience setters (WithConnString, WithHost, WithPort, WithUser, WithPassword, WithDatabase, WithSSLMode, WithMaxConns, ...) override the matching fields, so call them after WithPoolConfig if you combine them. Pass a config returned by pgxpool.ParseConfig when you need TLS roots, runtime params, or other pgx-only settings.
func WithQueryTracer ¶
func WithQueryTracer(t pgx.QueryTracer) Option
WithQueryTracer sets the pgx query tracer, invoked around every Query, QueryRow, and Exec call. Use it for observability hooks (otel spans, slow-query logging) without importing an instrumentation library into this module — the tracer interface lives in pgx. It is part of the option base config, so it survives pool rebuilds on configuration reload. Apps that need several tracers chain them with a small composite type; pgx calls a single tracer per query.
func WithSSLMode ¶
WithSSLMode sets the sslmode (disable, prefer, require, verify-ca, verify-full). The default from the base connection string is "prefer". The TLS config and fallback chain are re-derived from the current host/port, so sslmode="prefer" still attempts plaintext as a fallback on the same address. An unknown mode is recorded and returned from Init (New does not panic): silently ignoring an explicit TLS requirement would be worse than failing startup.
func WithStatementTimeout ¶
WithStatementTimeout sets the per-connection statement_timeout (0 = unset). The value is applied as a runtime parameter in milliseconds on every connection, guarding against runaway queries.
func WithTLSClientCertFile ¶
WithTLSClientCertFile configures the client PEM certificate and key pair for mTLS. Both paths must be non-empty. Re-read on every pool build, so rotated Secret mounts are picked up on the next configuration reload.
func WithTLSRootCAFile ¶
WithTLSRootCAFile configures a PEM CA bundle used to verify the server certificate (mutual trust for sslmode verify-ca/verify-full, or a private CA for require). The file is re-read on every pool build, so a rotated Secret mount is picked up on the next configuration reload. Requires sslmode require/verify-ca/verify-full.
type PostgresConfig ¶
type PostgresConfig struct {
Host string `json:"host,omitempty" yaml:"host,omitempty" env:"HOST"`
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"PORT"`
User string `json:"user,omitempty" yaml:"user,omitempty" env:"USER"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD" secret:"redact"`
Database string `json:"database,omitempty" yaml:"database,omitempty" env:"DATABASE"`
SSLMode string `json:"ssl_mode,omitempty" yaml:"ssl_mode,omitempty" env:"SSL_MODE"`
MaxConns int32 `json:"max_conns,omitempty" yaml:"max_conns,omitempty" env:"MAX_CONNS"`
MinConns int32 `json:"min_conns,omitempty" yaml:"min_conns,omitempty" env:"MIN_CONNS"`
MaxConnLifetimeSec int32 `json:"max_conn_lifetime_sec,omitempty" yaml:"max_conn_lifetime_sec,omitempty" env:"MAX_CONN_LIFETIME_SEC"`
MaxConnIdleTimeSec int32 `json:"max_conn_idle_time_sec,omitempty" yaml:"max_conn_idle_time_sec,omitempty" env:"MAX_CONN_IDLE_TIME_SEC"`
HealthCheckPeriodSec int32 `json:"health_check_period_sec,omitempty" yaml:"health_check_period_sec,omitempty" env:"HEALTH_CHECK_PERIOD_SEC"`
ConnectTimeoutSec int32 `json:"connect_timeout_sec,omitempty" yaml:"connect_timeout_sec,omitempty" env:"CONNECT_TIMEOUT_SEC"`
// ApplicationName sets the application_name runtime parameter on every
// connection (parity with WithApplicationName, which wins when both set).
ApplicationName string `json:"application_name,omitempty" yaml:"application_name,omitempty" env:"APPLICATION_NAME"`
// StatementTimeoutSec and LockTimeoutSec are per-connection statement/lock
// timeouts (Postgres milliseconds are derived from the seconds value; 0 =
// unset). Guards against runaway queries and stuck advisory locks.
StatementTimeoutSec int32 `json:"statement_timeout_sec,omitempty" yaml:"statement_timeout_sec,omitempty" env:"STATEMENT_TIMEOUT_SEC"`
LockTimeoutSec int32 `json:"lock_timeout_sec,omitempty" yaml:"lock_timeout_sec,omitempty" env:"LOCK_TIMEOUT_SEC"`
// TLSRootCAFile, TLSClientCertFile, TLSClientKeyFile point at PEM files
// (typically External Secrets / Secret mounts) used to build the TLS
// config on every connect and reload — rotation is picked up without a
// process restart. Requires an ssl_mode of require/verify-ca/verify-full.
TLSRootCAFile string `json:"tls_root_ca_file,omitempty" yaml:"tls_root_ca_file,omitempty" env:"TLS_ROOT_CA_FILE"`
TLSClientCertFile string `json:"tls_client_cert_file,omitempty" yaml:"tls_client_cert_file,omitempty" env:"TLS_CLIENT_CERT_FILE"`
TLSClientKeyFile string `json:"tls_client_key_file,omitempty" yaml:"tls_client_key_file,omitempty" env:"TLS_CLIENT_KEY_FILE"`
// DegradedMode — when true, a failed Init ping (or pool create) does not
// abort the process. The pool is kept when create succeeded so later
// reconnect can work; metrics/logs scream. Default off (pointer so
// omitted ≠ explicit false). Off by default (hard Init).
DegradedMode *bool `json:"degraded_mode,omitempty" yaml:"degraded_mode,omitempty" env:"DEGRADED_MODE"`
// HealthWhenDegraded: "not_ready" (default) or "ready". Controls Health()
// (and thus /readyz) while the pool cannot ping after a degraded Init
// or while disconnected. "ready" is break-glass: send LB traffic anyway.
HealthWhenDegraded string `json:"health_when_degraded,omitempty" yaml:"health_when_degraded,omitempty" env:"HEALTH_WHEN_DEGRADED"`
}
PostgresConfig is the file/env-drivable connection configuration. Load it through the configuration component (caerus-framework-configuration) and pass it via WithConfig; both JSON and YAML tags are provided. Durations are in seconds.
func ParseDSN ¶
func ParseDSN(dsn string) (PostgresConfig, error)
ParseDSN parses a PostgreSQL connection URL or keyword/value DSN into a PostgresConfig. Supported forms are those accepted by pgxpool.ParseConfig (e.g. postgres://user:pass@host:5432/db?sslmode=require).
type SourceOption ¶
type SourceOption func(*sourceOptions)
SourceOption configures the self-registered configuration source created by WithConfigSource.
func WithSourceEnvPrefix ¶
func WithSourceEnvPrefix(prefix string) SourceOption
WithSourceEnvPrefix sets the environment overlay prefix for the source (default: the uppercase source name with "-" replaced by "_", plus "_" — "valkey-cache" → "VALKEY_CACHE_"). An empty prefix disables env overlay.
func WithSourceFormat ¶
func WithSourceFormat(f cf_configuration.Format) SourceOption
WithSourceFormat forces the file format instead of inferring it from the path extension (".yaml"/".yml" → YAML; anything else JSON).