Documentation
¶
Overview ¶
Package executor defines the boundary between the control plane and the place a step actually runs.
The interface is deliberately not shaped around containers. A bare host process and a virtual machine are first-class backends: nothing here names an image layer, a mount, a namespace or a daemon. Spec.Image is a hint an executor is free to ignore — the process backend has no image at all — and anything a backend cannot do is a capability it declines to advertise, never a special case in the core (ADR 0006).
Sandbox lifetime is explicit rather than implicit per backend: a LeaseScope says how long the sandbox outlives the step that acquired it. That is what makes reuse safe to reason about — a pooled sandbox carries state from earlier runs that cannot be hashed, so steps that run in one are not cacheable.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoStableIdentity = errors.New("executor: no stable environment identity")
ErrNoStableIdentity is returned by EnvironmentIdentity when a backend, or one of its sandboxes, has no reproducible environment to hash. A host-process backend runs against whatever toolchain the host happens to carry, and no honest digest describes that; the scheduler treats such steps as non-cacheable rather than caching them against an identity that quietly changes.
Functions ¶
This section is empty.
Types ¶
type Cmd ¶
type Cmd struct {
// Args is the full argument vector; Args[0] is the program. There is no
// shell in between: a caller that wants shell semantics asks for a shell.
Args []string
// Env adds to, and overrides, the sandbox environment for this command.
Env map[string]string
// Dir is relative to the sandbox working directory. Empty means that
// directory itself.
Dir string
// Stdin, Stdout and Stderr are optional. A nil Stdin reads EOF; nil
// outputs are discarded.
Stdin io.Reader
Stdout, Stderr io.Writer
}
Cmd is one command to run inside a sandbox.
type Executor ¶
type Executor interface {
// Acquire returns a sandbox matching spec, ready to run commands.
Acquire(ctx context.Context, spec Spec) (Sandbox, error)
// Capabilities is what this backend can honestly grant. A backend that
// cannot enforce a capability must not advertise it.
Capabilities() []dholev1.Capability
// Kind is the backend's stable identifier, as configuration names it.
Kind() string
// EnvironmentIdentity is the digest of the environment a sandbox acquired
// with an EMPTY Spec would run in — this backend's own default, and
// nothing about any particular step. A backend with no reproducible
// environment returns ErrNoStableIdentity and an empty string.
//
// It is what an engine announces on registration and what its tier's cache
// keys are hashed against (ADR 0021): the plane needs one identity per
// tier before it dispatches anything, and it cannot acquire a sandbox on
// another machine to ask. A step that names its OWN image is a different
// environment from this one, and cache.StepEnvironment keys it against
// what the step named instead.
EnvironmentIdentity() (string, error)
}
Executor is one backend: a place sandboxes come from.
func WithDeclaredEnvironment ¶
WithDeclaredEnvironment returns e reporting id as its environment identity.
It exists for the operator whose environment IS reproducible but whose backend cannot prove it. A process executor runs against whatever its host carries and so reports no stable identity (ADR 0021), which is the honest answer for a laptop and the wrong one for a fleet of engines built from one immutable image: those hosts really are identical, and nothing in the executor can see that.
This is a DECLARATION, not a discovery, and the difference is the whole risk. An operator who declares one identity across hosts that are not actually identical gets one host's results served as another's — silently, because that is exactly what a cache hit looks like. So it is opt-in, it is never inferred, and the value should be something that changes when the environment does: the image digest the hosts were built from, not "prod".
The backend keeps every other behaviour, including its capabilities: naming an environment says what the steps run IN, never what they are allowed to do.
type LeaseScope ¶
type LeaseScope string
LeaseScope is how long a sandbox lives. It is chosen by the caller, not inferred from the backend, because it decides both cleanup and cacheability.
const ( // LeaseStep releases the sandbox when the step finishes. The default, and // the only scope with no unhashable carried state. LeaseStep LeaseScope = "step" // LeaseJob keeps the sandbox for every step of one job. LeaseJob LeaseScope = "job" // LeasePipeline keeps the sandbox for the whole pipeline run. LeasePipeline LeaseScope = "pipeline" // LeasePool takes a warm sandbox reused across runs. Faster, and // explicitly dirty: its prior state is an input that cannot be hashed, so // steps that run in one are not cacheable. LeasePool LeaseScope = "pool" // LeaseService is a sidecar sandbox bounded by the pipeline that started // it, released when that pipeline ends. LeaseService LeaseScope = "service" )
type Requirements ¶
type Requirements struct {
// OS and Arch name the target platform, in Go's GOOS/GOARCH vocabulary.
// Empty means the step does not care.
OS string
Arch string
// Capabilities the step needs the sandbox to grant. An executor that does
// not advertise one cannot be given the step.
Capabilities []dholev1.Capability
// EngineType is the executor KIND the step must run on — "process",
// "kubernetes", "containerd" — matched against the kinds an engine
// registered. Empty means the step does not care.
//
// Placement had no other lever than capability before this: a pipeline
// that needed a pod asked for NETWORK and hoped, and a step that had to be
// a host process could not be expressed at all.
EngineType string
}
Requirements are what a step needs of the place it runs. The scheduler matches these against what each executor advertises.
type Sandbox ¶
type Sandbox interface {
// Exec runs a command to completion and returns its exit code. A command
// that runs and exits non-zero is not an error: err is reserved for the
// sandbox failing to run it at all.
Exec(ctx context.Context, cmd Cmd) (exitCode int32, err error)
// Put writes a file into the sandbox under name, a path relative to the
// sandbox root. It never escapes that root.
Put(ctx context.Context, name string, r io.Reader) error
// Get reads a file back out. The caller closes the reader.
Get(ctx context.Context, name string) (io.ReadCloser, error)
// Mkdir creates a directory in the sandbox under name, a path relative to
// the sandbox root, together with any parent it needs. It never escapes
// that root, and creating one that already exists is not an error.
//
// It exists because a step's output ports live under outputs/ (see
// docs/wire-contract.md, "Port layout on disk") and nothing else creates
// that directory: a step whose command is `... > outputs/copy` failed
// with "No such file or directory" on every backend, which reads as a
// broken pipeline rather than as a missing sandbox convention. Put
// creates the parents of a FILE it is given, and an output port has no
// file until the step writes one.
Mkdir(ctx context.Context, name string) error
// Signal delivers sig to everything the sandbox is currently running. It
// is not an error to signal an idle sandbox.
Signal(ctx context.Context, sig Signal) error
// Release tears the sandbox down. It is idempotent: releasing an already
// released sandbox is not an error, so cleanup paths can be unconditional.
Release(ctx context.Context) error
// EnvironmentIdentity is a digest of the environment THIS sandbox runs
// commands in — the resolved digest of the image it was acquired with, a
// VM snapshot id — used as a cache key input. A sandbox with no
// reproducible environment returns ErrNoStableIdentity and an empty
// string.
//
// It lives here because this is where the environment is. Identity used to
// be a property of the executor alone, and for a container backend it is
// not: the image comes off the Spec, so one Kubernetes executor asked
// about two steps running two different images answered with the digest of
// its configured pod template both times. The two steps then hashed to one
// cache key, and a content-addressed cache that collides hands one step's
// outputs back as another step's result.
EnvironmentIdentity() (string, error)
}
Sandbox is one acquired execution environment. Every method is meaningful for a process, a container and a VM alike; a method only one of them could implement does not belong here.
type Signal ¶
type Signal string
Signal is a process signal expressed backend-neutrally. A backend maps it to whatever its platform offers — a POSIX signal, a Windows console control event, an ACPI button on a VM — so callers never speak syscall numbers.
const ( // SIGINT asks the running command to stop, as an interactive interrupt. SIGINT Signal = "INT" // SIGTERM asks the running command to stop and clean up. The signal // cancellation uses first. SIGTERM Signal = "TERM" // SIGKILL stops the running command without giving it a chance to react. SIGKILL Signal = "KILL" )
type Spec ¶
type Spec struct {
// Image is a hint. Backends that have a notion of an image resolve it;
// those that do not — the process backend, a bare VM — ignore it. It is
// never an error to ask for one a backend cannot use.
Image string
// Env is the base environment every command in the sandbox inherits. A
// Cmd may add to or override it.
Env map[string]string
// WorkDir is the directory commands start in, interpreted by the backend.
// Empty means the sandbox root.
WorkDir string
// Lease is how long the sandbox lives. The zero value means LeaseStep.
Lease LeaseScope
// Requirements the sandbox must satisfy.
Requirements Requirements
}
Spec describes the sandbox a caller wants.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package containerd runs steps as OCI containers on a containerd daemon.
|
Package containerd runs steps as OCI containers on a containerd daemon. |
|
Package executortest holds the executor conformance contract: the behaviour every executor backend must exhibit, expressed once so a container, VM or remote backend can be dropped in unchanged.
|
Package executortest holds the executor conformance contract: the behaviour every executor backend must exhibit, expressed once so a container, VM or remote backend can be dropped in unchanged. |
|
Package kubernetes runs steps as pods in a Kubernetes cluster.
|
Package kubernetes runs steps as pods in a Kubernetes cluster. |
|
Package pool keeps sandboxes warm between runs.
|
Package pool keeps sandboxes warm between runs. |
|
Package process runs steps as bare host processes.
|
Package process runs steps as bare host processes. |
|
Package vm runs steps in microVMs: the strong-isolation tier, where the boundary between a step and everything else is a guest kernel rather than a set of namespaces.
|
Package vm runs steps in microVMs: the strong-isolation tier, where the boundary between a step and everything else is a guest kernel rather than a set of namespaces. |
|
agent
command
Command dhole-vm-agent is the guest half of the VM executor: the init process of a Dhole microVM.
|
Command dhole-vm-agent is the guest half of the VM executor: the init process of a Dhole microVM. |
|
vmwire
Package vmwire is the protocol spoken between the VM executor on the host and the guest agent inside a microVM.
|
Package vmwire is the protocol spoken between the VM executor on the host and the guest agent inside a microVM. |