feat(provider): daemonless oci:// + ::<path> for custom binary location - #143
Merged
Conversation
…cation Adds a new oci:// provider that pulls and extracts binaries from any OCI registry (Docker Hub, ghcr.io, quay.io, private) without requiring a container runtime. Uses go-containerregistry for native auth (reads ~/.docker/config.json) and multi-arch manifest resolution. Also adds ::<path> syntax to both docker:// and oci:// refs to pin an explicit in-container binary path, so consumers no longer depend on the provider guessing the right search path: docker://docker@cli::/usr/local/bin/docker oci://ghcr.io/org/img@v1::/bin/tool Binary size impact (stripped): +1.03 MB (~10.7 MB → ~11.8 MB). - pkg/provider/oci.go — daemonless pull + layer tar extract - pkg/provider/docker.go — supports ::<path> override - pkg/provider/provider.go — ParseImageRef, ParseRef/BinaryName aware of :: - pkg/binary/download.go — dispatches OCI case - pkg/cli/install.go — parseBinaryArg and parseSCPArg skip :: Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- List oci:// in the providers example in b.yaml config - Note that oci:// uses ~/.docker/config.json for registry auth Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new daemonless oci:// provider (OCI registry pull + in-image binary extraction) and extends both docker:// and oci:// refs with a ::<path> suffix to pin an explicit in-image binary location.
Changes:
- Introduce
oci://provider usinggo-containerregistryto pull images without a container runtime and extract a single binary from layers. - Add
::<in-container-path>parsing/behavior fordocker://andoci://refs, including correct@tagparsing and binary name derivation. - Update CLI parsing/tests/docs plus Go module dependencies to support the new provider.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/provider/oci.go | New OCI provider implementation: remote pull + platform selection + tar-layer extraction. |
| pkg/provider/oci_test.go | Basic unit tests for OCI provider metadata and ParseImageRef. |
| pkg/provider/docker.go | Teach Docker provider to accept ::<path> and reuse ParseImageRef. |
| pkg/provider/provider.go | Extend ref parsing (ParseRef, BinaryName) and add shared ParseImageRef. |
| pkg/provider/provider_test.go | Add tests for ParseRef and BinaryName covering ::<path>. |
| pkg/binary/download.go | Route downloads to the new provider.OCI install path. |
| pkg/cli/install.go | Update CLI arg parsing to preserve ::<path> while parsing @version. |
| pkg/cli/install_test.go | Ensure ::<path> isn’t misinterpreted as SCP syntax. |
| pkg/cli/cli_extra_test.go | Add CLI parse tests for docker:///oci:// with ::<path>. |
| README.md | Document new oci:// provider and ::<path> examples. |
| go.mod / go.sum | Add go-containerregistry and update dependency graph accordingly. |
Comments suppressed due to low confidence (1)
pkg/provider/docker.go:53
- Docker.Install(): ParseImageRef currently does not strip or parse docker-style ":tag" in the image portion, but Install always appends ":" + tag. As a result, refs like "docker://org/image:1.0" will produce an invalid imageRef ("org/image:1.0:latest" when version is empty). Consider teaching ParseImageRef (or Install) to recognize and handle ":tag" (including the registry-port case) so existing docker-style refs don’t break.
rest := strings.TrimPrefix(ref, "docker://")
image, refTag, inContainerPath := ParseImageRef(rest)
tag := version
if tag == "" {
tag = refTag
}
if tag == "" {
tag = "latest"
}
imageRef := image + ":" + tag
name := BinaryName(ref)
- Syntax changes from '::/path' to ':/path', consistent with b's other providers where '@' is the tag separator. The leading '/' on the path disambiguates it from docker's native 'image:tag' (which b never uses). - SplitImagePath skips the "://" scheme prefix and uses the last ":/" so registry ports like "localhost:5000/org/img" parse correctly. - Docker/OCI refs are no longer eligible for SCP-style env install; the parser short-circuits on their prefixes. Copilot review follow-ups: - BinaryName falls back to default derivation when path is empty / trailing slash so we never return "". - dockerImage only strips "image:tag" when ':' is after the last '/', preserving registry ports. - OCI.Install scans each layer once against a set of candidate paths (instead of O(layers x paths) re-decompressions), writing to a temp file and renaming once the highest-priority match is known. - resolveImage replaced with remote.Image + WithPlatform, which handles variant matching and fallback correctly via go-containerregistry. - New oci_extract_test.go covers priority, no-match, non-regular files, and empty search paths against in-memory tar layers. Docs: - README binary+config examples updated to the ':' path syntax. - docs/b/subcommands/install.mdx gets a "container images" section. - docs/authentication.mdx documents OCI auth via ~/.docker/config.json. - docs/glossary.mdx lists the new prefixes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses the second round of Copilot review: - OCI.Install now tracks whiteouts (.wh.<name> and .wh..wh..opq) while walking layers newest-first, so a deleted-in-newer-layer path can't be resurrected from an older layer. - Dropped the stale '::<path>'/'docker://oci://' wording from docstrings and examples — everything now uses ':/<path>' and 'docker:// or oci://'. - docs/b/subcommands/install.mdx uses an absolute '/authentication' link so it resolves correctly in the Docusaurus build. - Provider Reference glossary entry is no longer git-centric. Added whiteout-specific tests (file whiteout, opaque-dir whiteout, whiteout map recording) against in-memory tar layers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 'if whiteouts["/"] { return true }; return false' with
direct 'return whiteouts["/"]'. Functionally identical.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
pkg/provider/docker.go:54
- Docker.Install now uses ParseImageRef() and then builds imageRef as image+":"+tag. If the user passes a docker-style image ref containing a ':tag' (common copy/paste), image will already include the tag and this will produce an invalid reference like "alpine:3.19:latest" (and a confusing runtime error). Consider explicitly detecting docker-style tags (only ':' after last '/') and either parsing them into refTag when version is empty, stripping them before appending tag, or returning a clear error instructing users to use '@tag'.
rest := strings.TrimPrefix(ref, "docker://")
image, refTag, inContainerPath := ParseImageRef(rest)
tag := version
if tag == "" {
tag = refTag
}
if tag == "" {
tag = "latest"
}
imageRef := image + ":" + tag
name := BinaryName(ref)
- ParseImageRef also accepts docker-style 'image:tag' as a copy-paste convenience. @tag remains the preferred syntax, but users can paste 'oci://alpine:3.19' from docker docs without a confusing error. Registry ports are still preserved (the scan only treats a ':' as a tag when it's after the last '/'). - parseBinaryArg now delegates to provider.ParseRef, eliminating the duplicated docker://oci://-aware '@' split logic. - extractBinaryFromLayer skips whiteout-blocked candidates during the scan rather than only after, so a lower-priority fallback in the same layer is correctly used when the preferred path is whited out. - Regular-file check uses FileInfo().Mode().IsRegular() so both TypeReg and the deprecated NUL-byte TypeRegA (still seen in some tar encodings) are accepted, without referencing the deprecated constant. New tests cover: fallback after whiteout in same layer, legacy NUL typeflag, and docker-style 'image:tag' parsing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ParseRef now also tolerates docker-style 'image:tag' (with ':' only after the last '/' so registry ports survive). Previously the tag was silently kept on base, producing inconsistent lock/config representations vs. the preferred '@tag' form. - Root opaque whiteout ('/.wh..wh..opq') is now stored under the '/' sentinel so isWhiteoutBlocked actually hides older layers. Previously path.Dir("/")+"/" produced '//' and the root sentinel never matched. - Minor wording fix in parseBinaryArg comment. New test: TestExtractBinaryFromLayer_RootOpaqueBlocksEverything. New ParseRef cases cover 'image:tag', 'image:tag:/path', registry ports. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fentas
pushed a commit
that referenced
this pull request
Apr 16, 2026
🤖 I have created a release *beep* *boop* --- ## [4.15.0](v4.14.1...v4.15.0) (2026-04-16) ### Features * **provider:** daemonless oci:// + ::<path> for custom binary location ([#143](#143)) ([6fc80e6](6fc80e6)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
oci://provider: pulls binaries directly from any OCI registry (Docker Hub, ghcr.io, quay.io, private) without a container runtime. Usesgo-containerregistryfor native auth (reads~/.docker/config.json) and platform-aware manifest resolution.:/<path>suffix on bothdocker://andoci://refs to pin an explicit in-container binary location. The leading/disambiguates the path from docker's nativeimage:tagsyntax (whichbnever uses — tags always go after@, consistent with every other provider).docker://docker@cli:/usr/local/bin/dockeroci://ghcr.io/org/img@v1:/bin/toolWhy
docker://forces a running container runtime and only searches hardcoded paths, which doesn't cover custom image layouts.oci://works in CI/minimal environments without docker, and makes the "any registry" intent explicit.@tagworks for standard images, but there was no way to install a binary from a non-standard in-container path.Design notes
@(consistent with every otherbprovider). Docker-styleimage:tagis not used.:/(single:followed by absolute path). Registry ports likelocalhost:5000/org/imgare preserved becauseSplitImagePathusesLastIndex(":/")after skipping the://scheme prefix.docker:///oci://refs are never interpreted as SCP-style env installs.Test plan
go test ./...— all passParseImageRef,SplitImagePath,BinaryNamecovering registry ports, empty paths, and docker-style refs.oci_extract_test.go): priority, no-match, non-regular files, empty search paths — no network needed.b i oci://docker@cli:/usr/local/bin/docker→ extracts working docker CLI (41M) without docker daemon.b i oci://ghcr.io/linuxcontainers/alpine@latest:/bin/busybox→ daemonless pull from ghcr.io.docker://refs continue to work unchanged.Docs
README.md— examples and config snippet updated for:/pathsyntax.docs/b/subcommands/install.mdx— new "Install from container images" section covering both providers.docs/authentication.mdx— OCI auth via~/.docker/config.json.docs/glossary.mdx— provider reference entry lists all supported prefixes.🤖 Generated with Claude Code