cargoproxy

package
v0.2.6 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: MIT Imports: 16 Imported by: 0

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:

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

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

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

View Source
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 New

func New(cfg Config) *Proxy

New creates a Cargo caching proxy. Call Start() to begin serving.

func (*Proxy) Addr

func (p *Proxy) Addr() string

Addr returns the address the proxy is listening on.

func (*Proxy) EnvVars

func (p *Proxy) EnvVars() []string

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

func (p *Proxy) Mounts() []proxies.Mount

Mounts returns the read-only bind mount that carries the generated Cargo config into job containers.

func (*Proxy) Name

func (p *Proxy) Name() string

Name returns the proxy name for logging.

func (*Proxy) Start

func (p *Proxy) Start() error

Start begins serving the proxy. Returns after the listener is bound and the container-side Cargo config has been written.

func (*Proxy) Stop

func (p *Proxy) Stop() error

Stop shuts down the proxy and optionally wipes the cache.

Ordering matters: the watchdog is stopped and joined FIRST, so a proxy on its way out cannot probe its own half-closed listener, decide it is wedged, and rewrite the mounted config as a parting gift.

Jump to

Keyboard shortcuts

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