Repository navigation
feat: Pull images through containerd on Linux - #127
Conversation
bfca5e7 to
6fa5aeb
Compare
The Linux pull is to accept the same --platform strings as the macOS pull and fail the same way on the rest. The parser lived in pkg/ociclient, which the Linux CLI does not link: it is the macOS store's pull and unpack code, built on go-containerregistry, while the Linux pull goes through containerd. internal/imageplatform now holds the parser and returns an OCI platform, the type containerd takes. pkg/ociclient converts it to go-containerregistry's. Nothing changes on macOS. Refs: #105 Signed-off-by: Charalampos Mainas <cmainas@nofire.ai>
imageplatform.Parse checked only the number of parts, so "linux/" parsed as a platform with no architecture. On a multi-platform image the pull then failed at the registry, with a message about the index rather than the flag. A single-platform image has no index to match against, so the macOS pull fetched it whatever its architecture and recorded its platform as "linux/". Parse now refuses an empty OS, architecture or variant with the message it gives any other malformed --platform, before anything is fetched. Refs: #105 Signed-off-by: Charalampos Mainas <cmainas@nofire.ai>
The Linux pull is to report progress the way the macOS pull does, but the code that draws the line lived in pkg/ociclient, which the Linux CLI does not link. internal/pullprogress now draws the line: whether it refreshes in place or prints a line at a time, how often, and the final line. Each pull keeps its own counting and writes its own text, because the two count different things. The macOS pull counts bytes as hull extracts them, one layer after another. containerd downloads layers concurrently and unpacks them itself, so the Linux pull can only count what has been downloaded. The macOS output does not change. Refs: #105 Signed-off-by: Charalampos Mainas <cmainas@nofire.ai>
`hull pull` printed each label of the image as it came. Labels are chosen by whoever built the image, so a value could break the "key: value" layout with newlines, as the description of Ubuntu's image does, or carry an escape sequence to the terminal: OSC 52 reads and writes the clipboard, and CSI 21t types its reply into the shell's input. `hull images --json` already escapes labels for the same reason (see printJSON). writeLabels prints a key or value as it is only when it is valid UTF-8 made of printable characters and spaces, and Go-quotes it otherwise, so newlines, escape sequences, C1 controls, invalid bytes and bidi overrides show as escapes. A value that starts with a double quote is quoted too, so a quoted value cannot be mistaken for a plain one. Keys are sorted rather than printed in map order. writeLabels has no build tag, so the Linux pull can use it as well. Refs: #105 Signed-off-by: Charalampos Mainas <cmainas@nofire.ai>
6fa5aeb to
3f40285
Compare
|
@claude-fleet review |
|
Review task queued. Task: 5f09882b-31e9-49bc-8e29-dfd50c58c5a6 |
There was a problem hiding this comment.
Request changes: two should_fix findings. The pull path itself checks out — I verified the credential, HTTP/1.1, plain-HTTP and namespace claims against the dependency source, and the revert checks confirm the three fixes are covered by tests.
Findings:
- should_fix, internal/containerd/pull_test.go:376 — the platform assertion fails on an arm64 Linux host
- should_fix, internal/containerd/images.go:92 —
DescribePlatformdrops index entries with noplatform, so a pull that succeeded reports an error - nit, internal/containerd/pull.go:201 —
registrytokenin the Docker config is dropped, unlike the macOS pull
Verified:
- The plain-HTTP rule is go-containerregistry's own, RFC 1918 included —
Registry.Schemeatpkg/name/registry.go:72,88-105of go-containerregistry@v0.20.6, against pull.go:163-169 - HTTP/2 really is off:
docker.DefaultHTTPTransportsetsDialContextandTLSClientConfigand leavesForceAttemptHTTP2false (containerd v2.3.5core/remotes/docker/registry.go:254-269), which is exactly the casenet/http/transport.go:477skips auto-enabling h2 for — pull.go:147-156 docker loginfiles Hub underindex.docker.iowhile containerd pulls fromregistry-1.docker.io:ConfigureDefaultRegistriesrewritesdocker.iotoregistry-1.docker.io— pull.go:184-188- The raw
ctxhanded toprog.watchandDescribePlatformstill carries the namespace:client.Newinstalls the NS interceptors fordefaultns(client/client.go:159-161), soListStatusesis not silently erroring — pull.go:82,92 - The unpacker fetches layers through the wrapped handler (
unpacker.Unpack(wrapper(h))andu.unpack(uctx, h, ...),client/pull.go:144-148) and skips a layer whose snapshot exists (core/unpack/unpacker.go:418-425), which is what progress.go:68-71 and images.go:65-70 claim MakeRefKeydepends on the context only throughWithMediaTypeKeyPrefix, which nothing in the pull path sets, so the refs registered inwrapare the refs the fetch handler opens its ingests under —core/remotes/handlers.go:79-92,141against progress.go:84,131- The docs' snapshotter claim:
resolveSnapshotterNamereadsdefaults.DefaultSnapshotterNSLabeland falls back to overlayfs —client/client.go:909-916
Checks run:
go test ./internal/containerd/... ./internal/pullprogress/... ./internal/imageplatform/... ./cmd/hull/...→ ok (the two daemon tests skip, noHULL_TEST_CONTAINERD_ADDRESShere)- Removed
slices.Contains(parts, "")fromimageplatform.Parse→TestParsePlatformfails on 4 subtests (linux/,/amd64,linux//v8,linux/arm64/) - Made
printableLabelreturnsunquoted →TestPrintableLabelfails on 7 inputs andTestWriteLabelson its whole output - Disabled the
onlyPreffilter indescribeImage→ bothTestDescribePlatform*tests fail GOOS=darwin GOARCH=arm64 go build ./...→ ok;go veton the touched packages → clean- Working tree restored after each revert (
git status --porcelainshows only untrackednode_modules)
Smaller things:
- The Changes list omits two commits that are on the branch:
internal/pullprogress(the progress line moved out ofpkg/ociclient) andcmd/hull/imagelabels.go. The latter changes the macOS pull's output too — labels carrying controls are now Go-quoted — while the description only says "Nothing changes on macOS" about theimageplatformmove. Worth a line, since it is the user-visible part a macOS reader would otherwise miss. - pull.go:68:
name, err := NormalizeRef(ref)shadows the importednamepackage for the rest ofPull. It compiles becausePulldoes not use the package, but the twoname.uses a few lines below inkeychainCredsread oddly next to it. - The credential-helper-failure rule is now explained in three places — pkg/ociclient/client.go:110-113, internal/containerd/pull.go:177-181, docs/build.md — and the HTTP/1.1 pin in two. The new copies do point at the originals, so this is only a note that the next round should not add a fourth.
- No test covers the HTTP/1.1 pin or that
newResolverroutes a plain-HTTP-allowed host to the fallback client; onlyallowsPlainHTTPin isolation is tested. Given the pin exists because of a real ghcr failure, a test assertingNextProtos/ForceAttemptHTTP2on the transport would be cheap insurance against a containerd default changing underneath it. - go-containerregistry does not try HTTPS first for the hosts its
Scheme()callshttp— it goes straight to HTTP. The Linux path adds one failed HTTPS round trip per host before falling back. Same outcome, slightly slower; the comment at pull.go:158-162 reads as if the two are identical.
Not checked:
TestPullThroughDaemonandTestPullCommandAgainstDaemonagainst a live containerd — no daemon in this environment, so neither the unpack assertion nor thehull imagestable assertion ("ghcr.io/containerd/busybox 1.36", which depends on column widths) was exercised.- docs/build.md prose beyond the four claims listed under Verified.
Not checked:
- TestPullThroughDaemon and TestPullCommandAgainstDaemon against a live containerd daemon: none available here, so the unpack assertion and the
hull imagestable assertion were not exercised - docs/build.md prose beyond the platform default, full-name storage, snapshotter label and plain-HTTP claims listed under Verified
Task 5f09882b-31e9-49bc-8e29-dfd50c58c5a6 on rp2, model claude-opus-5, effort medium.
| if got.IndexDigest == "" || got.Digest == "" || got.Digest == got.IndexDigest { | ||
| t.Errorf("digests = %q (index %q), want a platform manifest under an index", got.Digest, got.IndexDigest) | ||
| } | ||
| if got.Platform != platforms.Format(platform) { |
There was a problem hiding this comment.
should_fix: The platform assertion compares a normalized request against an un-normalized image config, so it fails on an arm64 Linux host.
Failure: on arm64, platforms.DefaultSpec() returns linux/arm64/v8 and line 361 clears the variant, so platforms.Format(platform) is "linux/arm64". got.Platform comes from images.go:124-126, which formats the image config's own platform without normalizing, and ghcr.io/containerd/busybox:1.36's arm64 config carries "variant": "v8" — so got.Platform is "linux/arm64/v8" and the test reports platform = "linux/arm64/v8", want "linux/arm64". CI runs this job on ubuntu-latest (amd64), where the config has no variant and the comparison holds, so the failure only shows on an arm64 Linux box — make test on one, which the Makefile supports via LINUX_ARCH=arm64.
Evidence: pull_test.go:360-361 clears the variant; images.go:124-126 formats config.Platform as read; platforms.Format is path.Join(OS, Architecture, Variant) with no normalization (containerd/platforms@v1.0.0-rc.5/platforms.go:386-392). I confirmed platforms.DefaultSpec() is "linux/arm64/v8" on this aarch64 host, and fetched the image's arm64 config blob (sha256:abaa813f…) from ghcr: {'architecture': 'arm64', 'os': 'linux', 'variant': 'v8'}.
Fix: compare normalized platforms, e.g. platforms.Format(platforms.Normalize(...)) on both sides, or keep platforms.DefaultSpec() intact and let OnlyStrict normalize the request as it already does.
| if onlyPref { | ||
| manifests = nil | ||
| for _, m := range idx.Manifests { | ||
| if m.Platform != nil && pref.Match(*m.Platform) { |
There was a problem hiding this comment.
should_fix: The onlyPref filter requires a non-nil Platform on every index entry, but containerd's own filter treats a missing platform as a match, so a pull that succeeded ends in an error.
Failure: for an index whose entries omit the optional platform field, images.FilterPlatforms keeps those children (if d.Platform == nil || m.Match(*d.Platform)), containerd fetches the manifest and the unpacker unpacks it with platforms.All, so c.Pull returns success. DescribePlatform then filters every entry out, pickManifest gets an empty slice, and hull pull exits non-zero with "no image manifest of the index is in the content store" — while hull images, which goes through DescribeImage, lists the image fine. Narrow: Docker, BuildKit and podman all set platform on index entries, so this needs an index built by something that leaves it out.
Evidence: images.go:91-96 against containerd v2.3.5 core/images/handlers.go:292-313 (FilterPlatforms) and core/unpack/unpacker.go reached via client/pull.go:108-112, where platformMatcher is platforms.All; images.go:186-191 produces the message.
Fix: match containerd — if m.Platform == nil || pref.Match(*m.Platform). A nil-platform candidate gets matches: false at images.go:183, so the pref.Less call at images.go:200 stays unreachable for it (the a.matches != b.matches branch returns first).
| if cfg.IdentityToken != "" { | ||
| return "", cfg.IdentityToken, nil | ||
| } | ||
| return cfg.Username, cfg.Password, nil |
There was a problem hiding this comment.
nit: The keychain adapter forwards only IdentityToken and Username/Password, dropping AuthConfig.RegistryToken, so a registry authenticated that way works on macOS and goes anonymous on Linux.
Failure: with a registrytoken entry for a private registry in the Docker config, authn.DefaultKeychain resolves it and Authorization() returns an AuthConfig whose only populated field is RegistryToken. This function then falls through to return cfg.Username, cfg.Password, nil — both empty — so containerd's authorizer sends no credentials and the pull fails with a 401 or a "not found". The macOS pull sends it as Authorization: Bearer <token>.
Evidence: pull.go:194-202; go-containerregistry@v0.20.6 reads the field in both pkg/v1/remote/transport/basic.go and bearer.go (see transport/basic_test.go:61-68, bearer_test.go:199-212), and defaultKeychain.Resolve copies RegistryToken out of the Docker config.
Since containerd's creds callback has no bearer channel, the honest options are to document the gap next to the IdentityToken comment or to return an error naming it, rather than silently pulling anonymously.
`hull pull` on Linux fetches an image into hull's containerd namespace and prints what the macOS pull prints. The pull runs in hull's process through containerd's client, not through the transfer service in the daemon: the daemon has none of the user's registry credentials, and hull reads them from the Docker config and its credential helpers, as on macOS. A helper that fails, as one needing a locked keychain does in a headless session, leaves the pull anonymous, on the same test of the error the macOS pull uses. containerd's credentials callback can only return a username and a secret, so a registry token in the Docker config, which the macOS pull sends as a bearer token on every request, goes out as a header of that registry's host instead. The keychain is asked once per registry for the life of the resolver, since containerd asks for a host's configuration on every request and a credential helper can be slow or prompt. The registry client matches the macOS one where the two would otherwise differ. It pins HTTP/1.1, because large ghcr layers fail part-way over Go's HTTP/2 stack (see registryTransport in pkg/ociclient). It tries plain HTTP after HTTPS for the registries go-containerregistry allows it for, taken from go-containerregistry itself, so a local registry works on both OSes. containerd pulls Docker Hub from registry-1.docker.io, but `docker login` files Hub's credentials under index.docker.io, so the credentials lookup maps one to the other. Some differences follow from containerd and the host: - --platform defaults to the host's architecture rather than linux/arm64, so an amd64 host pulls an image it can boot. - The image is stored under its full name, docker.io/library/busybox for `busybox`, which containerd's resolver needs and ctr and nerdctl use. - The daemon unpacks the image into a snapshot, so the progress line counts bytes downloaded rather than extracted. It is drawn by internal/pullprogress, as the macOS line is. containerd's unpacker does not download a layer whose snapshot already exists, such as a base layer shared with an image pulled before. A successful pull can therefore leave the requested platform's layers missing, and its handlers never see those layers finish. The result is described with DescribePlatform, which considers only the index entries the pull can have fetched, where DescribeImage would rank an earlier pull's complete image of another platform first. Those are the entries that match, or the entries that name no platform when none matches: containerd keeps an entry without a platform but sorts it after the rest. The progress line counts every layer once the pull succeeds, with the bytes of only those it fetched. The platform must match exactly, so an index without it fails the pull instead of yielding a compatible platform. containerd's client logs a failed registry request at info level and then returns the same failure, so its info messages are dropped unless --debug is given. The unit-linux job now pulls containerd's busybox from ghcr.io twice. One pull goes through a credential helper that always fails, and checks that it went ahead anonymously and left the image unpacked. The other runs `hull pull` and `hull images` through the command tree and checks what they print. Refs: #105 Signed-off-by: Charalampos Mainas <cmainas@nofire.ai>
3f40285 to
f07cc0a
Compare
|
@claude-fleet review |
|
Review task queued. Task: cca5550b-f2fa-4bd8-9ec6-f8d30e100441 |
There was a problem hiding this comment.
Previous round at 3f40285: 3 findings.
| Previous finding | Classification | Cited line |
|---|---|---|
internal/containerd/pull_test.go:376 (should_fix): The platform assertion compares a normalized request against an un-normalized image config, so it fails on an arm64 Linux host. Failure: on arm64, platforms.DefaultSpec() returns linux/arm64/v8 and line 361 clears the variant, so platforms.Format(platform) is "linux/arm64". got.Platform comes from images.go:124-126, which formats the image config's own platform without normalizing, and ghcr.io/containerd/busybox:1.36's arm64 config carries "variant": "v8" — so got.Platform is "linux/arm64/v8" and the test reports platform = "linux/arm64/v8", want "linux/arm64". CI runs this job on ubuntu-latest (amd64), where the config has no variant and the comparison holds, so the failure only shows on an arm64 Linux box — make test on one, which the Makefile supports via LINUX_ARCH=arm64. Evidence: pull_test.go:360-361 clears the variant; images.go:124-126 formats config.Platform as read; platforms.Format is path.Join(OS, Architecture, Variant) with no normalization (containerd/platforms@v1.0.0-rc.5/platforms.go:386-392). I confirmed platforms.DefaultSpec() is "linux/arm64/v8" on this aarch64 host, and fetched the image's arm64 config blob (sha256:abaa813f…) from ghcr: {'architecture': 'arm64', 'os': 'linux', 'variant': 'v8'}. Fix: compare normalized platforms, e.g. platforms.Format(platforms.Normalize(...)) on both sides, or keep platforms.DefaultSpec() intact and let OnlyStrict normalize the request as it already does. |
fixed | internal/containerd/pull_test.go:494 |
internal/containerd/images.go:92 (should_fix): The onlyPref filter requires a non-nil Platform on every index entry, but containerd's own filter treats a missing platform as a match, so a pull that succeeded ends in an error. Failure: for an index whose entries omit the optional platform field, images.FilterPlatforms keeps those children (if d.Platform == nil || m.Match(*d.Platform)), containerd fetches the manifest and the unpacker unpacks it with platforms.All, so c.Pull returns success. DescribePlatform then filters every entry out, pickManifest gets an empty slice, and hull pull exits non-zero with "no image manifest of the index is in the content store" — while hull images, which goes through DescribeImage, lists the image fine. Narrow: Docker, BuildKit and podman all set platform on index entries, so this needs an index built by something that leaves it out. Evidence: images.go:91-96 against containerd v2.3.5 core/images/handlers.go:292-313 (FilterPlatforms) and core/unpack/unpacker.go reached via client/pull.go:108-112, where platformMatcher is platforms.All; images.go:186-191 produces the message. Fix: match containerd — if m.Platform == nil || pref.Match(*m.Platform). A nil-platform candidate gets matches: false at images.go:183, so the pref.Less call at images.go:200 stays unreachable for it (the a.matches != b.matches branch returns first). |
fixed | internal/containerd/images.go:135 |
internal/containerd/pull.go:201 (nit): The keychain adapter forwards only IdentityToken and Username/Password, dropping AuthConfig.RegistryToken, so a registry authenticated that way works on macOS and goes anonymous on Linux. Failure: with a registrytoken entry for a private registry in the Docker config, authn.DefaultKeychain resolves it and Authorization() returns an AuthConfig whose only populated field is RegistryToken. This function then falls through to return cfg.Username, cfg.Password, nil — both empty — so containerd's authorizer sends no credentials and the pull fails with a 401 or a "not found". The macOS pull sends it as Authorization: Bearer <token>. Evidence: pull.go:194-202; go-containerregistry@v0.20.6 reads the field in both pkg/v1/remote/transport/basic.go and bearer.go (see transport/basic_test.go:61-68, bearer_test.go:199-212), and defaultKeychain.Resolve copies RegistryToken out of the Docker config. Since containerd's creds callback has no bearer channel, the honest options are to document the gap next to the IdentityToken comment or to return an error naming it, rather than silently pulling anonymously. |
fixed | internal/containerd/pull.go:153 |
No findings.
Verified both fixes by reverting them; all three previous findings are addressed. No new findings.
Approve.
Previous findings:
- 1 fixed —
internal/containerd/pull_test.go:490-496parsesgot.Platformand compares throughplatforms.OnlyStrict(platform).Match, so both sides normalize.normalizeArchmapsarm64/v8toarm64with an empty variant (platforms@v1.0.0-rc.5database.go:87-94), andmatcher.Matchnormalizes its argument (platforms.go:189-194), so the arm64 config'sv8no longer breaks the assertion. - 2 fixed —
internal/containerd/images.go:135-149(pulledManifests) keeps entries without a platform when none matches, which is what containerd's own chain does:FilterPlatformskeeps nil-platform children (core/images/handlers.go:304-308) andLimitManifestssorts them last and takes the first (handlers.go:330-347,limit1 viaclient/pull.go:234-235). Preference for a matching entry is kept, so an earlier pull's complete nil-platform entry cannot outrank it. - 3 fixed —
internal/containerd/pull.go:149-157resolves the credentials in theHostscallback and putsAuthorization: Bearer <registrytoken>on eachRegistryHost, merged into every request atcore/remotes/docker/resolver.go:551-556. The overwrite loses nothing: noWithHeadersis used, and the resolver's ownUser-Agentlives inr.header, nothost.Header.
Verified:
- The keychain is consulted once per registry across repeated callbacks, under either Hub name —
internal/containerd/pull.go:214-246, covered bypull_test.go:140-150. - A failing credential helper still leaves the pull anonymous after the refactor, and a config that does not parse still fails it —
pull.go:234-240,pull_test.go:155-167. pulledManifests's claim about containerd's ordering matchescore/images/handlers.go:292-350andclient/pull.go:225-236.cmd.ErrWriteris never nil for the Linux pull, so progress reaches stderr and the tty check works: cli v3 inherits it from the parent or defaults toos.Stderr(command_setup.go:68-74).
Checks run:
go test ./internal/containerd/... ./internal/pullprogress/... ./internal/imageplatform/... ./cmd/hull/...→ ok (the two daemon/ghcr tests do not run here; no daemon).- Reverted
images.go:91to the old nil-rejecting filter →TestDescribePlatformOfAnIndexWithoutPlatformsfails with "no image manifest of the index is in the content store"; restored. - Disabled the
cfg.RegistryToken != ""branch atpull.go:153→TestResolverSendsTheRegistryTokenfails withregistry with a token got Authorization ""; restored (git statusclean apart from untrackednode_modules). GOOS=darwin GOARCH=arm64 go build ./...→ ok, so theimageplatformextraction does not break the macOS side.
Smaller things:
images.go:120-122formats the image config's platform without normalizing, so on an arm64 hosthull pull/hull imagesprintlinux/arm64/v8where an amd64 host printslinux/amd64. Pre-existing (it comes from the images change), cosmetic, and the test now tolerates it — worth oneplatforms.Normalizesomeday.- A registry token for an RFC 1918 or
.localregistry can go out over the plain-HTTP fallback. go-containerregistry does the same on macOS, so the two OSes stay level.
Not checked: the two tests that reach ghcr.io (TestPullThroughDaemon, TestPullCommandAgainstDaemon) did not run — no containerd daemon in this workspace; their logic I read only.
Not checked:
- internal/containerd/pull_test.go TestPullThroughDaemon and cmd/hull/pull_linux_test.go TestPullCommandAgainstDaemon did not execute: no containerd daemon in this workspace, so the ghcr.io pull path was read but not run
- cmd/hull/imagelabels_test.go and internal/pullprogress/pullprogress_test.go were run but not read line by line
Task cca5550b-f2fa-4bd8-9ec6-f8d30e100441 on ci-16gb-fsn1-1, model claude-opus-5, effort medium.
ananos
left a comment
There was a problem hiding this comment.
thanks @cmainas, looks great!
I ran it on a Linux box against containerd 2.3.5 under rootlesskit. go test -race ./... passes with the daemon tests on. hull pull works for a short Hub name, an arm64 image on an amd64 host and a ghcr image, and ctr sees the same names. A bad --platform, a platform missing from the index, a missing tag and a missing repo each fail with one clear error.
Two small things: the "Depends on the hull images PR" line in the description is stale now that #124 is in, and this conflicts with #128 in main_linux.go and docs/build.md, so whichever lands second needs a quick rebase.
Summary
On Linux, hull keeps its images in containerd, and the only verb so far was
hull images, which can list images but has no way to get one there. Thisadds
hull pull. It takes the same arguments as the macOS pull and printsthe same lines, with progress on stderr.
The pull runs in hull's process through containerd's client, not through
the transfer service in the daemon. The daemon has none of the user's
registry credentials. hull reads them the way it does on macOS, from the
Docker config and its credential helpers, through an adapter from
go-containerregistry's keychain to containerd's authorizer. A credential
helper that fails, as one needing a locked keychain does in a headless
session, leaves the pull anonymous, on the same test of the error the macOS
pull uses.
The registry client matches the macOS one where the two would otherwise
differ:
which is why
pkg/ociclientpins it too.allows that for: loopback, RFC 1918 addresses and
.localnames. The ruleis taken from go-containerregistry itself, so a local registry works on
both OSes.
registry-1.docker.io, toindex.docker.io, wheredocker loginstores Hub's credentials.containerd unpacks the image into a snapshot as part of the pull, so the
image is ready for
hull runonce that verb exists.Where Linux differs from macOS
These are documented under "Building on Linux" in
docs/build.md:--platformdefaults to the host's (linux/amd64orlinux/arm64), notto
linux/arm64, so an amd64 host pulls an image it can boot.hull pull busyboxlists asdocker.io/library/busybox. containerd's resolver needs the registryspelled out, and
ctrand nerdctl use the same names.snapshot. A containerd run as an ordinary user, outside rootlesskit, cannot
unpack, and the pull fails there.
nothing to the client while it unpacks.
re-unpacking a damaged image, keeping an image that instances use,
sweeping staging leftovers, and carrying an index digest over between
pulls. containerd's content store and garbage collector cover most of it.
Whether containerd keeps an image an instance uses only matters once
runexists.
The requested platform must match exactly, so an index without it fails the
pull instead of yielding a compatible platform. The result is described with
a new
DescribePlatform, which considers only that platform's manifests.DescribeImagewould rank an earlier pull's complete image of anotherplatform first on a containerd that discards layers once it has unpacked
them.
Related issues
Refs #105. Depends on the
hull imagesPR (feat/images), whose commits areat the base of this branch.
Changes
internal/imageplatform(refactor, its own commit): the--platformparser moves out of
pkg/ociclient, so both pulls accept the same stringsand fail the same way without the Linux CLI linking the macOS pull code.
It returns an OCI platform, which
pkg/ociclientconverts togo-containerregistry's. Nothing changes on macOS.
hull pullon Linux (cmd/hull/pull_linux.go): the macOS flags andoutput, with
--platformparsed byinternal/imageplatform.internal/containerd/pull.go:Pull,NormalizeRef, the resolver andthe credentials adapter. containerd's client logs a failed registry request
at info level and then returns the same failure, so its info messages are
dropped unless
--debugis given.internal/containerd/progress.go: the progress line, which redraws inplace on a terminal. Otherwise it prints a line every 5s and when a layer
finishes, as on macOS. It learns
the layers from the manifest and finds each download by its ingest ref in
the content store, which does not report the digest an ingest expects.
internal/containerd/images.go:DescribePlatform.docs/build.md:hull pulland how it differs from macOS.go.mod:containerd/loganddistribution/referencemove from indirectto direct dependencies.
reference normalization,
DescribePlatformand the progress output. Theparser's tests move with it. Two daemon tests pull
ghcr.io/containerd/busybox:1.36. One goes through a credential helperthat always fails and checks that the pull went ahead anonymously and left
the image unpacked. The other runs
hull pullandhull imagesthrough thecommand tree and checks what they print. These are the first tests that
reach a registry, so
unit-linuxnow needs network access to ghcr.io.Checklist
make testpassesmake macosbuilds hull, vz-runner and hvi, if Go, Swift or Rust code changedtest/*.py) for changes touching boot, console, or the run pathdocs/)