Skip to content
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
211 changes: 153 additions & 58 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@ on:
- '**'
workflow_dispatch:

env:
BUILD_SUFFIX: -build-${{ github.run_id }}_${{ github.run_attempt }}
DOCKER_METADATA_SET_OUTPUT_ENV: 'true'

jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
outputs:
build-image: ${{ steps.build-meta.outputs.tags }}
image-arm64: ${{ steps.gen-output.outputs.image-arm64 }}
image-x64: ${{ steps.gen-output.outputs.image-x64 }}
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-24.04
- ubuntu-24.04-arm
steps:
- name: Checkout code
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

Expand All @@ -28,41 +36,122 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Produce the build image tag
id: build-meta
- id: build-meta
name: Docker meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: type=sha,suffix=${{ env.BUILD_SUFFIX }}

# Build cache is shared among all builds of the same architecture
- id: cache-meta
name: Docker meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: type=sha,suffix=-build-${{ github.run_id }}_${{ github.run_attempt }}
tags: type=raw,value=buildcache-${{ runner.arch }}

- id: get-registry
name: Get the sanitized registry name
run: |
echo "registry=$(echo '${{ steps.build-meta.outputs.tags }}' | cut -f1 -d:)" | tee -a "$GITHUB_OUTPUT"

- name: Build and push the untested image
- id: build
name: Build/push the arch-specific image
uses: docker/build-push-action@v6
with:
push: true
cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
labels: ${{ steps.build-meta.outputs.labels }}
tags: ${{ steps.build-meta.outputs.tags }}
provenance: true
provenance: mode=max
sbom: true
cache-from: type=gha
cache-to: type=gha
tags: ${{ steps.get-registry.outputs.registry }}
outputs: type=image,push-by-digest=true,push=true

test:
runs-on: ubuntu-latest
needs:
- build
strategy:
fail-fast: false
matrix:
test:
- mypy .
- pydoclint .
# pylint returns error codes if the checks fail
# https://pylint.readthedocs.io/en/latest/user_guide/usage/run.html#exit-codes
- pylint -v .
- python -m unittest -v
- id: gen-output
name: Write arch-specific image digest to outputs
run: |
echo "image-${RUNNER_ARCH,,}=${{ steps.get-registry.outputs.registry }}@${{ steps.build.outputs.digest }}" | tee -a "$GITHUB_OUTPUT"

merge:
runs-on: ubuntu-24.04
needs: build
env:
DOCKER_APP_IMAGE_ARM64: ${{ needs.build.outputs.image-arm64 }}
DOCKER_APP_IMAGE_X64: ${{ needs.build.outputs.image-x64 }}
outputs:
image: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- id: meta
name: Generate tag for the app image
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: type=sha,suffix=${{ env.BUILD_SUFFIX }}

- name: Push the multi-platform app image
run: |
docker buildx imagetools create \
--tag "$DOCKER_METADATA_OUTPUT_TAGS" \
"$DOCKER_APP_IMAGE_ARM64" "$DOCKER_APP_IMAGE_X64"

test-mypy:
runs-on: ubuntu-24.04
needs: merge
container:
image: ${{ needs.merge.outputs.image }}
defaults:
run:
working-directory: /app
steps:
- name: Run mypy
run: mypy .

test-pydoclint:
runs-on: ubuntu-24.04
needs: merge
container:
image: ${{ needs.merge.outputs.image }}
defaults:
run:
working-directory: /app
steps:
- name: Run pydoclint
run: pydoclint .

# pylint returns error codes if the checks fail
# @see https://pylint.readthedocs.io/en/latest/user_guide/usage/run.html#exit-codes
test-pylint:
runs-on: ubuntu-24.04
needs: merge
container:
image: ${{ needs.merge.outputs.image }}
defaults:
run:
working-directory: /app
steps:
- name: Run pylint
run: pylint -v .

