Documentation
¶
Overview ¶
Package extdb connects to databases this platform does not run: validating their addresses, probing them read-only, and opening them for the viewer through short-lived helper containers. It never writes to a remote database.
Index ¶
- Constants
- Variables
- func DefaultPort(engine string) int
- func DefaultTLSMode(engine string) string
- func EngineForImage(image string) string
- func HelperEnv(c Conn) map[string]string
- func IntervalFromEnv() time.Duration
- func ProbeScript(engine string) (string, error)
- func Refresh(ctx context.Context, rt docker.Runtime, rec store.ExternalDatabase) (store.ExternalDatabase, bool)
- func ResolveField(ep Endpoint, password, field string) (string, error)
- func Supported(engine string) bool
- func SupportsField(engine, field string) bool
- func ValidName(name string) bool
- type AttentionItem
- type Candidate
- type Conn
- type Endpoint
- type Execer
- type HealthStore
- type Helper
- type Monitor
- type PasswordSource
- type Policy
- type Prober
- type Resolver
- type Result
Constants ¶
const ( EnginePostgres = "postgres" EngineMySQL = "mysql" EngineMariaDB = "mariadb" EngineMongoDB = "mongodb" EngineRedis = "redis" )
Engine ids, identical to the managed engine identifiers.
const ( TLSDisable = "disable" TLSPrefer = "prefer" TLSRequire = "require" )
TLS modes an operator can pick.
const ( FieldHost = "host" FieldPort = "port" FieldUsername = "username" FieldDatabase = "database" FieldPassword = "password" FieldURL = "url" )
Fields an app can reference as { from: "<database>.<field>" }.
const ( StatusReachable = store.ExternalHealthReachable StatusSlow = store.ExternalHealthSlow StatusAuthFailed = store.ExternalHealthAuthFailed StatusTLSError = store.ExternalHealthTLSError StatusUnreachable = store.ExternalHealthUnreachable StatusUnknown = store.ExternalHealthUnknown )
Probe outcomes, shared with the stored record.
const AllowLinkLocalEnv = "APP_EXTERNAL_DB_ALLOW_LINK_LOCAL"
AllowLinkLocalEnv opts in to link-local and cloud metadata addresses.
Variables ¶
var ErrBlockedAddress = errors.New("extdb: address is not allowed")
ErrBlockedAddress marks a host the address policy forbids.
Functions ¶
func DefaultTLSMode ¶
DefaultTLSMode is the sensible default for engine.
func EngineForImage ¶
EngineForImage reports which database engine an image runs, or "" when it is not one of the supported engines.
func HelperEnv ¶
HelperEnv is the environment a helper container runs with. Standard client variables are set too so commands never put the password in argv.
func IntervalFromEnv ¶
IntervalFromEnv returns the probe interval, zero meaning disabled.
func ProbeScript ¶
ProbeScript is the shell script a helper runs. Output and exit status both arrive on stdout so a failing client cannot hide its message in a stream error.
func Refresh ¶
func Refresh(ctx context.Context, rt docker.Runtime, rec store.ExternalDatabase) (store.ExternalDatabase, bool)
Refresh applies ResolveSource to a stored record and reports whether its host or network changed.
func ResolveField ¶
ResolveField returns the value of field for ep. password may be empty.
func SupportsField ¶
SupportsField reports whether engine can resolve field. It matches the managed engines' field set, plus a password for Redis because an existing Redis often has one.
Types ¶
type AttentionItem ¶
type AttentionItem struct {
Severity string `json:"severity"`
Kind string `json:"kind"`
Subject string `json:"subject"`
Detail string `json:"detail"`
}
AttentionItem is a record that needs an operator, in a shape the attention center can adopt without this package importing it.
func AttentionItems ¶
func AttentionItems(recs []store.ExternalDatabase) []AttentionItem
AttentionItems lists records whose last probe was not healthy.
type Candidate ¶
type Candidate struct {
ContainerID string `json:"container_id"`
Container string `json:"container"`
Image string `json:"image"`
Engine string `json:"engine"`
Port int `json:"port"`
SuggestedHost string `json:"suggested_host,omitempty"`
Network string `json:"network,omitempty"`
Networks []string `json:"networks,omitempty"`
PublishedPort int `json:"published_port,omitempty"`
SuggestedUser string `json:"suggested_user,omitempty"`
Note string `json:"note,omitempty"`
}
Candidate is one running database container that could be adopted.
type Conn ¶
type Conn struct {
Engine string
Host string
Port int
User string
Password string
Database string
AuthDatabase string
TLSMode string
Network string
SourceContainer string
}
Conn is everything needed to reach one database. Password is held in memory only; the stored record never carries it.
func ConnFromRecord ¶
func ConnFromRecord(ctx context.Context, rec store.ExternalDatabase, secrets PasswordSource) (Conn, error)
ConnFromRecord builds the in-memory connection for a stored record. A record saved without a password resolves with an empty one.
func ResolveSource ¶
ResolveSource re-reads where an adopted container lives now. Containers get recreated, so the network and address recorded at adoption can be stale. It only reads container metadata; nothing is ever connected or changed. When the container cannot be found the connection is returned unchanged.
func (Conn) CheckResolved ¶
CheckResolved rejects a hostname that resolves to a forbidden address. A lookup failure is not an error: container names only resolve inside Docker networks.
type Endpoint ¶
type Endpoint struct {
Engine string
Host string
Port int
User string
Database string
TLSMode string
}
Endpoint is the non-secret half of a connection.
type Execer ¶
Execer adapts a Helper to the viewer's Execer: every call starts a helper, runs the command in it, and removes it when the stream is closed.
type HealthStore ¶
type HealthStore interface {
ListExternalDatabases(ctx context.Context) ([]store.ExternalDatabase, error)
SetExternalDatabaseHealth(ctx context.Context, name, status, reason string, latencyMs int, checkedAt time.Time) error
UpdateExternalDatabase(ctx context.Context, d store.ExternalDatabase) error
}
HealthStore is what the monitor needs from the store.
type Helper ¶
Helper starts short-lived containers that run an engine's own client against a remote database. Nothing stays running: each helper is removed as soon as its caller is done, and expires on its own after a few minutes.
type Monitor ¶
type Monitor struct {
Store HealthStore
Secrets PasswordSource
Runtime func(nodeID string) (docker.Runtime, error)
Logger *slog.Logger
}
Monitor probes every external database on an interval, one at a time, each through a short-lived helper container.
type PasswordSource ¶
type PasswordSource interface {
Exists(ctx context.Context, serviceName, envKey string) (bool, error)
Resolve(ctx context.Context, serviceName, envKey string) (string, error)
}
PasswordSource reads a record's stored password.
type Policy ¶
type Policy struct {
AllowLinkLocal bool
}
Policy decides which hosts may be connected to.
func PolicyFromEnv ¶
func PolicyFromEnv() Policy
PolicyFromEnv reads the opt-in from the environment.
type Prober ¶
type Prober struct {
Runtime docker.Runtime
Logger *slog.Logger
// SlowAfter overrides the slow threshold. Zero reads APP_EXTERNAL_DB_SLOW_MS.
SlowAfter time.Duration
}
Prober checks connectivity with a read-only statement, once, from a helper container.
type Result ¶
type Result struct {
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
LatencyMs int `json:"latency_ms"`
Version string `json:"version,omitempty"`
User string `json:"user,omitempty"`
Database string `json:"database,omitempty"`
Databases []string `json:"databases,omitempty"`
}
Result is one probe outcome. Reason is already scrubbed of the password.