Skip to content

Fail the build if the rustup install download fails#764

Merged
reaperhulk merged 1 commit into
mainfrom
harden-rustup-install
May 22, 2026
Merged

Fail the build if the rustup install download fails#764
reaperhulk merged 1 commit into
mainfrom
harden-rustup-install

Conversation

@alex
Copy link
Copy Markdown
Member

@alex alex commented May 22, 2026

Problem

All the curl https://sh.rustup.rs -sSf | sh -s -- ... rustup installs share a footgun: a shell pipeline's exit status is that of the last command, not curl. So when curl fails — e.g. the transient curl: (6) Could not resolve host: sh.rustup.rs seen in the 2026-05-22 scheduled Docker Image Builder run — it writes nothing to stdout, the downstream sh reads empty stdin and exits 0, and the Docker layer "succeeds." The result is an image with no Rust toolchain, pushed to the mutable :ppc64le tag.

Downstream blast radius

That Rust-less cryptography-manylinux_2_28:ppc64le image then broke pyca/cryptography wheel/test jobs. With no cargo on PATH, maturin's PEP 517 get_requires_for_build_wheel hook does:

if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"):
    requirements += ["puccinialin"]

puccinialin isn't pinned in cryptography's hashed build-requirements.txt, so uv build --require-hashes aborts with In --require-hashes mode, all requirements must be pinned upfront with ==, but found: puccinialin. Everything in cryptography was correctly pinned — the unpinned, half-built container image was the culprit.

Fix

Download the script first, then run it as a separate &&-chained step, so a failed download fails the layer loudly:

RUN curl -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh && \
    sh /tmp/rustup-init.sh -y <args> && \
    rm /tmp/rustup-init.sh

Applied to all four affected Dockerfiles (cryptography-linux, runners/{ubuntu,debian,rhel}). Kept as POSIX sh (no pipefail/bash dependency) so it works on every base image and doesn't alter the shell for other RUNs. runners/fedora and runners/alpine are unaffected — they install rust via the distro rustup package and call rustup-init directly, no pipe.

After merge, re-running the image builder will rebuild a correct image and unstick cryptography CI.

🤖 Generated with Claude Code

The rustup install used `curl https://sh.rustup.rs -sSf | sh -s -- ...`.
A pipeline's exit status is that of the last command, so if curl failed
(e.g. a transient `Could not resolve host: sh.rustup.rs`), it produced no
output, the downstream `sh` read empty stdin and exited 0, and the Docker
layer "succeeded" — pushing an image with no Rust toolchain installed.

That broke pyca/cryptography wheel/test jobs using the freshly-built
image: with no `cargo` on PATH, maturin's `get_requires_for_build_wheel`
hook requests `puccinialin` to bootstrap rustup, which is unpinned, so
`uv --require-hashes` aborts.

Download the script first and run it as a separate `&&`-chained step so a
failed download fails the layer. Kept as POSIX sh (no `pipefail`/bash
dependency) so it works on all base images. fedora/alpine are unaffected
(they install rust via the distro `rustup` package, no pipe).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@reaperhulk reaperhulk merged commit 5b7e746 into main May 22, 2026
33 checks passed
@reaperhulk reaperhulk deleted the harden-rustup-install branch May 22, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants