Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/nornir-build-thing/src/containerfiles/cargo-test.Containerfile

# nordisk CARGO TEST — the image a repo's TEST SUITE runs in.
#
# cargo-toolchain.Containerfile is the image every cargo variant COMPILES in,
# and it is right to be small: "Every package below names the STEP that needs
# it. Nothing rides 'just in case'" — an appliance build has no business
# carrying an ssh client. This is its sibling for the other road, built the way
# that file says a repo with a heavier need should get one: "A repo whose forge
# runs clippy declares it and gets its own image."
#
# WHY THIS FILE EXISTS — the measurement it closes (2026-09-12, oden):
#   gunnar's `cargo test --workspace` was run in the cargo TOOLCHAIN image and
#   the numbers moved with nothing but how it was invoked:
#
#     as root, plain image        2064 passed / 13 failed
#     as uid 1000, plain image    1972 passed / 110 failed
#     as uid 1000, THIS image     2075 passed /  7 failed
#
#   None of the differences were gunnar's code. Two of them are packages, and
#   they are below. The third is not a package at all and is stated at the
#   bottom, because an image cannot fix it and a reader has to know.
#
# Reproducibility rides the base: the snapshot apt source, the apt validity
# override and `SOURCE_DATE_EPOCH` are all set by cargo-toolchain.Containerfile
# and INHERITED here. They are deliberately not restated — the base is the one
# writer of that instant, and a second copy is a number that can drift.
# `CargoTestImage::build` hands the same instant to podman's `timestamp`.

# The image this is FROM. **Pass the DIGEST you pin**, exactly as a repo's
# `[build.toolchain] image_sha256` does: a tag is a convenience, not a pin, and
# a test image built on a moving base tells you nothing about the run it
# witnessed. `DraupnirCargoRunner::build_test_image` passes the reference it is
# given; the default below is for a human building one by hand.
ARG CARGO_TOOLCHAIN_REF=nordisk-cargo-toolchain:live
FROM ${CARGO_TOOLCHAIN_REF}

# cargo-deny is NOT in Debian trixie (`apt-cache policy cargo-deny` finds no
# candidate, measured 2026-09-12), so it arrives as the project's own prebuilt
# **static musl** binary, pinned by version AND by the sha256 of the tarball.
# These two ARGs are the ONE writer of that pin: the host-side stager reads
# them out of this file (`cargo_test_cargo_deny_pin`) rather than restating
# them, so the fetch and the image can never disagree about which bytes.
#
# The fetch happens HOST-side, verified in Rust, and the tarball is staged into
# the build context — the same road kernel-toolchain.Containerfile's source
# tarball takes, and the one cargo-toolchain.Containerfile's honest-gap section
# already names as the way to bring in a tool Debian does not carry.
ARG CARGO_DENY_VERSION=0.20.2
ARG CARGO_DENY_SHA256=9f12ed4c49936e09b48bf862b595cde2fe64fcbd9d74dfacac6131ca824c8d5f

RUN set -eu; \
    apt-get update && apt-get install -y --no-install-recommends \
    `# step: TEST (ssh interop) — gunnar's integration tests drive STOCK ssh` \
    `# through GIT_SSH_COMMAND and assert that real clients reach its wire.` \
    `# MEASURED 2026-09-12: without it ~90 tests across gunnar-cli fail with` \
    `# "ssh: not found", and they fail the same way for any repo whose suite` \
    `# proves it speaks to real clients rather than to its own client code.` \
    `# ssh-keygen comes in the same package and is what key fixtures use.` \
    openssh-client \
    `# step: TEST (building a sibling TOOL the suite drives) — a matrix test` \
    `# that shells out to another repo's binary needs that binary built HERE,` \
    `# not on the host: a host build inside this image dies in the dynamic` \
    `# loader. MEASURED 2026-09-12, gunnar-recipe cli_matrix against the` \
    `# host-built dwarves: "libm.so.6: version GLIBC_2.43 not found", after` \
    `# which eighteen variants reported dwarves REFUSING them. Building it` \
    `# here needs three things the compile road legitimately does not:` \
    `#   g++   - esaxx-rs, a dwarves dependency, compiles C++ in its build` \
    `#           script and dies on: failed to find tool "c++". The base` \
    `#           carries gcc as rustc linker driver, not a C++ compiler.` \
    `#   clang - dwarves DECLARES linker = clang in its own .cargo/config,` \
    `#           on purpose, so a box without it fails loudly instead of` \
    `#           segfaulting. Honouring another repo declared linker is not` \
    `#           a host choice leaking in; it is the declaration obeyed.` \
    `#   mold  - that same config points clang at --ld-path=/usr/bin/ld.mold.` \
    `#           The compile image leaves mold out deliberately, and that` \
    `#           argument is about the HOST cargo config adding -fuse-ld=mold` \
    `#           to a build that never declared it. Here it IS declared, by` \
    `#           the repo being built, and this image makes the declaration` \
    `#           satisfiable off any box.` \
    g++ clang mold \
    `# step: TEST (LFS interop) — gunnar-lfs drives the REAL git-lfs client` \
    `# over pure ssh. Its own test says so: "git lfs version failed. Install` \
    `# git-lfs; this test is not skipped." Four tests, and the refusal is` \
    `# deliberate — a skipped interop test is a green nobody has seen red.` \
    git-lfs \
    `# step: TEST (building a crate that links the system OpenSSL) — the` \
    `# lanes' suites compile openssl-sys (native-tls/git2 roads through` \
    `# siblings), which dies on "Could not find directory of OpenSSL" without` \
    `# the headers; pkg-config is how openssl-sys finds them. MEASURED on dwarf` \
    `# 2026-10-18 (lane L15): the hand-made :live had neither.` \
    libssl-dev pkg-config \
    `# step: TEST (diagnosis) — the hand-made :live this file replaces carried` \
    `# strace, and lanes trace hung tests with it; kept so the swap loses nothing.` \
    strace \
    `# The clock's fingerprints, deleted IN THE LAYER THAT MAKES THEM, for the` \
    `# reason the base states: a later rm leaves the bytes in the layer below.` \
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /var/log/apt /var/log/dpkg.log /var/log/alternatives.log \
    && rm -f /var/cache/ldconfig/aux-cache \
    && rm -rf /root/.cache

