Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions internal/imagebuild/assets/provision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,22 @@ for toolchain_name in "${toolchain_names[@]}"; do
[[ "$(pnpm --version)" == "${toolchain_version}" ]]
;;
rustup)
install -o root -g root -m 0755 "${toolchain_archive}" "${toolchain_scratch}/rustup-init"
export RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo
install -d -o root -g root -m 0755 "${RUSTUP_HOME}" "${CARGO_HOME}"
"${toolchain_scratch}/rustup-init" -y --no-modify-path --profile minimal \
# rustup lives in the runner's own home, exactly where
# actions-rust-lang/setup-rust-toolchain and a bare cargo on PATH
# resolve it. It was installed system-wide once and exported through
# /etc/environment -- and no job ever saw it: runuser starts the runner
# without pam_env, so no login file reaches a job's environment.
# Workers are disposable one-job containers, so runner ownership
# shares nothing with a later job.
export RUSTUP_HOME=/home/runner/.rustup CARGO_HOME=/home/runner/.cargo
install -d -o runner -g runner -m 0755 "${RUSTUP_HOME}" "${CARGO_HOME}"
install -o runner -g runner -m 0755 "${toolchain_archive}" "${toolchain_scratch}/rustup-init"
chmod 0755 "${toolchain_scratch}"
runuser -u runner -- env HOME=/home/runner \
RUSTUP_HOME="${RUSTUP_HOME}" CARGO_HOME="${CARGO_HOME}" \
"${toolchain_scratch}/rustup-init" -y --no-modify-path --profile minimal \
--default-toolchain none >/dev/null
[[ "$("${CARGO_HOME}/bin/rustup" --version 2>/dev/null)" == "rustup ${toolchain_version} "* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner "${CARGO_HOME}/bin/rustup" --version 2>/dev/null)" == "rustup ${toolchain_version} "* ]]
# Every channel the estate pins, with the two components its
# rust-toolchain.toml files name, so the action finds them already present.
mapfile -t rust_channels < <(jq -r '.channels[]?' <<<"${entry}")
Expand All @@ -348,20 +358,19 @@ for toolchain_name in "${toolchain_names[@]}"; do
exit 1
fi
for channel in "${rust_channels[@]}"; do
"${CARGO_HOME}/bin/rustup" toolchain install "${channel}" \
runuser -u runner -- env HOME=/home/runner \
"${CARGO_HOME}/bin/rustup" toolchain install "${channel}" \
--profile minimal --component clippy --component rustfmt >/dev/null
[[ "$("${CARGO_HOME}/bin/rustup" run "${channel}" cargo clippy --version)" == "clippy "* ]]
[[ "$("${CARGO_HOME}/bin/rustup" run "${channel}" rustfmt --version)" == "rustfmt "* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner "${CARGO_HOME}/bin/rustup" run "${channel}" cargo clippy --version)" == "clippy "* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner "${CARGO_HOME}/bin/rustup" run "${channel}" rustfmt --version)" == "rustfmt "* ]]
done
"${CARGO_HOME}/bin/rustup" default "${rust_default}" >/dev/null
# Readable by the runner without being writable by it: a job must not be
# able to edit a toolchain the next job inherits.
chmod -R a+rX "${RUSTUP_HOME}" "${CARGO_HOME}"
runuser -u runner -- env HOME=/home/runner "${CARGO_HOME}/bin/rustup" default "${rust_default}" >/dev/null
# Shims for jobs that call cargo or rustc without the action; the rustup
# proxy resolves the toolchain through the calling user's home, which
# for every job is the runner's.
for shim in rustup cargo rustc rustfmt cargo-clippy cargo-fmt clippy-driver; do
ln -sfn "${CARGO_HOME}/bin/${shim}" "/usr/local/bin/${shim}"
done
printf 'RUSTUP_HOME=%s\nCARGO_HOME=%s\n' "${RUSTUP_HOME}" "${CARGO_HOME}" \
>> /etc/environment
;;
uv)
tar --extract --gzip --file "${toolchain_archive}" \
Expand Down
25 changes: 11 additions & 14 deletions internal/imagebuild/assets/smoke-integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -129,25 +129,22 @@ for smoke_toolchain in "${smoke_toolchain_names[@]}"; do
;;
pnpm) [[ "$(pnpm --version)" == "${expected_version}" ]] ;;
rustup)
# rustup owns the whole Rust surface: the estate pins channels in
# rust-toolchain.toml, actions-rust-lang/setup-rust-toolchain resolves
# them through rustup, and the shims on PATH serve jobs that never call
# the action. Every pinned channel must already hold clippy and rustfmt,
# or the per-job download this image exists to remove comes back.
[[ "$(rustup --version 2>/dev/null)" == "rustup ${expected_version} "* ]]
[[ "$(stat --format='%U' -- /usr/local/rustup)" == root ]]
[[ "$(stat --format='%U' -- /usr/local/cargo)" == root ]]
grep -q '^RUSTUP_HOME=/usr/local/rustup$' /etc/environment
# Verified exactly as a job sees it: through the runner user, whose
# home is where the action and the shims resolve toolchains. The
# root-eye view once stayed green while every job saw nothing.
[[ "$(runuser -u runner -- env HOME=/home/runner rustup --version 2>/dev/null)" == "rustup ${expected_version} "* ]]
[[ "$(stat --format='%U' -- /home/runner/.rustup)" == runner ]]
[[ "$(stat --format='%U' -- /home/runner/.cargo)" == runner ]]
mapfile -t smoke_rust_channels < <(jq -r '.channels[]?' <<<"${entry}")
smoke_rust_default="$(jq -er '.default_channel' <<<"${entry}")"
[[ ${#smoke_rust_channels[@]} -gt 0 ]]
for smoke_rust_channel in "${smoke_rust_channels[@]}"; do
[[ "$(rustup run "${smoke_rust_channel}" rustc --version)" == "rustc ${smoke_rust_channel}"* ]]
[[ "$(rustup run "${smoke_rust_channel}" cargo clippy --version)" == clippy* ]]
[[ "$(rustup run "${smoke_rust_channel}" rustfmt --version)" == rustfmt* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustup run "${smoke_rust_channel}" rustc --version)" == "rustc ${smoke_rust_channel}"* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustup run "${smoke_rust_channel}" cargo clippy --version)" == clippy* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustup run "${smoke_rust_channel}" rustfmt --version)" == rustfmt* ]]
done
[[ "$(rustc --version)" == "rustc ${smoke_rust_default}"* ]]
[[ "$(cargo --version)" == "cargo "* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustc --version)" == "rustc ${smoke_rust_default}"* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner cargo --version)" == "cargo "* ]]
;;
uv)
[[ "$(uv --version)" == "uv ${expected_version}"* ]]
Expand Down
25 changes: 11 additions & 14 deletions internal/imagebuild/assets/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -116,25 +116,22 @@ for smoke_toolchain in "${smoke_toolchain_names[@]}"; do
;;
pnpm) [[ "$(pnpm --version)" == "${expected_version}" ]] ;;
rustup)
# rustup owns the whole Rust surface: the estate pins channels in
# rust-toolchain.toml, actions-rust-lang/setup-rust-toolchain resolves
# them through rustup, and the shims on PATH serve jobs that never call
# the action. Every pinned channel must already hold clippy and rustfmt,
# or the per-job download this image exists to remove comes back.
[[ "$(rustup --version 2>/dev/null)" == "rustup ${expected_version} "* ]]
[[ "$(stat --format='%U' -- /usr/local/rustup)" == root ]]
[[ "$(stat --format='%U' -- /usr/local/cargo)" == root ]]
grep -q '^RUSTUP_HOME=/usr/local/rustup$' /etc/environment
# Verified exactly as a job sees it: through the runner user, whose
# home is where the action and the shims resolve toolchains. The
# root-eye view once stayed green while every job saw nothing.
[[ "$(runuser -u runner -- env HOME=/home/runner rustup --version 2>/dev/null)" == "rustup ${expected_version} "* ]]
[[ "$(stat --format='%U' -- /home/runner/.rustup)" == runner ]]
[[ "$(stat --format='%U' -- /home/runner/.cargo)" == runner ]]
mapfile -t smoke_rust_channels < <(jq -r '.channels[]?' <<<"${entry}")
smoke_rust_default="$(jq -er '.default_channel' <<<"${entry}")"
[[ ${#smoke_rust_channels[@]} -gt 0 ]]
for smoke_rust_channel in "${smoke_rust_channels[@]}"; do
[[ "$(rustup run "${smoke_rust_channel}" rustc --version)" == "rustc ${smoke_rust_channel}"* ]]
[[ "$(rustup run "${smoke_rust_channel}" cargo clippy --version)" == clippy* ]]
[[ "$(rustup run "${smoke_rust_channel}" rustfmt --version)" == rustfmt* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustup run "${smoke_rust_channel}" rustc --version)" == "rustc ${smoke_rust_channel}"* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustup run "${smoke_rust_channel}" cargo clippy --version)" == clippy* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustup run "${smoke_rust_channel}" rustfmt --version)" == rustfmt* ]]
done
[[ "$(rustc --version)" == "rustc ${smoke_rust_default}"* ]]
[[ "$(cargo --version)" == "cargo "* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner rustc --version)" == "rustc ${smoke_rust_default}"* ]]
[[ "$(runuser -u runner -- env HOME=/home/runner cargo --version)" == "cargo "* ]]
;;
uv)
[[ "$(uv --version)" == "uv ${expected_version}"* ]]
Expand Down
2 changes: 1 addition & 1 deletion internal/imagebuild/orchestrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func TestRecipeFingerprintIsDeterministic(t *testing.T) {
// alias is part of the recipe, so a manifest whose contents changed under an
// unchanged alias would otherwise ask the builder to produce different bytes
// for a name that is already promoted.
if first != "sha256:6b92afb396a9d31a6772e489343006f2ce05c93a59a641701e2bf49ff8fe23fc" {
if first != "sha256:522a61fbf8567198ac81be2a0b6ffe5cdbc1a7cdf5e8beec13d415db470ab83e" {
t.Fatalf("deployed standard recipe fingerprint drifted: %q", first)
}
smoke, err := SmokeFingerprint(plan)
Expand Down