executor

package
v0.0.0-...-7d97447 Latest Latest
Warning

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

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

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

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

func WithDeclaredEnvironment(e Executor, id string) (Executor, error)

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

Jump to

Keyboard shortcuts

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