Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ b install gitlab.com/org/tool
b install codeberg.org/user/app@v1.0
b install go://golang.org/x/tools/cmd/goimports
b install docker://alpine/helm
b install docker://docker@cli # tag via @ (e.g. docker:cli image)
b install docker://docker@cli:/usr/local/bin/docker # explicit in-container path
b install oci://ghcr.io/org/img@v1 # daemonless, any OCI registry
b install oci://docker@cli:/usr/local/bin/docker # daemonless docker CLI
b install "git:///home/user/myrepo:.scripts/tool"
b install "git://github.com/org/repo:bin/app@v1.0"

Expand Down Expand Up @@ -120,13 +124,18 @@ binaries:
alias: renvsubst # alias to renvsubst
kubectl:
file: ../kc # custom path (relative to config)
# Install from any provider by ref (GitHub, GitLab, Gitea, go://, docker://, git://)
# Install from any provider by ref (GitHub, GitLab, Gitea, go://, docker://, oci://, git://)
github.com/sharkdp/bat:
version: v0.24.0
# Install from a git repo (local or remote)
git:///home/user/myproject:.scripts/tool:
git://github.com/org/repo:bin/app:
version: v1.0
# Install from an OCI registry (daemonless — works without docker)
oci://ghcr.io/org/img:
version: v1.0
oci://docker:/usr/local/bin/docker:
version: cli

envs:
# Sync files from upstream git repos
Expand Down Expand Up @@ -164,6 +173,8 @@ Set environment variables to authenticate with providers for higher rate limits
| `GITLAB_TOKEN` | GitLab |
| `GITEA_TOKEN` | Gitea / Forgejo (Codeberg) |

For `oci://` the daemonless client reuses your local registry auth from `~/.docker/config.json` (or the `DOCKER_CONFIG` override) — the same credentials `docker login` writes.

 

### 🐳 Using Docker
Expand Down
15 changes: 15 additions & 0 deletions docs/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ A Gitea token is required for private repositories on Gitea or Forgejo instances
export GITEA_TOKEN="..."
```

## OCI Registry Authentication

For `oci://` refs, `b` pulls images daemonlessly but reuses your local docker
credentials. It reads `~/.docker/config.json` (or the location pointed to by
`DOCKER_CONFIG`) and uses whichever helper is configured there. In other
words, if `docker login ghcr.io` works, `b install oci://ghcr.io/...` works too.

```bash
# Log in once; b picks it up automatically
docker login ghcr.io
b install oci://ghcr.io/org/private-img
```

No `b`-specific environment variable is needed for OCI auth.

## SSH Authentication

For private repos, you can also use SSH keys instead of tokens. SSH authentication uses your system's ssh-agent and works for both binaries and env sync:
Expand Down
24 changes: 24 additions & 0 deletions docs/b/subcommands/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ b install "git:///home/user/myrepo:bin/tool"
b install "git://../../shared-repo:scripts/deploy.sh"
```

### Install from container images

Use `docker://` to pull from a local container runtime, or `oci://` to pull
**daemonless** from any OCI registry (Docker Hub, ghcr.io, quay.io, private).

```bash
# docker:// — requires a running docker/podman/nerdctl
b install docker://alpine/helm
b install docker://docker@cli # tag via @ (docker:cli image)
b install docker://docker@cli:/usr/local/bin/docker # explicit in-container path

# oci:// — daemonless; works in CI containers without docker
b install oci://ghcr.io/org/img@v1
b install oci://docker@cli:/usr/local/bin/docker
```

The syntax is: `<docker://|oci://><image>[@<tag>][:/<path-in-image>]`

- **image** — An image reference, including optional registry (e.g., `alpine`, `ghcr.io/org/img`)
- **tag** (optional, after `@`) — Image tag; defaults to `latest`. Use `@` consistently with every other `b` provider rather than docker's native `image:tag` syntax
- **path** (optional, after `:/`) — Absolute path to the binary inside the image. When omitted, `b` searches `/usr/local/bin`, `/usr/bin`, `/bin`, and `/app` for a file named after the image's last segment
Comment thread
fentas marked this conversation as resolved.

The leading `/` on the path disambiguates it from an `image:tag` pasted from docker documentation. For private registries, `oci://` reads credentials from `~/.docker/config.json` (same as `docker login`); see the [authentication](/authentication) page.

## Flags

| Flag | Description |
Expand Down
2 changes: 1 addition & 1 deletion docs/glossary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ This glossary defines key terms and concepts used throughout the **b** documenta

**Profile** - A named file set published in an upstream repo's `b.yaml` under the `profiles` section. Consumers discover profiles via `b env profiles` and install them via `b env add`, which copies the configuration into their local `envs`.

**Provider Reference** - A git-cloneable path used to install binaries or sync env files from GitHub (e.g., `github.com/org/repo`).
**Provider Reference** - A provider-specific reference used to install binaries or sync env files (e.g., `github.com/org/repo`). Supported prefixes include `go://` (Go install), `git://` (any git repo), `docker://` (via container runtime), and `oci://` (daemonless OCI pull).

