toolchain

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 24 Imported by: 0

Documentation

Overview

Package toolchain provides a governed execution substrate for external tools.

StageFreight owns its toolchains. Every tool used is resolved, downloaded, verified, cached, and reported. No silent host fallback. No DinD. No containers-for-tools. No environment luck.

Contract properties:

  • Immutable installs — once cached, a version directory is never mutated
  • Checksum verification required — every download verified against official checksums
  • Explicit provenance — .metadata.json records source URL, checksum, install time
  • Deterministic resolution — same version = same binary, always
  • No silent host fallback — system binaries in PATH are not used
  • Hard failure on verification miss — checksum mismatch = error, not warning

Index

Constants

View Source
const (
	TrustPinned   = "pinned"   // explicit config fingerprint (strongest; user-asserted)
	TrustChecksum = "checksum" // upstream-published checksum (integrity vs the origin's claim)
	TrustTOFU     = "tofu"     // trust-on-first-use: no upstream claim; established locally, re-verified every run
)

Trust levels record HOW an artifact was trusted on first fetch — the strongest mechanism available wins. Surfaced so a trust-evaluation system communicates not just THAT a tool resolved but HOW CONFIDENTLY it was trusted.

View Source
const LockfileVersion = 1

LockfileVersion is the schema version of .stagefreight/toolchains.lock. Bump only on a breaking shape change; a reader may then migrate older versions.

Variables

This section is empty.

Functions

func AcquireInstallLock

func AcquireInstallLock(dir string, timeout time.Duration) (*os.File, error)

AcquireInstallLock acquires an exclusive file lock for a tool install directory. Blocks up to timeout. Returns the lock file handle (caller must call ReleaseInstallLock). If lock cannot be acquired within timeout, returns an error — no deadlock.

func CacheBinPathIn

func CacheBinPathIn(root, tool, version, binary string) string

CacheBinPathIn returns the binary path for a tool within a specific cache root.

func CacheDirIn

func CacheDirIn(root, tool, version string) string

CacheDirIn returns the versioned install directory within a specific cache root.

func CacheRoot added in v0.7.0

func CacheRoot() string

