proxies

package
v0.2.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 8 Imported by: 0

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

View Source
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 Listen added in v0.2.2

func Listen(addr string, log *slog.Logger) (net.Listener, error)

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.

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:

  1. 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.

  2. 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

func NewServer(name string, ln net.Listener, h http.Handler, log *slog.Logger) *Server

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

func (s *Server) Addr() net.Addr

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

func (s *Server) Context() context.Context

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

func (s *Server) ProbeAddr() string

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

func (s *Server) Shutdown(grace time.Duration) error

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 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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL