Documentation
¶
Overview ¶
Package image resolves OCI images and serves their flattened rootfs, backed by primordium's content-addressable store (concurrency-safe, GC'd).
go-containerregistry pulls the manifest/config; the in-tree flatten package (a fixed fork of mutate.Extract) squashes the layers, resolving plain AND opaque whiteouts and repairing hardlinks whose target a higher layer deleted or overwrote; the flattened tar is converted into an uncompressed EROFS filesystem image, sector-padded, and cached by content hash under a per-(digest,codec,generation) identifier, so amd64/arm64 variants coexist. The cached blob doubles as a raw disk image: warm boots attach it to the microVM as a read-only virtio-blk device (RootfsFile pins it against GC for the VM's lifetime) and the guest MOUNTS it under a tmpfs-upper overlay rather than unpacking it; a cold pull stages the conversion to disk, commits it, then attaches.
The store lives under dirs.CacheDir()/images/<version> (i.e. ~/Library/Caches/ossein/images/<version> on darwin) — the version segment lets a future layout change ship beside the old cache.
Index ¶
Constants ¶
const ( PullAlways = "always" // always re-resolve from the registry PullMissing = "missing" // resolve offline if seen before; else hit the registry PullNever = "never" // offline only; error if never resolved locally )
Pull policies (Docker semantics). Default is PullMissing.
Variables ¶
var ( // ErrUnsupportedPlatform indicates a platform ossein cannot run // (anything other than linux/amd64 or linux/arm64). ErrUnsupportedPlatform = errors.New("unsupported platform") // ErrCache indicates a failure in the local content-addressed image cache // (locating, creating, or writing under the cache directory). ErrCache = errors.New("image cache failure") // ErrResolve indicates the image could not be pulled or resolved from its // registry. ErrResolve = errors.New("image resolution failure") )
Functions ¶
func CanonicalPlatform ¶
CanonicalPlatform validates and canonicalizes a Docker-style platform string to "linux/amd64" or "linux/arm64" — the only two ossein supports — and returns the bare arch ("amd64"/"arm64"). Empty means the host (arm64). amd64 on our arm64 host is what triggers Rosetta downstream.
Types ¶
type Cache ¶
type Cache interface {
// AcquireFile returns the blob for identifier as a complete, immutable,
// GC-pinned file on disk, calling fetch to completion on a miss (staged
// to disk, not RAM). ossein always passes a NIL digest — the rootfs hash
// is only known after flattening. The pin must be Released once the VM
// no longer uses the file.
AcquireFile(identifier string, dgst digest.Digest, fetch content.FetchFunc) (*cache.PinnedFile, error)
// Invalidate drops identifier's index entry, so the next AcquireFile
// re-fetches and re-hashes instead of verifying against the recorded
// digest. RootfsFile needs it to recover from a stale entry (a surviving
// index entry whose blob was reclaimed).
Invalidate(identifier string) error
// GarbageCollect reclaims cached blobs over the quota.
GarbageCollect() (cache.GCStats, error)
// Close flushes and releases the cache.
Close() error
}
Cache is the content-addressable blob cache ossein needs: identifier → cached blob with fetch-on-miss, plus GC and Close. primordium's *content.Store satisfies it, so NewCache returns one directly — no wrapper.
The signatures deliberately use primordium types: this is a swap seam (the one place a different storage backend would plug in), not a full storage abstraction. Everything else in this package depends on Cache, not on *content.Store.
type Image ¶
type Image struct {
Ref string
Digest string // manifest digest hex
Config v1.Config
Platform v1.Platform
// contains filtered or unexported fields
}
Image is a resolved image: manifest/config metadata plus a handle to fetch the flattened rootfs on demand.
func Import ¶ added in v0.2.0
Import records src — an image that exists only locally (a build's output) — under ref for platformStr, exactly as a registry resolution would be, and returns it bound to the cache. After Import, `ossein run ref` resolves offline under the default pull policy, and the returned Image's RootfsFile flattens and caches the rootfs like a pull does (callers wanting a warm cache drive it to completion). The resolution is marked local: a later GC of the blob is reported as "rebuild", never re-fetched from a registry that never had it. ref must parse as an image reference; the digest is src's manifest digest.
func Resolve ¶
Resolve returns a bound *Image for ref at platform (empty = host), honoring the pull policy. It does NOT flatten the rootfs — that happens lazily via Rootfs. ctx bounds all registry traffic, including a lazy rootfs re-fetch triggered later by Rootfs (resolveSource captures this ctx).
PullAlways — always hit the registry (remote.Get), then cache the resolution.
PullMissing — if ref@platform was resolved before, use the cached digest+config
OFFLINE (no network); the rootfs blob is fetched lazily by Rootfs
only if it turns out to be absent — by the pinned digest, so the
cached resolution holds even if the tag has moved upstream (tags
are only re-resolved on PullAlways). First-ever ref hits the
registry. This is the default and skips the ~800 ms resolve on a
warm run.
PullNever — offline only: use the cached resolution; error if there is none.
Any other pull value is an error. The content-cache key is the manifest digest + codec: equivalent refs (debian == docker.io/library/debian:latest) and both arches coexist; a moved tag yields a new digest and the stale blob falls to GC. The codec segment makes a codec change (gzip→zstd) a clean miss rather than serving undecodable bytes.
func (*Image) RootfsFile ¶
func (i *Image) RootfsFile() (*cache.PinnedFile, error)
RootfsFile returns the flattened rootfs blob as a complete, immutable, GC-pinned file (flattening and caching on miss), sized to a 512-byte multiple so it can be attached to the microVM as a raw read-only virtio-blk disk. The caller must Release the pin once the VM no longer needs the file.
Healing (previously healingReader): blob GC deletes cached blobs but never the index, so an identifier can survive with a digest whose blob is gone. The next acquire then re-flattens and verifies against the RECORDED digest, and any drift in how those bytes are produced (a codec bump, a flatten change) makes that verification fail forever — retries reproduce it, and --pull=always re-resolves the tag to the same identifier. Keyed on fault.ErrWriteFailure rather than a specific mismatch: every reason the store could not persist the blob leaves the same useless entry behind. Dropping it here turns a permanent failure into a one-boot one.