container

package
v0.2026250.903 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package container is the spec fabric slice for container-engine host helpers — resolving which engine binary to invoke, its GPU run-args, and auto-detecting the installed engine. RELOCATED from sdk/kit (#55 fabric-primitive extraction). It carries os/exec (the engine auto-detect shells `LookPath`) in its OWN slice (Rule 2) so a consumer needing only value types never drags os/exec. charly core inlines from here; sdk/kit re-exports the same symbols so existing kit.EngineBinary / kit.GPURunArgs / kit.DetectEngine call sites are untouched.

spec/container/transfer.go — the container-engine image-transfer host primitive, RELOCATED from sdk/kit by #55 coneC (charly/ off sdk/kit): it pipes an image from one engine's local store to another via `save | load` (os/exec), the fabric twin of the LocalImageExists probe that already lives in this slice. sdk/kit re-exports it (var TransferImage = container.TransferImage) so every existing kit.TransferImage call site (candy/plugin-build, candy/plugin-box, candy/plugin-deploy-pod, …) is unchanged; charly core inlines from here. Co-located with EngineBinary (same package) and the LocalImageExists family this transfer path complements.

Index

Constants

This section is empty.

Variables

View Source
var InspectLabels = InspectImageLabels

InspectLabels reads OCI labels from a local image via engine inspect. Package-level var for testability — defaults to the canonical InspectImageLabels (spec/container/image_inspect.go, coneA). Override this var (not kit/deploykit's re-exports) to stub label reads in tests of ExtractMetadata's decode logic.

View Source
var ListLocalImages = cachedListLocalImages

ListLocalImages returns all images in the engine's local storage, CACHED persistently (the image list does not change often — only a build or pull mutates it). The first call after the TTL expires re-fetches with user feedback; every subsequent call within the TTL reads the cache, so `charly status` is sub-second after the first run. Package-level var for testability (same pattern as LocalImageExists, DetectEngine).

View Source
var LocalImageExists = defaultLocalImageExists

LocalImageExists checks whether an image reference exists in the given engine's local store. Package-level var for testability (same pattern as DetectEngine). Co-located with the resolution family because ResolveLocalImageRef reads it; kit re-exports it (kit.LocalImageExists = container.LocalImageExists) so existing direct callers (candy/plugin-build, candy/plugin-box, candy/plugin-deploy-pod, candy/plugin-kube, charly core) are unchanged.

Functions

func ContainerImage

func ContainerImage(engine, containerName string) string

ContainerImage returns the image ref for a running container, best-effort ("" on error). Thin wrapper over ContainerImageRef.

func ContainerImageRef

func ContainerImageRef(engine, containerName string) (string, error)

ContainerImageRef returns the image ref backing a running container (.Config.Image via `<engine> inspect`). THE single container→image-ref inspector — there is exactly one inspect implementation.

func DetectEngine

func DetectEngine() (string, error)

DetectEngine auto-detects the container engine: prefers podman, falls back to docker.

func EngineBinary

func EngineBinary(engine string) string

EngineBinary returns the CLI binary name for a container engine. "auto" resolves via DetectEngine (podman preferred), falling back to "docker".

func ExtractCalVerTag

func ExtractCalVerTag(ref string) string

ExtractCalVerTag returns the CalVer portion of a ref's tag, or "" if the tag is not a recognisable CalVer (`YYYY.DDD.HHMM`). Lets the resolver distinguish CalVer tags from legacy floats like `:latest` (which should never be chosen as the newest candidate).

func ExtractMetadata

func ExtractMetadata(engine, imageRef string) (*spec.BoxMetadata, error)

ExtractMetadata reads OCI labels from a local image and returns parsed spec.BoxMetadata. Returns nil if the image has no ai.opencharly labels. Returns spec.ErrImageNotLocal wrapped with the image ref if the image is not in local storage.

func GPURunArgs

func GPURunArgs(engine string) []string

GPURunArgs returns the engine-specific run flags that expose all host GPUs to a container.

func InspectImageLabels

func InspectImageLabels(engine, imageRef string) (map[string]string, error)

InspectImageLabels reads a local image's OCI labels via engine inspect. Pure container-storage probe: no charly-core coupling. CACHED persistently — the labels are image METADATA (they do not change unless the image is rebuilt), so the first call after the TTL expires re-fetches and every subsequent call within the TTL reads the cache. The LIVE container state (podman ps) is never cached.

func InvalidateImageCache added in v0.2026241.1322

func InvalidateImageCache()

InvalidateImageCache clears the persistent image-list cache. Called by the build and deploy commands (charly box build / deploy add / update) — every operation that creates or pulls an image — so the next status run re-fetches the fresh image list instead of serving a stale cache.

func IsHostNetworked

func IsHostNetworked(engine, containerName string) bool

IsHostNetworked checks if a running container uses --network host.

func LooksLikeFullRef

func LooksLikeFullRef(ref string) bool

LooksLikeFullRef returns true if the image ref contains a registry segment (a "/" before any ":") — e.g. "ghcr.io/org/name:tag" — so it can be pulled without charly.yml resolution.

func ParsePublishedPort

func ParsePublishedPort(output string, port int) (string, error)

ParsePublishedPort extracts the host "ip:port" from `<engine> port` output for an in-container port, normalizing 0.0.0.0 / [::] to 127.0.0.1.

func ResolveBuiltImageRef added in v0.2026227.1655

func ResolveBuiltImageRef(engine, input string) (string, error)

ResolveBuiltImageRef resolves like ResolveLocalImageRef and then REFUSES the resolution when the elected image is not the newest local BUILD of that short name.

It is the resolver for every verb that pronounces a VERDICT on a built artifact — `charly check box`, `charly box feature run`, `charly box labels`. Those verbs answer "what is in the image I just built", and the election cannot answer that on its own: its PRIMARY key is the content-derived `ai.opencharly.version` label, so an image built from a differently-versioned source tree outranks a newer build regardless of when either was produced. A green verdict against the older artifact is the worst failure this system has — it looks exactly like a passing one — so the ambiguity is surfaced as an error and the operator names the artifact.

A full ref or an explicit `<name>:<tag>` states the intent and passes through untouched, which is why the R10 bed sequence (which builds and then checks `<image>:<run-tag>`) never reaches this guard.

func ResolveDeliverableRef added in v0.2026230.2320

func ResolveDeliverableRef(engine, input string) (string, error)

ResolveDeliverableRef resolves the image an image-DELIVERY verb should ship: an explicit ref that exists locally is used exactly as authored — naming a tag IS the choice — and anything else resolves through the STRICT ResolveBuiltImageRef, which refuses to elect an image older than the newest local build.

It exists because both delivery verbs need exactly this and had grown their own copy: `charly vm cp-box` (VM venue) and `charly box load` (container venue). The bodies were structurally identical and their justification comments near-verbatim rewords — in a cutover whose own thesis is that a second venue costs a constructor, not a copy (R3).

The strictness is the point, and it is easy to under-rate: delivering a stale artifact is the wrong-artifact class `charly check box` refuses to certify, and it is HARDER to notice at a delivery verb than at a build one, because the load succeeds and the venue simply runs the wrong image.

func ResolveLocalImageRef

func ResolveLocalImageRef(engine, input string) (string, error)

ResolveLocalImageRef resolves a user-supplied image reference against the engine's local storage — never reads charly.yml. Used by test-mode commands (charly check live, charly check box) so they stay within the test-mode input set.

For full refs (registry prefix present) it validates the image exists locally and passes through unchanged. For short names it resolves via CalVer: collect every candidate ref and pick the one with the highest `ai.opencharly.version` label (falling back to the highest tag CalVer). charly is CalVer-only — no `:latest` fallback. See `/charly-build:build` "CalVer-only" for the contract.

A candidate ref must satisfy BOTH halves: its image is identified as the short name (preferred: an `ai.opencharly.image=<short>` label; fallback: no such label) AND the ref ITSELF is named `<short>` in its trailing repo component. The second half is not cosmetic — a label-family image accumulates the deploy-name alias tags of every OTHER deployment built on the same base box, and they inherit its label, so without it an untagged resolve can return a sibling deployment's image (see the invariant comment in the label branch below).

Returns `spec.ErrImageNotLocal` when nothing matches. An ambiguous result across multiple repos with the same highest CalVer tag surfaces as an explicit error asking for a full ref.

This is the LENIENT form: it elects a ref and says nothing about what it passed over. A verb that pronounces a VERDICT on the artifact (`charly check box`, `charly box feature run`, `charly box labels`) must call ResolveBuiltImageRef instead, which refuses to elect an image older than the newest local build.

func ResolveNewestLocalCalVer

func ResolveNewestLocalCalVer(engine, short string) (string, error)

ResolveNewestLocalCalVer is the canonical "find the newest local image for this short name" helper. Thin wrapper around ResolveLocalImageRef — exposed so callers that start with an explicit short-name + empty-tag can resolve uniformly.

func ResolveShellImageRef

func ResolveShellImageRef(registry, name, tag string) string

ResolveShellImageRef builds the full image reference from registry, name, and tag, for a caller about to run an engine command (podman/docker) against it. When tag is empty, it resolves to the newest local CalVer for the given short name via ResolveNewestLocalCalVer — the CalVer-only contract (`/charly-build:build` "Cache Efficiency"). A caller that wants a specific tag passes it; a caller whose `--tag` flag is empty gets the newest CalVer with no extra work. When registry is set AND tag is empty, there's no way to guess a remote CalVer without a registry-list call, so the caller gets `<registry>/<name>` back with no tag suffix — the engine resolves it locally first (matching any single local tag) or errors. (P14: relocated from charly/shell.go's resolveShellImageRef, which now delegates here — R3, single source for every caller: candy/plugin-box's `merge` command plus charly core's own deploy_add/config_image/ensure_image/remote_image/pod_lifecycle_resolve/update_deploy_dispatch.)

func StreamLoad added in v0.2026230.1417

func StreamLoad(save, load *exec.Cmd) error

StreamLoad pipes a `save` command's stdout straight into a `load` command's stdin — an image transfer with NO intermediate tarball on either side. It is the ONE streaming primitive behind every image-delivery path in the tree: TransferImage (engine→engine on one host), kit.TransferToRootful (rootless→rootful), the VM host→guest cp-box, and the container-venue host→nested-store `charly box load`. Each caller supplies its own pair of commands; the only thing they ever differed in was how the load side is reached, so that — and nothing else — stays at the call site.

A tarball is not merely wasteful here: a multi-GB image cannot land in a size-limited tmpfs /tmp, which is what the destination of several of these paths actually has.

Both commands' stderr is routed to the caller's stderr so a failure is visible rather than swallowed, and the load side's stdout goes there too — `podman load` writes its "Loaded image:" line to stdout, which is diagnostic output, not a value any caller parses.

NOTE for callers: `podman load` can exit 0 on a TRUNCATED stream, registering an image whose overlay layers are incomplete. A green return from StreamLoad is therefore NOT proof of an intact image; a caller that cares must probe the loaded image afterwards. deploykit's venue transfer does exactly that.

func TransferImage

func TransferImage(srcEngine, dstEngine, imageRef string) error

TransferImage pipes an image from one engine to another via save | load.

Types

type LocalImageInfo

type LocalImageInfo struct {
	ID     string            // image ID (sha256:...) — used by `charly clean` to skip in-use images
	Names  []string          // Full refs: ["ghcr.io/opencharly/jupyter:latest", ...]
	Labels map[string]string // OCI labels from the image config
	Size   int64             // reported storage size in bytes (podman's "Size" field; 0 if absent/unparsed)
	// Created is the image's creation time (podman/docker "Created", unix seconds; "CreatedAt" is
	// the RFC3339 rendering of the same instant). 0 when absent/unparsed.
	//
	// This is the ONLY build-recency key that is TOTAL over the tags charly mints. `charly box
	// build --tag <x>` REPLACES the CalVer tag rather than adding to it, so every bed build carries
	// exactly one tag of the form `check-<bed>-<YYYY.DDD.HHMM>` — which ExtractCalVerTag reports as
	// EMPTY, because it is not three decimal dot-parts. Ordering by the tag therefore ties every
	// bed-built candidate and falls through to a meaningless last resort. Creation time does not
	// tie, needs no tag convention, and costs nothing: it rides the same `images --format json`
	// rows Size already comes from.
	Created int64
}

LocalImageInfo describes an image present in the engine's local storage. Populated by ListLocalImages from `{podman,docker} images --format json`.

func ParseLocalImagesJSON

func ParseLocalImagesJSON(out []byte) ([]LocalImageInfo, error)

ParseLocalImagesJSON parses `{podman,docker} images --format json` output into ONE LocalImageInfo per distinct image ID, with that id's tag refs merged into Names.

This dedup is load-bearing: podman emits ONE ROW PER TAG, and each row's Names array already lists EVERY tag on that id. A naive row-by-row mapping therefore produces N near-identical entries for an id with N tags — which made `charly clean`'s keep-N retention over-count entries and, worse, remove an id's whole Names array per "extra" entry (deleting tags it meant to keep). Collapsing to one-id-with-a-tag-list matches the struct's shape and what every consumer (retention prune, short-name resolver) expects.

Empty-id rows (dangling/untagged) are kept separate via a per-row sentinel key so they never merge into one another.

type LocalImageResolution added in v0.2026227.1655

type LocalImageResolution struct {
	// Ref is the elected reference — what ResolveLocalImageRef returns.
	Ref string
	// NewestBuildRef is the ref of the most recently CREATED candidate across BOTH families
	// (label-identified AND name-identified), so a build whose `ai.opencharly.box` label disagrees
	// with its own repo name is still counted. Empty only when the ordering could not be
	// established at all (see OrderKnown).
	NewestBuildRef string
	// SameArtifact reports that Ref and NewestBuildRef name the SAME image ID. Many tags on one id
	// are one artifact — a `--tag` build and a plain CalVer build of identical content share an id
	// — so there is no older/newer to choose between them and nothing to refuse.
	SameArtifact bool
	// OrderKnown reports that every candidate carried a creation time, i.e. that "newest" is a
	// fact rather than a guess. False means the engine did not report one for some candidate; the
	// guard then REFUSES rather than passing, because permissive-on-unknown is precisely the shape
	// that let a stale artifact through before.
	OrderKnown bool
	// Pinned reports that the input named a full ref or an explicit tag: the operator stated
	// which artifact they meant, so no election happened and nothing is ambiguous.
	Pinned bool
}

LocalImageResolution is the COMPLETE answer to "which local image does this reference name?": the ref the ordering elects (Ref) plus the newest local BUILD carrying the same short name (NewestBuildRef). The two diverge whenever the ordering's PRIMARY key — the content-derived `ai.opencharly.version` label — elects an image that is not the most recently built one, which is exactly the shape that makes a build-scope verdict certify the wrong artifact. The lenient ResolveLocalImageRef keeps only Ref; a verb that PRONOUNCES on an artifact resolves through ResolveBuiltImageRef instead, which refuses that divergence.

func ResolveLocalImage added in v0.2026227.1655

func ResolveLocalImage(engine, input string) (LocalImageResolution, error)

ResolveLocalImage is ResolveLocalImageRef's full-answer form — same election, same errors, plus the newest-build ref the election may have passed over. Every caller that only needs the elected ref goes through ResolveLocalImageRef; the build-scope verdict verbs go through ResolveBuiltImageRef. Both are thin wrappers over this one body (R3 — one resolution, one candidate gather, one ordering).

func (LocalImageResolution) RefuseIfStale added in v0.2026227.1655

func (r LocalImageResolution) RefuseIfStale(input string) error

RefuseIfStale returns a `spec.ErrStaleLocalImage`-wrapped error when the elected ref is older (by build tag) than the newest local build of the same short name. Pinned resolutions never refuse: the operator already said which artifact they meant.

Jump to

Keyboard shortcuts

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