# step: TEST (the CVE gate) — `cargo xtask cve` / `cargo xtask ci` run
# cargo-deny, and `xtask/tests/cve.rs::require_tool` makes a MISSING tool a hard
# failure rather than a skip: "0 CVE that cannot be measured is not a pass."
# Three tests, and without this the gate simply cannot run on the container
# road at all.
#
# Verified in the layer that installs it, against the pin above — a tarball
# whose bytes changed under the same version is refused HERE, at mint time,
# with the two digests in the message, rather than becoming a tool of unknown
# provenance inside an image everything else trusts.
COPY cargo-deny.tar.gz /tmp/cargo-deny.tar.gz
RUN set -eu; \
    echo "${CARGO_DENY_SHA256}  /tmp/cargo-deny.tar.gz" | sha256sum -c - \
      || { echo "FATAL: staged cargo-deny tarball does not match CARGO_DENY_SHA256"; exit 1; }; \
    tar -xzf /tmp/cargo-deny.tar.gz -C /tmp; \
    install -m 0755 \
      "/tmp/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl/cargo-deny" \
      /usr/local/bin/cargo-deny; \
    rm -rf /tmp/cargo-deny.tar.gz "/tmp/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl"

# step: TEST (the format and lint gates) — `cargo fmt --check` and `cargo clippy`
# run in the test road. They are COMPONENTS of the toolchain this image is FROM
# (the base installs `--profile minimal`), added to that same default toolchain:
# no second rustc, no new target, the rustc the artefact was built with.
RUN set -eu; \
    rustup component add rustfmt clippy \
    && rm -rf /usr/local/rustup/downloads /usr/local/rustup/tmp /root/.cache

# ── THE IMAGE ASSERTS ITS OWN CONTENTS ───────────────────────────────────────
#
# The base file learned this the hard way: `rustc --print target-list` printed a
# target it had no std for and exited 0, and "a check that cannot fail is
# indistinguishable from a law being obeyed". So each tool is RUN, not merely
# looked for on PATH, and the failure names the tool and the step.
RUN set -eu; \
    ssh -V 2>/dev/null || { echo "FATAL: openssh-client installed but \`ssh -V\` does not run"; exit 1; }; \
    ssh-keygen -A -f /tmp/keygen-probe >/dev/null 2>&1 || true; \
    git lfs version >/dev/null || { echo "FATAL: git-lfs installed but \`git lfs version\` fails — the LFS interop tests would report the product broken"; exit 1; }; \
    cargo deny --version >/dev/null || { echo "FATAL: cargo-deny is on PATH but \`cargo deny --version\` fails"; exit 1; }; \
    cargo fmt --version >/dev/null || { echo "FATAL: rustfmt component added but \`cargo fmt --version\` fails"; exit 1; }; \
    cargo clippy --version >/dev/null || { echo "FATAL: clippy component added but \`cargo clippy --version\` fails"; exit 1; }; \
    mold --version >/dev/null || { echo "FATAL: mold installed but \`mold --version\` fails"; exit 1; }; \
    pkg-config --exists openssl || { echo "FATAL: libssl-dev installed but pkg-config cannot find openssl — openssl-sys would not build"; exit 1; }; \
    rm -rf /tmp/keygen-probe; \
    echo "test tools OK: ssh, git-lfs, cargo-deny, rustfmt, clippy, mold, openssl"

# ── WHAT THIS IMAGE CANNOT FIX, stated rather than papered over ──────────────
#
# **Run it as an ordinary user, not as root.** Three tests in gunnar inject a
# filesystem failure by making a directory mode 0555 and asserting the write
# under it fails. Root ignores the mode, the write succeeds, and the test
# panics at its `expect_err` — which reads like the product broke. No package
# can fix that: it is a property of the uid the engine runs as. gunnar's
# `repo_create_transactional::make_unwritable` now says so by name when it
# happens, and the invocation is:
#
#   podman run --rm --userns=keep-id -e HOME=$H -e CARGO_HOME=$H …
#
# `--userns=keep-id` maps the host uid to itself so the bind mounts stay
# writable; `CARGO_HOME` must then be a directory that uid owns, because
# /usr/local/cargo in the base belongs to root.
#
# **Never mix uids into one CARGO_TARGET_DIR.** MEASURED 2026-09-12: a root
# build and a uid-1000 build sharing one target directory left every doc test
# failing with `can't find crate for …` — sixteen target failures that were
# pure contamination and nothing to do with the code under test.

WORKDIR /work