docker

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

Documentation

Overview

Package docker is the container capability: the Docker Engine reached over its API socket, granted to the code that runs containers.

It wraps the upstream Docker Engine SDK client (github.com/moby/moby/client) rather than the docker CLI run through ipc/proc. The SDK is already the client CSF's local simulations consume, it is typed end to end (no parsing of CLI output), and it gives the owner of a container its whole lifecycle — create, wait, start, kill, logs, remove — as separate calls, which is what cleanup on cancellation needs. Talking to the Engine socket is a kernel I/O crossing, so the client is constructed here, under ipc, and nowhere else.

A binary constructs one ContainerHost and passes it to whatever runs containers. Consumers that need only part of the Engine API declare their own narrow interface, which ContainerHost satisfies; ContainerHost.RunSandboxed is the bounded, isolated run-to-completion that sandboxed evaluation and the node executor are built on; ContainerHost.EnsureService keeps one long-lived named container up, such as the database CSF owns, and ContainerHost.Exec runs a command inside it.

Index

Constants

View Source
const (
	// DefaultOutputBytes bounds each captured output stream of a sandboxed run.
	DefaultOutputBytes = 1 << 20
	// DefaultPidsLimit bounds the processes a sandboxed container may create.
	DefaultPidsLimit = 256
	// RemoveTimeout bounds the cleanup that removes a finished or canceled
	// sandbox, which runs even after the caller's context has ended.
	RemoveTimeout = 30 * time.Second
)
View Source
const ModelServerLabel = "dev.csf.model-server"

ModelServerLabel marks a container that serves a model on request and is idle otherwise, such as a reranker: holding the GPU is not using it, so a GPU consumer that yields to others does not yield to it.

View Source
const (

	// OwnedHealthLimit is how long Ensure waits for the health check.
	OwnedHealthLimit = 2 * time.Minute
)

Variables

View Source
var (
	// ErrImageRequired is returned for a sandbox spec without an image.
	ErrImageRequired = errors.New("ipc/docker: sandbox image is required")
	// ErrInvalidOption is returned by NewContainerHost for a nil, empty or
	// conflicting option.
	ErrInvalidOption = errors.New("ipc/docker: invalid container host option")
)
View Source
var (
	// ErrServiceRecordLost reports a container with no record: what the record
	// held (a password) is gone, so the owner refuses to guess rather than
	// create a second one.
	ErrServiceRecordLost = errors.New("ipc/docker: the container exists but its record is missing")
	// ErrServiceNotHealthy reports a container whose health check did not pass
	// within [OwnedHealthLimit].
	ErrServiceNotHealthy = errors.New("ipc/docker: the health check did not pass")
	// ErrServiceNotOwned reports a state directory that owns no such
	// container.
	ErrServiceNotOwned = errors.New("ipc/docker: this state directory owns no such container")
)
View Source
var ErrNoContainer = errors.New("ipc/docker: no such container")

ErrNoContainer reports a named container the Engine does not have.

View Source
var ErrRecordInvalid = errors.New("ipc/docker: state record cannot be used")

ErrRecordInvalid is what every RecordError wraps.

Functions

This section is empty.

Types

type ContainerHost

type ContainerHost struct {
	IContainerAPI
	// contains filtered or unexported fields
}

ContainerHost is the Docker Engine granted as a capability. Its Engine API methods are promoted from the client it wraps, so it satisfies a consumer's narrow interface directly.

func NewContainerHost

func NewContainerHost(options ...ContainerHostOption) (*ContainerHost, error)

NewContainerHost connects the container capability. Construction does not contact the Engine; the first call does.

func (*ContainerHost) ContainerUsage

func (host *ContainerHost) ContainerUsage(started StartedContainer) (sandbox.Usage, error)

ContainerUsage reads the running container's cpu and memory receipt from its cgroup, through its init process.

func (*ContainerHost) EnsureService

func (host *ContainerHost) EnsureService(ctx context.Context, spec ServiceSpec) (ServiceState, error)

EnsureService brings the named container up: it pulls the image when the Engine lacks it, creates the container when none has the name, and starts it when it is not running. An existing container is never recreated, so its configuration is the one it was created with.

func (*ContainerHost) Exec

func (host *ContainerHost) Exec(ctx context.Context, name string, spec ExecSpec) (ExecResult, error)

Exec runs one command inside the running container and waits for it.

func (*ContainerHost) InspectService

func (host *ContainerHost) InspectService(ctx context.Context, name string) (ServiceState, error)

InspectService reports the named container, or ErrNoContainer.

func (*ContainerHost) RemoveContainer

func (host *ContainerHost) RemoveContainer(ctx context.Context, id string) error

