diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14180b7c..9206974c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,24 @@ jobs: env: DOCKER_DEFAULT_PLATFORM: ${{ matrix.platform }} + validate-platforms: + name: Validate platforms + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: docker/setup-qemu-action@v2 + + - uses: docker/login-action@v2 + with: + registry: ghcr.io + username: nextstrain-bot + password: ${{ secrets.GH_TOKEN_NEXTSTRAIN_BOT_MANAGE_PACKAGES }} + + - name: Validate final images + run: ./devel/validate-platforms -r ghcr.io -t ${{ needs.build.outputs.tag }} + push-branch: if: startsWith(needs.build.outputs.tag, 'branch-') && github.event_name != 'pull_request' needs: build @@ -103,7 +121,7 @@ jobs: push-build: if: startsWith(needs.build.outputs.tag, 'build-') - needs: [build, test] + needs: [build, test, validate-platforms] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -126,7 +144,7 @@ jobs: cleanup-registry: if: always() - needs: [build, test, push-branch, push-build] + needs: [build, test, validate-platforms, push-branch, push-build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/devel/validate-platforms b/devel/validate-platforms new file mode 100755 index 00000000..360c4336 --- /dev/null +++ b/devel/validate-platforms @@ -0,0 +1,118 @@ +#!/bin/bash +# +# Validate different platform builds of the final Nextstrain image. +# +set -euo pipefail + +# Set default values. +registry=localhost:5000 +tag=latest + +# Read command-line arguments. +while getopts "r:t:" opt; do + case "$opt" in + r) registry="$OPTARG";; + t) tag="$OPTARG";; + *) echo "Usage: $0 [-r ] [-t ]" 1>&2; exit 1;; + esac +done + +IMAGE="$registry/nextstrain/base:$tag" + +main() { + report_names=() + + version_commands=( + "nextclade --version" + "augur --version" + "auspice --version" + "pip show evofr | grep Version" + "datasets --version" + "dataformat version" + ) + + for platform in linux/amd64 linux/arm64; do + echo "[$platform] Pulling image..." + docker pull -q --platform "$platform" "$IMAGE" + + echo "[$platform] Checking that the platform is expected..." + check-platform "$platform" + + # Initialize a report file for each platform. + normalized_platform="$(normalize-platform "$platform")" + report_name="report_$normalized_platform" + :> "$report_name" + report_names+=("$report_name") + + # Check software where downloaded versions are not deterministic at + # different points in time (e.g. if "latest" is downloaded). + echo "[$platform] Determining software versions..." + for command in "${version_commands[@]}"; do + report-output "$platform" "$command" >> "$report_name" + done + done + + # Compare contents of the first platform's report file against others. + + first_report="${report_names[0]}" + echo "The first report ($first_report) has the following contents:" + cat "$first_report" + + echo "Comparing against other platforms..." + for report in "${report_names[@]:1}"; do + diff -U 1 "$first_report" "$report" + done + + echo "Success!" + + # TODO: cleanup on error + rm -rf "${report_names[@]}" +} + +report-output() { + # Print a section of the report for the output of a given command. + local platform="$1" + local command="$2" + + echo "Output of \"$command\":" + echo "$(docker-run "$platform" "$command")" + echo +} + +normalize-platform() { + local platform="$1" + + # Replace '/' with '_' + echo "${platform//\//_}" +} + +check-platform() { + # Check that the platform is actually what we expect it to be. + local platform="$1" + + python_platform_string="$(docker-run "$platform" "python -c \"import platform; print(platform.platform())\"")" + + case "$platform" in + linux/amd64) + if [[ "$python_platform_string" != *"x86_64"* ]]; then + echo "Platform $platform not detected." 1>&2; exit 1 + fi;; + linux/arm64) + if [[ "$python_platform_string" != *"aarch64"* ]]; then + echo "Platform $platform not detected." 1>&2; exit 1 + fi;; + *) + echo "Platform $platform not supported." 1>&2; exit 1;; + esac +} + +docker-run() { + # Run a command under the final Nextstrain image built for a specific + # platform. + local platform="$1" + local command="$2" + + docker run --platform "$platform" "$IMAGE" bash -c "$command" +} + +main