Documentation
¶
Overview ¶
Package postgres provides a shared PostgreSQL connection layer for the ToolHive ecosystem. It wraps github.com/jackc/pgx/v5/pgxpool with a single Config type, a NewPool factory, and a dynamic-authentication dispatcher. Schema management — migrations, queries, and per-application type codecs — remains the caller's responsibility.
Quick Start ¶
cfg := &postgres.Config{
Host: "db.example.com",
Port: 5432,
User: "appuser",
Password: "s3cret",
Database: "appdb",
}
pool, err := postgres.NewPool(ctx, cfg)
if err != nil {
return err
}
defer pool.Close()
Dynamic Authentication ¶
Setting Config.DynamicAuth causes NewPool to install a BeforeConnect hook that resolves a fresh credential before every connection attempt.
Currently supported backends:
- AWS RDS IAM — short-lived tokens signed with the workload's ambient AWS credentials (env vars, EC2 instance profile, EKS web identity, …). Region "detect" auto-discovers the region via IMDS.
Example:
cfg.DynamicAuth = &postgres.DynamicAuthConfig{
AWSRDSIAM: &postgres.DynamicAuthAWSRDSIAM{Region: "us-east-1"},
}
For short-lived connections that cannot use a pool hook (for example golang-migrate's one-shot migration connection), call NewAuthToken to materialize a single token, then embed it via BuildConnectionStringWithAuth:
token, _ := postgres.NewAuthToken(ctx, cfg, cfg.GetMigrationUser()) connStr := cfg.BuildConnectionStringWithAuth(cfg.GetMigrationUser(), token)
Hooks ¶
WithAfterConnect installs an AfterConnect callback — the canonical place to register application-specific type codecs (for example, codecs for PostgreSQL enum array types defined in the caller's schema):
pool, err := postgres.NewPool(ctx, cfg,
postgres.WithAfterConnect(func(ctx context.Context, conn *pgx.Conn) error {
return registerMyEnumCodecs(ctx, conn)
}),
)
WithBeforeConnect installs a BeforeConnect hook directly. It is mutually exclusive with Config.DynamicAuth — NewPool refuses both, because silently dropping one would leave production tokens to expire ~15 minutes after deploy. Callers that genuinely need both should call NewDynamicAuthFunc, compose with their hook, and pass the composition via WithBeforeConnect with Config.DynamicAuth left nil.
TLS and SSL Mode ¶
DefaultSSLMode is "require", which mandates an encrypted connection but does not verify the server certificate against a CA. This defends against passive eavesdropping; it does not defend against an active attacker that can present a forged certificate. For production deployments against cloud Postgres services that publish a CA bundle (for example AWS RDS, Google Cloud SQL, Azure Database for PostgreSQL), set SSLMode to "verify-full" and configure pgx with the appropriate CA roots — typically by placing the bundle on disk and setting PGSSLROOTCERT, or by attaching a *tls.Config via WithAfterConnect. "require" is kept as the package default because tighter modes break self-signed and dev environments out of the box; production callers should opt up explicitly.
Logging ¶
NewPool emits a single info-level message on success, redacting password fields via Config's slog.LogValuer implementation. StartPoolStatsLogger is an opt-in helper that periodically logs connection-pool statistics at debug level until its context is cancelled.
Secrets Handling ¶
This package treats Password and MigrationPassword as already-resolved strings. File-based secret loading, environment-variable overrides, and pgpass fallback all live in the caller's configuration layer. Config implements slog.LogValuer to redact credentials when logged.
Index ¶
- Constants
- func NewAuthToken(ctx context.Context, cfg *Config, user string) (string, error)
- func NewPool(ctx context.Context, cfg *Config, opts ...Option) (*pgxpool.Pool, error)
- func StartPoolStatsLogger(ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger, ...)
- type BeforeConnectFn
- type Config
- func (c *Config) BuildConnectionStringWithAuth(user, password string) string
- func (c *Config) ConnectionString() string
- func (c *Config) GetMigrationPassword() string
- func (c *Config) GetMigrationUser() string
- func (c *Config) LogValue() slog.Value
- func (c *Config) MigrationConnectionString() string
- func (c *Config) Validate() error
- type DynamicAuthAWSRDSIAM
- type DynamicAuthConfig
- type Option
Constants ¶
const DefaultPoolStatsInterval = 60 * time.Second
DefaultPoolStatsInterval is the cadence at which StartPoolStatsLogger emits a connection-pool snapshot when no other interval is configured.
const DefaultSSLMode = "require"
DefaultSSLMode is applied when Config.SSLMode is empty. It mandates an encrypted connection but does not verify the server certificate against a CA — encryption-only, not authentication. Use "verify-ca" or "verify-full" when the deployment provides a CA bundle (for example, cloud Postgres services); see the package overview for production guidance.
Variables ¶
This section is empty.
Functions ¶
func NewAuthToken ¶
NewAuthToken returns a short-lived password for user produced by the dynamic-authentication backend configured in cfg.DynamicAuth. When DynamicAuth is nil, the empty string is returned and no error is raised — this lets callers fall back to a static Password or PGPASSFILE.
This entry point is intended for short-lived connections (for example, running migrations) where pgxpool's BeforeConnect hook is not available. For pooled connections, prefer NewDynamicAuthFunc.
func NewPool ¶
NewPool creates a *pgxpool.Pool from cfg. When cfg.DynamicAuth is set, NewPool installs the appropriate dynamic-auth hook on BeforeConnect.
Passing both cfg.DynamicAuth and WithBeforeConnect is an error — the failure mode of a silently-replaced auth hook (tokens expiring 15 min after deploy) is severe enough to refuse the ambiguity. Callers that genuinely want to layer logic on top of dynamic auth should call NewDynamicAuthFunc, compose the hooks themselves, and pass the composition via WithBeforeConnect with cfg.DynamicAuth left nil.
cfg is validated; cfg is not mutated.
func StartPoolStatsLogger ¶
func StartPoolStatsLogger(ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger, interval time.Duration)
StartPoolStatsLogger emits a connection-pool snapshot at DEBUG every interval until ctx is cancelled. When interval is zero, the default cadence is used. When logger is nil, slog.Default() is used.
This is an opt-in helper; consumers that want pool metrics through a different sink (OpenTelemetry, Prometheus) should read pool.Stat() themselves.
Types ¶
type BeforeConnectFn ¶
type BeforeConnectFn func(ctx context.Context, conn *pgx.ConnConfig) error
BeforeConnectFn rewrites a connection config (typically by replacing the password) immediately before pgx dials. It is the contract used by pgxpool.Config.BeforeConnect and by this package's dynamic-auth backends.
func NewDynamicAuthFunc ¶
NewDynamicAuthFunc returns a BeforeConnect hook that resolves a fresh dynamic-auth credential on every connection attempt. The returned hook writes the resolved token into connConfig.Password.
Returns an error when cfg.DynamicAuth is nil — callers that may or may not be configured for dynamic auth should branch on cfg.DynamicAuth before calling this constructor, or use NewPool which handles both shapes transparently.
type Config ¶
type Config struct {
// Host is the database server hostname or IP address.
Host string
// Port is the database server port. Required.
Port int
// User is the database username for normal operations
// (SELECT/INSERT/UPDATE/DELETE).
User string
// Password is the application-user password. When empty, pgx falls back
// to PGPASSFILE / ~/.pgpass. Mutually exclusive with DynamicAuth at the
// caller's option — this package does not enforce mutual exclusion
// because some callers legitimately want a static fallback during local
// development.
Password string //nolint:gosec // G101: field name, not a hardcoded credential
// MigrationUser is the database username for schema migrations. When
// empty, defaults to User.
MigrationUser string
// MigrationPassword is the password for MigrationUser. When empty and
// MigrationUser equals User, falls back to Password. Otherwise pgx falls
// back to PGPASSFILE / ~/.pgpass.
MigrationPassword string //nolint:gosec // G101: field name, not a hardcoded credential
// Database is the database name.
Database string
// SSLMode is the SSL mode for the connection (disable, require, verify-ca,
// verify-full). When empty, DefaultSSLMode is applied by the connection
// string builder.
SSLMode string
// DynamicAuth, when non-nil, generates short-lived credentials at connect
// time via NewPool's automatically-installed BeforeConnect hook.
DynamicAuth *DynamicAuthConfig
// MaxOpenConns sets the upper bound on open connections in the pool. When
// zero, pgxpool's default is used.
MaxOpenConns int32
// MinConns is the minimum number of connections pgxpool actively
// maintains in the pool — the pool keeps this many connections open
// even when the application is idle. When zero, pgxpool's default is
// used.
//
// Note for readers used to database/sql: this is the opposite of
// database/sql's MaxIdleConns (which is a ceiling on idle
// connections). pgxpool has no idle-ceiling concept; the floor is the
// only knob.
MinConns int32
// ConnMaxLifetime is the maximum lifetime of a connection. When zero,
// pgxpool's default is used.
ConnMaxLifetime time.Duration
}
Config configures a PostgreSQL connection pool. Password fields are resolved by the caller — file-based secrets, environment variables, and pgpass fallback all live outside this package.
func (*Config) BuildConnectionStringWithAuth ¶
BuildConnectionStringWithAuth builds a libpq-style connection URL using the supplied user and password. When password is empty, the resulting URL omits credentials and pgx will fall back to PGPASSFILE / ~/.pgpass.
The caller is responsible for resolving credentials — dynamic-auth token generation, secret-file reads, and env-var overrides all happen outside this package.
func (*Config) ConnectionString ¶
ConnectionString builds a libpq-style connection URL for the application user. When Password is empty, pgx falls back to PGPASSFILE / ~/.pgpass.
func (*Config) GetMigrationPassword ¶
GetMigrationPassword returns the password for the migration user. When MigrationPassword is unset and the migration user matches User, the application Password is returned. Otherwise an empty string is returned so pgx can fall back to PGPASSFILE / ~/.pgpass.
func (*Config) GetMigrationUser ¶
GetMigrationUser returns the user that owns schema migrations. Falls back to User when MigrationUser is unset.
func (*Config) LogValue ¶
LogValue implements slog.LogValuer. It redacts password fields and reports only presence-indicators for credentials, preventing accidental secret disclosure in logs.
func (*Config) MigrationConnectionString ¶
MigrationConnectionString builds a libpq-style connection URL for the migration user. Useful for short-lived migration tooling where a BeforeConnect hook is not available.
type DynamicAuthAWSRDSIAM ¶
type DynamicAuthAWSRDSIAM struct {
// Region is the AWS region used to sign IAM tokens. Use "detect" to
// auto-discover the region from the EC2 instance metadata service (IMDS).
Region string
}
DynamicAuthAWSRDSIAM configures AWS RDS IAM dynamic authentication.
type DynamicAuthConfig ¶
type DynamicAuthConfig struct {
// AWSRDSIAM enables AWS RDS IAM authentication tokens.
AWSRDSIAM *DynamicAuthAWSRDSIAM
}
DynamicAuthConfig selects a dynamic-authentication backend. Exactly one backend field must be non-nil when DynamicAuthConfig itself is non-nil.
type Option ¶
type Option func(*options)
Option customizes NewPool. See WithBeforeConnect, WithAfterConnect, and WithLogger.
func WithAfterConnect ¶
WithAfterConnect installs a hook that runs immediately after a new connection has been established. The typical use case is registering custom type codecs (for example, application-defined enum array codecs).
func WithBeforeConnect ¶
func WithBeforeConnect(fn BeforeConnectFn) Option
WithBeforeConnect installs a hook that runs immediately before pgx dials.
NewPool rejects a combination of WithBeforeConnect and cfg.DynamicAuth — a silently-replaced auth hook would leave production tokens to expire 15 minutes after deploy. Callers that need both must call NewDynamicAuthFunc explicitly, compose the two hooks themselves in the order they want, and pass the composed result via WithBeforeConnect (with cfg.DynamicAuth left nil so this package does not also try to install an auth hook).
func WithLogger ¶
WithLogger sets the slog.Logger used for pool-creation messages and (when invoked) StartPoolStatsLogger output. When unset, slog.Default() is used.