CacheRoot is the persistent build-cache root (<resolved>/cache), a sibling of the toolchains root — or "" when no persistent backing is available (the build then falls back to Go's ephemeral $HOME caches). For display; resolving may create the root dir.

func CargoCacheDir added in v0.7.0

func CargoCacheDir() string

CargoCacheDir returns the persistent CARGO_HOME (rust/downloads) — crate sources + registry index reused across runs, the Rust analog of GOMODCACHE. "" off-mount.

func CargoTargetDir added in v0.7.0

func CargoTargetDir(key string) string

CargoTargetDir returns a persistent, per-project CARGO_TARGET_DIR (rust/build/<key>), the Rust analog of GOCACHE: dependency crates (and their C build outputs, e.g. aws-lc) compile once and are reused — only changed local code rebuilds. Keyed PER PROJECT so concurrent builds don't contend on cargo's target lock. "" off-mount.

func CleanEnv

func CleanEnv() []string

CleanEnv returns a minimal execution environment for toolchain-resolved binaries. No implicit PATH. No host leakage. All tools are invoked by absolute path.

Tools that need specific env vars (e.g. GRYPE_DB_CACHE_DIR, COSIGN_YES) append them to the returned slice at the call site.

func EnsureRustLlvmTools added in v0.7.0

func EnsureRustLlvmTools(cargoPath, version string) error

EnsureRustLlvmTools grafts the llvm-tools-preview component (llvm-cov, llvm-profdata) into an installed Rust prefix so cargo-llvm-cov can produce coverage. Idempotent: a no-op once llvm-cov is present. The component tarball URL + SHA256 come from the verified Rust channel manifest, matching rustc's exact LLVM version. cargoPath is the resolved cargo binary (<prefix>/bin/cargo).

func FetchArtifactSHA256 added in v0.7.0

func FetchArtifactSHA256(tool, version string) (string, error)

FetchArtifactSHA256 downloads a tool's release artifact and returns its SHA256 — the onboarding/deps derivation step that makes a pinned digest ecosystem-agnostic (works whether upstream publishes SHA256, BLAKE3, or nothing at all).

func GoCacheDirs added in v0.6.1

func GoCacheDirs() (gomod, gocache string)

GoCacheDirs returns the persistent GOMODCACHE (go/downloads) and GOCACHE (go/build), so a build reuses downloaded modules and compiled packages across CI jobs instead of paying the full cold-cache cost every run. ("", "") on local dev (no mount) leaves Go's default $HOME caches untouched.

func GoCacheStatus added in v0.6.1

func GoCacheStatus() (gomod, gocache string, warm bool)

GoCacheStatus returns the resolved GOMODCACHE/GOCACHE plus whether the build cache is already warm (a prior run populated it). gomod=="" means no persistent mount is available, so the build will fall back to Go's ephemeral $HOME caches — surfaced as an explicit "cache off" row. warm=true means the persistent GOCACHE already holds compiled packages, so this build reuses them (the fast path); cold means the build is populating the cache for next time.

func InstallRoot

func InstallRoot(rootDir string) string

InstallRoot returns the directory where new toolchain installs are written — the resolved persistent root when writable, else the workspace-local fallback. The persistent mount is supplied empty, so its toolchains/ subdir is created on first use. Without a persistent root the fallback is used, which the git checkout wipes every job, forcing a fresh download each run — hence the resolver prefers the mount/XDG.

func LockPath added in v0.7.0

func LockPath(rootDir string) string

LockPath returns the absolute path of the lock under rootDir.

func ManagedToolNames

func ManagedToolNames() []string

ManagedToolNames returns a sorted list of all managed tool names.

func MetadataPathIn

func MetadataPathIn(root, tool, version string) string

MetadataPathIn returns the metadata file path within a specific cache root.

func PersistentCacheRoot

func PersistentCacheRoot() string

PersistentCacheRoot returns the resolved out-of-tree toolchains cache root — the dind mount (/stagefreight/toolchains) or the native XDG cache — for display and du. Falls back to the nominal default when no persistent backing is resolved.

func ReadRoots

func ReadRoots(rootDir string) []string

ReadRoots returns cache roots to search for existing toolchain installs, in priority order: the resolved persistent root first (mount or XDG), then the workspace-local fallback.

func ReleaseInstallLock

func ReleaseInstallLock(f *os.File)

ReleaseInstallLock releases the file lock and closes the handle.

func RenderPanel

func RenderPanel(w io.Writer, results []Result, cacheRoot string, elapsed fmt.Stringer)

RenderPanel writes the toolchain resolution panel to w. Called once during audition after all tools are resolved.

func Report

func Report(w io.Writer, r Result)

Report writes a human-readable toolchain resolution summary to w. This is the ONLY human-output surface in the toolchain package. All other functions return structured data — no stderr, no prints.

func ResolveGoVersion

func ResolveGoVersion(dir, repoRoot string) string

ResolveGoVersion extracts the Go version directive from go.work or go.mod: the `toolchain` directive if present (full, e.g. "1.26.1"), otherwise the `go` directive (normally a bare major.minor, e.g. "1.24"). It returns the directive verbatim — a semantic version, NOT necessarily a downloadable one. Canonicalizing a bare major.minor to its latest stable patch happens at distribution time in resolveGo, which is where a concrete downloadable toolchain is actually required. Falls back to "1.24" when no directive is found.

func ResolveRustVersion added in v0.7.0

func ResolveRustVersion(dir, repoRoot string) string

ResolveRustVersion reads the pinned toolchain from rust-toolchain.toml / rust-toolchain (the Rust equivalent of go.mod's toolchain directive). It honors an exact numeric pin ("1.86.0") AND a named channel ("stable"/"beta"/"nightly") — a named channel is resolved to its CONCRETE current version at download time (so a run is reproducible and cached, while a project that tracks stable still builds). The default is "stable", matching rustup, rather than a stale numeric default that would fail to compile a newer edition.

func ResolveVersion

func ResolveVersion(rootDir, tool, requestedVersion string, desired map[string]config.ToolConstraint) (version string, isPinned bool)

ResolveVersion determines the concrete version to provision for a tool.

Precedence (strict):

  1. requestedVersion — explicit caller override (non-empty = use it)
  2. desired[tool].Constraint — config intent: EXACT is itself the version; a WILDCARD (1.26.x) resolves to its locked version from .stagefreight/toolchains.lock (read from rootDir). A wildcard with no lock yet is not provisionable from a range, so it falls through to the default until an update pass writes the lock.
  3. ToolDef.DefaultVer — hardcoded fallback

Returns the version string and whether it came from config intent (isPinned). The isPinned flag is critical: pinned versions that fail to resolve must hard-fail with no fallback to default. rootDir locates the lock; pass "" when unknown (wildcards then fall back to default, exact pins are unaffected).

func SeedDesired

func SeedDesired() map[string]config.ToolConstraint

SeedDesired returns a complete map of all managed tools with their current default versions. Used to populate toolchains.desired for new repos so that all managed tools are explicitly materialized in config from the start. Go is excluded — its version comes from go.mod, not the toolchain registry.

func StampMetadata

func StampMetadata(m *Metadata)

StampMetadata sets the install timestamp and author.

func SubstrateCacheDir added in v0.7.0

func SubstrateCacheDir() string

SubstrateCacheDir returns the persistent apk package cache (substrate/apk) — so native build-substrate realization is download-once, offline-after-first. "" off-mount.

func TrustLabel added in v0.7.0

func TrustLabel(trust string) string

TrustLabel renders a trust source as a plain-language phrase for output — a trust-evaluation system communicates HOW CONFIDENTLY a tool was trusted, not just that it resolved.

func WriteLock added in v0.7.0

func WriteLock(rootDir string, l *Lock) error

WriteLock serializes the lock deterministically (sorted entries, stable indent) to rootDir, creating the .stagefreight/ namespace if needed. It is a durable committed artifact — the workspace ignore allowlist carves it out.

Types

type GoInstallSource added in v0.7.0

type GoInstallSource struct {
	Module    string // e.g. "golang.org/x/vuln/cmd/govulncheck"
	GoVersion string // Go toolchain used to BUILD the tool; "" → the repository's go directive
	// contains filtered or unexported fields
}

GoInstallSource provisions a Go module tool via `go install module@version`. This is the provisioning model for the whole class of go-installable tools (govulncheck, stringer, mockgen, controller-gen, …), none of which ship release binaries. The Go toolchain that builds the tool is itself resolved through the toolchain — no host fallback — defaulting to the repository's own Go version so nothing hardcodes one. Module integrity is enforced by the Go checksum database during install; the resulting binary is trusted TOFU (its digest is established on first use and re-verified every run, exactly like any tool without an upstream binary checksum).

func (GoInstallSource) Materialize added in v0.7.0

func (s GoInstallSource) Materialize(req SourceRequest) (SourceResult, error)

type Lock added in v0.7.0

type Lock struct {
	LockfileVersion int         `json:"lockfileVersion"`
	Toolchains      []LockEntry `json:"toolchains"`
}

Lock is .stagefreight/toolchains.lock: the durable, committed record of what each toolchain constraint resolved to (the Cargo.lock to .stagefreight.yml's Cargo.toml). The config declares INTENT (constraints); this records the machine-maintained RESOLUTION. Entries are kept sorted by Name so serialization is deterministic. (Distinct from the install flock in lock.go — that guards a download directory; this is persisted project state.)

func ReadLock added in v0.7.0

func ReadLock(rootDir string) (*Lock, error)

ReadLock loads the lock from rootDir. A missing file is not an error — it returns an empty lock (nothing resolved yet), so first-lock is just an empty lock being filled.

func (*Lock) Get added in v0.7.0

func (l *Lock) Get(name string) (LockEntry, bool)

Get returns the entry for a tool and whether it exists.

func (*Lock) Resolved added in v0.7.0

func (l *Lock) Resolved(name string) string

Resolved returns the locked version of a tool, or "" when the tool is not locked.

func (*Lock) Set added in v0.7.0

func (l *Lock) Set(name, resolved, sha256 string) bool

Set upserts a tool's resolution and keeps the entries sorted. Returns true if the lock changed (a new entry, or a different resolved/sha256), so callers can avoid a no-op write. An empty resolved is rejected — the lock never records "nothing".

type LockEntry added in v0.7.0

type LockEntry struct {
	Name     string `json:"name"`
	Resolved string `json:"resolved"`
	SHA256   string `json:"sha256,omitempty"`
}

LockEntry is the machine-maintained resolution of one tool: the concrete version a constraint currently resolves to, plus its artifact digest. For an EXACT constraint this simply mirrors the version; for a WILDCARD (1.26.x) it is the selected in-line member — the "lock" that keeps a wildcard reproducible between deliberate moves.

type Metadata

type Metadata struct {
	Tool      string `json:"tool"`
	Version   string `json:"version"`
	Platform  string `json:"platform"`
	SourceURL string `json:"source_url"`

	// SHA256 is the checksum of the downloaded archive (provenance).
	// Verified against official release checksums at download time.
	SHA256 string `json:"sha256"`

	// BinSHA256 is the checksum of the extracted binary (cache validation).
	// Used on cache hit to verify the binary hasn't been tampered with.
	BinSHA256 string `json:"bin_sha256"`

	// Trust records HOW the artifact was trusted on first fetch: TrustPinned
	// (explicit config fingerprint), TrustChecksum (upstream-published checksum),
	// or TrustTOFU (established on first use — no upstream claim to verify against).
	Trust string `json:"trust,omitempty"`

	InstalledAt string `json:"installed_at"`
	InstalledBy string `json:"installed_by"`
}

Metadata is the provenance record for a cached toolchain install. Immutable after write. If missing or checksum mismatch, the install is treated as corrupt and skipped (or re-downloaded to a writable root).

type Result

type Result struct {
	Tool      string // "go", "trivy", etc.
	Version   string // "1.26.1", "0.69.3", etc.
	Path      string // absolute path to binary
	CacheHit  bool   // true if served from cache, false if downloaded
	SourceURL string // where it was (or would be) fetched from
	SHA256    string // verified archive/binary checksum (provenance)
	BinSHA256 string // extracted binary checksum (cache validation)
	Trust     string // how it was trusted: TrustPinned | TrustChecksum | TrustTOFU
}

Result is the outcome of a toolchain resolution. Every field is populated. Callers use Path for execution and can report provenance from the rest.

func Resolve

func Resolve(rootDir, tool, version string) (Result, error)

Resolve ensures a tool at the requested version is available and verified. Returns the binary path and provenance. Downloads if not cached. Hard-fails on checksum mismatch, download error, or metadata write failure. Empty version uses ToolDef.DefaultVer. No fallback. No stderr output — callers own presentation.

func ResolvePinned added in v0.7.0

func ResolvePinned(rootDir, tool, version, sha256 string) (Result, error)

ResolvePinned is Resolve for tools whose integrity is a project-pinned SHA256 (from toolchains.desired.<tool>.sha256), not an upstream checksum manifest — e.g. cargo-llvm-cov, whose upstream ships BLAKE3. The downloaded artifact is verified against sha256. An empty sha256 falls back to the tool's ChecksumURL, so this is a strict superset of Resolve.

type SourceRequest added in v0.7.0

type SourceRequest struct {
	Def        ToolDef
	Version    string
	GOOS       string
	GOARCH     string
	RootDir    string // workspace root — for sources that resolve a sibling toolchain (go install needs go)
	InstallDir string // the versioned dir; bin/ already created under it
	BinPath    string // where the binary must end up (== <InstallDir>/bin/<binary>)
	PinnedSHA  string // explicit config fingerprint, or ""
}

SourceRequest is everything a source needs, filled by the resolver from the ToolDef, the resolved version, the platform, and the chosen versioned install directory.

type SourceResult added in v0.7.0

type SourceResult struct {
	SourceURL string // where/what it materialized from (a URL, or a `go install module@ver` ref)
	SHA256    string // artifact digest when there is an archive; "" when there is none (e.g. go install)
	Trust     string // TrustPinned | TrustChecksum | TrustTOFU
}

SourceResult is the provenance a source reports. The resolver folds it into Metadata alongside the binary checksum it computes itself.

type ToolDef

type ToolDef struct {
	Name         string
	BinaryName   string                                    // name of the binary file
	DefaultVer   string                                    // default version if none specified
	DownloadURL  func(version, goos, goarch string) string // archive or binary URL
	ChecksumURL  func(version, goos, goarch string) string // checksum file URL
	Format       string                                    // "tar.gz" or "binary"
	ArchiveStrip string                                    // prefix to strip from tar entries (e.g. "go/")
	GitHubOwner  string                                    // GitHub owner for release checking (e.g. "aquasecurity")
	GitHubRepo   string                                    // GitHub repo for release checking (e.g. "trivy")

	// ReleaseSource names WHERE version discovery reads the release list — declared
	// data, so the resolver dispatches on strategy, never on the tool's name: "github"
	// (releases API, the default when GitHubOwner/Repo are set) or "k8s" (dl.k8s.io
	// stable channels). This is distinct from Source, which materializes the binary.
	ReleaseSource string

	// Source materializes the binary. When nil, the tool is a released-binary download
	// (DownloadURL + Format) — the historical default. A non-nil Source (e.g. GoInstallSource)
	// provisions by another means; DownloadURL/ChecksumURL/Format are then unused.
	Source ToolSource
}

ToolDef describes how to download and verify a tool.

func AllTools

func AllTools() []ToolDef

AllTools returns a copy of all registered tool definitions. Callers can inspect metadata without mutating the registry.

func LookupTool

func LookupTool(name string) (ToolDef, bool)

LookupTool returns the ToolDef for a named tool.

func (ToolDef) ReleaseSourceKind added in v0.7.0

func (d ToolDef) ReleaseSourceKind() string

ReleaseSourceKind resolves the version-discovery strategy: an explicit ReleaseSource, else "github" when a GitHub repo is declared, else "" (no upstream version discovery). Lets the resolver dispatch on capability, not identity.

type ToolSource added in v0.7.0

type ToolSource interface {
	// Materialize places the tool binary at req.BinPath and returns its provenance. bin/ under
	// req.InstallDir already exists. Errors are returned bare; the resolver adds the
	// "toolchain <name> <version>:" prefix.
	Materialize(req SourceRequest) (SourceResult, error)
}

ToolSource materializes a tool's binary into a versioned install directory. It is the seam that lets StageFreight provision tools that arrive by fundamentally different means — a released binary download, `go install`, and later `cargo install` / npm / pip — without any of them being a special case in the resolver.

The division of labour is strict: the resolver owns caching, install locking, the binary checksum, and metadata; a source only knows how to OBTAIN the binary and report its provenance. Because the cache lookup lives above the source, every source inherits the same version-keyed persistent cache — a source runs only on a cache miss.

Jump to

Keyboard shortcuts

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