Documentation
¶
Overview ¶
Package docker — database backup and restore execution (managed-databases.md §7). The plane sends the engine (never a shell command — threat-model §8 req 4); this file derives the dump/restore command from its own matrix and moves the data through the Docker archive API (docker cp), so no exec-stream framing corrupts the dump and no exec stdin is needed.
Package docker — database reconciler (managed-databases.md §6).
The database reconciler lives alongside the Application reconciler and reuses the same Docker client, label conventions, and network-creation logic. Key differences from Application reconciliation:
- No build stage — images are public registry tags pulled by the daemon.
- Named volumes — created if absent, never removed unless explicitly instructed by a DbRemoveWork with delete_volume=true.
- Health check — engine-specific shell command via Docker HEALTHCHECK.
- Resource limits — NanoCPUs + Memory in HostConfig.
Package docker is the standalone-Docker reconciler — the only orchestrator driver at v1 launch (ADR-006). It converges a server's containers toward the desired set of Applications with the zero-downtime sequence, and reports what is actually running (ADR-005; docs/features/application-deploy.md).
Everything Docker-specific is behind the Client interface (consumer-defined, ENGINEERING rule 6): the real implementation wraps the Docker Engine API; the tests use a recording fake, so the convergence logic — the part that must be correct — is verified without a daemon. The route flip and health probe are likewise injected (Router, HealthProber), keeping the reconciler pure logic.
Index ¶
- Constants
- type BackupExecutor
- func (b *BackupExecutor) ExecuteBackup(ctx context.Context, work *agentv1.DbBackupWork) *agentv1.DbBackupEvent
- func (b *BackupExecutor) ExecutePrune(ctx context.Context, work *agentv1.DbBackupPruneWork) *agentv1.DbBackupPruneEvent
- func (b *BackupExecutor) ExecuteRestore(ctx context.Context, work *agentv1.DbRestoreWork) *agentv1.DbRestoreEvent
- type Client
- type Container
- type ContainerSpec
- type DatabaseReconciler
- type DbClient
- type DbContainer
- type DbContainerSpec
- type Driver
- func (d *Driver) ExecAndWait(ctx context.Context, containerID string, argv []string) (exitCode int, output []byte, err error)
- func (d *Driver) Name() string
- func (d *Driver) OnProxyHealth(fn func(error))
- func (d *Driver) Reconcile(ctx context.Context, desired []*agentv1.AppSpec) ([]*agentv1.AppStatus, error)
- func (d *Driver) RunningContainerForApp(ctx context.Context, appID string) (containerID string, ok bool, err error)
- type ExecClient
- type HealthProber
- type Image
- type PendingRef
- type PortBinding
- type RealS3Client
- type Router
- type S3Client
Constants ¶
const ( LabelDbManaged = "cypherpanel.db.managed" LabelDbID = "cypherpanel.db-id" LabelDbRevisionID = "cypherpanel.db.revision-id" )
Database management label — distinct from Application labels so the two resource types never collide in ListManaged queries.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BackupExecutor ¶
type BackupExecutor struct {
// contains filtered or unexported fields
}
BackupExecutor runs one backup or restore against a database container.
func NewBackupExecutor ¶
func NewBackupExecutor(engine ExecClient, s3 S3Client, log *slog.Logger) *BackupExecutor
NewBackupExecutor wires the executor.
func (*BackupExecutor) ExecuteBackup ¶
func (b *BackupExecutor) ExecuteBackup(ctx context.Context, work *agentv1.DbBackupWork) *agentv1.DbBackupEvent
ExecuteBackup dumps the database inside its container, copies the dump out via the archive API, gzips it, and uploads it to S3. It always returns an event (never a bare error) — success or failure is the observation the plane acts on.
func (*BackupExecutor) ExecutePrune ¶
func (b *BackupExecutor) ExecutePrune(ctx context.Context, work *agentv1.DbBackupPruneWork) *agentv1.DbBackupPruneEvent
ExecutePrune deletes the given S3 objects (a retention sweep the plane computed) and reports which keys were removed vs. left behind. Deleting an absent object is treated as success — S3 DELETE is idempotent — so redelivery and a partially-applied prior sweep both converge. The plane deletes the matching BackupRecord rows only for the keys reported deleted.
func (*BackupExecutor) ExecuteRestore ¶
func (b *BackupExecutor) ExecuteRestore(ctx context.Context, work *agentv1.DbRestoreWork) *agentv1.DbRestoreEvent
ExecuteRestore downloads a backup, copies it into the container via the archive API, and runs the engine's restore (or, for RDB engines, restarts the container so it reloads the placed dump). Returns a terminal event.
type Client ¶
type Client interface {
// EnsureNetwork creates the named network if absent (idempotent).
EnsureNetwork(ctx context.Context, name string, labels map[string]string) error
// EnsureVolume creates a named volume if absent (idempotent). App volumes
// persist across container recreation and are never touched by GC.
EnsureVolume(ctx context.Context, name string, labels map[string]string) error
// ListManaged returns every container carrying this driver's managed label.
ListManaged(ctx context.Context) ([]Container, error)
CreateContainer(ctx context.Context, spec ContainerSpec) (id string, err error)
StartContainer(ctx context.Context, id string) error
// StopContainer stops with a drain timeout, then RemoveContainer deletes it.
StopContainer(ctx context.Context, id string, timeout time.Duration) error
RemoveContainer(ctx context.Context, id string) error
// ContainerIP returns the container's address on the given network.
ContainerIP(ctx context.Context, id, network string) (string, error)
StreamLogs(ctx context.Context, id string, out io.Writer) error
// ListManagedImages returns every image this driver has an association with,
// each carrying all of its references so GC can drop them together.
ListManagedImages(ctx context.Context) ([]Image, error)
RemoveImage(ctx context.Context, id string) error
// EnsureImage makes the local daemon hold the bits this reference currently
// designates. A digest is immutable, so a local copy satisfies it; a tag is
// mutable and is re-fetched, because a redeploy of `acme/web:latest` must
// pick up whatever that tag points at now rather than silently reusing the
// cached image. Only pull-marked specs reach it (AppSpec.pull — deploy from
// container image); built images keep the ADR-008 local/relay contract.
EnsureImage(ctx context.Context, image string) error
// ImageDigest returns the immutable digest reference (repo@sha256:…) of a
// local image, or "" when it has none (a locally-built image never pushed
// anywhere). This is what lets the plane pin a revision to the artifact it
// actually ran instead of to a tag that can move underneath it.
ImageDigest(ctx context.Context, image string) (string, error)
// TagImage points a managed reference at an existing local image. Pulled
// images cannot carry our labels, so this is how they become visible to
// desired-state GC. Idempotent.
TagImage(ctx context.Context, source, target string) error
// HasImage reports whether a reference already exists locally. Used before
// a pull to learn whether the reference is one we are about to create — the
// only way to know later whether it is ours to remove.
HasImage(ctx context.Context, image string) (bool, error)
// ExecAndWait runs argv in a running container to completion, returning its
// exit code and captured output (scheduled tasks, backups). A non-zero exit
// is not an error — the caller interprets it.
ExecAndWait(ctx context.Context, containerID string, cmd []string) (exitCode int, output []byte, err error)
}
Client is the subset of the Docker Engine API the reconciler needs.
type Container ¶
Container is a managed container as the driver observes it. Identity comes from labels the driver itself stamped, never from in-memory bookkeeping — so a freshly-constructed driver can converge a host it has never seen (the crash-recovery path is the same as a normal deploy).
type ContainerSpec ¶
type ContainerSpec struct {
Name string
Image string
Env map[string]string
Network string
Port uint32
Labels map[string]string
CPULimit float64 // fractional cores; 0 = no limit
MemoryLimitMB uint32 // 0 = no limit
Binds []string // "<volume>:<path>" mounts
Ports []PortBinding // raw host-port publishes (tcp/udp)
}
ContainerSpec is the create request the driver builds from an AppSpec.
type DatabaseReconciler ¶
type DatabaseReconciler struct {
// contains filtered or unexported fields
}
DatabaseReconciler converges database containers toward desired state.
func NewDatabaseReconciler ¶
func NewDatabaseReconciler(client DbClient, log *slog.Logger) *DatabaseReconciler
NewDatabaseReconciler wires the database reconciler.
func (*DatabaseReconciler) ReconcileDatabases ¶
func (r *DatabaseReconciler) ReconcileDatabases(ctx context.Context, desired []*agentv1.DbSpec) ([]*agentv1.DbStatus, error)
ReconcileDatabases converges the local Docker daemon toward the desired set of database containers and reports the observed status of each.
Convergence contract (rule 13): reconciling twice equals reconciling once. A DbProvisionWork for a container that already matches the desired spec reports success without recreation. A container whose image or config differs is stopped, removed, and recreated with the same named volume.
func (*DatabaseReconciler) RemoveDatabase ¶
func (r *DatabaseReconciler) RemoveDatabase(ctx context.Context, dbID string, deleteVolume bool) error
RemoveDatabase handles a DbRemoveWork: stops the container, removes it, and optionally removes the volume.
type DbClient ¶
type DbClient interface {
// EnsureNetwork creates the named network if absent (idempotent).
EnsureNetwork(ctx context.Context, name string, labels map[string]string) error
// EnsureVolume creates a named volume if absent (idempotent).
EnsureVolume(ctx context.Context, name string, labels map[string]string) error
// RemoveVolume removes a named volume.
RemoveVolume(ctx context.Context, name string) error
// ListManagedDb returns every container carrying the database managed label.
ListManagedDb(ctx context.Context) ([]DbContainer, error)
// CreateDbContainer creates a database container with volumes, health
// checks, and resource limits.
CreateDbContainer(ctx context.Context, spec DbContainerSpec) (id string, err error)
StartContainer(ctx context.Context, id string) error
StopContainer(ctx context.Context, id string, timeout time.Duration) error
RemoveContainer(ctx context.Context, id string) error
// PullImage pulls an image from a registry.
PullImage(ctx context.Context, image string) error
// WaitHealthy blocks until the container's HEALTHCHECK reports healthy,
// or the context expires.
WaitHealthy(ctx context.Context, containerID string, timeout time.Duration) error
}
DbClient extends the base Client with database-specific operations.
type DbContainer ¶
type DbContainer struct {
ID string
Name string
DbID string
RevisionID string
Running bool
Healthy bool // Docker HEALTHCHECK status
}
DbContainer is a managed database container as the driver observes it.
type DbContainerSpec ¶
type DbContainerSpec struct {
Name string
Image string
Env map[string]string
Network string
VolumeName string
DataPath string // mount target inside the container
ExposePort uint32 // 0 = no host publishing
Labels map[string]string
HealthCmd string // shell command for HEALTHCHECK
CPULimit float64 // fractional cores; 0 = no limit
MemoryLimitMB uint32 // 0 = no limit
}
DbContainerSpec is the create request the driver builds from a DbSpec.
type Driver ¶
type Driver struct {
// contains filtered or unexported fields
}
Driver reconciles standalone-Docker containers. Construct with New.
func (*Driver) ExecAndWait ¶
func (d *Driver) ExecAndWait(ctx context.Context, containerID string, argv []string) (exitCode int, output []byte, err error)
ExecAndWait runs argv in a container to completion (ADR-011: argv straight to exec, never a shell), returning its exit code and captured output.
func (*Driver) OnProxyHealth ¶
OnProxyHealth registers a sink for Proxy reconciliation outcomes. Passing nil (or never calling it) leaves the driver silent, which is what the unit tests and builder-role agents want.
func (*Driver) Reconcile ¶
func (d *Driver) Reconcile(ctx context.Context, desired []*agentv1.AppSpec) ([]*agentv1.AppStatus, error)
Reconcile converges local containers toward desired and reports observed status. A total inability to reconcile (daemon unreachable) is returned as an error; a single app's failure is captured in its AppStatus and does not stop the others (reconciler-development skill). Apps absent from desired are torn down; a teardown that fails is itself reported as an observed error status — the plane must see that removal has not actually converged.
func (*Driver) RunningContainerForApp ¶
func (d *Driver) RunningContainerForApp(ctx context.Context, appID string) (containerID string, ok bool, err error)
RunningContainerForApp resolves the app's currently-running container by its app-id label — the exec target for a scheduled task (ADR-011: the app's own container, nothing else). ok is false when no container is running (the task run is skipped, scheduled-tasks.md §5).
type ExecClient ¶
type ExecClient interface {
ExecAndWait(ctx context.Context, containerID string, cmd []string) (exitCode int, output []byte, err error)
CopyFromContainer(ctx context.Context, containerID, path string) (io.ReadCloser, error)
CopyToContainer(ctx context.Context, containerID, destDir string, tarStream io.Reader) error
StartContainer(ctx context.Context, id string) error
StopContainer(ctx context.Context, id string, timeout time.Duration) error
WaitHealthy(ctx context.Context, containerID string, timeout time.Duration) error
}
ExecClient is the container-execution surface the executor needs (consumer-defined; *engine.Client satisfies it).
type HealthProber ¶
type HealthProber interface {
Probe(ctx context.Context, upstream string, hc *agentv1.HealthCheck) error
}
HealthProber checks that an upstream is serving before the route flips. The real prober performs an HTTP GET; the fake returns a configured result.
type Image ¶
type Image struct {
ID string
// AppIDs is every application with a managed association to this image:
// the label a build stamped, and/or the cypher/<app>:<revision> aliases a
// pull was tagged with. More than one when two apps run the same image.
AppIDs []string
// References is every managed name the daemon holds for it. Only ours: an
// image can also carry tags an operator or another tool made, and deleting
// an application must never untag those.
References []string
// Pending is the tidy-up an earlier rollout could not finish — a registry
// reference our own pull created and failed to drop.
Pending []PendingRef
}
Image is a managed image as garbage collection sees it. Identity is the image itself, not one of its names, because reclaiming disk means dropping *every* reference the daemon holds — a pulled image keeps its layers for as long as the registry reference it arrived under still exists, however many managed aliases were removed.
type PendingRef ¶
PendingRef pairs a registry reference this driver's pull created with the marker reference recording it as ours (driver.PullMarkerRef).
The pair is what makes the removal retryable. Drop the marker first and the reference becomes indistinguishable from one the operator made — which is to say permanently unreclaimable, since GC may never touch what is not ours. So the marker always outlives the reference it names.
type PortBinding ¶
PortBinding publishes a container port to a host port on one protocol. The engine maps it onto the container's ExposedPorts + HostConfig.PortBindings.
type RealS3Client ¶
type RealS3Client struct {
// contains filtered or unexported fields
}
RealS3Client implements S3Uploader using AWS Signature Version 4. Tiny footprint, no external SDKs (vision.md §1).
func (*RealS3Client) Delete ¶
func (s *RealS3Client) Delete( ctx context.Context, endpoint, bucket, region, key, accessKey, secretKey string, ) error
Delete removes an object from S3 (used by retention pruning).
func (*RealS3Client) Download ¶
func (s *RealS3Client) Download( ctx context.Context, endpoint, bucket, region, key, accessKey, secretKey string, ) (io.ReadCloser, error)
Download downloads data from S3 using GET.
type Router ¶
type Router interface {
// EnsureProxy makes the node's Proxy exist and run (routing-and-tls.md).
// Idempotent; a converged Proxy is a no-op.
EnsureProxy(ctx context.Context) error
// AttachNetwork connects the Proxy to an environment network so it can
// reach that environment's upstreams. Idempotent.
AttachNetwork(ctx context.Context, network string) error
SetRoute(ctx context.Context, appID string, route *agentv1.RouteSpec, upstream string) error
RemoveRoute(ctx context.Context, appID string) error
// Route returns the upstream the app's route currently points at, or
// ok=false when no route is applied. Used by the converged fast path to
// re-assert a route lost to a crash between start and flip.
Route(ctx context.Context, appID string) (upstream string, ok bool, err error)
}
Router applies (or removes) an Application's route on the local proxy, and — because convergence must observe route state, not remember it — reports the route currently applied. The agent/proxy Traefik driver satisfies it structurally (ADR-004): the fragment file on disk is the observable truth.
type S3Client ¶
type S3Client interface {
Upload(ctx context.Context, endpoint, bucket, region, key, accessKey, secretKey string, body io.Reader, size int64) error
Download(ctx context.Context, endpoint, bucket, region, key, accessKey, secretKey string) (io.ReadCloser, error)
Delete(ctx context.Context, endpoint, bucket, region, key, accessKey, secretKey string) error
}
S3Client is the object-storage surface (consumer-defined; *RealS3Client satisfies it).
Directories
¶
| Path | Synopsis |
|---|---|
|
Package engine is the real Docker Engine API client behind the docker driver's Client interface (and the builder's image builds).
|
Package engine is the real Docker Engine API client behind the docker driver's Client interface (and the builder's image builds). |