Skip to content

build: add development flow #28

build: add development flow

build: add development flow #28

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
jobs:
docker:
services:
registry:
image: registry:3
ports:
- 5000:5000
strategy:
fail-fast: true
matrix:
repository:
- 'localhost:5000'
python:
- '3.14'
- '3.13'
- '3.12'
- '3.11'
- '3.10'
- '3.9'
- '3.8'
alpine:
- '3.20'
- '3.21'
- '3.22'
os:
- 'ubuntu-latest'
exclude:
# No tag
- python: '3.8'
alpine: '3.21'
- python: '3.8'
alpine: '3.22'
- python: '3.14'
alpine: '3.20'
runs-on: ${{ matrix.os }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
-
id: image_env
run: |
. ./env.sh \
'${{ matrix.alpine }}' \
'${{ matrix.python }}' \
'${{ github.repository_owner }}' \
'${{ matrix.repository }}'
docker pull "${SOURCE_IMAGE}" || true
echo "IMAGE_HOME=$(mktemp -d)" >> "$GITHUB_OUTPUT"
echo ALPINE_VERSION="${ALPINE_VERSION}" >> "$GITHUB_OUTPUT"
echo PYTHON_VERSION="${PYTHON_VERSION}" >> "$GITHUB_OUTPUT"
echo SOURCE_IMAGE="${SOURCE_IMAGE}" >> "$GITHUB_OUTPUT"
echo IMAGE_TAG="${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
echo IMAGE_BUILDROOT_TAG="${IMAGE_TAG}-buildroot" >> "$GITHUB_OUTPUT"
echo IMAGE_TAG_SAFE="$(echo "$IMAGE_TAG" | base64 -w 0 )" >> "$GITHUB_OUTPUT"
echo IMAGE_BUILDROOT_TAG_SAFE="$(echo "${IMAGE_TAG}-buildroot" | base64 -w 0 )" >> "$GITHUB_OUTPUT"
echo REPOSITORY="${REPOSITORY}" >> "$GITHUB_OUTPUT"
echo REPOSITORY_SAFE="$(echo "${REPOSITORY}" | base64 -w 0 )" >> "$GITHUB_OUTPUT"
echo BASE_IMAGE_DIGEST="$(digest_of "$SOURCE_IMAGE")" >> "$GITHUB_OUTPUT"
echo 'IMAGE_DESCRIPTION=${{ github.event.repository.description }}. See ${{ github.server_url }}/${{ github.repository }} for more info.' >> "$GITHUB_OUTPUT"
-
name: Create Buildroot
uses: docker/build-push-action@v6
id: buildroot
with:
push: true
platforms: ${{ env.TARGET_PLATFORMS }}
context: "."
file: Dockerfile.alpine
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"
outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_BUILDROOT_TAG_SAFE }}.tar
-
name: Create Image
id: runtime
uses: docker/build-push-action@v6
env:
SOURCE_DATE_EPOCH: 0
with:
push: true
context: "."
platforms: ${{ env.TARGET_PLATFORMS }}
file: 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 }}
BUILD_ROOT=/d
tags: "${{ steps.image_env.outputs.IMAGE_TAG }}"
outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/image-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar
labels: ${{steps.image_env.outputs.IMAGE_LABELS}}
sbom: true
annotations: |
index,manifest:org.opencontainers.image.authors=distroless-python image developers <[email protected]>
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
uses: docker/build-push-action@v6
with:
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-amd64"
outputs: type=oci,dest=${{ steps.image_env.outputs.IMAGE_HOME }}/example1-${{ steps.image_env.outputs.IMAGE_TAG_SAFE }}.tar
-
id: post_build
run: |
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: Fetch packages version ID
if: ${{ matrix.repository == 'ghcr.io' }}
id: fetch_version_ids
run: |
tag=$(echo ${{ steps.image_env.outputs.IMAGE_TAG }} | rev | cut -d: -f1 | rev)
package_name=$(echo ${{ steps.image_env.outputs.IMAGE_TAG }} | rev | cut -d: -f2- | cut -d/ -f1 | rev)
repo_name=${{ github.repository }}
owner=${{ github.repository_owner }}
version_id=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/orgs/${owner}/packages/container/${package_name}/versions" | \
jq -r --arg tag "$tag" '.[] | select(.metadata.container.tags[] == $tag) | .id')
package_url="https://github.com/${repo_name}/pkgs/container/${package_name}/${version_id}?tag=${tag}"
echo "${{steps.image_env.outputs.IMAGE_TAG}}@$package_url" | tee -a ${{ steps.image_env.outputs.IMAGE_HOME }}/packages.txt
- name: upload build
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
retention-days: 1
name: images-${{steps.image_env.outputs.REPOSITORY_SAFE}}-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-${{steps.image_env.outputs.REPOSITORY_SAFE}}-alpine${{steps.image_env.outputs.ALPINE_VERSION}}-python${{steps.image_env.outputs.PYTHON_VERSION}}
path: |
${{ steps.image_env.outputs.IMAGE_HOME }}/*.txt
render-dockerhub-desc:
needs: [docker]
runs-on: "ubuntu-latest"
steps:
-
name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-python@v6
with:
python-version: '3.12'
-
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 \
README.rst.tmpl file://$PWD/values.json
-
name: Convert README.rst to markdown
uses: docker://pandoc/core:3.8
with:
args: >-
-s
--wrap=none
-t gfm
-o README.md
README.rst
-
name: Print out markdown
run: |
cat README.md >>"$GITHUB_STEP_SUMMARY"