Documentation
¶
Index ¶
- Constants
- func CacheDir() (string, error)
- func FormatBytes(b int64) string
- func FormatMode(m fs.FileMode) string
- func FormatPlatform(p *ocispec.Platform) string
- func FormatSignedBytes(b int64) string
- func IsBinary(data []byte) bool
- func IsWhiteoutName(name string) bool
- func ParsePlatform(s string) (*ocispec.Platform, error)
- func PlatformsEqual(want, got *ocispec.Platform) bool
- func TreeLiveFileBytes(tree *FileTree) int64
- type Analysis
- func Analyze(ctx context.Context, resolver Resolver, imageRef string) (*Analysis, error)
- func AnalyzeWithOptions(ctx context.Context, resolver Resolver, imageRef string, opts AnalyzeOptions) (*Analysis, error)
- func AnalyzeWithProgress(ctx context.Context, resolver Resolver, imageRef string, ...) (*Analysis, error)
- type AnalyzeOptions
- type ArchiveExtractor
- func (e *ArchiveExtractor) Extract(ctx context.Context, imageRef string, filePath string) (*FileContent, error)
- func (e *ArchiveExtractor) ExtractFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) (*FileContent, error)
- func (e *ArchiveExtractor) ExtractRaw(ctx context.Context, imageRef string, filePath string) ([]byte, error)
- func (e *ArchiveExtractor) ExtractRawFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) ([]byte, error)
- type ArchiveResolver
- func (r *ArchiveResolver) ImageID(ctx context.Context, imageRef string) (string, error)
- func (r *ArchiveResolver) Inspect(ctx context.Context, imageRef string) (*ImageMeta, error)
- func (r *ArchiveResolver) NewExtractor() Extractor
- func (r *ArchiveResolver) Resolve(ctx context.Context, imageRef string) ([]Layer, error)
- func (r *ArchiveResolver) ResolveWithProgress(ctx context.Context, imageRef string, progress chan<- ProgressEvent) ([]Layer, error)
- type CacheEntry
- type CompareResult
- type DiffType
- type DockerExtractor
- func (e *DockerExtractor) Extract(ctx context.Context, imageRef string, filePath string) (*FileContent, error)
- func (e *DockerExtractor) ExtractFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) (*FileContent, error)
- func (e *DockerExtractor) ExtractRaw(ctx context.Context, imageRef string, filePath string) ([]byte, error)
- func (e *DockerExtractor) ExtractRawFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) ([]byte, error)
- type DockerResolver
- func (r *DockerResolver) ImageID(ctx context.Context, imageRef string) (string, error)
- func (r *DockerResolver) Inspect(ctx context.Context, imageRef string) (*ImageMeta, error)
- func (r *DockerResolver) NewExtractor() Extractor
- func (r *DockerResolver) Resolve(ctx context.Context, imageRef string) ([]Layer, error)
- func (r *DockerResolver) ResolveWithProgress(ctx context.Context, imageRef string, progress chan<- ProgressEvent) ([]Layer, error)
- type EfficiencyResult
- type EfficiencySummary
- type ErrArchiveInfra
- type ErrArchiveNotFound
- type ErrArchivePermission
- type ErrDaemonNotRunning
- type ErrImageNotFound
- type ErrInvalidArchive
- type ErrNoEngineFound
- type ErrPlatformInvalid
- type ErrPlatformNotInImage
- type ErrPodmanSocketNotSet
- type ErrPullFailed
- type Extractor
- type ExtractorSource
- type FileContent
- type FileDiff
- type FileDiffSummary
- type FileNode
- type FileTree
- type ImageMeta
- type ImageSummary
- type Layer
- type LayerDiff
- type Option
- type ProgressEvent
- type ProgressPhase
- type PruneOptions
- type PruneResult
- type Resolver
- type WasteDiff
- type WastedFile
Constants ¶
const MaxArchiveEntries = 1_000_000
MaxArchiveEntries caps the number of tar entries scanResolveMetadata will visit. A real Docker/OCI image has at most a few thousand entries (manifest, config, and one entry per layer blob). 1 million entries is orders of magnitude above that ceiling; a tar with more is adversarial input.
const MaxArchiveSize = 64 << 30 // 64 GiB
MaxArchiveSize bounds the total bytes spooled from a daemon's ImageSave response to a temp file. A rogue or compromised daemon (a hostile DOCKER_HOST=tcp://…, an SSH-tunnelled Podman) can stream an unbounded chunked response and fill the local disk before parsing ever begins. The per-blob MaxLayerBlobSize cap only fires later, during the tar walk, so the spool itself needs its own ceiling. 64 GiB comfortably exceeds any real multi-layer image while stopping an endless stream from exhausting /tmp.
const MaxLayerBlobSize = 16 << 30 // 16 GiB
MaxLayerBlobSize bounds a single layer-tar blob loaded from a spooled image archive. Distinct from MaxSaveSize because layer blobs aggregate many files (apt caches, ML model directories, full base-image userlands) and legitimately exceed the per-file cap. 16 GiB is permissive enough for production ML images while still rejecting a malformed manifest that claims a single blob is petabytes.
const MaxMetadataSize = 64 << 20 // 64 MiB
MaxMetadataSize bounds the small text descriptors read whole into memory during archive resolution: manifest.json, the image config, and any root-level *.json (legacy config payloads). Real descriptors are a few KiB; a hostile tar can declare an 8 GiB manifest.json header and OOM the process via io.ReadAll before any layer is touched. 64 MiB is orders of magnitude above any legitimate descriptor while cheap to reject.
const MaxSaveSize = 2 << 30 // 2 GiB
MaxSaveSize bounds save-to-disk extraction. The viewer caps reads at MaxViewSize, but the save path historically called io.ReadAll with no limit — a 10 GB file inside a layer (or a maliciously crafted tar entry with an inflated header.Size) would OOM the process. 2 GiB is generous enough for legitimate single-file extracts and well below typical RAM.
const MaxViewSize = 1 << 20 // 1 MB
const SchemaVersion = 2
SchemaVersion is the on-disk schema version for cached analysis files. Bump when the cacheEnvelope, cachedLayer, or cachedNode shape changes (or when the parse output produced by docker.go/parseLayers changes in a way that affects what we persist). Do NOT bump for stack.go or size.go changes — those run on every load and are not persisted.
Variables ¶
This section is empty.
Functions ¶
func CacheDir ¶ added in v1.2.0
CacheDir returns the directory where layerx persists analysis caches. Resolution order:
- LAYERX_CACHE_DIR (if set and usable as a directory)
- os.UserCacheDir() joined with "layerx"
CacheDir does not create the directory; saveCache creates digest subdirectories on demand with mode 0700.
func FormatBytes ¶
func FormatMode ¶
func FormatPlatform ¶ added in v1.5.0
FormatPlatform renders an ocispec.Platform back into the canonical "os/arch[/variant]" form used by the Docker CLI. Returns "" for a nil or zero-value platform so error messages can elide it cleanly.
func FormatSignedBytes ¶
FormatSignedBytes formats b like FormatBytes but with an explicit sign for non-zero values: "+12 MB", "-3.0 MB", "0 B". Used for net deltas.
func IsBinary ¶
IsBinary reports whether data appears to be binary content. Trusts net/http content detection for text/* (covering UTF-8/16/32 with BOMs) and only falls back to null-byte scanning when detection is inconclusive.
func IsWhiteoutName ¶ added in v1.3.0
IsWhiteoutName reports whether name is an overlay whiteout marker: either an opaque-directory marker (`.wh..wh..opq`) or a per-file tombstone (`.wh.<name>`). Exported so consumers outside image/ — such as ci/path-rules — can identify these markers in raw per-layer trees, where tar entries land as regular FileNodes (DiffType=Unchanged) until stack.go interprets them.
func ParsePlatform ¶ added in v1.5.0
ParsePlatform converts a Docker-CLI-style platform string into an ocispec.Platform suitable for the moby client's pull / save / inspect options. Accepts the same shapes the Docker CLI does:
"amd64" → os=linux, arch=amd64 "arm64" → os=linux, arch=arm64 "linux/amd64" → os=linux, arch=amd64 "linux/arm64" → os=linux, arch=arm64 "linux/arm/v7" → os=linux, arch=arm, variant=v7 "linux/arm64/v8" → os=linux, arch=arm64, variant=v8 "windows/amd64" → os=windows, arch=amd64
The OS defaults to "linux" when only an architecture is given, mirroring the Docker CLI. The function does not contact any registry — it parses the string syntactically; a returned platform may still fail at lookup time if the image does not contain that variant.
The empty string returns a nil platform with no error: callers use that to mean "use the daemon's default platform" and skip the per-call API option entirely (preserving existing behaviour for users who never pass --platform).
func PlatformsEqual ¶ added in v1.5.0
PlatformsEqual reports whether two platforms refer to the same image variant. Empty fields on either side compare equal to any value (the CLI commonly omits the variant for arches that only have one). nil on either side means "no preference" and matches anything.
func TreeLiveFileBytes ¶
Types ¶
type Analysis ¶
type Analysis struct {
ImageRef string
Layers []Layer
StackedTrees []*FileTree // per-layer delta view: filesystem changes introduced by each layer only.
AggregatedTrees []*FileTree // cumulative provenance view: full filesystem state from layer 0 up to each layer.
TotalSize int64
}
func AnalyzeWithOptions ¶ added in v1.2.0
func AnalyzeWithOptions(ctx context.Context, resolver Resolver, imageRef string, opts AnalyzeOptions) (*Analysis, error)
AnalyzeWithOptions is the cache-aware entry point.
Flow:
- Try to obtain image content digest via Resolver.ImageID. On error (e.g. image not local yet, daemon hiccup) we skip the cache lookup and proceed to a cold resolve; ResolveWithProgress will pull as needed.
- If !opts.NoCache and the digest is known, try loadCache. On hit we skip ResolveWithProgress entirely; emit PhaseCacheLoad.
- Otherwise call ResolveWithProgress (existing pull/export/parse path).
- After a successful resolve, re-read ImageID and require the result to match the pre-resolve digest before persisting the cache. A mismatch means the tag flipped during the run (concurrent docker pull) — caching either set of layers under either digest would lie.
- Stack + assignNetDeltas + TotalSize, build Analysis, return.
Hit and miss paths converge at step 5: the same Analysis assembly runs either way. ImageRef is taken from imageRef (the current arg), never from the envelope.
func AnalyzeWithProgress ¶
type AnalyzeOptions ¶ added in v1.2.0
type AnalyzeOptions struct {
// NoCache forces a cold resolve. The cache is still written on success
// so the next default-mode run is a hit.
NoCache bool
Progress chan<- ProgressEvent
// CacheRoot overrides CacheDir() for this call. Empty = use CacheDir().
// Tests can also set LAYERX_CACHE_DIR; this field exists for callers
// that want explicit control without environment.
CacheRoot string
// Platform is the canonical "os/arch[/variant]" string the caller
// requested via --platform (e.g. "linux/arm64"). When non-empty it is
// appended to the cache key so that analyses for different platforms of
// the same image digest never share a cache entry. Empty = no platform
// pin; the behaviour is identical to before this field was added.
Platform string
}
AnalyzeOptions controls the analyze pipeline. Zero value is valid: caching enabled, no progress channel, default cache directory.
type ArchiveExtractor ¶ added in v1.2.3
type ArchiveExtractor struct {
// contains filtered or unexported fields
}
func (*ArchiveExtractor) Extract ¶ added in v1.2.3
func (e *ArchiveExtractor) Extract(ctx context.Context, imageRef string, filePath string) (*FileContent, error)
Extract is provided for interface completeness. The TUI uses ExtractFromLayer; full-image extract via container-create has no archive equivalent, so we route to the layer-walk over all layers.
func (*ArchiveExtractor) ExtractFromLayer ¶ added in v1.2.3
func (e *ArchiveExtractor) ExtractFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) (*FileContent, error)
ExtractFromLayer mirrors DockerExtractor.ExtractFromLayer: walk back from layerCursor toward layer 0, return the file from the most recent layer whose tar contains it as a regular entry, or surface a typed whiteout / non-regular error if encountered first.
func (*ArchiveExtractor) ExtractRaw ¶ added in v1.2.3
func (*ArchiveExtractor) ExtractRawFromLayer ¶ added in v1.2.3
type ArchiveResolver ¶ added in v1.2.3
type ArchiveResolver struct {
// contains filtered or unexported fields
}
ArchiveResolver resolves image layers from a local image archive on disk (docker save / OCI layout tarball) without contacting any container runtime.
The archive is opened lazily on each method call rather than held open by the resolver. This keeps the resolver cheap to construct and lets the caller move or delete the file between calls (the active call still finishes against the file handle it opened).
func NewArchiveResolver ¶ added in v1.2.3
func NewArchiveResolver(path string) *ArchiveResolver
NewArchiveResolver creates a resolver that reads from the archive at path. The path is not validated here; errors surface when methods are called (so the constructor cannot fail and Resolve / Inspect / ImageID share a single file-open code path with consistent error wrapping).
func NewArchiveResolverWithPlatform ¶ added in v1.5.0
func NewArchiveResolverWithPlatform(path string, p *ocispec.Platform) *ArchiveResolver
NewArchiveResolverWithPlatform pins the archive resolver to a specific platform. The pin is used purely as a sanity check: a "docker save" / OCI-layout tarball normally contains exactly one image variant, so the flag is tolerated when it matches what the archive recorded and rejected (with available platform context) otherwise. Passing a nil platform is equivalent to NewArchiveResolver — no check is performed.
func (*ArchiveResolver) ImageID ¶ added in v1.2.3
ImageID returns the image's content digest, derived from the config blob's bytes. This is the same identity used by Docker and OCI registries: the sha256 of the JSON config file referenced by manifest.json. Two archives with identical image content (same config + same layers) share an ImageID regardless of where they live on disk or how the tar was rebuilt; two archives that differ in any layer or config field do not.
For OCI layouts the config path embeds the digest (blobs/sha256/<hex>) and we use it directly. For legacy docker-save layouts the filename stem (<hex>.json) is conventionally the digest but is not authoritative — a rewritten tar can keep the filename while changing the bytes — so we hash the config blob itself.
func (*ArchiveResolver) Inspect ¶ added in v1.2.3
Inspect returns lightweight metadata (total declared layer size) by scanning the outer tar headers once. Does not read layer bodies.
"Total layer size" is summed strictly over the entries listed in the manifest's Layers — manifest.json, the config blob, and any unrelated root-level JSON are excluded. This matches what users mean by "image size" and what the spinner / status bar displays.
func (*ArchiveResolver) NewExtractor ¶ added in v1.2.3
func (r *ArchiveResolver) NewExtractor() Extractor
func (*ArchiveResolver) ResolveWithProgress ¶ added in v1.2.3
func (r *ArchiveResolver) ResolveWithProgress(ctx context.Context, imageRef string, progress chan<- ProgressEvent) ([]Layer, error)
ResolveWithProgress reads the archive, emitting Parsing progress. There is no Pulling phase (no daemon, no network) and no Exporting phase (no ImageSave call) — archive mode goes straight to parse.
type CacheEntry ¶ added in v1.4.0
type CacheEntry struct {
Digest string
Size int64 // bytes — combined size of all layers*.gob files in the digest directory
CachedAt time.Time // newest mtime among all layers*.gob files; honestly named (not "LastUsed")
// ImageRef is the image reference the cache was originally written with
// (e.g. "nginx:latest", "/build/app.tar"). Empty when the sidecar
// meta.json is missing — entries written by older versions, or by
// PruneCache which doesn't bother reading the sidecar. Display-only;
// loadCache and PruneCache do not consult this field.
ImageRef string
}
func ListCache ¶ added in v1.4.0
func ListCache(root string) ([]CacheEntry, []string, error)
ListCache returns every valid digest dir under root with its size and mtime. A missing root is not an error; it returns ([], nil, nil) — the cache simply hasn't been populated yet. Foreign files at root, dirs whose names fail digest validation, and dirs with no layers*.gob files are silently skipped (same rule as the auto-prune).
Warnings are returned as plain strings so the caller (cmd/cache.go) can render them. ListCache itself never writes to stderr.
Entries are returned sorted oldest-first by mtime. Predictable order keeps the renderer test-stable and matches PruneCache's eviction order.
type CompareResult ¶ added in v1.3.0
type CompareResult struct {
Before ImageSummary
After ImageSummary
BeforeEfficiency EfficiencySummary
AfterEfficiency EfficiencySummary
LayerDiffs []LayerDiff
FileDiffs []FileDiff
WasteDiffs []WasteDiff
FileSummary FileDiffSummary
Warnings []string
}
func CompareAnalysis ¶ added in v1.3.0
func CompareAnalysis(before, after *Analysis) *CompareResult
CompareAnalysis computes a diff between two image analyses. Either input may be nil — a nil analysis is treated as an empty image (no layers, no files), so "compare against nothing" is symmetric with regular diffs. Returns nil only when both inputs are nil. CompareAnalysis never mutates its inputs.
When one side is empty (nil or zero layers), Efficiency() returns the degenerate Score=1.0 baseline for that side. To prevent that phantom baseline from making the populated side look "worse" (a spurious regression in the verdict), the EfficiencySummary deltas are zeroed when either side has no layers — the diff still surfaces every Added/Removed file, but score/waste deltas only carry meaning when both sides are real.
func (*CompareResult) IsRegression ¶ added in v1.3.0
func (r *CompareResult) IsRegression() bool
IsRegression reports whether `after` is materially worse than `before`: wasted bytes increased OR efficiency score dropped beyond a small epsilon. Returns false on a nil receiver. Reads the precomputed delta fields so the verdict and the rendered numbers share one source of truth.
func (*CompareResult) RegressionReasons ¶ added in v1.3.0
func (r *CompareResult) RegressionReasons() []string
RegressionReasons returns the canonical list of regression reasons, in the stable order ["efficiency", "waste"]. Empty slice when no regression. Defined alongside IsRegression so the verdict line and the gate share a single decision; callers must not recompute reasons from raw fields.
type DockerExtractor ¶
type DockerExtractor struct {
// contains filtered or unexported fields
}
func NewDockerExtractor ¶
func NewDockerExtractor(cli client.APIClient) *DockerExtractor
func (*DockerExtractor) Extract ¶
func (e *DockerExtractor) Extract(ctx context.Context, imageRef string, filePath string) (*FileContent, error)
func (*DockerExtractor) ExtractFromLayer ¶ added in v1.2.1
func (e *DockerExtractor) ExtractFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) (*FileContent, error)
func (*DockerExtractor) ExtractRaw ¶
func (*DockerExtractor) ExtractRawFromLayer ¶ added in v1.2.1
type DockerResolver ¶
type DockerResolver struct {
// contains filtered or unexported fields
}
func (*DockerResolver) ImageID ¶ added in v1.2.0
ImageID returns the image content digest from the Docker daemon. It does not pull; the caller is expected to ensure the image is local first (AnalyzeWithProgress does this).
func (*DockerResolver) Inspect ¶
Inspect returns lightweight image metadata without exporting the full tar. It does not pull the image — if the image is not local, it returns an error.
func (*DockerResolver) NewExtractor ¶
func (r *DockerResolver) NewExtractor() Extractor
func (*DockerResolver) ResolveWithProgress ¶
func (r *DockerResolver) ResolveWithProgress(ctx context.Context, imageRef string, progress chan<- ProgressEvent) ([]Layer, error)
type EfficiencyResult ¶
type EfficiencyResult struct {
Score float64
WastedBytes int64
WastedFiles []WastedFile
}
func Efficiency ¶
func Efficiency(layers []Layer) *EfficiencyResult
Efficiency computes how much space is wasted by files appearing in multiple layers. It stacks layers internally; prefer EfficiencyFromAnalysis when the caller already has stacked trees.
Within a run of a path's occurrences (a span with no deletion in between), all but the last occurrence are wasted — the last is the copy that survives. A run that ends in a deletion (whiteout or opaque whiteout) has ALL of its occurrences wasted: the bytes were written into earlier layers, still ship in the image, and are never reclaimed by the later deletion, yet none of them survive into the final filesystem.
func EfficiencyFromAnalysis ¶ added in v1.3.0
func EfficiencyFromAnalysis(a *Analysis) *EfficiencyResult
EfficiencyFromAnalysis is the preferred entry point when the caller already has stacked trees (which Analysis carries). Avoids re-running Stack.
Falls back to recomputing Stack(layers) if the carried StackedTrees are missing or out of sync with the layer count — defensive against an Analysis that was built or mutated outside AnalyzeWithOptions (e.g. by a test, or by a future caller that constructs an Analysis directly). The efficiency computation requires len(stacked) == len(layers) to align snapshot indices; without this guard a divergent input would produce a silently wrong score.
type EfficiencySummary ¶ added in v1.3.0
type EfficiencySummary struct {
Score float64
WastedBytes int64
ScoreDelta float64
ScoreDeltaPercent float64
WastedBytesDelta int64
WastedBytesPercent float64
}
EfficiencySummary mirrors EfficiencyResult plus deltas relative to a peer. Percent fields are 0 when their denominator is 0 (no sentinel value).
type ErrArchiveInfra ¶ added in v1.2.3
ErrArchiveInfra signals an infrastructure-class failure encountered while processing an archive: temp file creation, disk-full while spooling, etc. Distinct from ErrInvalidArchive — the archive may be perfectly valid; the host environment failed. Callers should surface the cause directly rather than telling the user their tarball is malformed.
func (*ErrArchiveInfra) Error ¶ added in v1.2.3
func (e *ErrArchiveInfra) Error() string
func (*ErrArchiveInfra) Unwrap ¶ added in v1.2.3
func (e *ErrArchiveInfra) Unwrap() error
type ErrArchiveNotFound ¶ added in v1.2.3
ErrArchiveNotFound is returned when the archive file path does not exist or is not a regular file. Distinct from ErrInvalidArchive so callers can give a different message for "wrong path" vs "wrong contents".
func (*ErrArchiveNotFound) Error ¶ added in v1.2.3
func (e *ErrArchiveNotFound) Error() string
func (*ErrArchiveNotFound) Unwrap ¶ added in v1.2.3
func (e *ErrArchiveNotFound) Unwrap() error
type ErrArchivePermission ¶ added in v1.2.3
ErrArchivePermission is returned when the archive file exists but the current user cannot open it (permission denied). Distinct from ErrArchiveNotFound so the user sees "fix your permissions" rather than "fix your path".
func (*ErrArchivePermission) Error ¶ added in v1.2.3
func (e *ErrArchivePermission) Error() string
func (*ErrArchivePermission) Unwrap ¶ added in v1.2.3
func (e *ErrArchivePermission) Unwrap() error
type ErrDaemonNotRunning ¶
ErrDaemonNotRunning signals that the container engine's daemon socket / endpoint could not be reached. Engine and Host are best-effort context used by friendly renderers:
- Engine is the layerx --engine label ("docker", "podman", "") that was in effect when the failure happened. Empty means the resolver did not know its engine name (test fakes, or a resolver constructed via the FromEnv path with no cmd-level tag). Callers that render user-facing text must treat empty Engine as "container engine", never as "docker".
- Host is the URL the client was pointed at ("tcp://…", "unix://…", "ssh://…"), when the resolver knew it up front. NewDockerResolver via FromEnv leaves this empty because the client resolves the host from DOCKER_HOST internally; NewDockerResolverWithHost fills it in.
Never use fmt.Sprintf("Docker daemon …") in code that consumes this error — the engine may be Podman.
func (*ErrDaemonNotRunning) Error ¶
func (e *ErrDaemonNotRunning) Error() string
func (*ErrDaemonNotRunning) Unwrap ¶
func (e *ErrDaemonNotRunning) Unwrap() error
type ErrImageNotFound ¶
func (*ErrImageNotFound) Error ¶
func (e *ErrImageNotFound) Error() string
func (*ErrImageNotFound) Unwrap ¶
func (e *ErrImageNotFound) Unwrap() error
type ErrInvalidArchive ¶ added in v1.2.3
func (*ErrInvalidArchive) Error ¶ added in v1.2.3
func (e *ErrInvalidArchive) Error() string
func (*ErrInvalidArchive) Unwrap ¶ added in v1.2.3
func (e *ErrInvalidArchive) Unwrap() error
type ErrNoEngineFound ¶ added in v1.4.0
type ErrNoEngineFound struct {
Tried []string
}
func (*ErrNoEngineFound) Error ¶ added in v1.4.0
func (e *ErrNoEngineFound) Error() string
type ErrPlatformInvalid ¶ added in v1.5.0
ErrPlatformInvalid is returned when --platform cannot be parsed at all (empty component, too many slashes). Distinct from ErrPlatformNotInImage: the spec itself is malformed, no image lookup happened.
func (*ErrPlatformInvalid) Error ¶ added in v1.5.0
func (e *ErrPlatformInvalid) Error() string
type ErrPlatformNotInImage ¶ added in v1.5.0
ErrPlatformNotInImage is returned when --platform is well-formed but the requested variant does not exist in the image's manifest list. Available holds the platforms the image does carry, formatted as "os/arch[/variant]"; it is best-effort and may be empty when the daemon does not expose a manifest list (legacy image store, older daemons). When Available is empty the error renders as the terse "platform X not found" — the user still sees what they asked for, with no guess at what the image carries.
The Error() output deliberately omits the image ref to keep the user- facing message tight (the ref is already in the user's command line). Callers that want to render the ref alongside the message should read e.Ref directly rather than parse Error() output.
func (*ErrPlatformNotInImage) Error ¶ added in v1.5.0
func (e *ErrPlatformNotInImage) Error() string
type ErrPodmanSocketNotSet ¶ added in v1.4.0
type ErrPodmanSocketNotSet struct {
Platform string
}
func (*ErrPodmanSocketNotSet) Error ¶ added in v1.4.0
func (e *ErrPodmanSocketNotSet) Error() string
type ErrPullFailed ¶
func (*ErrPullFailed) Error ¶
func (e *ErrPullFailed) Error() string
func (*ErrPullFailed) Unwrap ¶
func (e *ErrPullFailed) Unwrap() error
type Extractor ¶
type Extractor interface {
Extract(ctx context.Context, imageRef string, filePath string) (*FileContent, error)
ExtractRaw(ctx context.Context, imageRef string, filePath string) ([]byte, error)
// ExtractFromLayer returns the file as it exists at the given layer.
// It walks back from layerCursor toward layer 0 to find the most recent
// layer that physically contains the path. A whiteout encountered during
// the walk-back stops the search and returns an error.
ExtractFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) (*FileContent, error)
// ExtractRawFromLayer is the raw-bytes variant of ExtractFromLayer; same
// walk-back semantics, no truncation, no binary detection.
ExtractRawFromLayer(ctx context.Context, imageRef string, filePath string, layerCursor int) ([]byte, error)
}
type ExtractorSource ¶
type ExtractorSource interface {
NewExtractor() Extractor
}
ExtractorSource is implemented by resolvers that can produce an Extractor.
type FileContent ¶
type FileDiff ¶ added in v1.3.0
type FileDiff struct {
Path string
DiffType DiffType
BeforeSize int64
AfterSize int64
SizeDelta int64
ChangeReason string
}
FileDiff records a change to one file in the final stacked filesystem. ChangeReason is set only for Modified entries and lists the differing fields in the canonical order: size,mode,uid,gid,linkname,hardlink.
type FileDiffSummary ¶ added in v1.3.0
type FileNode ¶
type FileNode struct {
Name string
Path string
Linkname string
Size int64
// EffectiveSize is the sum of Size for all non-removed file descendants
// (or Size itself for non-directory nodes). Populated once by
// computeEffectiveSizes after the tree is fully built; used by the TUI
// sort path to avoid an O(N²) subtree walk per sort invocation.
EffectiveSize int64
Mode fs.FileMode
UID int
GID int
DiffType DiffType
IntroducedInLayer int
Children []*FileNode
IsDir bool
IsHardlink bool
// Inferred is true for directory nodes created implicitly by insertNode
// because no explicit tar header existed for them. Their Mode/UID/GID are
// structural defaults, not authoritative image metadata.
Inferred bool
}
func (*FileNode) RemoveChild ¶
type FileTree ¶
type FileTree struct {
Root *FileNode
}
func BuildAggregatedTrees ¶ added in v1.5.0
BuildAggregatedTrees returns one FileTree per input layer, where result[i] is Dive's CompareAllLayers view at index i: a tree anchored at layer 0 (everything Added against the empty baseline) with every layer 1..i overlaid in order, preserving DiffType labels accumulated across iterations.
Contrast with Stack(layers), which produces the CompareSingleLayer view: result[i] there shows only what layer i changed against the cumulative state of layers 0..i-1, with prior labels stripped.
Memory bound: peak heap during the call is one mutable baseline tree (the cumulative state at the latest layer processed) plus the returned slice of one snapshot per layer. For an N-layer image, total ≈ 2× the final tree size (baseline + final snapshot) plus ΣN intermediate snapshots, comparable to Stack(layers)'s footprint.
func NewFileTree ¶
func NewFileTree() *FileTree
func ParseLayerTar ¶
ParseLayerTar reads a tar archive (a single layer) and builds a FileTree.
type ImageMeta ¶
type ImageMeta struct {
Size int64
}
ImageMeta holds lightweight metadata obtainable without exporting the full image.
type ImageSummary ¶ added in v1.3.0
type Layer ¶
type Layer struct {
Index int
ID string // short digest (first 12 chars of sha256)
Size int64
Command string // Dockerfile instruction that created this layer
Tree *FileTree // nil for empty layers
// NetDelta is the merged-filesystem change at this step.
// [0] = live file bytes after the first layer (full size, not 0).
// [i>0] = live bytes in stacked[i] minus stacked[i-1]. Negative when
// a layer net-removes more than it adds (cleanup, whiteout deletes).
NetDelta int64
}
Layer holds metadata for a single image layer.
type LayerDiff ¶ added in v1.3.0
type LayerDiff struct {
Index int
BeforeSize int64
AfterSize int64
SizeDelta int64
BeforeNetDelta int64
AfterNetDelta int64
NetDeltaDiff int64
BeforeCommand string
AfterCommand string
CommandsMatch bool
}
LayerDiff is a side-by-side comparison of two layers at the same index.
Index-based matching is the only stable option for Docker layers across rebuilds: a layer inserted in the middle of a Dockerfile shifts every subsequent index. CommandsMatch and the command-divergence warning surface that condition so consumers can interpret later rows skeptically.
type Option ¶
type Option func(*DockerResolver)
func WithClient ¶
func WithEngineTag ¶ added in v1.5.1
WithEngineTag records the engine label ("docker" / "podman") for use in error messages the resolver emits. Empty is fine — friendly renderers fall back to a generic "container engine" phrase when the tag is missing. This is context, NOT dispatch: the resolver still talks to whatever Docker Engine REST API the client is pointed at.
func WithHostTag ¶ added in v1.5.1
WithHostTag records the daemon URL for use in error messages. Kept separate from the moby-client's own host because the client resolves its host from DOCKER_HOST via FromEnv when NewDockerResolver is used without an explicit host; the resolver otherwise has no way to recover that string for a user-facing message.
func WithPlatform ¶ added in v1.5.0
WithPlatform pins the resolver to a specific image variant for multi-platform images. nil (or unset) means "use the daemon's default platform", which is the historical behaviour.
The platform flows into ImagePull (so the right manifest is fetched on a cold pull), ImageSave (so only the requested variant is exported on a multi-platform image store), and ImageInspect (for the digest read used as the cache key). Pull requires API 1.32, save requires API 1.48, and the inspect-side platform option requires API 1.49 (the moby client gates it with requiresVersion and returns an error on older daemons).
Cache-key invariant: AnalyzeWithOptions keys cache entries by ImageID, which on API 1.49+ daemons returns the platform-specific image config digest when WithPlatform is set. Different platforms therefore land in different cache directories — `layerx nginx --platform linux/amd64` and `layerx nginx --platform linux/arm64` cannot collide on cache. On older daemons the inspect call errors out (requiresVersion 1.49), digestErr is non-nil, and AnalyzeWithOptions skips the cache entirely. Either way, platform-mismatched entries cannot share a cache slot.
type ProgressEvent ¶
type ProgressEvent struct {
Phase ProgressPhase
LayersDone int
LayersTotal int
BytesCurr int64
BytesTotal int64
// Message carries human-readable diagnostic text for PhaseCacheWarn.
// Empty for normal phases.
Message string
}
ProgressEvent reports loading progress back to the caller.
type ProgressPhase ¶
type ProgressPhase int
ProgressPhase identifies what the resolver is currently doing.
const ( // PhaseUnknown is the zero value, used by callers to mean "no phase // observed yet" so a UI can avoid rendering a stale default phase // before the first event arrives. PhaseUnknown ProgressPhase = iota PhasePulling PhaseExporting PhaseParsing PhaseCacheLoad // emitted on cache hit; no live Docker work follows // PhaseCacheWarn is a non-fatal diagnostic: the cache load or save // failed with an unexpected I/O error. The run continues with live // data; UIs typically render Message in a status bar. PhaseCacheWarn )
type PruneOptions ¶ added in v1.4.0
type PruneOptions struct {
TTL time.Duration
MaxBytes int64
All bool
DryRun bool
Keep string
Now func() time.Time
}
PruneOptions controls what PruneCache evicts. Zero value is a no-op.
TTL > 0 remove entries whose CachedAt is older than now-TTL
MaxBytes > 0 after the TTL pass, evict oldest entries until the
surviving total is at or below MaxBytes
All == true ignore TTL/MaxBytes; remove every entry under root
DryRun walk and decide, but never call os.RemoveAll
Keep digest never to evict, regardless of other options.
Used by saveCache to protect the just-written entry,
even when its own size exceeds MaxBytes. The user-
driven `cache prune --all` always passes Keep="", so
it genuinely empties the cache.
Now injectable clock for tests; nil → time.Now
type PruneResult ¶ added in v1.4.0
type PruneResult struct {
Removed []CacheEntry
Kept []CacheEntry
Warnings []string
}
PruneResult is the return value of PruneCache.
Removed entries that were (or, in DryRun, would be) evicted, in
the order they were processed: TTL victims first (by
scan order), then size-cap victims oldest-first.
Kept entries that survived this prune, in scan order.
Warnings user-facing strings about partial failures (RemoveAll
errors, single-entry-exceeds-cap). cmd/ chooses how to
render; image/ does not print.
func PruneCache ¶ added in v1.4.0
func PruneCache(root string, opts PruneOptions) (PruneResult, error)
PruneCache applies opts and returns what it did. Best-effort: a RemoveAll failure is recorded in Warnings and skipped, never returned as the function's err. The function's err is reserved for "could not even read the cache root" — every other failure is per-entry.
The first RemoveAll failure within a single call is appended to Warnings as `cache prune partial: <err>`; subsequent failures during the same call are silently counted to preserve I-03's "fifty broken evictees should not produce fifty stderr lines" contract.
Concurrent prunes against the same root may both try to remove the same entry; the second RemoveAll returns ErrNotExist which is treated as success (Removed gets the entry, no warning).
type Resolver ¶
type Resolver interface {
Resolve(ctx context.Context, imageRef string) ([]Layer, error)
ResolveWithProgress(ctx context.Context, imageRef string, progress chan<- ProgressEvent) ([]Layer, error)
Inspect(ctx context.Context, imageRef string) (*ImageMeta, error)
// ImageID returns the image content digest (e.g. "sha256:abc...") for ref.
// Implementations may return an error if the image is not local; callers
// that want pull-on-miss must call ensure-image semantics first.
ImageID(ctx context.Context, imageRef string) (string, error)
}
Resolver fetches image layer information from a container runtime.