extdb

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

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

View Source
const (
	EnginePostgres = "postgres"
	EngineMySQL    = "mysql"
	EngineMariaDB  = "mariadb"
	EngineMongoDB  = "mongodb"
	EngineRedis    = "redis"
)

Engine ids, identical to the managed engine identifiers.

View Source
const (
	TLSDisable = "disable"
	TLSPrefer  = "prefer"
	TLSRequire = "require"
)

TLS modes an operator can pick.

View Source
const (
	FieldHost     = "host"
	FieldPort     = "port"
	FieldUsername = "username"
	FieldDatabase = "database"
	FieldPassword = "password"
	FieldURL      = "url"
)

Fields an app can reference as { from: "<database>.<field>" }.

View Source
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.

View Source
const AllowLinkLocalEnv = "APP_EXTERNAL_DB_ALLOW_LINK_LOCAL"

AllowLinkLocalEnv opts in to link-local and cloud metadata addresses.

Variables

View Source
var ErrBlockedAddress = errors.New("extdb: address is not allowed")

ErrBlockedAddress marks a host the address policy forbids.

Functions

func DefaultPort

func DefaultPort(engine string) int

DefaultPort is the engine's well known port.

func DefaultTLSMode

func DefaultTLSMode(engine string) string

DefaultTLSMode is the sensible default for engine.

func EngineForImage

func EngineForImage(image string) string

EngineForImage reports which database engine an image runs, or "" when it is not one of the supported engines.

func HelperEnv

func HelperEnv(c Conn) map[string]string

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

func IntervalFromEnv() time.Duration

IntervalFromEnv returns the probe interval, zero meaning disabled.

func ProbeScript

func ProbeScript(engine string) (string, error)

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

Refresh applies ResolveSource to a stored record and reports whether its host or network changed.

func ResolveField

func ResolveField(ep Endpoint, password, field string) (string, error)

ResolveField returns the value of field for ep. password may be empty.

func Supported

func Supported(engine string) bool

Supported reports whether engine can be connected to.

func SupportsField

func SupportsField(engine, field string) bool

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.

func ValidName

func ValidName(name string) bool

ValidName reports whether name is usable as a record name.

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.

func ListCandidates

func ListCandidates(ctx context.Context, rt docker.Runtime) ([]Candidate, error)

ListCandidates finds running database containers on rt. Containers this platform created itself carry its instance label and are never offered. It only reads container metadata.

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

func ResolveSource(ctx context.Context, rt docker.Runtime, c Conn) Conn

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

func (c Conn) CheckResolved(ctx context.Context, p Policy, r Resolver) error

CheckResolved rejects a hostname that resolves to a forbidden address. A lookup failure is not an error: container names only resolve inside Docker networks.

func (Conn) Scrub

func (c Conn) Scrub(text string) string

Scrub replaces the password, raw or URL-escaped, with a placeholder.

func (*Conn) Validate

func (c *Conn) Validate(p Policy) error

Validate normalizes c in place and reports the first problem. It is pure: no DNS lookups happen here, see CheckResolved.

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

type Execer struct {
	Helper *Helper
	Name   string
	Conn   Conn
}

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.

func (*Execer) ExecWithInput

func (e *Execer) ExecWithInput(ctx context.Context, _ string, cmd []string, stdin io.Reader) (io.ReadCloser, error)

ExecWithInput implements dbviewer.Execer. The container id is ignored: the helper this call creates is the only target.

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

type Helper struct {
	Runtime docker.Runtime
	Logger  *slog.Logger
}

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.

func (*Helper) Start

func (h *Helper) Start(ctx context.Context, name string, c Conn) (string, func(), error)

Start creates and starts a helper for c and returns its container id and a remove function that is safe to call more than once.

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.

func (*Monitor) CheckOne

func (m *Monitor) CheckOne(ctx context.Context, rec store.ExternalDatabase) Result

CheckOne probes rec and records the outcome.

func (*Monitor) Run

func (m *Monitor) Run(ctx context.Context, interval time.Duration)

Run blocks until ctx is cancelled.

func (*Monitor) Sweep

func (m *Monitor) Sweep(ctx context.Context)

Sweep probes every record once.

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.

func (*Prober) Probe

func (p *Prober) Probe(ctx context.Context, name string, c Conn) Result

Probe runs the engine's own client against c. It never returns an error: every failure mode is a Result with a status.

type Resolver

type Resolver interface {
	LookupHost(ctx context.Context, host string) ([]string, error)
}

Resolver looks a hostname up; net.DefaultResolver satisfies it.

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.

func Classify

func Classify(engine, out string, execErr error) Result

Classify turns a probe's captured output into a status. It is pure so every failure shape can be table tested.

Jump to

Keyboard shortcuts

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