Documentation
¶
Overview ¶
Package proxies defines the CacheProxy interface for language-specific caching reverse proxies. Each proxy sits on the bridge gateway so job containers can reach it, caches downloads on disk, and injects the appropriate env var into containers.
Implementations live in sub-packages:
pkg/proxies/go/ — Go module proxy (GOPROXY) pkg/proxies/cargo/ — Cargo sparse registry + crate/rustup proxy pkg/proxies/npm/ — (future) npm registry proxy pkg/proxies/pip/ — (future) pip index proxy
Index ¶
- Constants
- func EnvHostPorts(env []string) []string
- func HostAddrInSubnet(subnet *net.IPNet) (string, bool)
- func HostAddrVisibleTo(peerIP string) (string, bool)
- func Listen(addr string, log *slog.Logger) (net.Listener, error)
- func Reachable(hostPort string, timeout time.Duration) bool
- func RewriteEnvHost(env []string, host string) []string
- type CacheProxy
- type Mount
- type MountProvider
- type Server
Constants ¶
const DefaultShutdownGrace = 5 * time.Second
DefaultShutdownGrace bounds how long a cache proxy waits for in-flight requests to finish before it stops asking nicely and closes them.
Variables ¶
This section is empty.
Functions ¶
func EnvHostPorts ¶ added in v0.2.17
EnvHostPorts extracts the host:port authority of every URL-valued entry, so a caller can probe each advertised address before injecting it.
func HostAddrInSubnet ¶ added in v0.2.17
HostAddrInSubnet returns this host's own IPv4 address inside subnet, which is the address a guest on that subnet can reach the host on.
Separate from HostAddrVisibleTo because the caller often knows the hypervisor's NAT range but not any particular guest's address — at daemon start there may be no guest yet.
Lives here, rather than in the platform file that uses it, so it is covered by tests that run on every OS. The darwin build cannot be compiled on a non-macOS machine (its vz dependency needs macOS cgo), so keeping logic out of that file is the difference between tested and merely plausible.
func HostAddrVisibleTo ¶ added in v0.2.17
HostAddrVisibleTo returns the local address that peerIP can reach this host on: the address of whichever local interface shares a subnet with peerIP.
Derived rather than hardcoded. Apple's Virtualization.framework usually NATs on 192.168.64.0/24 and Hyper-V picks its own, so a constant per platform would be a guess that silently rots when the hypervisor changes its range. Asking "which of my addresses is on the VM's network" is true by construction.
ok is false when nothing matches, and callers MUST treat that as "do not advertise" rather than falling back to a guess — a wrong address is what caused the outage this exists to fix.
func Listen ¶ added in v0.2.2
Listen binds a cache-proxy listener on addr, falling back to the wildcard address on the same port when addr itself cannot be bound.
WHY THIS EXISTS (this is the bug that made the Go module proxy cache stay empty forever — see docs and the review notes on this change):
Cache proxies are asked to listen on the CNI bridge gateway IP (e.g. 10.88.0.1:8082) so job containers can reach them. But ephemerd deletes the ephemerd0 bridge on shutdown and again on startup, and the CNI bridge plugin only (re)creates the bridge — and only then assigns the gateway IP to it — when the FIRST job container is networked. Proxies start long before that, during daemon boot, so net.Listen("tcp", "10.88.0.1:8082") fails with EADDRNOTAVAIL every single time. The caller logs a warning and carries on without the proxy, which means its env vars are never injected into any container: the cache directory is created and then never written to again.
Binding the wildcard address fixes this without racing the bridge: a wildcard (INADDR_ANY) socket accepts connections addressed to interface addresses that appear AFTER the bind, so the gateway IP becomes reachable the moment CNI brings the bridge up.
The requested address is always tried first, so a host where the gateway already exists keeps the narrower binding. When both fail (e.g. the port is already in use, which fails on the wildcard too) the ORIGINAL error is returned — the fallback never masks a real problem.
SECURITY NOTE: the wildcard binding also exposes the proxy on the host's other interfaces. These proxies only ever serve public package-registry content and hold no credentials, so the exposure is limited to an open caching mirror. Operators who need it closed should firewall the port at the host edge; see docs/getting-started/configuration.md.
func Reachable ¶ added in v0.2.17
Reachable reports whether a TCP connection to hostPort succeeds.
This is the gate the old loopback health probe could not provide. A proxy that answers on 127.0.0.1 tells you the process is alive; it tells you nothing about whether the address being advertised to jobs exists. Probing the advertised address is the only check that would have caught either the macOS breakage or the Windows one.
A failure means "do not inject this var". That is the safe direction: a missing GOPROXY costs bandwidth, while a black-holed one costs a build — Windows jobs HANG on an unreachable proxy rather than failing over, which is why proxy env is suppressed there wholesale today.
func RewriteEnvHost ¶ added in v0.2.17
RewriteEnvHost rewrites the host in each "KEY=http://host:port/path" entry to host, preserving scheme, port and path. Entries that are not URLs are passed through untouched, because proxies also emit plain-value vars.
Rewriting rather than re-rendering is deliberate: each proxy already knows how to build its own env (GOPROXY, CARGO_*, GHREL_PROXY, the lot), and only the authority is wrong. Re-deriving that per platform would duplicate seven proxies' env logic and let the copies drift.
Types ¶
type CacheProxy ¶
type CacheProxy interface {
// Start begins serving the proxy. Returns after the listener is bound.
Start() error
// Stop shuts down the proxy and optionally cleans up the cache.
Stop() error
// Addr returns the address the proxy is listening on (host:port).
Addr() string
// EnvVars returns environment variables to inject into job containers
// so they use this proxy (e.g., GOPROXY=http://10.88.0.1:8082,direct).
EnvVars() []string
// Name returns a human-readable name for logging (e.g., "go", "npm").
Name() string
}
CacheProxy is a language-specific caching proxy that sits between job containers and an upstream package registry. Implementations handle protocol-specific caching (e.g., Go module proxy, npm registry).
type Mount ¶ added in v0.2.2
type Mount struct {
// Source is an absolute host path (a directory).
Source string
// Destination is the absolute path inside the container.
Destination string
// ReadOnly mounts the source read-only. Config material should always
// be read-only: a job must never be able to rewrite what the next job
// on this host will read.
ReadOnly bool
}
Mount is a host→container bind mount a cache proxy needs in every job container. Env vars are enough for toolchains that take their proxy from the environment (Go, rustup); Cargo is not one of them — its source replacement is only read from a config file, never from CARGO_* env vars (verified empirically; see pkg/proxies/cargo). Such proxies generate the file on the host and declare it here.
type MountProvider ¶ added in v0.2.2
type MountProvider interface {
// Mounts returns the bind mounts to add to every job container spec.
Mounts() []Mount
}
MountProvider is an OPTIONAL interface a CacheProxy may implement when env vars alone cannot point a toolchain at the proxy. Callers type-assert for it; a proxy that does not implement it needs no mounts.
type Server ¶ added in v0.2.2
type Server struct {
// contains filtered or unexported fields
}
Server is the HTTP server every cache proxy runs.
It exists because the obvious spelling —
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) err := srv.Shutdown(ctx)
does not reliably stop. Two separate problems:
UNREAD CONNECTIONS. Go's http.Transport dials speculatively: under a burst of parallel requests it opens more connections than it ends up using, and the spares land in the client's idle pool having never sent a single byte. On the server side those are ConnState "new" and stay that way. http.Server.Shutdown will not close a "new" connection until it has sat in that state for more than five seconds (net/http's issue-22682 heuristic), so Shutdown busy-polls for the full five seconds and a caller with a five-second deadline gets context.DeadlineExceeded rather than a clean stop. That is exactly the intermittent CI failure this type was written for: "Stop: shutting down cargo proxy: context deadline exceeded" after 5.01s, with a single connection stuck in "new". A connection that has not sent a request has nothing to drain, so Shutdown closes those up front and finishes in microseconds instead.
UNBOUNDED HANDLERS. A handler blocked on a slow upstream keeps its connection active, and http.Server.Shutdown neither cancels request contexts nor gives up on its own. Every request context here derives from Context(), so when the grace window closes, cancelling it unblocks the in-flight upstream fetches, and Close() then drops what is left. Shutdown always returns; the daemon never hangs on a wedged proxy.
Shutdown also joins the accept goroutine, so no goroutine outlives it.
func NewServer ¶ added in v0.2.2
NewServer wires a cache proxy's handler onto an already-bound listener. Call Serve to start accepting.
func (*Server) Addr ¶ added in v0.2.2
Addr is the address actually bound, which after a wildcard fallback is not the address that was requested. See Listen.
func (*Server) Context ¶ added in v0.2.2
Context is the proxy's lifetime context. Request contexts descend from it, and Shutdown cancels it, so work started on behalf of a request — an upstream fetch in particular — unblocks when the proxy stops.
func (*Server) ProbeAddr ¶ added in v0.2.2
ProbeAddr is an address on which this server can be reached from the host it runs on. It is NOT the address advertised to containers: after a wildcard fallback the bound address is "[::]:8083", which nothing can dial, so a wildcard binding is probed on loopback instead.
Health checks must use this rather than the advertised gateway address. On Linux the gateway IP does not exist until CNI creates the bridge with the first job container, so probing the advertised address would report the proxy as dead on every freshly booted daemon — precisely when it is fine.
func (*Server) Serve ¶ added in v0.2.2
func (s *Server) Serve()
Serve begins accepting connections in the background and returns immediately. The goroutine it starts is joined by Shutdown.
func (*Server) Shutdown ¶ added in v0.2.2
Shutdown stops the server within a bounded time and joins its goroutines. It is idempotent.
A non-nil return means the forced close itself failed. Merely having to force the shutdown is logged, not returned: the server is stopped either way, and callers (including tests) should not treat a slow drain as a failure to stop.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cargoproxy implements proxies.CacheProxy for the Rust ecosystem.
|
Package cargoproxy implements proxies.CacheProxy for the Rust ecosystem. |
|
Package composerproxy implements proxies.CacheProxy for PHP Composer.
|
Package composerproxy implements proxies.CacheProxy for PHP Composer. |
|
Package ghrelproxy implements proxies.CacheProxy for GitHub release artifacts: the release metadata a build tool reads, and the asset bytes that metadata points at.
|
Package ghrelproxy implements proxies.CacheProxy for GitHub release artifacts: the release metadata a build tool reads, and the asset bytes that metadata points at. |
|
Package goproxy implements proxies.CacheProxy for Go modules.
|
Package goproxy implements proxies.CacheProxy for Go modules. |
|
Package npmproxy implements proxies.CacheProxy for the npm ecosystem.
|
Package npmproxy implements proxies.CacheProxy for the npm ecosystem. |
|
Package pipproxy implements proxies.CacheProxy for the Python ecosystem.
|
Package pipproxy implements proxies.CacheProxy for the Python ecosystem. |
|
Package pkgcache is the shared engine behind ephemerd's language package caches (npm, pip, pub).
|
Package pkgcache is the shared engine behind ephemerd's language package caches (npm, pip, pub). |
|
Package pubproxy implements proxies.CacheProxy for the Dart/Flutter ecosystem (pub.dev).
|
Package pubproxy implements proxies.CacheProxy for the Dart/Flutter ecosystem (pub.dev). |