Skip to content

Alpine 3.23, Test vector, Clean up output, reduce messages #178

Alpine 3.23, Test vector, Clean up output, reduce messages

Alpine 3.23, Test vector, Clean up output, reduce messages #178

Workflow file for this run

name: development
on:
pull_request:
push:
branches:
- '!main' # excludes main
- '!master' # excludes master
env:
TARGET_PLATFORMS: linux/amd64,linux/arm64
COMPOSE_DOCKER_CLI_BUILD: '1'
DOCKER_BUILDKIT: '1'
jobs:
build_images:
name: "Create runtime and buildroot OCI Images"
strategy:
fail-fast: true
matrix:
python:
- '3.14'
- '3.13'
- '3.12'
- '3.11'
- '3.10'
- '3.9'
- '3.8'
alpine:
- '3.20'
- '3.21'
- '3.22'
- '3.23'
os:
- 'ubuntu-latest'
exclude:
# No tag
- python: '3.8'
alpine: '3.21'
- python: '3.8'
alpine: '3.22'
- python: '3.8'
alpine: '3.23'
- python: '3.9'
alpine: '3.23'
- python: '3.14'
alpine: '3.20'
- python: '3.15'
alpine: '3.20'
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
id: setup
name: Setup
run: |
DEBUG_OUTPUT=/dev/null
if [ x"${DEBUG_DOCKERD:-}" = x1 ]; then
DEBUG_OUTPUT="$GITHUB_STEP_SUMMARY"
fi
setup_registry () {
>/dev/null docker pull -q docker.io/library/registry:3
registry_id=$(docker run \
-p5000:5000 \
--name registry \
--rm -d registry:3)
echo 'REGISTRY_CONTAINER_ID='$registry_id
}
setup_containerd_snapshotter () {
local _sudo=sudo
if [ $(id -u) -eq 0 ]; then
_sudo=sudo
fi
if ! [ -f /etc/docker/daemon.json ]; then
eval $_sudo mkdir -p /etc/docker
echo '{}' | >/dev/null $_sudo tee /etc/docker/daemon.json
fi
cat /etc/docker/daemon.json | jq '. | .+{"features": {"containerd-snapshotter": true}}' | >/dev/null eval $_sudo tee /etc/docker/daemon.json
printf '# Docker config\n\n```\n'
cat /etc/docker/daemon.json
printf '\n```\n\n'
eval $_sudo systemctl restart docker
sleep 1
printf '# Docker Info\n\n```'
docker info
printf '\n```\n'
}
sudo apt-get -y install skopeo
setup_containerd_snapshotter | tee -a "$DEBUG_OUTPUT"
{
printf '## Registry info:\n\n'
setup_registry | tee -a "$GITHUB_OUTPUT"
} | tee -a "$DEBUG_OUTPUT"
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: ${{ env.TARGET_PLATFORMS }}
driver-opts: network=host
buildkitd-flags: '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host'
-
id: image_env
run: |
. ./env.sh \
'${{ matrix.alpine }}' \
'${{ matrix.python }}' \
'${{ github.repository_owner }}' \
'localhost:5000'
docker pull -q "${SOURCE_IMAGE}" || true
echo "IMAGE_HOME=$(mktemp -d)" >> "$GITHUB_OUTPUT"
echo ALPINE_VERSION="${ALPINE_VERSION}"| tee -a "$GITHUB_OUTPUT"
echo PYTHON_VERSION="${PYTHON_VERSION}"| tee -a "$GITHUB_OUTPUT"
echo SOURCE_IMAGE="${SOURCE_IMAGE}"| tee -a "$GITHUB_OUTPUT"
echo IMAGE_TAG="${IMAGE_TAG}"| tee -a "$GITHUB_OUTPUT"
echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot"| tee -a "$GITHUB_OUTPUT"
echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT"
echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT"
echo 'EXAMPLE_IMAGE_TAG='"${IMAGE_TAG}-example1" | tee -a "$GITHUB_OUTPUT"
echo EXAMPLE_IMAGE_TAG_SAFE="$(echo "${IMAGE_TAG}-example1" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT"
echo REPOSITORY="${REPOSITORY}"| tee -a "$GITHUB_OUTPUT"
echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )"| tee -a "$GITHUB_OUTPUT"
echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")"| tee -a "$GITHUB_OUTPUT"
echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' | tee -a "$GITHUB_OUTPUT"
-
name: Create Buildroot
uses: docker/build-push-action@v6
id: buildroot
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
allow: network.host,security.insecure # this is for the bind mount
push: true
platforms: ${{ env.TARGET_PLATFORMS }}
context: "."
file: buildroot/Dockerfile.alpine
sbom: true
provenance: mode=max
target: buildroot
cache-to: |
type=gha,mode=max
cache-from: |
type=gha
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
type=registry,ref=${{ steps.image_env.outputs.SOURCE_IMAGE }}@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
build-args: |
ALPINE_VERSION=${{ steps.image_env.outputs.ALPINE_VERSION }}
BASE_IMAGE_DIGEST=${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
PYTHON_VERSION=${{ steps.image_env.outputs.PYTHON_VERSION }}
SOURCE_IMAGE=${{ steps.image_env.outputs.SOURCE_IMAGE }}
BUILD_ROOT=/d
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot"
-
name: Create Image
id: runtime
uses: docker/build-push-action@v6
env:
SOURCE_DATE_EPOCH: 0
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
push: true
context: "."
platforms: ${{ env.TARGET_PLATFORMS }}
file: runtime/Dockerfile.alpine
cache-to: |
type=gha,mode=max
cache-from: |
type=gha
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
type=registry,ref=${{ steps.image_env.outputs.SOURCE_IMAGE }}@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
build-args: |
ALPINE_VERSION=${{ steps.image_env.outputs.ALPINE_VERSION }}
BASE_IMAGE_DIGEST=${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
PYTHON_VERSION=${{ steps.image_env.outputs.PYTHON_VERSION }}
SOURCE_IMAGE=${{ steps.image_env.outputs.SOURCE_IMAGE }}
BUILDROOT_IMAGE=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot@${{ steps.buildroot.outputs.digest }}
BUILD_ROOT=/d
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}"
labels: ${{steps.image_env.outputs.IMAGE_LABELS}}
sbom: true
provenance: mode=max
annotations: |
index,manifest:org.opencontainers.image.authors=distroless-python image developers <autumn.jolitz+distroless-python@gmail.com>
index,manifest:org.opencontainers.image.source=https://github.com/autumnjolitz/distroless-python
index,manifest:org.opencontainers.image.title=distroless-python${{ steps.image_env.outputs.PYTHON_VERSION }}-alpine${{ steps.image_env.outputs.ALPINE_VERSION }}
index,manifest:org.opencontainers.image.description=${{ steps.image_env.outputs.IMAGE_DESCRIPTION }}
index,manifest:org.opencontainers.image.base.digest=${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
index,manifest:org.opencontainers.image.base.name=${{ steps.image_env.outputs.SOURCE_IMAGE }}
index,manifest:distroless.python-version=${{ steps.image_env.outputs.PYTHON_VERSION }}
index,manifest:distroless.alpine-version=${{ steps.image_env.outputs.ALPINE_VERSION }}
index,manifest:distroless.base-image=alpine${{ steps.image_env.outputs.ALPINE_VERSION }}
-
name: examples/simple-flask
id: build_test
uses: docker/build-push-action@v6
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
push: true
context: "examples/simple-flask"
platforms: ${{ env.TARGET_PLATFORMS }}
cache-from: |
type=gha
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}
type=registry,ref=${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot
type=registry,ref=${{ steps.image_env.outputs.SOURCE_IMAGE }}@${{ steps.image_env.outputs.BASE_IMAGE_DIGEST }}
build-args: |
SOURCE_IMAGE=${{ steps.image_env.outputs.IMAGE_TAG }}
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}-example1"
-
id: test
name: Test Example
env:
IMAGE_URI: '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}'
IMAGE_DIGEST: '${{ steps.build_test.outputs.digest }}'
test_command: curl -sLv 'http://localhost:8080/'
run: |
set -o pipefail
run_test () {
local rv=0
local T="$(mktemp)"
trap 'rm -f '"$T" RETURN
>"$T" 2>&1 eval "$@" || rv=$?
if [ $rv -eq 0 ] && case "$(tail -1 $T)" in '<p>Hello, World!</p>') false ;; *) true ;; esac ; then
>&2 echo 'failed! got "'"$(tail -1 $T)"'" !'
rv=1
fi
printf '`%s` (returned code: ' "$@"
printf '%s)\n```\n' "$rv"
cat "$T"
printf '\n```\n\n'
return "$rv"
}
rc=0
REMOTE_DIGEST="$(skopeo inspect --tls-verify=false --raw docker://"$IMAGE_URI" | skopeo manifest-digest /dev/stdin)"
if [ "$REMOTE_DIGEST" != "${IMAGE_DIGEST}" ]; then
echo 'generated a diferent manifest than '"${IMAGE_DIGEST}"': '"$REMOTE_DIGEST"
exit 2
fi
docker pull -q "$(echo "$IMAGE_URI" | rev | cut -d: -f2- | rev )@${IMAGE_DIGEST}"
for platform in $(echo $TARGET_PLATFORMS | tr , '\n')
do
docker pull --platform $platform -q "$IMAGE_URI"
>&2 printf 'pulled %s (%s)\n' "$IMAGE_TAG" "$platform"
done
docker pull -q "$IMAGE_URI"
{
printf '# Test\n\n'
printf '## Source Images\n\n```'
docker images --tree $IMAGE_URI | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g"
printf '\n```\n'
} | tee -a "$GITHUB_STEP_SUMMARY"
for platform in $(echo $TARGET_PLATFORMS | tr , '\n')
do
rc=0
container_id="$(docker run \
--platform $platform \
--detach -p 8080:8080 \
--rm $IMAGE_URI)" || rc=$?
trap 'docker stop $container_id' EXIT
if [ $rc -ne 0 ]; then
printf 'docker run failed (return code '$rc')!\n' | >&2 tee \
-a "$GITHUB_OUTPUT"
exit $rc
fi
start_ts=$(date +%s)
until [ x$(curl \
--silent --output /dev/null \
-w '%{http_code}\n' --fail 'http://localhost:8080/_health') = x200 ]
do
sleep 1
if [ "$(expr "${start_ts}" \+ 30)" -lt "$(date +%s)" ]; then
>&2 echo 'failed to get a good response in 30 seconds!'
break
fi
done
rc=0
printf '## Test (%s)\n\n' "$(curl 'http://localhost:8080/_arch')" | tee -a "$GITHUB_STEP_SUMMARY"
(run_test "$test_command" | tee -a "$GITHUB_STEP_SUMMARY") || rc=$?
if [ $rc -ne 0 ]; then
exit $rc
fi
trap -- EXIT
docker stop $container_id
done
-
id: post_build
name: 'download multiplatform images'
run: |
skopeo copy \
--src-no-creds \
--src-tls-verify=false \
--quiet \
--multi-arch all \
docker://$(echo '${{ steps.image_env.outputs.IMAGE_TAG }}' | rev | cut -d: -f2- | rev)@${{ steps.runtime.outputs.digest }} \
oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar \
&
skopeo copy \
--src-no-creds \
--src-tls-verify=false \
--quiet \
--multi-arch all \
docker://$(echo '${{ steps.image_env.outputs.IMAGE_TAG }}-buildroot' | rev | cut -d: -f2- | rev)@${{steps.buildroot.outputs.digest}} \
oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar \
&
skopeo copy \
--src-no-creds \
--src-tls-verify=false \
--quiet \
--multi-arch all \
docker://$(echo '${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG }}' | rev | cut -d: -f2- | rev)@${{steps.build_test.outputs.digest}} \
oci-archive://${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.EXAMPLE_IMAGE_TAG_SAFE }}.tar \
&
wait
docker stop '${{ steps.setup.outputs.REGISTRY_CONTAINER_ID }}'
IMAGE_DIGEST='${{steps.runtime.outputs.digest}}'
IMAGE_BUILDROOT_DIGEST='${{steps.buildroot.outputs.digest}}'
echo 'IMAGE_DIGEST='$IMAGE_DIGEST >> "GITHUB_OUTPUT"
echo 'IMAGE_BUILDROOT_DIGEST='$IMAGE_BUILDROOT_DIGEST >> "GITHUB_OUTPUT"
echo '${{ steps.image_env.outputs.IMAGE_TAG }}@'"$IMAGE_DIGEST" | tee -a ${{ steps.image_env.outputs.IMAGE_HOME }}/manifest.txt
echo '${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG }}@'"$IMAGE_BUILDROOT_DIGEST" | tee -a ${{ steps.image_env.outputs.IMAGE_HOME }}/manifest.txt
ls ${{ steps.image_env.outputs.IMAGE_HOME }}
-
name: upload build
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
retention-days: 1
name: images-alpine${{steps.image_env.outputs.ALPINE_VERSION}}-python${{steps.image_env.outputs.PYTHON_VERSION}}
path: |
${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar
${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar
-
name: upload build info
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
retention-days: 1
name: metadata-alpine${{steps.image_env.outputs.ALPINE_VERSION}}-python${{steps.image_env.outputs.PYTHON_VERSION}}
path: |
${{ steps.image_env.outputs.IMAGE_HOME }}/*.txt
post-build:
name: "Render metadata"
needs: [build_images]
runs-on: "ubuntu-latest"
steps:
-
name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
-
name: install dependencies
run: |
python -m pip install jinplate
-
name: fetch metadata
uses: actions/download-artifact@v5
with:
pattern: metadata-*
path: dist-images
-
name: aggregate metadata
run: |
cat $(find dist-images -type f -name manifest.txt -print) > manifest.txt
cat $(find dist-images -type f -name packages.txt -print) > packages.txt
find dist-images -type f \( -name manifest.txt -o -name packages.txt \) -delete
{
printf '### manifest.txt\n\n'
printf '```\n'
cat manifest.txt
printf '```\n'
} >>"$GITHUB_STEP_SUMMARY"
{
printf '### packages.txt\n\n'
printf '```\n'
cat packages.txt
printf '```\n'
} >>"$GITHUB_STEP_SUMMARY"
-
name: fetch images
uses: actions/download-artifact@v5
with:
pattern: images-*
path: dist-images
merge-multiple: true
-
name: parse metadata for README
run: |
for filename in $(echo dist-images/image-*.tar)
do
IMAGE_URI="$(basename -s .tar $filename | sed 's/image-//g' | base64 -d)"
mkdir -p "$(dirname "$IMAGE_URI")"
mv $filename "${IMAGE_URI}.tar"
filename="${IMAGE_URI}.tar"
PACKAGE_URL="$(grep "${IMAGE_URI}@" packages.txt | cut -d@ -f2-)"
echo '{
"filename": "'"$(basename $filename)"'",
"url": "'"$IMAGE_URI"'",
"tag": "'$(echo $IMAGE_URI | rev | cut -d: -f1 | rev)'",
"path": "'$(echo $IMAGE_URI | cut -d/ -f2- | cut -d: -f1)'",
"name": "'$(echo $IMAGE_URI | rev | cut -d: -f2- | cut -d/ -f1 | rev)'",
"registry": "'"$(echo $IMAGE_URI | cut -d/ -f1)"'",
"size": '"$(wc -c "$filename" | xargs | cut -f1 -d' ')"',
"digest": "'$(grep "${IMAGE_URI}@" manifest.txt | cut -d@ -f2-)'",
"package_url": "'$PACKAGE_URL'"
}'
done | >./values.json jq -s '. | {"images": .}'
cat ./values.json
>./README.rst python \
-m jinplate.cli \
docs/README.rst.tmpl file://$PWD/values.json
-
name: Convert README.rst to markdown
uses: docker://pandoc/core:3.8
with:
args: >-
--wrap=none
-t gfm
-o README.md
README.rst
-
name: Print out markdown
run: |
cat README.md >>"$GITHUB_STEP_SUMMARY"
printf '\n\n# README.rst\n\n```\n' >> "$GITHUB_STEP_SUMMARY"
cat README.rst >> "$GITHUB_STEP_SUMMARY"
printf '\n```\n' >> "$GITHUB_STEP_SUMMARY"