diff --git a/.claude/commands/validate-docker-image.md b/.claude/commands/validate-docker-image.md new file mode 100644 index 00000000..31e2e450 --- /dev/null +++ b/.claude/commands/validate-docker-image.md @@ -0,0 +1,3 @@ +Run `mise run dev:validate-docker-image` to check that the Dockerfile builder image tag exists on Docker Hub and supports all required architectures (linux/amd64 and linux/arm64). + +Report the results. If validation fails, explain which architecture is missing and suggest checking the available tags at https://hub.docker.com/r/hexpm/elixir/tags. diff --git a/docs/available-tasks.md b/docs/available-tasks.md index 854b3070..18615036 100644 --- a/docs/available-tasks.md +++ b/docs/available-tasks.md @@ -70,6 +70,12 @@ Open an SQLite console to the development database with extensions loaded Update dependencies +## `dev:validate-docker-image` + +- **Usage**: `dev:validate-docker-image` + +Validate that the Dockerfile builder image exists and supports required architectures + ## `dev:worktree-setup` - **Usage**: `dev:worktree-setup` diff --git a/docs/project-conventions.md b/docs/project-conventions.md index 90865977..4fedd41c 100644 --- a/docs/project-conventions.md +++ b/docs/project-conventions.md @@ -100,6 +100,7 @@ Rules extracted from commit history that are specific to this project and not al - **Sobelow runs on CI and pre-commit** in skip mode for security analysis. - **All modules require `@moduledoc`.** The Credo `ModuleDoc` check is enforced in strict mode. - **The project does not use dialyzer**. If any skill suggests its usage, ignore it. +- **Validate Docker builder image before updating versions.** When changing `ELIXIR_VERSION`, `OTP_VERSION`, or `DEBIAN_VERSION` in the Dockerfile, run `mise run dev:validate-docker-image` to confirm the generated `hexpm/elixir` tag exists on Docker Hub and supports both `linux/amd64` and `linux/arm64`. ## Reviews & Audits diff --git a/scripts/dev/validate-docker-image b/scripts/dev/validate-docker-image new file mode 100755 index 00000000..6c0f8c89 --- /dev/null +++ b/scripts/dev/validate-docker-image @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +#MISE description="Validate that the Dockerfile builder image exists and supports required architectures" + +set -e + +source "$(git rev-parse --show-toplevel)/scripts/_helpers.sh" + +ensure_working_directory! + +required_archs=("amd64" "arm64") + +# Parse Dockerfile ARGs +elixir_version=$(grep -E '^ARG ELIXIR_VERSION=' Dockerfile | sed 's/ARG ELIXIR_VERSION=//') +otp_version=$(grep -E '^ARG OTP_VERSION=' Dockerfile | sed 's/ARG OTP_VERSION=//') +debian_version=$(grep -E '^ARG DEBIAN_VERSION=' Dockerfile | sed 's/ARG DEBIAN_VERSION=//') + +if [[ -z "$elixir_version" || -z "$otp_version" || -z "$debian_version" ]]; then + echo "Error: could not parse all version ARGs from Dockerfile" + exit 1 +fi + +tag="${elixir_version}-erlang-${otp_version}-debian-${debian_version}" +image="hexpm/elixir:${tag}" + +debug_msg "Checking ${image}" + +# Obtain anonymous auth token +token=$(curl -sf "https://auth.docker.io/token?service=registry.docker.io&scope=repository:hexpm/elixir:pull" | jq -r '.token') + +if [[ -z "$token" || "$token" == "null" ]]; then + echo "Error: failed to obtain Docker Hub auth token" + exit 1 +fi + +# Fetch manifest list +http_code=$(curl -s -o /tmp/manifest.json -w '%{http_code}' \ + -H "Authorization: Bearer ${token}" \ + -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \ + "https://registry.hub.docker.com/v2/hexpm/elixir/manifests/${tag}") + +if [[ "$http_code" != "200" ]]; then + debug_msg "Error: image tag not found on Docker Hub (HTTP ${http_code})" + rm -f /tmp/manifest.json + exit 1 +fi + +# Extract supported architectures for linux OS +supported_archs=$(jq -r '.manifests[]? | select(.platform.os == "linux") | .platform.architecture' /tmp/manifest.json) +rm -f /tmp/manifest.json + +if [[ -z "$supported_archs" ]]; then + debug_msg "Error: no linux platform entries found in manifest" + exit 1 +fi + +all_valid=true + +for arch in "${required_archs[@]}"; do + if echo "$supported_archs" | grep -qx "$arch"; then + debug_msg "linux/${arch} supported" + else + debug_msg "linux/${arch} NOT supported" + all_valid=false + fi +done + +if [[ "$all_valid" == true ]]; then + debug_msg "Image is valid for all required architectures" +else + debug_msg "Error: image does not support all required architectures" + exit 1 +fi