RemoveContainer force-removes the container, killing every process in it. It outlives the caller's context, bounded by RemoveTimeout.

func (*ContainerHost) RunSandboxed

func (host *ContainerHost) RunSandboxed(ctx context.Context, spec SandboxSpec) (result SandboxResult, err error)

RunSandboxed creates the container, starts it, waits for it to exit and collects its output, then removes it. The container is removed on every path — success, failure, and cancellation, which kills it first — so a sandbox never outlives the call. A nonzero exit returns the result together with an *ExitError.

func (*ContainerHost) SampleContainer

func (host *ContainerHost) SampleContainer(ctx context.Context, id string) (ContainerSample, error)

SampleContainer takes one usage reading of a running container, at once rather than after the Engine's one-second prior sample.

func (*ContainerHost) SignalService

func (host *ContainerHost) SignalService(ctx context.Context, name string, signal string) error

SignalService sends signal, such as "HUP", to the named container's main process: how a long-lived service is told to reread its configuration.

func (*ContainerHost) StartDetached

func (host *ContainerHost) StartDetached(ctx context.Context, spec SandboxSpec) (StartedContainer, error)

StartDetached creates and starts a long-lived container under the sandbox defaults and returns once it runs; the caller owns it and ends it with ContainerHost.RemoveContainer. A container that fails to start is removed.

func (*ContainerHost) StopService

func (host *ContainerHost) StopService(ctx context.Context, name string) error

StopService stops the named container and leaves it, and its volumes, in place for a later ContainerHost.EnsureService.

type ContainerHostOption

type ContainerHostOption func(settings *containerHostSettings) error

ContainerHostOption configures a ContainerHost.

func WithContainerAPI

func WithContainerAPI(api IContainerAPI) ContainerHostOption

WithContainerAPI supplies an already-constructed Engine client, which the host then owns and closes.

func WithDockerHost

func WithDockerHost(host string) ContainerHostOption

WithDockerHost names the Engine endpoint, such as unix:///var/run/docker.sock. The default is the SDK's platform default; the environment is never read.

func WithOutputBytes

func WithOutputBytes(limit int64) ContainerHostOption

WithOutputBytes bounds each captured output stream of a sandboxed run.

type ContainerSample

type ContainerSample struct {
	// CPUNanoseconds is the container's cumulative cpu time.
	CPUNanoseconds uint64
	// SystemNanoseconds is the host's cumulative cpu time across every cpu.
	SystemNanoseconds uint64
	// OnlineCPUs is how many cpus the host had online at the reading.
	OnlineCPUs uint32
	// MemoryBytes is the memory the container's cgroup charges.
	MemoryBytes uint64
}

ContainerSample is one reading of a running container's usage: cumulative cpu time beside the host's, so two readings give a cpu share, and the memory it holds now.

type ExecResult

type ExecResult struct {
	ExitCode int
	Stdout   []byte
	Stderr   []byte
}

ExecResult is what a finished command left behind, each output bounded by the host's output limit.

type ExecSpec

type ExecSpec struct {
	Command []string
	// User is the user[:group] the command runs as; empty means the
	// container's.
	User string
}

ExecSpec is one command run inside a running container.

type ExitError

type ExitError struct {
	ContainerID string
	Code        int64
}

ExitError reports a sandbox whose command exited unsuccessfully.

func (*ExitError) Error

func (exitError *ExitError) Error() string

Error names the container and its status.

type IContainerAPI

type IContainerAPI interface {
	ContainerCreate(ctx context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error)
	ContainerInspect(ctx context.Context, id string, options client.ContainerInspectOptions) (client.ContainerInspectResult, error)
	ContainerStart(ctx context.Context, id string, options client.ContainerStartOptions) (client.ContainerStartResult, error)
	ContainerStop(ctx context.Context, id string, options client.ContainerStopOptions) (client.ContainerStopResult, error)
	ContainerKill(ctx context.Context, id string, options client.ContainerKillOptions) (client.ContainerKillResult, error)
	ContainerPause(ctx context.Context, id string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error)
	ContainerUnpause(ctx context.Context, id string, options client.ContainerUnpauseOptions) (client.ContainerUnpauseResult, error)
	ContainerStats(ctx context.Context, id string, options client.ContainerStatsOptions) (client.ContainerStatsResult, error)
	ContainerWait(ctx context.Context, id string, options client.ContainerWaitOptions) client.ContainerWaitResult
	ContainerRemove(ctx context.Context, id string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error)
	ContainerLogs(ctx context.Context, id string, options client.ContainerLogsOptions) (client.ContainerLogsResult, error)
	ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error)
	ImageList(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error)
	ImagePrune(ctx context.Context, options client.ImagePruneOptions) (client.ImagePruneResult, error)
	BuildCachePrune(ctx context.Context, options client.BuildCachePruneOptions) (client.BuildCachePruneResult, error)
	DiskUsage(ctx context.Context, options client.DiskUsageOptions) (client.DiskUsageResult, error)
	ImagePull(ctx context.Context, reference string, options client.ImagePullOptions) (client.ImagePullResponse, error)
	ExecCreate(ctx context.Context, containerID string, options client.ExecCreateOptions) (client.ExecCreateResult, error)
	ExecAttach(ctx context.Context, execID string, options client.ExecAttachOptions) (client.ExecAttachResult, error)
	ExecInspect(ctx context.Context, execID string, options client.ExecInspectOptions) (client.ExecInspectResult, error)
	Close() error
}

