Documentation
¶
Overview ¶
Package database implements the managed database controller: the reconcile.Controller that converges a store-backed store.DesiredDatabase to a running, volume-backed container, the same architectural pattern internal/reconcile/application already establishes (level-triggered, deterministic naming, a narrow store interface for testability), applied to a database instead of a built application image.
Redis and Postgres diverge deliberately:
Redis can run safely with no password configured. That is not the recommended production posture, but it is not an active vulnerability the way unauthenticated Postgres is, so this controller reconciles Redis for real: create, mount its volume, start, report Ready. That "not an active vulnerability" reasoning assumes internal-network-only reachability; PubliclyAccessible (migrations/0026_database_public_access.sql) breaks that assumption for a passwordless Redis, so the operator UI carries an explicit warning when enabling it rather than this controller silently refusing, matching the project's operator-trust posture elsewhere (no built-in service mesh or per-resource firewall).
Postgres cannot run safely without credentials: database auth needs the same envelope-encrypted secret storage the secrets design specifies (internal/secrets.Manager), which exists but is only wired into this controller when the control plane itself has a master key configured (cmd/levelrail's dynamicSource calls postgresCredentialsFor and passes the result via WithPostgresCredentials below). Without one, Reconcile refuses to start Postgres at all and reports a loud, explicit condition explaining why, mirroring how internal/deploy.requireNoUnresolvedEnv fails loudly rather than silently deploying a container missing values it needs, rather than starting it in trust-auth mode (no password) as a "temporary" workaround. Controller carries an optional postgresCredentials field, nil whenever no master key is configured or credential generation itself fails for this pass, so Postgres support activates purely by populating it, no restructuring of this controller needed either way.
Index ¶
- Constants
- func ContainerName(dbName string) string
- func ContainerPort(engine string) (int, bool)
- func GenerateSelfSignedCert(commonName string) (certPEM, keyPEM []byte, err error)
- func PasswordSecretKey(engine string) (key string, ok bool)
- func SupportsField(engine, field string) bool
- func SupportsTLS(engine string) bool
- func TLSContainerPort(engine string) (int, bool)
- type ClickHouseCredentials
- type Controller
- type MariaDBCredentials
- type MongoDBCredentials
- type MySQLCredentials
- type Option
- func WithClickHouseCredentials(creds *ClickHouseCredentials) Option
- func WithMariaDBCredentials(creds *MariaDBCredentials) Option
- func WithMeshDNSAddr(addr string) Option
- func WithMongoDBCredentials(creds *MongoDBCredentials) Option
- func WithMySQLCredentials(creds *MySQLCredentials) Option
- func WithPostgresCredentials(creds *PostgresCredentials) Option
- func WithTLS(material *TLSMaterial) Option
- type PostgresCredentials
- type Store
- type TLSMaterial
Constants ¶
const ( PostgresPasswordEnvKey = "postgres_password" MySQLPasswordEnvKey = "mysql_password" MongoPasswordEnvKey = "mongo_password" MariaDBPasswordEnvKey = "mariadb_password" ClickHousePasswordEnvKey = "clickhouse_password" )
PostgresPasswordEnvKey, MySQLPasswordEnvKey, MongoPasswordEnvKey are the internal/secrets envKeys each engine's generated password is stored under, keyed by the database's own name as the secrets manager's "serviceName" (cmd/levelrail's postgresCredentialsFor and its two siblings). Exported and centralized here, not left as unexported consts in cmd/levelrail, so internal/deploy and internal/reconcile/application can resolve a { from: ... } env var's password against the exact same keys those credential generators already write, with no second copy of the string literals to drift.
const ( TLSCertEnvKey = "tls_cert" TLSKeyEnvKey = "tls_key" )
TLSCertEnvKey, TLSKeyEnvKey are the internal/secrets envKeys a TLS-capable database's generated certificate and private key are stored under (cmd/levelrail's tlsMaterialFor), the same per-database keying PostgresPasswordEnvKey and its siblings already establish. Exported so internal/reconcile/application and internal/api can check whether TLS material exists for a given database without importing cmd/levelrail.
Variables ¶
This section is empty.
Functions ¶
func ContainerName ¶
ContainerName exports containerName: the Docker container name dbName's managed database reconciles to, also the host part of a resolved { from: ... } env var (internal/reconcile/application's resolveDatabaseField).
func ContainerPort ¶
ContainerPort returns the standard container port engine's image listens on, and whether engine is recognized.
func GenerateSelfSignedCert ¶
GenerateSelfSignedCert creates a new self-signed ed25519 certificate for commonName (a database's own container name, ContainerName), for cmd/levelrail's tlsMaterialFor to generate once and persist. ed25519 for the same reasons agent/pki.go's GenerateCA already gives: fast key generation, small keys, no RSA parameter-size decision to make.
func PasswordSecretKey ¶
PasswordSecretKey returns the internal/secrets envKey engine's generated password is stored under, and whether engine has a password at all: Redis runs passwordless (this package's own doc comment on why), so it reports ok=false rather than an empty key.
func SupportsField ¶
SupportsField reports whether field is resolvable for a database of engine. The single source of truth internal/deploy.Pipeline.validateEnv (deploy-time) and internal/reconcile/application's resolveDatabaseField (reconcile-time) both defer to, so which fields work for which engine can never drift between the two.
func SupportsTLS ¶
SupportsTLS reports whether engine's managed database gets TLS enabled by this controller (see WithTLS). Scoped to Postgres and Redis for now: both expose an "encrypt without verifying" mode entirely within a standard connection URI (sslmode=require, rediss://) that mainstream client libraries already honor with zero app-side code changes, the "no user action required" bar this feature is held to. The other engines this package reconciles (MySQL, MariaDB, MongoDB, ClickHouse, KeyDB, Dragonfly) don't share that property yet: MongoDB's own tlsAllowInvalidCertificates URI option is a plausible future candidate, KeyDB/Dragonfly fork Redis's TLS flags under different names not verified here, and MySQL/MariaDB have no driver-agnostic URI knob for "encrypt but don't verify" at all.
func TLSContainerPort ¶
TLSContainerPort returns the port engine's image listens on once TLS is enabled, which for Redis differs from ContainerPort's plaintext port (controller.go's own redisTLSContainerPort doc comment: --port 0 disables the plaintext port entirely once TLS is on). Postgres negotiates TLS on its one existing port, so its value is identical to ContainerPort.
Types ¶
type ClickHouseCredentials ¶
ClickHouseCredentials is what ClickHouse reconciliation needs, injected as CLICKHOUSE_USER/CLICKHOUSE_PASSWORD/CLICKHOUSE_DB. Unlike MySQL/MariaDB, the image doesn't refuse to start without these, but this controller still requires them, the same "never unauthenticated" posture MongoDBCredentials' own doc comment explains.
type Controller ¶
type Controller struct {
// contains filtered or unexported fields
}
Controller converges one named database's desired state (read fresh from Store on every Reconcile, never cached) to a running, volume-backed container.
func (*Controller) Teardown ¶
func (c *Controller) Teardown(ctx context.Context) error
Teardown stops and removes this database's container, if one exists. Used both by a caller that owns this database's full lifecycle (an ephemeral preview database, internal/api/preview_environments_databases.go) and by callers that must call it themselves right after moving or deleting desired state: Reconcile treats ErrDatabaseNotFound as "not deployed yet," not "stop everything," so a moved-off-this-node or deleted database is never reconciled here again otherwise. Idempotent and safe to call again after a partial failure: InspectByName reports the container's real state fresh on every call, so a Stop that already ran (or a Remove that already succeeded) is simply skipped rather than retried into an error. Does not remove the container's data volume; see dataVolumeName's own doc comment.
type MariaDBCredentials ¶
MariaDBCredentials is MySQLCredentials' counterpart for the official mariadb image. Injected as MARIADB_ROOT_PASSWORD/MARIADB_USER/ MARIADB_PASSWORD/MARIADB_DATABASE, not the MYSQL_* names: the mariadb image accepts MYSQL_* only as a legacy alias ("MARIADB_* variables will be used in preference to MYSQL_* variables" per its own docs), and from MariaDB 11 the mysqldump/mysql client binaries themselves are gone from the image, so this controller and internal/backup's dump/ restore commands both use the MariaDB-native names throughout, not the MySQL-shaped ones this image only keeps for someone else's old scripts.
type MongoDBCredentials ¶
MongoDBCredentials is what MongoDB reconciliation needs: the same dbName-scoped username/password shape PostgresCredentials/ MySQLCredentials already establish, injected as MONGO_INITDB_ROOT_USERNAME/MONGO_INITDB_ROOT_PASSWORD, the official mongo image's own root-credential env vars. Unlike MySQL's image, mongo's does not refuse to start without these, so this controller still requires them (WithMongoDBCredentials unset blocks reconciling just like an unset WithPostgresCredentials/WithMySQLCredentials does), keeping every managed database on the same "never unauthenticated" posture rather than making Mongo the one exception.
type MySQLCredentials ¶
MySQLCredentials is what MySQL reconciliation needs: the same dbName-scoped username/password shape PostgresCredentials already establishes, injected as MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE (see reconcileMySQLEnv). Password also backs MYSQL_ROOT_PASSWORD: unlike Postgres' image, MySQL's own image refuses to start at all without a root password (or an explicit, deliberately-not-used empty-password opt-out), so there is no unauthenticated-but-running MySQL state this controller could otherwise reach.
type Option ¶
type Option func(*Controller)
Option configures optional Controller behavior.
func WithClickHouseCredentials ¶
func WithClickHouseCredentials(creds *ClickHouseCredentials) Option
WithClickHouseCredentials supplies the credentials ClickHouse reconciliation needs. Without one, every ClickHouse database reports the credentials-blocked condition.
func WithMariaDBCredentials ¶
func WithMariaDBCredentials(creds *MariaDBCredentials) Option
WithMariaDBCredentials supplies the credentials MariaDB reconciliation needs, the same "populate to activate, no restructuring needed" shape the other WithXCredentials options already establish. Without one, every MariaDB database reports the credentials-blocked condition.
func WithMeshDNSAddr ¶
WithMeshDNSAddr points every container this controller creates at addr, a bare nameserver IP (never "ip:port": neither Docker's DNS HostConfig field nor a container's own resolv.conf supports a non-standard nameserver port, confirmed live while building this option, see cmd/levelrail/mesh.go's dockerNameserverPort doc comment), as an additional nameserver ahead of whatever Docker's own resolver would otherwise configure (docker.ContainerSpec.DNS). Without one configured (the default, empty string), Reconcile behaves exactly as before this field existed: no DNS override at all. addr is expected to already be a real, container-reachable address (cmd/levelrail/mesh.go's containerDNSAddr resolves Docker's own bridge gateway IP, only once the mesh DNS server is confirmed bound to port 53); this controller does not validate it.
func WithMongoDBCredentials ¶
func WithMongoDBCredentials(creds *MongoDBCredentials) Option
WithMongoDBCredentials supplies the credentials MongoDB reconciliation needs, the same "populate to activate, no restructuring needed" shape WithPostgresCredentials/WithMySQLCredentials already establish. Without one, every MongoDB database reports the credentials-blocked condition, identical reasoning to Postgres and MySQL.
func WithMySQLCredentials ¶
func WithMySQLCredentials(creds *MySQLCredentials) Option
WithMySQLCredentials supplies the credentials MySQL reconciliation needs, the same "populate to activate, no restructuring needed" shape WithPostgresCredentials already establishes. Without one, every MySQL database reports the credentials-blocked condition, identical reasoning to Postgres: no container starts without real auth.
func WithPostgresCredentials ¶
func WithPostgresCredentials(creds *PostgresCredentials) Option
WithPostgresCredentials supplies the credentials Postgres reconciliation needs. Until envelope-encrypted secrets exist nothing calls this, so every Postgres database reports the credentials-blocked condition instead of starting an unauthenticated container.
func WithTLS ¶
func WithTLS(material *TLSMaterial) Option
WithTLS supplies the self-signed certificate/key an engine that SupportsTLS should terminate TLS with, generated once and persisted the same way credentials already are (cmd/levelrail's tlsMaterialFor). Only read by the Postgres and Redis cases in Reconcile; every other engine ignores it. Nil (the default) reconciles exactly as before this option existed: plaintext, same as every database created before this feature.
type PostgresCredentials ¶
PostgresCredentials is what Postgres reconciliation needs once envelope-encrypted secrets land: a username and password to inject as POSTGRES_USER/POSTGRES_PASSWORD. Deliberately a plain struct, not wired to any secret store: Controller takes an optional, currently-always-nil *PostgresCredentials so real Postgres support turns on by populating it, not by rewriting this controller.
type Store ¶
type Store interface {
GetDesiredDatabase(ctx context.Context, name string) (*store.DesiredDatabase, error)
}
Store is the narrow surface this controller needs from internal/store, so tests can fake it without a real database. *store.DB satisfies this.
type TLSMaterial ¶
TLSMaterial is a self-signed certificate and private key pair for one managed database's server-side TLS, generated once at database creation time (cmd/levelrail's tlsMaterialFor) and persisted through internal/secrets the same way PostgresCredentials' password already is (TLSCertEnvKey/TLSKeyEnvKey, address.go). Self-signed, not issued by a shared platform CA: the only clients that ever use it (internal/reconcile/application's resolveDatabaseURL, via sslmode=require for Postgres or rediss:// for Redis) encrypt without verifying the certificate's issuer, so there is no second party that ever needs to trust it.