Skip to content

ci

ci #467

Workflow file for this run

name: ci
on:
pull_request:
push:
branches: [develop, main]
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
workflow_dispatch:
inputs:
environment:
description: GitHub Actions deployment environment
required: false
type: environment
env:
DOCKER_BUILDKIT: "1"
HATCH_ENV: "ci"
HATCH_VERSION: "1.13.0"
PIPX_VERSION: "1.7.1"
jobs:
setup:
runs-on: ubuntu-latest
outputs:
environment-name: ${{ steps.set-env.outputs.environment-name }}
environment-url: ${{ steps.set-env.outputs.environment-url }}
repo-name: ${{ steps.set-env.outputs.repo-name }}
steps:
- uses: actions/checkout@v4
- name: Set GitHub Actions deployment environment
id: set-env
run: |
repo_name=${GITHUB_REPOSITORY##*/}
if ${{ github.event_name == 'workflow_dispatch' }}; then
environment_name=${{ inputs.environment }}
elif ${{ github.ref_type == 'tag' }}; then
environment_name="PyPI"
else
environment_name=""
fi
if [ "$environment_name" = "PyPI" ]; then
url="https://pypi.org/project/$repo_name/"
environment_url="$url$GITHUB_REF_NAME/"
else
timestamp="$(date -Iseconds)"
url="https://api.github.com/repos/$GITHUB_REPOSITORY/deployments"
environment_url="$url?timestamp=$timestamp"
fi
echo "environment-name=$environment_name" >>"$GITHUB_OUTPUT"
echo "environment-url=$environment_url" >>"$GITHUB_OUTPUT"
echo "repo-name=$repo_name" >>"$GITHUB_OUTPUT"
- name: Create annotation for deployment environment
if: steps.set-env.outputs.environment-name != ''
run: echo "::notice::Deployment environment ${{ steps.set-env.outputs.environment-name }}"
python:
runs-on: ubuntu-latest
needs: [setup]
strategy:
matrix:
# python-version: ["3.9", "3.10", "3.11", "3.12"]
# WIP
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Set up pip cache
if: runner.os == 'Linux'
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
restore-keys: ${{ runner.os }}-pip-
- name: Install pipx for Python ${{ matrix.python-version }}
run: python -m pip install "pipx==$PIPX_VERSION"
- name: Install Hatch
run: pipx install "hatch==$HATCH_VERSION"
- name: Test Hatch version
run: |
HATCH_VERSION_INSTALLED=$(hatch --version)
echo "The HATCH_VERSION environment variable is set to $HATCH_VERSION."
echo "The installed Hatch version is ${HATCH_VERSION_INSTALLED##Hatch, version }."
case $HATCH_VERSION_INSTALLED in
*$HATCH_VERSION) echo "Hatch version correct." ;;
*) echo "Hatch version incorrect." && exit 1 ;;
esac
- name: Install dependencies
run: hatch env create ${{ env.HATCH_ENV }}
- name: Test virtualenv location
run: |
EXPECTED_VIRTUALENV_PATH=$GITHUB_WORKSPACE/.venv
INSTALLED_VIRTUALENV_PATH=$(hatch env find)
echo "The virtualenv should be at $EXPECTED_VIRTUALENV_PATH."
echo "Hatch is using a virtualenv at $INSTALLED_VIRTUALENV_PATH."
case "$INSTALLED_VIRTUALENV_PATH" in
"$EXPECTED_VIRTUALENV_PATH") echo "Correct Hatch virtualenv." ;;
*) echo "Incorrect Hatch virtualenv." && exit 1 ;;
esac
- name: Test that Git tag version and Python package version match
if: github.ref_type == 'tag' && matrix.python-version == '3.12'
run: |
GIT_TAG_VERSION=$GITHUB_REF_NAME
PACKAGE_VERSION=$(hatch version)
echo "The Python package version is $PACKAGE_VERSION."
echo "The Git tag version is $GIT_TAG_VERSION."
if [ "$PACKAGE_VERSION" = "$GIT_TAG_VERSION" ]; then
echo "Versions match."
else
echo "Versions do not match." && exit 1
fi
- name: Run Hatch script for code quality checks
run: hatch run ${{ env.HATCH_ENV }}:check
- name: Run tests
run: |
export COVERAGE_PROCESS_START="$PWD/pyproject.toml"
hatch run ${{ env.HATCH_ENV }}:coverage run
coverage_files_after_run=$(find . -name '.coverage*' | wc -l)
sleep_time=10
sleep "$sleep_time"
coverage_files_after_sleep=$(find . -name '.coverage*' | wc -l)
echo "[INFO] Verifying that coverage.py has stopped generating .coverage files.
[INFO] Number of coverage files after coverage run: $coverage_files_after_run
[INFO] Number of coverage files after $sleep_time second sleep: $coverage_files_after_sleep
"
if [ "$coverage_files_after_sleep" -gt "$coverage_files_after_run" ]; then
echo "[ERROR] Unexpected .coverage files detected."
exit 1
fi
timeout-minutes: 5
- name: Enforce test coverage
run: |
hatch run ${{ env.HATCH_ENV }}:coverage combine -q
hatch run ${{ env.HATCH_ENV }}:coverage report
- name: Build Python package
run: hatch build
- name: Upload Python package artifacts
if: >
github.ref_type == 'tag' &&
matrix.python-version == '3.12' &&
needs.setup.outputs.environment-name == 'PyPI'
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
name: ${{ needs.setup.outputs.repo-name }}-${{ github.ref_name }}
path: dist
# docker:
# runs-on: ubuntu-latest
# needs: [setup, python]
# strategy:
# fail-fast: false
# matrix:
# linux-version: ["alpine", "bookworm", "slim-bookworm"]
# python-version: ["3.9", "3.10", "3.11", "3.12"]
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-python@v5
# with:
# python-version: ${{ matrix.python-version }}
# - run: python3 -m pip install 'httpie>=3,<4' 'urllib3>=1,<2'
# - name: Set up versions and Docker tags for Python and Alpine Linux
# id: setup
# run: |
# LINUX_VERSION=${{ matrix.linux-version }}
# linux_version_without_debian_release_name="${LINUX_VERSION/bookworm/}"
# linux_tag="${linux_version_without_debian_release_name%-}"
# LINUX_TAG="${linux_tag:+-$linux_tag}"
# PYTHON_VERSION=${{ matrix.python-version }}
# PYTHON_TAG="-python$PYTHON_VERSION"
# echo "LINUX_VERSION=$LINUX_VERSION" >> $GITHUB_ENV
# echo "LINUX_TAG=$LINUX_TAG" >> $GITHUB_ENV
# echo "PYTHON_VERSION=$PYTHON_VERSION" >> $GITHUB_ENV
# echo "PYTHON_TAG=$PYTHON_TAG" >> $GITHUB_ENV
# - name: Build Docker images
# run: |
# docker build . --rm --target base \
# --build-arg BUILDKIT_INLINE_CACHE=1 \
# --build-arg HATCH_VERSION="$HATCH_VERSION" \
# --build-arg LINUX_VERSION="$LINUX_VERSION" \
# --build-arg PIPX_VERSION="$PIPX_VERSION" \
# --build-arg PYTHON_VERSION="$PYTHON_VERSION" \
# --cache-from ghcr.io/br3ndonland/inboard \
# -t ghcr.io/br3ndonland/inboard:base"$LINUX_TAG"
# docker build . --rm --target starlette \
# --build-arg BUILDKIT_INLINE_CACHE=1 \
# --build-arg HATCH_VERSION="$HATCH_VERSION" \
# --build-arg LINUX_VERSION="$LINUX_VERSION" \
# --build-arg PIPX_VERSION="$PIPX_VERSION" \
# --build-arg PYTHON_VERSION="$PYTHON_VERSION" \
# -t ghcr.io/br3ndonland/inboard:starlette"$LINUX_TAG"
# docker build . --rm --target fastapi \
# --build-arg BUILDKIT_INLINE_CACHE=1 \
# --build-arg HATCH_VERSION="$HATCH_VERSION" \
# --build-arg LINUX_VERSION="$LINUX_VERSION" \
# --build-arg PIPX_VERSION="$PIPX_VERSION" \
# --build-arg PYTHON_VERSION="$PYTHON_VERSION" \
# -t ghcr.io/br3ndonland/inboard:fastapi"$LINUX_TAG"
# - name: Run Docker containers for testing
# run: |
# docker run -d -p 80:80 --name inboard-base \
# -e "BASIC_AUTH_USERNAME=test_user" \
# -e "BASIC_AUTH_PASSWORD=r4ndom_bUt_memorable" \
# ghcr.io/br3ndonland/inboard:base"$LINUX_TAG"
# docker run -d -p 81:80 --name inboard-starlette \
# -e "BASIC_AUTH_USERNAME=test_user" \
# -e "BASIC_AUTH_PASSWORD=r4ndom_bUt_memorable" \
# ghcr.io/br3ndonland/inboard:starlette"$LINUX_TAG"
# docker run -d -p 82:80 --name inboard-fastapi \
# -e "BASIC_AUTH_USERNAME=test_user" \
# -e "BASIC_AUTH_PASSWORD=r4ndom_bUt_memorable" \
# ghcr.io/br3ndonland/inboard:fastapi"$LINUX_TAG"
# - name: Test Hatch version in Docker containers
# run: |
# test_hatch_version_in_docker() {
# echo "The HATCH_VERSION environment variable is set to $HATCH_VERSION."
# local hatch_version_in_docker hatch_version_in_docker_full
# for container_name in "$@"; do
# hatch_version_in_docker_full=$(docker exec "$container_name" hatch --version)
# hatch_version_in_docker="${hatch_version_in_docker_full##Hatch, version }"
# if [ -n "$hatch_version_in_docker" ]; then
# echo "Docker container $container_name has $hatch_version_in_docker."
# fi
# case $hatch_version_in_docker in
# *$HATCH_VERSION) echo "Hatch versions match for $container_name." ;;
# *) echo "Hatch version test failed for $container_name." && return 1 ;;
# esac
# done
# }
# test_hatch_version_in_docker inboard-base inboard-starlette inboard-fastapi
# - name: Test virtualenv location in Docker containers
# run: |
# test_virtualenv_location_in_docker() {
# local docker_virtualenv docker_python expected_virtualenv expected_python
# expected_virtualenv="/app/.venv"
# expected_python="$expected_virtualenv/bin/python"
# echo "The Hatch virtualenv should be at $expected_virtualenv."
# echo "The Python executable should be at $expected_python."
# for container_name in "$@"; do
# docker_virtualenv=$(docker exec "$container_name" hatch env find)
# docker_python=$(docker exec "$container_name" which python)
# case "$docker_virtualenv" in
# "$expected_virtualenv") echo "Correct Hatch virtualenv $docker_virtualenv for $container_name." ;;
# *) echo "Incorrect Hatch virtualenv $docker_virtualenv for $container_name." && return 1 ;;
# esac
# case "$docker_python" in
# "$expected_python") echo "Correct Python $docker_python for $container_name." ;;
# *) echo "Incorrect Python $docker_python for $container_name." && return 1 ;;
# esac
# done
# }
# test_virtualenv_location_in_docker inboard-base inboard-starlette inboard-fastapi
# - name: Smoke test Docker containers
# run: |
# handle_error_code() {
# case "$1" in
# 2) : 'Request timed out!' ;;
# 3) : 'Unexpected HTTP 3xx Redirection!' ;;
# 4) : 'HTTP 4xx Client Error!' ;;
# 5) : 'HTTP 5xx Server Error!' ;;
# 6) : 'Exceeded --max-redirects=<n> redirects!' ;;
# *) : 'Other Error!' ;;
# esac
# echo "$_"
# return "$1"
# }
# smoke_test() {
# if http --check-status --ignore-stdin -q --timeout=5 "$@"; then
# echo 'Smoke test passed. OK!'
# else
# handle_error_code "$?"
# fi
# }
# smoke_test_xfail() {
# if http --check-status --ignore-stdin -q --timeout=5 "$@" &>/dev/null; then
# echo 'Smoke test should have failed!'
# return 1
# else
# echo 'Smoke test expected to fail. OK!'
# fi
# }
# smoke_test :80
# smoke_test :81
# smoke_test :82
# smoke_test -a test_user:r4ndom_bUt_memorable :81/status
# smoke_test -a test_user:r4ndom_bUt_memorable :82/status
# smoke_test_xfail -a test_user:incorrect_password :81/status
# smoke_test_xfail -a test_user:incorrect_password :82/status
# smoke_test_xfail :81/status
# smoke_test_xfail :82/status
# - name: Log in to Docker registry
# if: >
# github.ref_type == 'tag' ||
# github.ref == 'refs/heads/develop' ||
# github.ref == 'refs/heads/main'
# run: |
# echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io \
# -u ${{ github.actor }} --password-stdin
# - name: Tag and push Docker images with latest tags
# if: >
# matrix.python-version == '3.12' &&
# (
# github.ref_type == 'tag' ||
# github.ref == 'refs/heads/develop' ||
# github.ref == 'refs/heads/main'
# )
# run: |
# docker push ghcr.io/br3ndonland/inboard:base"$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:starlette"$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:fastapi"$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:fastapi"$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:latest"$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:latest"$LINUX_TAG"
# - name: Tag and push Docker images with Python version
# if: github.ref_type == 'tag' || github.ref == 'refs/heads/main'
# run: |
# docker tag \
# ghcr.io/br3ndonland/inboard:base"$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:base"$PYTHON_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:starlette"$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:starlette"$PYTHON_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:fastapi"$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:fastapi"$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:base"$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:starlette"$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:fastapi"$PYTHON_TAG$LINUX_TAG"
# - name: Tag and push Docker images with Git tag
# if: github.ref_type == 'tag'
# run: |
# GIT_TAG_FULL=${{ github.ref_name }}
# GIT_TAG_MAJOR_MINOR=$(echo "$GIT_TAG_FULL" | cut -d '.' -f 1-2)
# for GIT_TAG in "$GIT_TAG_FULL" "$GIT_TAG_MAJOR_MINOR"; do
# docker tag \
# ghcr.io/br3ndonland/inboard:"base$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"base-$GIT_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"starlette$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"starlette-$GIT_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"fastapi$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"fastapi-$GIT_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"base$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"base-$GIT_TAG$PYTHON_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"starlette$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"starlette-$GIT_TAG$PYTHON_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"fastapi$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"fastapi-$GIT_TAG$PYTHON_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"base$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"$GIT_TAG-base$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"starlette$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"$GIT_TAG-starlette$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"fastapi$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"$GIT_TAG-fastapi$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"base$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"$GIT_TAG-base$PYTHON_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"starlette$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"$GIT_TAG-starlette$PYTHON_TAG$LINUX_TAG"
# docker tag \
# ghcr.io/br3ndonland/inboard:"fastapi$LINUX_TAG" \
# ghcr.io/br3ndonland/inboard:"$GIT_TAG-fastapi$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"base-$GIT_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"starlette-$GIT_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"fastapi-$GIT_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"base-$GIT_TAG$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"starlette-$GIT_TAG$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"fastapi-$GIT_TAG$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"$GIT_TAG-base$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"$GIT_TAG-starlette$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"$GIT_TAG-fastapi$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"$GIT_TAG-base$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"$GIT_TAG-starlette$PYTHON_TAG$LINUX_TAG"
# docker push ghcr.io/br3ndonland/inboard:"$GIT_TAG-fastapi$PYTHON_TAG$LINUX_TAG"
# done
# pypi:
# environment:
# name: ${{ needs.setup.outputs.environment-name }}
# url: ${{ needs.setup.outputs.environment-url }}
# if: github.ref_type == 'tag' && needs.setup.outputs.environment-name == 'PyPI'
# needs: [setup, python, docker]
# permissions:
# id-token: write
# runs-on: ubuntu-latest
# steps:
# - name: Download Python package artifacts
# uses: actions/download-artifact@v4
# with:
# merge-multiple: true
# name: ${{ needs.setup.outputs.repo-name }}-${{ github.ref_name }}
# path: dist
# - name: Publish Python package to PyPI
# uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70
# changelog:
# if: github.ref_type == 'tag'
# needs: [setup, python, docker, pypi]
# permissions:
# contents: write
# pull-requests: write
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# with:
# fetch-depth: 0
# ref: develop
# - name: Generate changelog from Git tags
# run: |
# echo '# Changelog
# ' >CHANGELOG.md
# echo '# Changelog
# [View on GitHub](https://github.com/${{github.repository}}/blob/HEAD/CHANGELOG.md)
# ' >docs/changelog.md
# GIT_LOG_FORMAT='## %(subject) - %(taggerdate:short)
# %(contents:body)
# Tagger: %(taggername) %(taggeremail)
# Date: %(taggerdate:iso)
# ```text
# %(contents:signature)```
# '
# git tag -l --sort=-taggerdate:iso --format="$GIT_LOG_FORMAT" >>CHANGELOG.md
# git tag -l --sort=-taggerdate:iso --format="$GIT_LOG_FORMAT" >>docs/changelog.md
# # shellcheck disable=SC2016
# ESCAPE_DUNDERS='s:([^`])(__)([a-z]+)(__)([^`]):\1\\_\\_\3\\_\\_\5:g'
# sed -Ei "$ESCAPE_DUNDERS" CHANGELOG.md
# sed -Ei "$ESCAPE_DUNDERS" docs/changelog.md
# - name: Format changelog with Prettier
# run: npx -s -y prettier@'^3.4' --write CHANGELOG.md docs/changelog.md
# - name: Create pull request with updated changelog
# uses: peter-evans/create-pull-request@v6
# with:
# add-paths: |
# CHANGELOG.md
# docs/changelog.md
# author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
# branch: create-pull-request/${{ github.ref_name }}
# commit-message: Update changelog for version ${{ github.ref_name }}
# title: Update changelog for version ${{ github.ref_name }}