IContainerAPI is the part of the Docker Engine API client this capability uses and hands on. *client.Client satisfies it; a test substitutes a double.

type IServices

type IServices interface {
	EnsureService(ctx context.Context, spec ServiceSpec) (ServiceState, error)
	InspectService(ctx context.Context, name string) (ServiceState, error)
	StopService(ctx context.Context, name string) error
}

IServices is the part of the container capability an owned service uses. ContainerHost satisfies it.

type Mount

type Mount struct {
	Source   string
	Target   string
	ReadOnly bool
}

Mount is one host path visible inside a sandbox.

type OwnedLocation

type OwnedLocation struct {
	Container string `json:"container"`
	Volume    string `json:"volume"`
	Image     string `json:"image"`
	Host      string `json:"host"`
	Port      uint16 `json:"port"`
}

OwnedLocation is where an owned container is, recorded on its first start and reused on every later one.

type OwnedRecord

type OwnedRecord[Settings any] struct {
	OwnedLocation
	Settings Settings `json:"settings"`
}

OwnedRecord is the record file: the location and the owner's own settings, such as a password, which only the record file holds.

func DecodeOwnedRecord

func DecodeOwnedRecord[Settings any](path string, content []byte,
	validate func(settings Settings) *RecordError) (record OwnedRecord[Settings], previous bool, err error)

DecodeOwnedRecord decodes an owned record in the current shape or the previous flat one, strictly: an unknown field is an error, not ignored, and a required field that is missing is an error, not a zero value the host then acts on. previous reports a record in the previous shape, which the caller rewrites. validate checks the owner's settings; nil checks none.

type OwnedService

type OwnedService[Settings any] struct {
	// contains filtered or unexported fields
}