## S

Expand Down
24 changes: 18 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ module github.com/fentas/b
go 1.26

require (
github.com/fatih/color v1.10.0
github.com/fatih/color v1.15.0
github.com/fentas/goodies v0.0.0-20250628100539-67031d6c92c6
github.com/google/go-containerregistry v0.21.5
github.com/jedib0t/go-pretty/v6 v6.5.6
github.com/jmespath-community/go-jmespath v1.1.1
github.com/spf13/cobra v1.8.0
github.com/spf13/cobra v1.10.2
github.com/tidwall/gjson v1.18.0
github.com/tidwall/sjson v1.2.5
github.com/ulikunitz/xz v0.5.12
Expand All @@ -17,19 +18,30 @@ require (

require (
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/docker/cli v29.4.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/goccy/go-yaml v1.11.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gotest.tools/v3 v3.5.2 // indirect
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect
)
64 changes: 45 additions & 19 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fentas/goodies v0.0.0-20250628100539-67031d6c92c6 h1:fEKwzohcEvjnilKbOqvizP44S22kWNWSxhroaB4uT/4=
Expand All @@ -17,34 +23,48 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.21.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM=
github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.5.6 h1:nKXVLqPfAwY7sWcYXdNZZZ2fjqDpAtj9UeWupgfUxSg=
github.com/jedib0t/go-pretty/v6 v6.5.6/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4=
github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
Expand All @@ -56,14 +76,18 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec h1:pAv+d8BM2JNnNctsLJ6nnZ6NqXT8N4+eauvZSb3P0I0=
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
Expand All @@ -72,5 +96,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY=
k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
7 changes: 7 additions & 0 deletions pkg/binary/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ func (b *Binary) downloadViaProvider() error {
}
b.File = path
return nil
case *provider.OCI:
path, err := pt.Install(b.ProviderRef, b.Version, destDir)
if err != nil {
return err
}
b.File = path
return nil
case *provider.Git:
path, err := pt.Install(b.ProviderRef, b.Version, destDir)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions pkg/cli/cli_extra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,10 @@ func TestParseBinaryArg(t *testing.T) {
{"jq@jq-1.7", "jq", "jq-1.7"},
{"kubectl@v1.28.0", "kubectl", "v1.28.0"},
{"terraform", "terraform", ""},
// docker:// / oci:// ":/<path>" must not swallow @tag into version.
{"docker://docker@cli:/usr/local/bin/docker", "docker://docker:/usr/local/bin/docker", "cli"},
{"oci://ghcr.io/org/img@v1:/bin/tool", "oci://ghcr.io/org/img:/bin/tool", "v1"},
{"oci://alpine:/bin/busybox", "oci://alpine:/bin/busybox", ""},
}

for _, tt := range tests {
Expand Down
21 changes: 12 additions & 9 deletions pkg/cli/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,14 +374,11 @@ func (o *InstallOptions) updateLock(binaries []*binary.Binary) error {
return lock.WriteLock(lockDir, lk, o.bVersion)
}

// parseBinaryArg parses binary argument in format "name" or "name@version"
// parseBinaryArg parses binary argument in format "name" or "name@version".
// Delegates to provider.ParseRef which already handles the docker:// or oci://
// quirk of preserving a ":/<path>" suffix on name.
func parseBinaryArg(arg string) (name, version string) {
parts := strings.SplitN(arg, "@", 2)
name = parts[0]
if len(parts) > 1 {
version = parts[1]
}
return
return provider.ParseRef(arg)
}

// parseSCPArg tries to parse an SCP-style env install:
Expand All @@ -390,12 +387,18 @@ func parseBinaryArg(arg string) (name, version string) {
//
// Returns the parsed envInstall, how many additional args were consumed, and whether it matched.
func parseSCPArg(arg string, remaining []string) (envInstall, int, bool) {
// docker:// and oci:// are always binary installs (never env SCP), even
// though they may contain ":/path" for in-container binary location.
if strings.HasPrefix(arg, "docker://") || strings.HasPrefix(arg, "oci://") {
return envInstall{}, 0, false
}

// Look for colon that signals SCP syntax — must come after a ref (contains /)
// and before a glob. Skip protocol prefixes (go://, docker://).
// and before a glob. Skip the "://" of protocol prefixes (go://, git://).
colonIdx := -1
for i := range arg {
if arg[i] == ':' {
// Skip protocol prefixes (e.g. go://, docker://)
// Skip protocol prefixes (e.g. go://, git://)
if i+2 < len(arg) && arg[i+1] == '/' && arg[i+2] == '/' {
continue
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/cli/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ func TestParseSCPArg(t *testing.T) {
arg: "kubectl",
wantOk: false,
},
{
// docker:// / oci:// — never SCP (binary-path separator ":/")
arg: "docker://docker@cli:/usr/local/bin/docker",
wantOk: false,
},
{
arg: "oci://ghcr.io/org/img@v1:/bin/tool",
wantOk: false,
},
}

for _, tt := range tests {
Expand Down
Loading
Loading