Help us shape the future of Go! Take the 2026 Go Developer Survey

cargoproxy

package
v0.2.22 Latest Latest
Warning

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

Go to latest
Published: Oct 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).

The GIT_HTTP_LOW_SPEED pair makes git give up on a transfer that moves under 1 KB/s for 30 seconds. It is what bounds the git-dependency fetches Cargo runs through the git binary (see netTOML); without it git, like libgit2, waits on a dead connection indefinitely. Real fetches never run that slowly, so actions/checkout and other git users lose nothing.

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