Documentation
¶
Overview ¶
Package agent runs steps on behalf of an engine it does not trust.
The other half of internal/execution/remote. The engine hands over a command and streams the result back; this is what is on the host.
It was written second, on purpose ¶
The executor exists and its contract is pinned by thirteen tests against a fake. Writing the agent first would have made it the specification by accident -- the contract would then be whatever that program happened to do, and the engine would have grown to match its quirks. Everything here is written to satisfy internal/execution/remote/protocol.go, and the test that matters puts the real engine and the real agent on a socket together.
What it refuses to be ¶
Not an orchestrator. It knows nothing of workflows, dependencies, schedules or retries, exactly as the TaskExec comment says of every executor. It takes a command, runs it, and reports what happened.
Not a secret store. It RESOLVES secrets from one -- a directory of files, the same shape the kubelet mounts and Docker uses -- under an allowlist that belongs to this host and not to the engine's cluster. The engine never sends a value and never learns one.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent holds the running executions.
func (*Agent) Cancel ¶
Cancel stops a step.
The process GROUP is signalled, not the process: the shell spawned whatever the command is, and killing only the shell leaves it running.
func (*Agent) Handler ¶
Handler serves the three routes the engine calls.
Thin on purpose: everything that decides behaviour is in the Agent, so the tests that matter drive it directly and this file is transport. The route names are remote.HTTPAgent's, and the two are the only pair that has to agree -- which is why a test puts them on a socket together rather than trusting that they do.
func (*Agent) Resume ¶
Resume serves the same execution from `after`.
A gap is answered rather than papered over: when the ring no longer reaches back that far the engine is told what it wanted and what is available, and it fails the step. Serving from the oldest line instead would hand back a stream with a hole in the middle and no way to know how big.
func (*Agent) Start ¶
func (a *Agent) Start(ctx context.Context, req remote.StartRequest) (io.ReadCloser, error)
Start runs a step and returns a reader over its stream.
The request is validated BEFORE anything is spawned: a refused step must leave no process behind, and an unresolvable secret must be a refusal rather than a command running with a variable silently unset.
type Options ¶
type Options struct {
// Token authenticates the engine. Empty accepts anyone, which is a
// development convenience and is warned about at startup rather than
// silently allowed.
//
// One token for every engine, and the gap is stated rather than
// discovered: no revoking one without changing them all, and no per-engine
// identity in the audit trail. See the package's README for what a second
// version would need.
Token string
// SecretsDir is the root of the secret store: `<dir>/<name>/<key>` holds
// one value, which is how the kubelet mounts a Secret and how Docker mounts
// one. Empty means this host serves no secrets at all, and a step that asks
// for one is refused naming the coordinate.
SecretsDir string
// AllowedSecrets limits which secret NAMES a step may ask for.
//
// The same division the pod executor has, with the boundary moved: the
// installation says which secrets exist, the workflow says which step gets
// which. Empty denies everything -- a host somebody else administers should
// not hand over its store because an engine asked nicely.
AllowedSecrets []string
// WorkDir is where a step runs when it names no directory of its own.
WorkDir string
// RingSize is how many lines are kept for a resumed connection.
//
// Bounded on purpose, and the consequence is stated rather than implied:
// this many lines of network outage survive, and beyond it the engine is
// told the gap is unrecoverable and fails the step. A step missing an
// unknown number of lines is worse than a step that failed.
RingSize int
// AliveEvery is how often a running step emits a heartbeat. Zero takes the
// protocol's default.
AliveEvery time.Duration
// StateDir is where the execID -> pid map is written, so cancel survives
// this process restarting. Empty keeps it in memory only, and cancel is
// then best-effort across a restart.
StateDir string
// Shell runs the command. Empty is `/bin/sh -c`, matching the local
// executor: `run:` in the YAML is a shell line, not an argv.
Shell []string
}
Options configure one agent.