Files
firehose/scripts/check-pins.sh
T
Firehose Bot 84539ec40f fix GNU-only \+ in check-pins sed patterns
\+ is a GNU BRE extension; /usr/bin/sed on macOS treats it as a literal
plus, so every Dockerfile ARG pin was reported "not found" and make
check false-failed even though mise.toml and the Dockerfile agree. Use
the POSIX form \{1,\} and note the constraint in the header.
2026-10-29 17:56:41 +00:00

89 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# check-pins.sh - keep mise.toml and the Dockerfile base-image pins in sync.
#
# The Phoenix build has two sources of truth for toolchain versions:
# - mise.toml [tools]: local dev toolchain (what `mise install` gives you)
# - Dockerfile ARG pins: the hexpm/elixir builder image used for deploys
# They must agree, otherwise local builds and deployed images run different
# Elixir/OTP combinations. Run standalone, or via `make check`.
#
# Also verifies that the BUILDER_IMAGE / RUNNER_IMAGE ARGs still expand to the
# tags composed from the pins (catches hand-edited image references).
#
# Pure shell + sed: no mise, docker or elixir needed, so it runs in CI and in
# sandboxes before the toolchain is installed. Keep regexes POSIX-BRE: GNU
# extensions like \+ silently break under /usr/bin/sed on macOS (it treats
# \+ as a literal plus), which false-fails every pin as "not found".
set -euo pipefail
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
mise_toml="$root/mise.toml"
dockerfile="$root/Dockerfile"
status=0
die() { echo "MISMATCH: $*" >&2; status=1; }
# sed-extract a `key = "value"` (or `ARG KEY=value`) line; dies when absent.
grab() { # grab <description> <key-regex> <file>
local value
value=$(sed -n "s/^$2[[:space:]]*=[[:space:]]*//p" "$3")
if [ -z "$value" ]; then
die "$1 not found in $3 (expected key = value)"
fi
value=${value%\"}; value=${value#\"} # strip optional surrounding quotes
value=$(printf '%s' "$value" | sed 's/[[:space:]]*$//') # trim trailing ws
printf '%s' "$value"
}
mise_elixir=$(grab 'mise elixir' 'elixir' "$mise_toml")
mise_erlang=$(grab 'mise erlang' 'erlang' "$mise_toml")
d_elixir=$(grab 'Dockerfile ELIXIR_VERSION' 'ARG[[:space:]]\{1,\}ELIXIR_VERSION' "$dockerfile")
d_otp=$(grab 'Dockerfile OTP_VERSION' 'ARG[[:space:]]\{1,\}OTP_VERSION' "$dockerfile")
d_debian=$(grab 'Dockerfile DEBIAN_VERSION' 'ARG[[:space:]]\{1,\}DEBIAN_VERSION' "$dockerfile")
if [ $status -eq 0 ]; then
mise_elixir_ver=${mise_elixir%%-otp-*}
mise_elixir_otp=${mise_elixir##*-otp-}
d_otp_major=${d_otp%%.*}
# Elixir: full version must match exactly.
[ "$mise_elixir_ver" = "$d_elixir" ] ||
die "Elixir: mise.toml has $mise_elixir_ver, Dockerfile ELIXIR_VERSION is $d_elixir"
# mise elixir builds embed their OTP major (…-otp-27); must match the
# major of the Dockerfile OTP_VERSION.
[ "$mise_elixir_otp" = "$d_otp_major" ] ||
die "OTP major: mise.toml elixir built for otp-$mise_elixir_otp, Dockerfile OTP_VERSION major is $d_otp_major"
# mise `erlang` IS OTP: must match the Dockerfile pin exactly.
[ "$mise_erlang" = "$d_otp" ] ||
die "OTP: mise.toml erlang is $mise_erlang, Dockerfile OTP_VERSION is $d_otp"
# BUILDER_IMAGE must expand to the tag composed from the pins.
expected_builder="docker.io/hexpm/elixir:${d_elixir}-erlang-${d_otp}-debian-${d_debian}"
d_builder=$(grab 'Dockerfile BUILDER_IMAGE' 'ARG[[:space:]]\{1,\}BUILDER_IMAGE' "$dockerfile")
expanded_builder=$(printf '%s' "$d_builder" |
sed -e "s/\${ELIXIR_VERSION}/$d_elixir/" \
-e "s/\${OTP_VERSION}/$d_otp/" \
-e "s/\${DEBIAN_VERSION}/$d_debian/")
[ "$expanded_builder" = "$expected_builder" ] ||
die "BUILDER_IMAGE expands to $expanded_builder, expected $expected_builder"
# RUNNER_IMAGE must expand to the Debian pin.
expected_runner="docker.io/debian:${d_debian}"
d_runner=$(grab 'Dockerfile RUNNER_IMAGE' 'ARG[[:space:]]\{1,\}RUNNER_IMAGE' "$dockerfile")
expanded_runner=${d_runner/\$\{DEBIAN_VERSION\}/$d_debian}
[ "$expanded_runner" = "$expected_runner" ] ||
die "RUNNER_IMAGE expands to $expanded_runner, expected $expected_runner"
fi
if [ $status -eq 0 ]; then
echo "pins ok: Elixir $d_elixir, OTP $d_otp, Debian $d_debian (mise and Dockerfile agree)"
echo " builder: $expected_builder"
echo " runner: $expected_runner"
echo " (upstream drift of these pins is reported by dokku-updates 'firehose check')"
else
echo >&2 "fix mise.toml and/or the Dockerfile ARG pins so they agree, then re-run."
fi
exit $status