Add goproxy virtual repositories (#159)
ci/woodpecker/tag/docker Pipeline was successful

Go clients need one GOPROXY for internal and public modules; goproxy has no virtual type, so a local goproxy repo and proxy.golang.org cannot be served together.

- union member `@v/list`s; compute `@latest` over the union (highest release, else highest prerelease)
- 302 `@latest` and `.info`/`.mod`/`.zip` to the first member with that version, root-relative and `no-store`; probe `.info` for unlisted pseudo-versions
- validate case-encoded module paths and versions
- fail closed with 502 on a member error, skip members answering 404/410
- cache merged lists, keyed on list and version owners, and module misses for the merge TTL
- add `docs/goproxy/virtual.md` and a stock `golang:1.25` e2e-docker case

requires unkin/artifactapi#143

Reviewed-on: #159
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
This commit was merged in pull request #159.
This commit is contained in:
2026-10-19 21:39:49 +11:00
committed by BenVincent
parent 29e4777eec
commit 4649878a6d
10 changed files with 572 additions and 4 deletions
+30
View File
@@ -0,0 +1,30 @@
# `goproxy` virtual
A goproxy virtual serves one GOPROXY over local and remote goproxy members:
`GOPROXY=https://artifactapi.example.com/api/v1/virtual/<name>`.
- `<module>/@v/list` is the union of member versions, sorted by semver.
- `<module>/@latest` is the highest release in that union, else the highest
prerelease; with no listed versions it falls back to the first member that
answers `@latest`.
- `@latest` and `@v/<version>.{info,mod,zip}` 302 to the first member, in
member order, that lists the version (or, for unlisted pseudo-versions,
serves its `.info`). Redirects are root-relative and `no-store`.
- Module paths are case-encoded (uppercase as `!lower`).
- A member error fails the request with 502; a member answering 404 or 410 is
skipped. Merged lists are cached for 60s.
```hcl
resource "artifactapi_virtual" "go" {
name = "go"
package_type = "goproxy"
members = [artifactapi_local_goproxy.internal.name, artifactapi_remote_goproxy.goproxy.name]
}
```
```sh
export GOPROXY=https://artifactapi.example.com/api/v1/virtual/go GONOSUMDB=example.unkin.net
go get example.unkin.net/hello@latest golang.org/x/text@latest
```
Publish to the local member directly; the virtual is read-only.