docker

package
v0.0.0-...-3354207 Latest Latest
Warning

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

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

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

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

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

type Container struct {
	ID         string
	Name       string
	AppID      string
	RevisionID string
	Running    bool
}

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 New

func New(client Client, router Router, prober HealthProber, log *slog.Logger) *Driver

New wires the driver with its collaborators.

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) Name

func (d *Driver) Name() string

Name reports the driver identity.

func (*Driver) OnProxyHealth

func (d *Driver) OnProxyHealth(fn func(error))

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

type PendingRef struct {
	Source string
	Marker string
}

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

type PortBinding struct {
	HostPort      uint32
	ContainerPort uint32
	Protocol      string // "tcp" or "udp"
}

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 NewS3Client

func NewS3Client() *RealS3Client

NewS3Client wires a new S3 client.

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.

func (*RealS3Client) Upload

func (s *RealS3Client) Upload(
	ctx context.Context,
	endpoint, bucket, region, key, accessKey, secretKey string,
	body io.Reader,
	size int64,
) error

Upload uploads data to S3 using PUT.

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).

Jump to

Keyboard shortcuts

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