Documentation
¶
Overview ¶
Package cargoproxy implements proxies.CacheProxy for the Rust ecosystem.
It is a pull-through cache for three upstreams that a Rust CI job hits on every run:
/index/… the crates.io SPARSE registry index (mutable)
/crates/{c}/{v}/… .crate tarballs (immutable, content-addressed)
/rustup/… rustup toolchain artifacts (dated ones immutable)
ephemerd runs one shared instance on the bridge gateway IP so every job container can reach it. Jobs only ever issue HTTP GETs; they have no write access to the cache.
Why both a mount and env vars: rustup takes its mirror from the environment (RUSTUP_DIST_SERVER), but Cargo does NOT — source replacement is only honoured from a config file, so the proxy generates a .cargo/config.toml on the host and declares it as a read-only mount at the container's filesystem root, where Cargo's ancestor-directory config search always finds it. See containerConfigTOML/containerConfigDest.
Fail-open ¶
This is the design constraint that shapes everything below. The Go module proxy is safe to enable because GOPROXY="<url>|direct" falls through to the origin on ANY proxy error. Cargo has no equivalent: once [source.crates-io] replace-with points at this proxy there is no second source, no fallback, and no retry-elsewhere — a dead proxy is a red build for every Rust job on the node. A cache must never be a hard dependency of the job path, so the proxy is built in three layers:
NOT STARTED → NOT INJECTED. A proxy that fails to Start is never added to the cache-proxy list, so neither the mount nor the env var reaches any container and Cargo talks to crates.io as if ephemerd had no cache. (cmd/ephemerd/main.go.)
RUNNING → ALWAYS ANSWERS. No route ever returns 5xx. Upstream down but something cached → the stale copy is served. Upstream down with nothing cached, cache unreadable, disk full, config.json unparseable → a 307 to the real origin, which Cargo follows. So "the proxy accepts connections" is the ONLY thing a build depends on; everything behind it can be broken. Genuine 404s still pass through as 404s, because "no such crate" is not an outage.
RUNNING BUT WEDGED → WITHDRAWS ITSELF. Layer 2 assumes the listener still answers. A listener can wedge while the daemon lives on (that has happened here before, in the self-upgrade path), which layer 2 cannot help with because the job never reaches a handler. So a watchdog probes the proxy's own listener end to end, and on repeated failure rewrites the mounted config.toml to an inert one with no source replacement at all. The mount is a directory, so a container sees the change immediately: the next `cargo` invocation — in a job already running, not just the next job — goes straight to crates.io. It is restored on recovery. See runWatchdog and inertConfigTOML.
RESIDUAL FAILURE MODE, stated honestly: a `cargo build` that has already read the active config and is mid-resolve when the listener wedges will fail, because nothing can retract a config file cargo has already parsed. The exposure is one cargo invocation, bounded by HealthInterval plus cargo's own retries (net.retry = 3 in the generated config). Shrinking it further would mean not using source replacement at all, which would mean not caching crates. If ephemerd's whole process freezes, the watchdog freezes with it — but then nothing is scheduling jobs either.
Index ¶
Constants ¶
const ( // DefaultIndexUpstream is the crates.io sparse index. DefaultIndexUpstream = "https://index.crates.io" // DefaultRustupUpstream is the rustup/toolchain distribution server. DefaultRustupUpstream = "https://static.rust-lang.org" // DefaultIndexTTL is how long a cached sparse-index entry is served // without contacting upstream. Short by design: the index is mutable // and a new dependency version must not take an hour to become // visible. Revalidation after the TTL is a conditional GET, so the // steady-state cost is a 304, not a re-download. DefaultIndexTTL = 10 * time.Minute // DefaultHealthInterval is how often the proxy checks that its own // listener still answers. See the package comment, layer 3. DefaultHealthInterval = 15 * time.Second )
Defaults. Kept here rather than in the config package so the proxy is usable standalone (and testable) without a config file.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// CacheDir is the on-disk cache root (e.g. <data>/cache/cargo).
CacheDir string
// ConfDir is where the container-side Cargo config is generated. It is
// deliberately OUTSIDE CacheDir so `ephemerd cache clear cargo` cannot
// pull the mounted config out from under a running job.
ConfDir string
// IndexUpstream is the sparse registry index base URL.
IndexUpstream string
// RustupUpstream is the rustup distribution server base URL.
RustupUpstream string
// ListenAddr is the address to bind (e.g. "10.88.0.1:8083"). It is also
// the address advertised to containers — see advertiseBase.
ListenAddr string
// IndexTTL is the revalidation interval for mutable index entries.
// Zero means "unset" and takes DefaultIndexTTL; a NEGATIVE value means
// "revalidate on every request" (useful for tests and for operators who
// want the index never served without a conditional GET).
IndexTTL time.Duration
// Cleanup wipes the cache dir on Stop. Defaults to false: a
// pull-through cache that is emptied on every restart saves nothing.
Cleanup bool
// ContainerOS is the OS of the job containers this proxy serves, used
// only to pick the mount destination. Defaults to the host GOOS.
ContainerOS string
// HealthInterval is how often the proxy probes its own listener and,
// on repeated failure, withdraws the Cargo config it mounts into jobs
// (package comment, layer 3). Zero takes DefaultHealthInterval; a
// NEGATIVE value disables the watchdog, which leaves a wedged listener
// able to fail Rust builds — only sensible in tests.
HealthInterval time.Duration
Log *slog.Logger
}
Config for the Cargo caching proxy.
type Proxy ¶
type Proxy struct {
// contains filtered or unexported fields
}
Proxy is a caching proxy for the Cargo registry and rustup distribution.
func (*Proxy) EnvVars ¶
EnvVars returns environment variables to inject into job containers.
Only rustup is configured here. Cargo's registry redirect cannot be done with environment variables (see Mounts).
func (*Proxy) Mounts ¶
Mounts returns the read-only bind mount that carries the generated Cargo config into job containers.
func (*Proxy) Start ¶
Start begins serving the proxy. Returns after the listener is bound and the container-side Cargo config has been written.