test-unit:
runs-on: ubuntu-24.04
needs: merge
env:
COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml
DOCKER_APP_IMAGE: ${{ needs.build.outputs.build-image }}
DOCKER_APP_IMAGE: ${{ needs.merge.outputs.image }}
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -77,35 +166,45 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Copy environment file
run: cp env.example .env
- name: Run test suite
run: |
docker compose run --rm --no-deps app python -m unittest -v

- name: Set ARTIFACTS_DIR
run: echo "ARTIFACTS_DIR=${RUNNER_TEMP}/artifacts" >> $GITHUB_ENV
test-startup:
runs-on: ubuntu-24.04
needs: merge
env:
COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml
DOCKER_APP_IMAGE: ${{ needs.merge.outputs.image }}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Create the artifacts directory
run: mkdir -p "$ARTIFACTS_DIR"
- name: Set up Docker Compose
uses: docker/setup-compose-action@v1

- name: Record start time
run: TEST_START=`date +%s` >> $GITHUB_ENV
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run the test command
- name: Start the stack
run: |
docker compose run --no-deps --rm app ${{ matrix.test }}

- name: Upload test report
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: Test Report - ${{ matrix.test }} (${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }})
path: ${{ env.ARTIFACTS_DIR }}
if-no-files-found: warn
docker compose up --wait app db

push:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- build
- test
- merge
- test-mypy
- test-pydoclint
- test-pylint
- test-startup
- test-unit
env:
DOCKER_APP_IMAGE: ${{ needs.merge.outputs.image }}
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -118,7 +217,6 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}

- name: Produce permanent image tags
id: branch-meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
Expand All @@ -128,10 +226,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}

- name: Retag and push the image
uses: docker/build-push-action@v6
with:
push: true
labels: ${{ steps.branch-meta.outputs.labels }}
tags: ${{ steps.branch-meta.outputs.tags }}
cache-from: type=registry,ref=${{ needs.build.outputs.build-image }}
target: app
run: |
docker pull "$DOCKER_APP_IMAGE"
echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$DOCKER_APP_IMAGE"
docker push --all-tags "$(echo "$DOCKER_APP_IMAGE" | cut -f1 -d:)"
59 changes: 59 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Push Release Tags

on:
push:
tags:
- '**'
workflow_dispatch:

env:
DOCKER_METADATA_SET_OUTPUT_ENV: 'true'

jobs:
retag:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Determine the sha-based image tag to retag
id: get-base-image
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: type=sha

- name: Verify that the image was previously built
env:
BASE_IMAGE: ${{ steps.get-base-image.outputs.tags }}
run: |
docker pull "$BASE_IMAGE"

- name: Produce release tags
id: tag-meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
flavor: latest=false
tags: |
type=ref,event=tag
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}

- name: Retag the pulled image
env:
BASE_IMAGE: ${{ steps.get-base-image.outputs.tags }}
run: |
echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$BASE_IMAGE"
docker push --all-tags "$(echo "$BASE_IMAGE" | cut -f1 -d:)"
12 changes: 7 additions & 5 deletions docker-compose.ci.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
services:
db: !reset
db:
volumes: !reset

app:
build: !reset
depends_on: !reset
ports: !reset
environment:
CHAT_BACKEND: ollama
DEFAULT_STORAGE_DIR: /tmp
EMBED_BACKEND: ollama
env_file: !override env.example
volumes: !reset

ollama: !reset

prisma: !reset

localstack: !reset
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ services:
POSTGRES_DB: ${POSTGRES_DB:-willa}
POSTGRES_USER: ${POSTGRES_USER:-root}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-root}
DATABASE_URL: ${DATABASE_URL:-postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db/${POSTGRES_DB}}
DATABASE_URL: ${DATABASE_URL:-postgresql://${POSTGRES_USER:-root}:${POSTGRES_PASSWORD:-root}@db/${POSTGRES_DB:-willa}}
ports:
- 5432:5432
volumes:
- ./.data/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-root} -d ${POSTGRES_DB:-willa}"]
interval: 10s
timeout: 5s
retries: 5
Expand Down