Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CI: Validate images across platforms #130

Merged
merged 2 commits into from
Mar 21, 2023
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
22 changes: 20 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,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" (copy) the builder and final images from GitHub Container Registry to
# Docker Hub, where they will persist. Do this regardless of test results.
push-branch:
Expand Down Expand Up @@ -110,7 +128,7 @@ jobs:
# Docker Hub, where they will persist. Only do this if tests pass.
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
Expand All @@ -134,7 +152,7 @@ jobs:
# Delete the builder and final images from GitHub Container Registry.
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
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ RUN pip3 install pysam==0.19.1

# Allow caching to be avoided from here on out by calling
# docker build --build-arg CACHE_DATE="$(date)"
# NOTE: All versioned software added below should be checked in
# devel/validate-platforms.
ARG CACHE_DATE

# Install our own CLI so builds can do things like `nextstrain deploy`
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ variable to a new timestamp first:
Otherwise, letting the build process use the cached layers will save you time
during development iterations.

### Validate the images

Before using the images, they should be checked for any inconsistencies.

./devel/validate-platforms

The output and exit code will tell you whether validation is successful.

### Using the images locally

Since the images are pushed directly to the local registry, they are not
Expand Down
112 changes: 112 additions & 0 deletions devel/validate-platforms
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/bin/bash
#
# Validate different platform builds of the final Nextstrain image.
#
set -euo pipefail

# Set default values.
registry=localhost:5000
victorlin marked this conversation as resolved.
Show resolved Hide resolved
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 <registry>] [-t <tag>]" 1>&2; exit 1;;
esac
done

IMAGE="$registry/nextstrain/base:$tag"
PLATFORMS=(linux/amd64 linux/arm64)

main() {
# Check that every platform image got the same versions of important (e.g.
# first-party) software for which we don't pin a specific version (e.g. we
# install whatever the latest version is at build time).

local report_dir
report_dir="$(mktemp -dt "$(basename "$0")"-XXXXXX)"

for platform in "${PLATFORMS[@]}"; do
echo "[$platform] Pulling image..."
docker pull -q --platform "$platform" "$IMAGE"

echo "[$platform] Checking that the platform is expected..."
check-platform "$platform"

# Initialize a directory for the report file, ensuring slashes in the
# platform name are subdirs.
report="$report_dir/$platform"
echo "[$platform] Generating report file: $report"
mkdir -p "$(dirname "$report")"

# Create a report file for the platform.
# This should include all software below ARG CACHE_DATE in the Dockerfile
# in addition to other important software.
echo "[$platform] Determining software versions..."
docker-run "$platform" bash -c '
function echo-command {
echo "$ $BASH_COMMAND"
}
trap echo-command DEBUG

nextstrain --version
nextalign --version
nextclade --version
augur --version
auspice --version
victorlin marked this conversation as resolved.
Show resolved Hide resolved
python3 -c "from importlib.metadata import version; print(version(\"evofr\"))"
datasets --version
dataformat version
tsibley marked this conversation as resolved.
Show resolved Hide resolved

python3 -c "from importlib.metadata import version; print(version(\"phylo-treetime\"))"
' >"$report"
done

# Compare contents of the first platform's report file against others.
first_report="$report_dir/${PLATFORMS[0]}"
echo "The report for ${PLATFORMS[0]} has the following contents:"
cat "$first_report"

echo "Comparing against other platforms..."
# NOTE: if running on macOS ≥13, you may need to install GNU diff for the
# --from-file option.
if cd "$report_dir" && diff --unified=1 --from-file="${PLATFORMS[0]}" "${PLATFORMS[@]:1}"; then
victorlin marked this conversation as resolved.
Show resolved Hide resolved
echo "Success! All versions the same." >&2
else
echo "Failure!" >&2
exit 1
fi
}

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 --rm --platform "$platform" "$IMAGE" "${command[@]}"
}

main