Documentation
¶
Overview ¶
Package spec defines the public task specification: the YAML/JSON document an operator writes and the validated Go type every Podium component codes against.
Index ¶
- Constants
- Variables
- func NormalizeCapability(c string) string
- func NormalizeRegistryHost(host string) string
- func RegistryHost(image string) (string, error)
- func ValidateRegistryHost(host string) (string, error)
- type Duration
- type Hardening
- type Readiness
- type Resources
- type SecretRef
- type Sidecar
- type TaskSpec
Constants ¶
const ( DefaultWorkingDir = "/workspace" DefaultTimeout = time.Hour DefaultMaxAttempts = 1 DefaultPIDs = 4096 DefaultReadinessTimeout = time.Minute DefaultHTTPPort = 80 )
Defaults applied by ApplyDefaults.
const ( // SecretTargetEnv puts the value in an environment variable named by Key. SecretTargetEnv = "env" // SecretTargetFile mounts the value read-only at the absolute path named by Key. SecretTargetFile = "file" )
Secret targets. A ref says where in the container the value should appear, never what the value is.
const DefaultSecretTarget = SecretTargetEnv
DefaultSecretTarget is what ApplyDefaults fills an unset target with.
const ReservedSidecarName = "task"
ReservedSidecarName is the network alias the task container itself answers to, so no sidecar may take it.
const SecretsMount = "/podium/secrets"
SecretsMount is the tmpfs every task container gets for secret files. A file target outside it works, but this is the path the docs use and the only one guaranteed to be writable, non-executable and gone when the container is.
const SpecDocs = "docs/task-spec.md"
SpecDocs is where a validation error points a reader who wants the whole picture.
Variables ¶
var AllowedCapabilities = []string{
"CHOWN", "DAC_OVERRIDE", "FOWNER", "SETUID", "SETGID", "NET_BIND_SERVICE", "KILL",
}
AllowedCapabilities is the complete set of Linux capabilities a task may add back after the executor drops them all. Anything outside it is rejected at validation: a task that needs more is a node-level operator decision, not a spec field.
var RegistryHostRE = regexp.MustCompile(`^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$`)
RegistryHostRE is what a registry host may look like once normalised: a hostname, with an optional port. It is what an image reference's domain part is.
var SecretNameRE = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_.-]*$`)
SecretNameRE is the shape of a secret name. It is deliberately narrow: a name reaches the node as a filename on the way to a file-target mount, and it is what appears in a redaction marker in stored logs.
Functions ¶
func NormalizeCapability ¶
NormalizeCapability is the spelling the executor passes to Docker: uppercase, with the optional CAP_ prefix stripped, so both "chown" and "CAP_CHOWN" name the same capability.
func NormalizeRegistryHost ¶
NormalizeRegistryHost reduces the ways people spell a registry to the domain an image reference carries: lower-case, no scheme, no path, and Docker Hub's legacy index address folded into "docker.io".
func RegistryHost ¶
RegistryHost is the registry an image reference is pulled from, normalised the same way: "alpine:3" is docker.io, "us-docker.pkg.dev/acme/images/app" is us-docker.pkg.dev.
func ValidateRegistryHost ¶
ValidateRegistryHost normalises host and rejects anything that could not be the domain of an image reference.
Types ¶
type Duration ¶
Duration is a time.Duration that marshals to and from a Go duration string ("30s", "1h") in both YAML and JSON.
func (Duration) MarshalJSON ¶
MarshalJSON encodes the duration as a string.
func (Duration) MarshalYAML ¶
MarshalYAML encodes the duration as a string.
func (*Duration) UnmarshalJSON ¶
UnmarshalJSON accepts a duration string ("30s") or a raw nanosecond count.
type Hardening ¶
type Hardening struct {
ReadOnlyRootfs bool `yaml:"read_only_rootfs,omitempty" json:"read_only_rootfs,omitempty"`
Capabilities []string `yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
}
Hardening relaxes or tightens the task container's sandbox. The defaults — every capability dropped, no new privileges, the engine's seccomp profile — are not negotiable; these two fields are the only dials.
type Readiness ¶
type Readiness struct {
TCPPort int `yaml:"tcp_port,omitempty" json:"tcp_port,omitempty"`
HTTPPath string `yaml:"http_path,omitempty" json:"http_path,omitempty"`
HTTPPort int `yaml:"http_port,omitempty" json:"http_port,omitempty"`
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
Timeout Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
}
Readiness is how the node decides a sidecar is usable. At most one probe may be set; a sidecar with none is ready as soon as its container is running.
type Resources ¶
type Resources struct {
CPU float64 `yaml:"cpu,omitempty" json:"cpu,omitempty"`
MemoryMB int `yaml:"memory_mb,omitempty" json:"memory_mb,omitempty"`
PIDs int `yaml:"pids,omitempty" json:"pids,omitempty"`
}
Resources caps one container. The node applies them blindly; refusing a task that asks for more than a node has is the scheduler's job.
type SecretRef ¶
type SecretRef struct {
Name string `yaml:"name" json:"name"`
Target string `yaml:"target,omitempty" json:"target,omitempty"`
Key string `yaml:"key" json:"key"`
}
SecretRef names a stored secret and says where the task wants it. It carries the name of a value, never the value: the server resolves it immediately before assignment.
type Sidecar ¶
type Sidecar struct {
Image string `yaml:"image" json:"image"`
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Readiness Readiness `yaml:"readiness,omitempty" json:"readiness,omitempty"`
Resources Resources `yaml:"resources,omitempty" json:"resources,omitempty"`
// Privileged runs the sidecar with every capability and no device restriction, which
// is root on the node's kernel. It exists for one thing — a Docker daemon beside the
// task, so a turn can run `docker compose` and testcontainers — and the spec only
// *asks*: a node started without --allow-privileged-sidecars refuses the task at
// provisioning. Validation lets it through on purpose, because whether a machine will
// host such a container is an operator's answer and not a parser's.
Privileged bool `yaml:"privileged,omitempty" json:"privileged,omitempty"`
// the task sees it. A nested daemon resolves a bind-mount source in its OWN
// filesystem, so a `docker run -v /workspace/...` or a build context under /workspace
// only works when the daemon sees that path too.
ShareWorkspace bool `yaml:"share_workspace,omitempty" json:"share_workspace,omitempty"`
}
Sidecar is a sibling container started before the task and reachable from it by the name it is keyed under, which becomes its DNS alias on the task network.
type TaskSpec ¶
type TaskSpec struct {
Image string `yaml:"image" json:"image"`
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Secrets []SecretRef `yaml:"secrets,omitempty" json:"secrets,omitempty"`
Sidecars map[string]Sidecar `yaml:"sidecars,omitempty" json:"sidecars,omitempty"`
Resources Resources `yaml:"resources,omitempty" json:"resources,omitempty"`
Hardening Hardening `yaml:"hardening,omitempty" json:"hardening,omitempty"`
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
Timeout Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
MaxAttempts int `yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"`
// RetryOnNodeLoss requeues the task as a new attempt when the node running it goes
// offline mid-run, instead of marking it lost. It is off by default because a task
// that is not idempotent must not be silently run twice.
RetryOnNodeLoss bool `yaml:"retry_on_node_loss,omitempty" json:"retry_on_node_loss,omitempty"`
}
TaskSpec is what a task runs. It mirrors podium.v1.TaskSpec on the wire.
func ParseTaskSpec ¶
ParseTaskSpec decodes a YAML task spec, applies defaults and validates it.
func (*TaskSpec) ApplyDefaults ¶
func (s *TaskSpec) ApplyDefaults()
ApplyDefaults fills unset fields. It never overwrites a value the caller set, so an invalid one (a negative timeout, say) survives for Validate to reject.
func (*TaskSpec) Images ¶
Images is every image reference a spec pulls: the task's own and each sidecar's.