OwnedService is one long-lived container a state directory owns. Ensure creates it on the first start (a free port probed once and then bound for good, the owner's settings filled once, all recorded mode 0600) and reuses it on every later one; stopping the process that owns it leaves it running, because it holds state.

func NewOwnedService

func NewOwnedService[Settings any](definition OwnedServiceDefinition[Settings], options ...OwnedServiceOption) (*OwnedService[Settings], error)

NewOwnedService validates the definition and the capabilities.

func (*OwnedService[Settings]) Ensure

func (owned *OwnedService[Settings]) Ensure(ctx context.Context) (OwnedRecord[Settings], error)

Ensure brings the container up and returns its record, once its health check passes. The host paths it mounts are created first, so the Engine never creates them root-owned.

func (*OwnedService[Settings]) Record

func (owned *OwnedService[Settings]) Record() (OwnedRecord[Settings], error)

Record reads the record, in the current or the previous shape, or reports ErrServiceNotOwned or a RecordError naming the file, the field and the fix.

func (*OwnedService[Settings]) Status

func (owned *OwnedService[Settings]) Status(ctx context.Context) (OwnedStatus, error)

Status reports the location and the container, without the settings.

func (*OwnedService[Settings]) Stop

func (owned *OwnedService[Settings]) Stop(ctx context.Context) error

Stop stops the container and keeps it and its volume.

type OwnedServiceDefinition

type OwnedServiceDefinition[Settings any] struct {
	// StateDirectory holds RecordFile, mode 0600; one container per state
	// directory.
	StateDirectory string
	RecordFile     string
	// NamePrefix and a digest of StateDirectory name the container; its
	// volume adds "-data" and is mounted at DataDirectory.
	NamePrefix    string
	DataDirectory string
	// Image is pinned by digest.
	Image string
	// Host is the address the port is published on: loopback, or a tailnet
	// address.
	Host netip.Addr
	// NewSettings fills the owner's settings on the first start only; nil
	// leaves them zero.
	NewSettings func() (Settings, error)
	// Spec is the container a record describes: environment, mounts, health
	// check and anything else of the owner's. Name, Image, HostAddress,
	// HostPort and the data volume are set from the record.
	Spec func(record OwnedRecord[Settings]) ServiceSpec
	// Validate checks the owner's settings each time the record is loaded,
	// naming a required field that is missing; nil checks none.
	Validate func(settings Settings) *RecordError
}

OwnedServiceDefinition is what an owner decides; OwnedService does the rest.

type OwnedServiceOption

type OwnedServiceOption func(capabilities *ownedCapabilities) error

OwnedServiceOption grants an OwnedService its capabilities.

func WithClock

func WithClock(source clock.IClock) OwnedServiceOption

WithClock replaces the host's clock, which paces the health wait.

func WithListener

func WithListener(listener ionet.IListener) OwnedServiceOption

WithListener grants the network capability the first start probes a free port with. Required.

func WithServices

func WithServices(services IServices) OwnedServiceOption

WithServices grants the container capability. Required.

type OwnedStatus

type OwnedStatus struct {
	Location OwnedLocation `json:"location"`
	State    ServiceState  `json:"state"`
}

OwnedStatus is what a lifecycle verb prints: the location and the container as the Engine sees it, never the owner's settings.

type RecordError

type RecordError struct {
	Path    string
	Field   string
	Problem string
	Fix     string
}

RecordError names a state record a host refuses to act on: the file, the field, what is wrong with it and how to fix it.

func RequiredField

func RequiredField(field string, fix string) *RecordError

RequiredField is a RecordError for a field a record must carry, for an owner's Validate: the decoder fills the path.

func (*RecordError) Error

func (recordError *RecordError) Error() string

Error names the file, the field, the problem and the fix.

func (*RecordError) Unwrap

func (recordError *RecordError) Unwrap() error

Unwrap is ErrRecordInvalid.

type SandboxResult

type SandboxResult struct {
	ContainerID     string
	ExitCode        int64
	Stdout          []byte
	Stderr          []byte
	StdoutTruncated bool
	StderrTruncated bool
}

SandboxResult is what a finished sandbox left behind.

type SandboxSpec

type SandboxSpec struct {
	Image            string
	Command          []string
	Environment      []string
	WorkingDirectory string
	User             string
	Mounts           []Mount
	Labels           map[string]string
	// Network is a Docker network mode; empty means no network at all.
	Network string
	// MemoryBytes and NanoCPUs are resource limits; zero means unlimited.
	MemoryBytes int64
	NanoCPUs    int64
	// PidsLimit bounds process creation; zero means [DefaultPidsLimit].
	PidsLimit int64
	// WritableRoot leaves the root filesystem writable.
	WritableRoot bool
}

SandboxSpec is one isolated run to completion. Unset limits fall back to the sandbox defaults: no network, every capability dropped, no privilege escalation, a read-only root filesystem with a small /tmp, an init process, and DefaultPidsLimit.

type ServiceSpec

type ServiceSpec struct {
	Name        string
	Image       string
	Environment []string
	Labels      map[string]string
	// Volumes maps a named volume to the path it is mounted at; the Engine
	// creates a missing volume.
	Volumes map[string]string
	// Mounts are host paths visible inside the container. A missing source is
	// an error rather than a root-owned directory the Engine creates.
	Mounts []Mount
	// Port is the container port ("5432/tcp") published on HostAddress at
	// HostPort, which [ServiceState.HostPort] reports. A fixed HostPort keeps
	// the same port across restarts; zero lets the Engine choose one on every
	// start.
	Port        string
	HostAddress netip.Addr
	HostPort    uint16
	// ExtraHostAddresses publish Port at the same HostPort on each further
	// address, such as a tailnet address beside the loopback one; they need a
	// fixed HostPort, so every address serves the one recorded port.
	ExtraHostAddresses []netip.Addr
	// Command replaces the image's default arguments; empty keeps them.
	Command []string
	// HealthCheck is the command the Engine runs every HealthInterval;
	// [ServiceState.Health] reports its verdict.
	HealthCheck    []string
	HealthInterval time.Duration
	// GPU grants the container the host's GPUs through the nvidia driver.
	GPU bool
}

ServiceSpec is one long-lived named container: created once, kept across restarts of the caller, and restarted by the Engine unless stopped. Its data lives in named volumes, which removing the container never removes.

type ServiceState

type ServiceState struct {
	ID      string `json:"id"`
	Status  string `json:"status"`
	Running bool   `json:"running"`
	// Health is the health check's verdict: starting, healthy or unhealthy.
	Health   string `json:"health"`
	HostPort uint16 `json:"host_port"`
}

ServiceState is what the Engine reports of a named container.

type StartedContainer

type StartedContainer struct {
	ID string
	// Pid is the host PID of the container's init process.
	Pid int
}

StartedContainer is a container ContainerHost.StartDetached left running.

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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