Documentation
¶
Overview ¶
Package dockerexec is the write-plane exec wrapper: it shells out to `docker compose` with STATIC ARGV only — never a shell, never string interpolation, always a `--` terminator before service names (plan §3, §5.6). It enforces the global one-docker-child semaphore and the ≥1 GB write-plane gate, and reaps the whole process group on context cancellation.
Index ¶
- Variables
- func WritePlaneGate(memTotal, swapTotal, floorBytes uint64) (bool, string)
- type Job
- type Runner
- func (r *Runner) CaptureTry(ctx context.Context, argv []string) (string, error)
- func (r *Runner) PruneBuildCache(ctx context.Context, keep string, onLine func(string)) error
- func (r *Runner) PruneBuildCacheHeld(ctx context.Context, keep string, onLine func(string)) error
- func (r *Runner) PruneImages(ctx context.Context, onLine func(string)) error
- func (r *Runner) PruneImagesHeld(ctx context.Context, onLine func(string)) error
- func (r *Runner) ReapOneOffHeld(ctx context.Context, project string)
- func (r *Runner) ReconcileVolumeOwner(ctx context.Context, volName string, uid int, onLine func(string)) (prevUID int, changed bool, err error)
- func (r *Runner) RemoveContainers(ctx context.Context, ids []string, onLine func(string)) error
- func (r *Runner) RemoveContainersHeld(ctx context.Context, ids []string, onLine func(string)) error
- func (r *Runner) Run(ctx context.Context, job Job, onLine func(string)) error
- func (r *Runner) RunHeld(ctx context.Context, job Job, onLine func(string)) error
- func (r *Runner) RunInternal(ctx context.Context, job Job, onLine func(string)) error
- func (r *Runner) RunStream(ctx context.Context, argv []string, stdout io.Writer, onErrLine func(string)) error
- func (r *Runner) RunStreamHeld(ctx context.Context, argv []string, stdout io.Writer, onErrLine func(string)) error
- func (r *Runner) RunStreamStdin(ctx context.Context, argv []string, stdin io.Reader, onLine func(string)) error
- func (r *Runner) RunStreamStdinHeld(ctx context.Context, argv []string, stdin io.Reader, onLine func(string)) error
- func (r *Runner) WriteAllowed() (bool, string)
- type Semaphore
Constants ¶
This section is empty.
Variables ¶
var ErrBusy = errors.New("dockerexec: another docker operation is in progress")
ErrBusy is returned when the one-docker-child semaphore could not be acquired.
var ErrWritePlaneDisabled = errors.New("dockerexec: write plane disabled (host below the RAM+swap resource gate)")
ErrWritePlaneDisabled is returned when the host is below the §0 resource gate (RAM + swap).
Functions ¶
func WritePlaneGate ¶
WritePlaneGate decides whether the write plane is armed from the host's addressable memory (RAM + swap). memTotal==0 means unknown (non-Linux dev) → armed, with a caveat note. floorBytes overrides the default floor when non-zero (operator's `docker.write_plane_min_mb`), so an operator who accepts the risk can run on a smaller box. Counting swap is what lets a 1 GB + swap VPS deploy.
Types ¶
type Job ¶
type Job struct {
Project string
Dir string
ConfigFiles []string
EnvFile string // optional 0600 --env-file rendered from the env store
Action []string // e.g. ["up","-d","--force-recreate"]
Service string // optional; appended after a "--" terminator
}
Job is one `docker compose` invocation. Project/Dir/ConfigFiles come from the app's compose labels; they are passed as discrete argv elements (no shell), so they cannot inject commands.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner executes Jobs under the gate + semaphore.
func (*Runner) CaptureTry ¶ added in v0.18.0
CaptureTry runs `docker <argv...>` and returns its captured stdout, WITHOUT ever queuing: if the one-docker-child slot is held it returns ErrBusy immediately (skip-don't-queue, like the cron/backup/GC paths) so a slow child — e.g. a registry round-trip — can never stall a deploy that is waiting on the slot. stderr is discarded (metadata reads don't need it). Static argv only (Mooring-authored, never operator input); runs under the §0 write gate and reaps the process group on ctx cancel. The caller MUST bound ctx with a timeout. Used by the image-update checker to read the local + registry image digests (`docker image inspect` / `docker buildx imagetools inspect`).
func (*Runner) PruneBuildCache ¶ added in v0.4.16
PruneBuildCache reclaims BuildKit build cache, keeping at most `keep` of the most-recently-used cache (LRU eviction): `docker builder prune -a -f --keep-storage <keep>`. It runs under the §0 write gate + the one-docker-child semaphore, exactly like a deploy. `keep` is a validated size (e.g. "5GB") passed as a discrete argv element (no shell). Best-effort by design — callers log the outcome and never fail a deploy on error.
func (*Runner) PruneBuildCacheHeld ¶ added in v0.6.0
PruneBuildCacheHeld is PruneBuildCache for a caller that ALREADY HOLDS the one-docker-child semaphore (the disk-pressure auto-GC, which holds the slot across the image + cache prune — calling the non-Held PruneBuildCache there would self-deadlock re-acquiring the one slot).
func (*Runner) PruneImages ¶ added in v0.6.0
PruneImages reclaims DANGLING (untagged, unreferenced) images — the superseded builds that pile up as apps redeploy and are the usual disk hog. `docker image prune -f` NEVER removes an image referenced by a container (running OR stopped) or a tagged image, so it frees only genuine garbage; and since a Mooring rollback rebuilds its image, this can't break rollback. Runs under the §0 write gate + the one-docker-child semaphore. Output (incl. "Total reclaimed space") streams to onLine.
func (*Runner) PruneImagesHeld ¶ added in v0.6.0
PruneImagesHeld is PruneImages for a caller that ALREADY HOLDS the one-docker-child semaphore (the disk-pressure auto-GC, which TryAcquires so it never queues a docker child).
func (*Runner) ReapOneOffHeld ¶ added in v0.6.0
ReapOneOffHeld force-removes any orphaned ONE-SHOT (`compose run`) containers of a project — the leftovers a KILLED `run --rm` CLI (timeout/shutdown) couldn't remove itself. Best-effort: all errors are ignored. The CALLER must hold the one-docker-child semaphore (the cron scheduler, which runs one task at a time, so no live one-shot is being reaped).
func (*Runner) ReconcileVolumeOwner ¶ added in v0.16.0
func (r *Runner) ReconcileVolumeOwner(ctx context.Context, volName string, uid int, onLine func(string)) (prevUID int, changed bool, err error)
ReconcileVolumeOwner ensures a named docker volume's root is owned by uid (recursively), but only when it currently differs — STAT-FIRST, so an already-correct volume costs one cheap helper-container run and no tree walk. It exists to heal the UID-drift class: a build image's user UID could shift when a package was added, and since Docker copies ownership onto a volume only while it is empty, a shifted UID left the app unable to write its own data. Running just before `up`, this restores the volume to the image's (now pinned) UID. Runs throwaway helper containers with STATIC argv (never a shell) under the §0 write gate + one-docker-child semaphore, like a deploy. `volName` is a compose-derived name and `uid` a fixed number — both are discrete argv elements. Returns whether it chowned. Best-effort by contract: the caller logs failures and never fails a deploy on them.
func (*Runner) RemoveContainers ¶ added in v0.9.0
RemoveContainers force-removes containers by id (`docker rm -f <id>...`) — write-plane, §0 resource-gated, acquiring the one-docker-child semaphore. It does NO scoping of its own: the CALLER must pass only ids it has already verified belong to the intended project (the web-layer conflict reconcile resolves each id's compose project/service label over the READ plane first, so a foreign or system container is never passed here). Returns the rm exit error (if any).
func (*Runner) RemoveContainersHeld ¶ added in v0.9.0
RemoveContainersHeld is RemoveContainers for a caller that ALREADY HOLDS the one-docker-child semaphore (e.g. the self-heal remediation path, which runs its `up` via RunHeld). It must not be called without holding the semaphore. Same no-scoping contract as RemoveContainers.
func (*Runner) Run ¶
Run executes the job, invoking onLine for each (truncated) output line. It acquires the semaphore (blocking on ctx), and on ctx cancellation kills the whole process group and reaps it. Returns the command error (incl. non-zero exit) or a gate/semaphore error.
func (*Runner) RunHeld ¶
RunHeld is Run for a caller that ALREADY HOLDS the one-docker-child semaphore (the self-healing supervisor, which acquires it non-blocking via its safety gate so it never queues a docker child). It must not be called without holding the semaphore.
func (*Runner) RunInternal ¶
RunInternal runs a Mooring-OWNED infrastructure job (bringing up the embedded read-only socket-proxy) REGARDLESS of the §0 write-plane resource gate — the read plane must come up even on a sub-1 GB box. It is NOT for app workloads: the caller passes a fixed, embedded, Mooring-authored compose (never operator input), so it bypasses the RAM gate while keeping the static-argv discipline, the one-docker-child semaphore, and process-group reaping. It acquires the semaphore (blocking on ctx) like Run.
func (*Runner) RunStream ¶ added in v0.6.0
func (r *Runner) RunStream(ctx context.Context, argv []string, stdout io.Writer, onErrLine func(string)) error
RunStream execs `docker <argv...>` and streams its RAW stdout bytes to `stdout` (an io.Writer — e.g. the tar→gzip→encrypt backup pipeline), while stderr is line-truncated to onErrLine for logging. Unlike Run, stdout is NOT line-buffered or merged with stderr, so a binary dump/tar stream passes through byte-for-byte. Static argv only (Mooring-authored, never operator input); it runs under the §0 write gate + the one-docker-child semaphore and reaps the process group on ctx cancel. Used for backup SIDECARS (`docker run --rm … pg_dump` / `… tar`) — a fresh one-shot container, never an exec into a running one.
func (*Runner) RunStreamHeld ¶ added in v0.6.0
func (r *Runner) RunStreamHeld(ctx context.Context, argv []string, stdout io.Writer, onErrLine func(string)) error
RunStreamHeld is RunStream for a caller that ALREADY HOLDS the one-docker-child semaphore (the backup scheduler, which TryAcquires so it never queues a docker child). It must not be called without holding the semaphore.
func (*Runner) RunStreamStdin ¶ added in v0.6.0
func (r *Runner) RunStreamStdin(ctx context.Context, argv []string, stdin io.Reader, onLine func(string)) error
RunStreamStdin execs `docker <argv...>` feeding `stdin` to the child's STDIN (e.g. a decrypted+gunzipped tar stream piped into a `docker run -i … tar -x` restore sidecar), while stdout+stderr are line-truncated to onLine. Static argv only (Mooring-authored); runs under the §0 write gate + the one-docker-child semaphore and reaps the process group on ctx cancel. The counterpart of RunStream for the RESTORE direction.
func (*Runner) RunStreamStdinHeld ¶ added in v0.6.0
func (r *Runner) RunStreamStdinHeld(ctx context.Context, argv []string, stdin io.Reader, onLine func(string)) error
RunStreamStdinHeld is RunStreamStdin for a caller that ALREADY HOLDS the semaphore.
func (*Runner) WriteAllowed ¶
WriteAllowed reports whether the write plane is armed, and why not if not.
type Semaphore ¶
type Semaphore struct {
// contains filtered or unexported fields
}
Semaphore caps concurrent `docker` children at 1 across the whole process (poller stats go through the proxy and don't count; only exec children do). Remediation/scale-up use TryAcquire (never queue — queuing children IS the OOM vector, plan §4).
func NewSemaphore ¶
func NewSemaphore() *Semaphore
NewSemaphore returns the global one-docker-child semaphore.
func (*Semaphore) TryAcquire ¶
TryAcquire grabs the slot without blocking; false means held.