From cb380f877dca7c1ee4291180841e03be45c058a1 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 26 May 2026 16:51:33 +0800 Subject: [PATCH 01/54] switch to uv-build --- pyproject.toml | 6 +++--- uv.lock | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 31c26b6..12775d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "api_demo_server" -dynamic = ["version"] +version = "2.0.0.dev0" description = "FastAPI demo server" authors = [ { name = "The Charm Tech team at Canonical Ltd." }, @@ -26,9 +26,9 @@ dev = [ [build-system] requires = [ - "flit_core >=3.2,<4", + "uv_build>=0.11.16,<1", ] -build-backend = "flit_core.buildapi" +build-backend = "uv_build" [tool.flit.module] name = "api_demo_server" diff --git a/uv.lock b/uv.lock index f4c9f31..4c49d96 100644 --- a/uv.lock +++ b/uv.lock @@ -36,6 +36,7 @@ wheels = [ [[package]] name = "api-demo-server" +version = "2.0.0.dev0" source = { editable = "." } dependencies = [ { name = "fastapi" }, From e63f2ca11c259ff4ffe642cfb3976d5a0496104f Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 26 May 2026 16:52:15 +0800 Subject: [PATCH 02/54] replace Dockerfile by rockcraft.yaml --- .gitignore | 1 + Dockerfile | 16 ---------------- rockcraft.yaml | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 .gitignore delete mode 100644 Dockerfile create mode 100644 rockcraft.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43c6239 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.rock diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index de0792b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM ubuntu:22.04 -LABEL org.opencontainers.image.source=https://github.com/canonical/api_demo_server - -RUN apt-get update && apt-get install -y \ - python3.10 \ - python3-pip -COPY ./pyproject.toml . -COPY ./LICENSE . - -# create dummy project folder just to keep the layer with dependencies untouched until pyproject is changed -RUN mkdir -p src/api_demo_server && echo "__version__ = '0.0.1.dev0'" > src/api_demo_server/__init__.py -RUN python3 -m pip install . -COPY ./src/api_demo_server ./src/api_demo_server -WORKDIR /src -EXPOSE 8000 -ENTRYPOINT ["uvicorn", "api_demo_server.app:app", "--host=0.0.0.0"] diff --git a/rockcraft.yaml b/rockcraft.yaml new file mode 100644 index 0000000..c315a8d --- /dev/null +++ b/rockcraft.yaml @@ -0,0 +1,39 @@ +name: api-demo-server +base: ubuntu@24.04 +version: "2.0.0.dev0" +summary: FastAPI demo server +description: A web server that can be integrated with a PostgreSQL database. +license: Apache-2.0 +platforms: + amd64: + arm64: + ppc64el: + +parts: + api-demo-server: + source: . + plugin: uv + build-snaps: + - astral-uv + stage-packages: + - python3-venv + +services: + api-demo-server: + override: replace + summary: FastAPI demo server + command: /bin/uvicorn api_demo_server.app:app --host 0.0.0.0 --port 8000 + startup: enabled + on-check-failure: + api-demo-server-up: restart + +# This check is internal to the container. +# It doesn't mean the container is ready to process external requests. +checks: + api-demo-server-up: + override: replace + period: 1s + timeout: 5s + threshold: 5 + http: + url: http://127.0.0.1:8000/version From 919bf5aac899d832d4d9d3a8cde73b834aed5d7a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 26 May 2026 16:52:31 +0800 Subject: [PATCH 03/54] update integration test --- .github/workflows/integration-test.yaml | 6 ++++++ .scripts/integration-test.sh | 22 +++++++++++++++++++++- compose.yaml | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index d64d9c9..914a9a5 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -16,5 +16,11 @@ jobs: with: persist-credentials: false + - name: Install Rockcraft + run: sudo snap install rockcraft --classic + + - name: Pack the rock + run: rockcraft pack + - name: Run the integration test run: make integration diff --git a/.scripts/integration-test.sh b/.scripts/integration-test.sh index d6e4c10..701a100 100755 --- a/.scripts/integration-test.sh +++ b/.scripts/integration-test.sh @@ -5,9 +5,29 @@ cleanup_docker() { docker compose down --volumes --remove-orphans } +rock_path="${ROCK_PATH:-}" +if [[ -z "$rock_path" ]]; then + set -- ./*.rock + if [[ "$1" == "./*.rock" ]]; then + echo "No rocks were found in $(pwd)" >&2 + exit 1 + elif [[ $# -gt 1 ]]; then + echo "Found more than one rock $*" >&2 + exit 1 + else + rock_path="$1" + fi +fi +if [[ ! -f "$rock_path" ]]; then + echo "$rock_path is not a file" >&2 + exit 1 +fi + set -x +rockcraft.skopeo --insecure-policy \ + copy "oci-archive:${rock_path}" "docker-daemon:api-demo-server:integration" trap cleanup_docker EXIT -docker compose up --build --detach --wait +docker compose up --detach --wait curl --silent --fail --request POST http://localhost:8000/createtable curl --silent --fail --request POST http://localhost:8000/addname/ --data "name=Alice" response="$(curl --silent --fail http://localhost:8000/names)" diff --git a/compose.yaml b/compose.yaml index 625fc22..c4681de 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,7 +10,7 @@ services: retries: 10 demo-server: - build: . + image: api-demo-server:integration environment: DEMO_SERVER_DB_HOST: postgres ports: From 973314b11821c766dd14b58b27dbae4ab4f0040a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 26 May 2026 17:09:57 +0800 Subject: [PATCH 04/54] install LXD in CI --- .github/workflows/integration-test.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 914a9a5..68bd05c 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -19,8 +19,15 @@ jobs: - name: Install Rockcraft run: sudo snap install rockcraft --classic + - name: Install LXD + run: | + sudo snap install lxd + sudo lxd waitready + sudo lxd init --auto + sudo usermod --append --groups lxd "$USER" + - name: Pack the rock - run: rockcraft pack + run: sg lxd -c 'rockcraft pack' - name: Run the integration test run: make integration From 3320cffd6ec3955c09b6e691dbbb87296bb5737a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 26 May 2026 17:12:12 +0800 Subject: [PATCH 05/54] prepare LXD (no need to install) --- .github/workflows/integration-test.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 68bd05c..fff23e0 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -19,9 +19,8 @@ jobs: - name: Install Rockcraft run: sudo snap install rockcraft --classic - - name: Install LXD + - name: Prepare LXD run: | - sudo snap install lxd sudo lxd waitready sudo lxd init --auto sudo usermod --append --groups lxd "$USER" From f38ea8bf0c912a49a638d3f6cbcf1698d55e9b37 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 26 May 2026 17:48:54 +0800 Subject: [PATCH 06/54] use craft-actions instead of manually configuring the runner --- .github/workflows/integration-test.yaml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index fff23e0..df6ba51 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -16,17 +16,8 @@ jobs: with: persist-credentials: false - - name: Install Rockcraft - run: sudo snap install rockcraft --classic - - - name: Prepare LXD - run: | - sudo lxd waitready - sudo lxd init --auto - sudo usermod --append --groups lxd "$USER" - - name: Pack the rock - run: sg lxd -c 'rockcraft pack' + uses: canonical/craft-actions/rockcraft-pack@210d55fa6cb7a1ab39e3194315d6a03249af3d34 - name: Run the integration test run: make integration From d0d38405edef828cf95b3cac84079e1aed0b0d0d Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 27 May 2026 16:14:55 +0800 Subject: [PATCH 07/54] restructure integration test --- .scripts/{integration-test.sh => register-rock.sh} | 13 +------------ .scripts/test-compose.sh | 14 ++++++++++++++ Makefile | 3 ++- compose.yaml | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) rename .scripts/{integration-test.sh => register-rock.sh} (55%) create mode 100755 .scripts/test-compose.sh diff --git a/.scripts/integration-test.sh b/.scripts/register-rock.sh similarity index 55% rename from .scripts/integration-test.sh rename to .scripts/register-rock.sh index 701a100..6e8ad4b 100755 --- a/.scripts/integration-test.sh +++ b/.scripts/register-rock.sh @@ -1,10 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -cleanup_docker() { - docker compose down --volumes --remove-orphans -} - rock_path="${ROCK_PATH:-}" if [[ -z "$rock_path" ]]; then set -- ./*.rock @@ -25,11 +21,4 @@ fi set -x rockcraft.skopeo --insecure-policy \ - copy "oci-archive:${rock_path}" "docker-daemon:api-demo-server:integration" -trap cleanup_docker EXIT -docker compose up --detach --wait -curl --silent --fail --request POST http://localhost:8000/createtable -curl --silent --fail --request POST http://localhost:8000/addname/ --data "name=Alice" -response="$(curl --silent --fail http://localhost:8000/names)" -echo "Response: $response" -echo "$response" | grep --quiet "Alice" + copy "oci-archive:${rock_path}" "docker-daemon:api-demo-server:latest" diff --git a/.scripts/test-compose.sh b/.scripts/test-compose.sh new file mode 100755 index 0000000..888ca86 --- /dev/null +++ b/.scripts/test-compose.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +cleanup_docker() { + docker compose down --volumes --remove-orphans +} + +trap cleanup_docker EXIT +docker compose up --detach --wait +curl --silent --fail --request POST http://localhost:8000/createtable +curl --silent --fail --request POST http://localhost:8000/addname/ --data "name=Alice" +response="$(curl --silent --fail http://localhost:8000/names)" +echo "Response: $response" +echo "$response" | grep --quiet "Alice" diff --git a/Makefile b/Makefile index 9cf0522..d7b9db7 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,5 @@ lint: uv run ty check integration: - .scripts/integration-test.sh + .scripts/register-rock.sh + .scripts/test-compose.sh diff --git a/compose.yaml b/compose.yaml index c4681de..e989da6 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,7 +10,7 @@ services: retries: 10 demo-server: - image: api-demo-server:integration + image: api-demo-server:latest environment: DEMO_SERVER_DB_HOST: postgres ports: From 1c08c7d0cbf6fb786c6b11e2bd35ebb04cc3284c Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 27 May 2026 17:49:32 +0800 Subject: [PATCH 08/54] revert to single test file --- .scripts/register-rock.sh | 24 ------------------------ .scripts/test-compose.sh | 23 ++++++++++++++++++++++- Makefile | 1 - 3 files changed, 22 insertions(+), 26 deletions(-) delete mode 100755 .scripts/register-rock.sh diff --git a/.scripts/register-rock.sh b/.scripts/register-rock.sh deleted file mode 100755 index 6e8ad4b..0000000 --- a/.scripts/register-rock.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -rock_path="${ROCK_PATH:-}" -if [[ -z "$rock_path" ]]; then - set -- ./*.rock - if [[ "$1" == "./*.rock" ]]; then - echo "No rocks were found in $(pwd)" >&2 - exit 1 - elif [[ $# -gt 1 ]]; then - echo "Found more than one rock $*" >&2 - exit 1 - else - rock_path="$1" - fi -fi -if [[ ! -f "$rock_path" ]]; then - echo "$rock_path is not a file" >&2 - exit 1 -fi - -set -x -rockcraft.skopeo --insecure-policy \ - copy "oci-archive:${rock_path}" "docker-daemon:api-demo-server:latest" diff --git a/.scripts/test-compose.sh b/.scripts/test-compose.sh index 888ca86..ec196b3 100755 --- a/.scripts/test-compose.sh +++ b/.scripts/test-compose.sh @@ -1,10 +1,31 @@ #!/usr/bin/env bash -set -xeuo pipefail +set -euo pipefail cleanup_docker() { docker compose down --volumes --remove-orphans } +rock_path="${ROCK_PATH:-}" +if [[ -z "$rock_path" ]]; then + set -- ./*.rock + if [[ "$1" == "./*.rock" ]]; then + echo "No rocks were found in $(pwd)" >&2 + exit 1 + elif [[ $# -gt 1 ]]; then + echo "Found more than one rock $*" >&2 + exit 1 + else + rock_path="$1" + fi +fi +if [[ ! -f "$rock_path" ]]; then + echo "$rock_path is not a file" >&2 + exit 1 +fi + +set -x +rockcraft.skopeo --insecure-policy \ + copy "oci-archive:${rock_path}" "docker-daemon:api-demo-server:latest" trap cleanup_docker EXIT docker compose up --detach --wait curl --silent --fail --request POST http://localhost:8000/createtable diff --git a/Makefile b/Makefile index d7b9db7..08af698 100644 --- a/Makefile +++ b/Makefile @@ -11,5 +11,4 @@ lint: uv run ty check integration: - .scripts/register-rock.sh .scripts/test-compose.sh From c8266dfb481bff172d83b05a270ae492f2f6d8ad Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 27 May 2026 17:49:53 +0800 Subject: [PATCH 09/54] add spread config --- .gitignore | 2 + spread.yaml | 27 ++++ spread/.extension | 195 +++++++++++++++++++++++++++++ spread/test-compose/task/task.yaml | 5 + 4 files changed, 229 insertions(+) create mode 100644 spread.yaml create mode 100755 spread/.extension create mode 100644 spread/test-compose/task/task.yaml diff --git a/.gitignore b/.gitignore index 43c6239..a3d1c28 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.rock +.craft-spread-* +.spread-reuse.* diff --git a/spread.yaml b/spread.yaml new file mode 100644 index 0000000..805adf7 --- /dev/null +++ b/spread.yaml @@ -0,0 +1,27 @@ +project: api-demo-server + +backends: + craft: + type: craft + systems: + - ubuntu-24.04: + +suites: + spread/test-compose/: + summary: Test the integration with PostgreSQL + + prepare: | + sudo snap install docker + + # For 'rockcraft.skopeo' + sudo snap install --classic rockcraft + + # Wait for docker daemon to come online + sudo apt install --yes retry + retry --times=10 --delay 2 -- docker run hello-world + sudo apt remove --yes retry + +exclude: + - .git + +kill-timeout: 1h diff --git a/spread/.extension b/spread/.extension new file mode 100755 index 0000000..e2e538b --- /dev/null +++ b/spread/.extension @@ -0,0 +1,195 @@ +#!/bin/bash + +usage() { + echo "usage: $(basename "$0") [command]" + echo "valid commands:" + echo " allocate Create a backend instance to run tests on" + echo " discard Destroy a backend instance used to run tests" + echo " backend-prepare Set up the system to run tests" + echo " backend-restore Restore the system after the tests ran" + echo " backend-prepare-each Prepare the system before each test" + echo " backend-restore-each Restore the system after each test run" +} + +prepare() { + case "$SPREAD_SYSTEM" in + fedora*) + dnf update -y + dnf install -y snapd + while ! snap install snapd; do + echo "waiting for snapd..." + sleep 2 + done + ;; + debian*) + apt update + apt install -y snapd + while ! snap install snapd; do + echo "waiting for snapd..." + sleep 2 + done + ;; + ubuntu*) + apt update + ;; + esac + + snap wait system seed.loaded + snap refresh --hold + + if systemctl is-enabled unattended-upgrades.service; then + systemctl disable --now unattended-upgrades.service + systemctl mask unattended-upgrades.service + fi +} + +restore() { + case "$SPREAD_SYSTEM" in + ubuntu* | debian*) + apt autoremove -y --purge + ;; + esac + + rm -Rf "$PROJECT_PATH" + mkdir -p "$PROJECT_PATH" +} + +prepare_each() { + true +} + +restore_each() { + true +} + +allocate_lxdvm() { + name=$(echo "$SPREAD_SYSTEM" | tr '[:punct:]' -) + system=$(echo "$SPREAD_SYSTEM" | tr / -) + if [[ "$system" =~ ^ubuntu- ]]; then + image="ubuntu:${system#ubuntu-}" + else + image="images:$(echo "$system" | tr - /)" + fi + + VM_NAME="${VM_NAME:-spread-${name}-${RANDOM}}" + DISK="${DISK:-20}" + CPU="${CPU:-4}" + MEM="${MEM:-8}" + + lxc launch --vm \ + "${image}" \ + "${VM_NAME}" \ + -c limits.cpu="${CPU}" \ + -c limits.memory="${MEM}GiB" \ + -d root,size="${DISK}GiB" + + while ! lxc exec "${VM_NAME}" -- true &>/dev/null; do sleep 0.5; done + lxc exec "${VM_NAME}" -- sed -i 's/^\s*#\?\s*\(PermitRootLogin\|PasswordAuthentication\)\>.*/\1 yes/' /etc/ssh/sshd_config + lxc exec "${VM_NAME}" -- bash -c "if [ -d /etc/ssh/sshd_config.d ]; then echo -e 'PermitRootLogin yes\nPasswordAuthentication yes' > /etc/ssh/sshd_config.d/00-spread.conf; fi" + lxc exec "${VM_NAME}" -- bash -c "echo root:${SPREAD_PASSWORD} | sudo chpasswd || true" + + # Print the instance address to stdout + ADDR="" + while [ -z "$ADDR" ]; do ADDR=$(lxc ls -f csv | grep "^${VM_NAME}" | cut -d"," -f3 | cut -d" " -f1); done + echo "$ADDR" 1>&3 +} + +discard_lxdvm() { + instance_name="$(lxc ls -f csv | sed ':a;N;$!ba;s/(docker0)\n/(docker0) /' | grep "$SPREAD_SYSTEM_ADDRESS " | cut -f1 -d",")" + lxc delete -f "$instance_name" +} + +allocate_ci() { + if [ -z "$CI" ]; then + echo "This backend is intended to be used only in CI systems." + exit 1 + fi + sudo sed -i 's/^\s*#\?\s*\(PermitRootLogin\|PasswordAuthentication\)\>.*/\1 yes/' /etc/ssh/sshd_config + if [ -d /etc/ssh/sshd_config.d ]; then echo -e 'PermitRootLogin yes\nPasswordAuthentication yes' | sudo tee /etc/ssh/sshd_config.d/00-spread.conf; fi + sudo systemctl daemon-reload + sudo systemctl restart ssh + + echo "root:${SPREAD_PASSWORD}" | sudo chpasswd || true + + # Print the instance address to stdout + echo localhost >&3 +} + +discard_ci() { + true +} + +allocate() { + exec 3>&1 + exec 1>&2 + + case "$1" in + lxd-vm) + allocate_lxdvm + ;; + ci) + allocate_ci + ;; + *) + echo "unsupported backend $1" 2>&1 + ;; + esac +} + +discard() { + case "$1" in + lxd-vm) + discard_lxdvm + ;; + ci) + discard_ci + ;; + *) + echo "unsupported backend $1" 2>&1 + ;; + esac +} + +set -e + +while getopts "" o; do + case "${o}" in + *) + usage + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +CMD="$1" +PARM="$2" + +if [ -z "$CMD" ]; then + usage + exit 0 +fi + +case "$CMD" in +allocate) + allocate "$PARM" + ;; +discard) + discard "$PARM" + ;; +backend-prepare) + prepare + ;; +backend-restore) + restore + ;; +backend-prepare-each) + prepare_each + ;; +backend-restore-each) + restore_each + ;; +*) + echo "unknown command $CMD" >&2 + ;; +esac diff --git a/spread/test-compose/task/task.yaml b/spread/test-compose/task/task.yaml new file mode 100644 index 0000000..1fa4c92 --- /dev/null +++ b/spread/test-compose/task/task.yaml @@ -0,0 +1,5 @@ +summary: Compose and verify + +execute: | + cd "$PROJECT_PATH" + ROCK_PATH="$CRAFT_ARTIFACT" .scripts/test-compose.sh From 180078bab0e24735ebc54fa618ec05b0b349a0af Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 27 May 2026 17:59:45 +0800 Subject: [PATCH 10/54] reduce changes --- .scripts/{test-compose.sh => integration-test.sh} | 2 +- Makefile | 2 +- compose.yaml | 2 +- spread.yaml | 4 ++-- spread/integration-test/task/task.yaml | 5 +++++ spread/test-compose/task/task.yaml | 5 ----- 6 files changed, 10 insertions(+), 10 deletions(-) rename .scripts/{test-compose.sh => integration-test.sh} (98%) create mode 100644 spread/integration-test/task/task.yaml delete mode 100644 spread/test-compose/task/task.yaml diff --git a/.scripts/test-compose.sh b/.scripts/integration-test.sh similarity index 98% rename from .scripts/test-compose.sh rename to .scripts/integration-test.sh index ec196b3..701a100 100755 --- a/.scripts/test-compose.sh +++ b/.scripts/integration-test.sh @@ -25,7 +25,7 @@ fi set -x rockcraft.skopeo --insecure-policy \ - copy "oci-archive:${rock_path}" "docker-daemon:api-demo-server:latest" + copy "oci-archive:${rock_path}" "docker-daemon:api-demo-server:integration" trap cleanup_docker EXIT docker compose up --detach --wait curl --silent --fail --request POST http://localhost:8000/createtable diff --git a/Makefile b/Makefile index 08af698..9cf0522 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,4 @@ lint: uv run ty check integration: - .scripts/test-compose.sh + .scripts/integration-test.sh diff --git a/compose.yaml b/compose.yaml index e989da6..c4681de 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,7 +10,7 @@ services: retries: 10 demo-server: - image: api-demo-server:latest + image: api-demo-server:integration environment: DEMO_SERVER_DB_HOST: postgres ports: diff --git a/spread.yaml b/spread.yaml index 805adf7..d29a6c0 100644 --- a/spread.yaml +++ b/spread.yaml @@ -7,8 +7,8 @@ backends: - ubuntu-24.04: suites: - spread/test-compose/: - summary: Test the integration with PostgreSQL + spread/integration-test/: + summary: Environment for the integration test prepare: | sudo snap install docker diff --git a/spread/integration-test/task/task.yaml b/spread/integration-test/task/task.yaml new file mode 100644 index 0000000..4ca6349 --- /dev/null +++ b/spread/integration-test/task/task.yaml @@ -0,0 +1,5 @@ +summary: Integration test + +execute: | + cd "$PROJECT_PATH" + ROCK_PATH="$CRAFT_ARTIFACT" make integration diff --git a/spread/test-compose/task/task.yaml b/spread/test-compose/task/task.yaml deleted file mode 100644 index 1fa4c92..0000000 --- a/spread/test-compose/task/task.yaml +++ /dev/null @@ -1,5 +0,0 @@ -summary: Compose and verify - -execute: | - cd "$PROJECT_PATH" - ROCK_PATH="$CRAFT_ARTIFACT" .scripts/test-compose.sh From b09c8ec6dbf01e72cb4da91c10277ce4f23518a5 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 27 May 2026 18:14:50 +0800 Subject: [PATCH 11/54] make is not installed in the spread runner --- spread/integration-test/task/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread/integration-test/task/task.yaml b/spread/integration-test/task/task.yaml index 4ca6349..23fcaf4 100644 --- a/spread/integration-test/task/task.yaml +++ b/spread/integration-test/task/task.yaml @@ -2,4 +2,4 @@ summary: Integration test execute: | cd "$PROJECT_PATH" - ROCK_PATH="$CRAFT_ARTIFACT" make integration + ROCK_PATH="$CRAFT_ARTIFACT" .scripts/integration-test.sh From 38ea980719e73498a149bba87f121e954809db27 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 1 Jun 2026 10:28:52 +0800 Subject: [PATCH 12/54] rewrite README --- README.md | 100 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 3905df1..4e39d84 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,55 @@ -# Description -This is a demo server based on Python FastAPI. -Server is used to show connections to PostgreSQL and Prometheus. - -To see API reference start the server and open: http://127.0.0.1:8000/docs -To get prometheus metrics: http://127.0.0.1:8000/metrics - -# Usage -Download and start PostgreSQL container: -``` -docker run --name postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres -``` - -Get psql container IP: -``` -docker ps # get postgres container ID -docker inspect | grep IPAddress -``` - -Build a docker container via: -``` -docker build -t api_demo_server . -``` - -Start demo server: -``` -docker run --rm -e DEMO_SERVER_DB_HOST= -p 8000:8000 api_demo_server -``` - -# Configuration via environment variables -You can configure application by applying following environment variables: - -| Environment Variable | Value | Description | -|------------------------- |------------------- |------------------------------------------------- | -| DEMO_SERVER_LOGFILE | \ | Path to the file where logs should be written | -| DEMO_SERVER_DB_HOST | \ | IP address of the host where Database is hosted | -| DEMO_SERVER_DB_PORT | \ | Port of the host where Database is hosted | -| DEMO_SERVER_DB_USER | \ | Username that has access to `names` Database | -| DEMO_SERVER_DB_PASSWORD | \ | Password to the `DEMO_SERVER_DB_USER` user | - -# Publish to registry - -``` -docker buildx build -t ghcr.io/canonical/api_demo_server:1.0.4 --platform linux/amd64,linux/arm64,linux/ppc64le --push . -``` +This is a demo Python server written with FastAPI. The server connects to PostgreSQL and exposes Prometheus metrics. + +The server is packaged as a "rock" using Canonical's OCI-compliant format for container images. + +# Build and run the container image + +You'll need [Rockcraft](https://documentation.ubuntu.com/rockcraft/stable/) and [Docker](https://docs.docker.com/). + +1. Build the container image from source: + + ```text + rockcraft pack + ``` + + This creates a `.rock` file. The name of the `.rock` file depends on your system architecture. + +2. Make the container image available to Docker: + + ```text + rockcraft.skopeo --insecure-policy \ + copy "oci-archive:" "docker-daemon:api-demo-server:integration" + ``` + + Where `` is the path to the `.rock` file. + +3. Run the container image alongside PostgreSQL: + + ```text + docker compose up + ``` + +4. In a separate terminal, check that the server is available: + + ```text + curl http://localhost:8000/version + ``` + + This returns a JSON object that contains the server's version number. + +The server has several other API endpoints, including: + +- API docs - http://localhost:8000/docs +- Prometheus metrics - http://localhost:8000/metrics + +# Deploy the container image as a Juju charm + +See [From zero to hero: Write your first Kubernetes charm](https://documentation.ubuntu.com/ops/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/) + +# Server environment variables + +- `DEMO_SERVER_LOGFILE` - Path to the file where logs should be written +- `DEMO_SERVER_DB_HOST` - IP address of the database host +- `DEMO_SERVER_DB_PORT` - Port of the database host +- `DEMO_SERVER_DB_USER` - Username that has access to the database +- `DEMO_SERVER_DB_PASSWORD` - Password for the `DEMO_SERVER_DB_USER` user From 3031db7236fec1ca2ae244f8323788d20917cf1a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 1 Jun 2026 11:08:03 +0800 Subject: [PATCH 13/54] add testing details to README --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e39d84..a4ca7e0 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,18 @@ The server has several other API endpoints, including: - API docs - http://localhost:8000/docs - Prometheus metrics - http://localhost:8000/metrics -# Deploy the container image as a Juju charm +# Run automated tests -See [From zero to hero: Write your first Kubernetes charm](https://documentation.ubuntu.com/ops/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/) +If you modify the server, rebuild the container image and run the PostgreSQL integration tests: + +```text +rockcraft pack +make integration +``` + +The integration tests require Docker. + +Alternatively, run `rockcraft test`. This builds the container image and runs the integration tests inside a LXD container. It's slower, but you don't need Docker on your system. # Server environment variables @@ -53,3 +62,7 @@ See [From zero to hero: Write your first Kubernetes charm](https://documentation - `DEMO_SERVER_DB_PORT` - Port of the database host - `DEMO_SERVER_DB_USER` - Username that has access to the database - `DEMO_SERVER_DB_PASSWORD` - Password for the `DEMO_SERVER_DB_USER` user + +# Deploy the container image as a Juju charm + +See [From zero to hero: Write your first Kubernetes charm](https://documentation.ubuntu.com/ops/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/) From 38d321dc2e966f7f8a00670b57b0790a22dcc31a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 1 Jun 2026 12:45:52 +0800 Subject: [PATCH 14/54] update dependabot config --- .github/dependabot.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 941ab1a..43548cf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,15 +8,7 @@ updates: - "dependencies" cooldown: default-days: 7 - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "monthly" - labels: - - "dependencies" - cooldown: - default-days: 7 - - package-ecosystem: "docker" + - package-ecosystem: "uv" directory: "/" schedule: interval: "monthly" From 0d6ff25dea7914048735acf921adaae86437a5eb Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 1 Jun 2026 12:52:42 +0800 Subject: [PATCH 15/54] bump uvicorn --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 12775d2..60ee4e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ license-files = ["LICENSE"] requires-python = ">=3.10" dependencies = [ "fastapi==0.136.1", - "uvicorn==0.46.0", + "uvicorn==0.48.0", "python-multipart==0.0.31", "psycopg2-binary==2.9.12", "starlette==1.3.1", diff --git a/uv.lock b/uv.lock index 4c49d96..d0574d5 100644 --- a/uv.lock +++ b/uv.lock @@ -61,7 +61,7 @@ requires-dist = [ { name = "python-multipart", specifier = "==0.0.31" }, { name = "starlette", specifier = "==1.3.1" }, { name = "starlette-exporter", specifier = "==0.23.0" }, - { name = "uvicorn", specifier = "==0.46.0" }, + { name = "uvicorn", specifier = "==0.48.0" }, ] [package.metadata.requires-dev] @@ -951,14 +951,14 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.46.0" +version = "0.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" }, ] From 6bff3d121e36e417d94774fa5ec2bb4a83b87458 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 1 Jun 2026 12:57:32 +0800 Subject: [PATCH 16/54] bump fastapi and python-multipart --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 60ee4e7..f2e08c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ license = "Apache-2.0" license-files = ["LICENSE"] requires-python = ">=3.10" dependencies = [ - "fastapi==0.136.1", + "fastapi==0.136.3", "uvicorn==0.48.0", "python-multipart==0.0.31", "psycopg2-binary==2.9.12", diff --git a/uv.lock b/uv.lock index d0574d5..62bff94 100644 --- a/uv.lock +++ b/uv.lock @@ -56,7 +56,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "fastapi", specifier = "==0.136.1" }, + { name = "fastapi", specifier = "==0.136.3" }, { name = "psycopg2-binary", specifier = "==2.9.12" }, { name = "python-multipart", specifier = "==0.0.31" }, { name = "starlette", specifier = "==1.3.1" }, @@ -272,7 +272,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.136.1" +version = "0.136.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -281,9 +281,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, + { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, ] [[package]] From 3540de0a9d8a5000fa8915612f3919d707b38e8f Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 16 Jun 2026 07:31:44 +0800 Subject: [PATCH 17/54] try build workflow on PRs --- .github/workflows/build.yaml | 48 ++++++++++-------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7012e34..5fea938 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,43 +1,21 @@ -name: Build and Publish Multi-arch Docker Image +name: Build Rock on: push: tags: - '*' + pull_request: + branches: + - main -permissions: {} +permissions: + contents: read jobs: - build-and-publish: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - - name: Login to GitHub Container Registry - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and Push Docker Image - env: - REF_NAME: ${{ github.ref_name }} - run: | - docker buildx build \ - -t "ghcr.io/canonical/api_demo_server:${REF_NAME}" \ - --platform linux/amd64,linux/arm64,linux/ppc64le \ - --push . + build-rock: + uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@631b6dde575075c167b77f71619777e27210926a + with: + rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + rock-repo-commit: ${{ github.head_ref || github.ref_name }} + rockfile-directory: . + oci-archive-name: api-demo-server-${{ github.head_ref || github.ref_name }} From 60e30cc4a674f1c0ec53f05c32ed752a7abd66f4 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 16 Jun 2026 07:32:18 +0800 Subject: [PATCH 18/54] remove ppc64el --- rockcraft.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/rockcraft.yaml b/rockcraft.yaml index c315a8d..51e947e 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -7,7 +7,6 @@ license: Apache-2.0 platforms: amd64: arm64: - ppc64el: parts: api-demo-server: From bbb978a377e21fcd5f0ec0b8901209bbdca68f9b Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 16 Jun 2026 07:33:23 +0800 Subject: [PATCH 19/54] fix branch name --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5fea938..d75586e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,7 +6,7 @@ on: - '*' pull_request: branches: - - main + - master permissions: contents: read From 02b8ff2998d21880b8cbcbe37890917df6d4633d Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 16 Jun 2026 07:40:00 +0800 Subject: [PATCH 20/54] add dispatch to workflow --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d75586e..c06b2ba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,6 +4,7 @@ on: push: tags: - '*' + workflow_dispatch: pull_request: branches: - master From 3a287f40ab7eb9ed4964a975b7e546e7616fc16e Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 22 Jun 2026 11:12:12 +0800 Subject: [PATCH 21/54] add workflows from template --- .github/ci.yaml | 43 ++++++++++ .github/workflows/cve-scan.yaml | 35 ++++++++ .github/workflows/image.yaml | 143 ++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 .github/ci.yaml create mode 100644 .github/workflows/cve-scan.yaml create mode 100644 .github/workflows/image.yaml diff --git a/.github/ci.yaml b/.github/ci.yaml new file mode 100644 index 0000000..ae472b2 --- /dev/null +++ b/.github/ci.yaml @@ -0,0 +1,43 @@ +version: 1 +ghcr: + upload: true + cve-scan: true + +registries: + ## Example of specifying registries + # docker.io: + # uri: docker.io/ubuntu + # auth: + # - method: basic + # config: + # username: secrets.DOCKERHUB_USERNAME # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ + # password: secrets.DOCKERHUB_TOKEN # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ + # rocksdev-ecr-public: + # uri: public.ecr.aws/rocksdev + # auth: + # - method: ecr-public + # config: + # region: us-east-1 + # username: secrets.AWS_ACCESS_KEY_ID # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ + # password: secrets.AWS_SECRET_ACCESS_KEY # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ + +images: + - directory: "*" + + ## Example of specifying different registries for a specific rock + # - directory: "my-rock-name/0.1-24.04" + # registries: + # - docker.io + # - rocksdev-ecr-public + + ## Example of specifying pro-services with custom config + # - directory: "my-rock-name/0.1-24.04" + # pro: + # services: + # - esm-apps + # - esm-infra + # config: + # token: secrets.MY_PRO_TOKEN # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ + # artifact-passphrase: secrets.MY_PASSPHRASE # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ + # registries: + # ... diff --git a/.github/workflows/cve-scan.yaml b/.github/workflows/cve-scan.yaml new file mode 100644 index 0000000..82dc9b7 --- /dev/null +++ b/.github/workflows/cve-scan.yaml @@ -0,0 +1,35 @@ +name: Scan CVEs dispatch + +on: + workflow_dispatch: + schedule: + - cron: "0 1 * * *" + +jobs: + + read-config: + runs-on: ubuntu-latest + outputs: + ghcr-scanning: ${{ steps.read-ci-config.outputs.ghcr-cve-scan }} + build-matrix: ${{ steps.read-ci-config.outputs.build-matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Read .github/ci.yaml + id: read-ci-config + uses: canonical/rocks-template-actions/actions/read-ci-config@v1 + + run-scan: + name: Run scan for released images + needs: [read-config] + uses: canonical/oci-factory/.github/workflows/Vulnerability-Scan.yaml@main + strategy: + matrix: ${{ fromJson(needs.read-config.outputs.build-matrix) }} + with: + oci-image-name: ghcr.io/${{ github.repository }}/${{ matrix.name }}:${{ matrix.tag }} + create-issue: true + trivyignore-path: ${{ matrix.directory }}/.trivyignore + permissions: + contents: read + packages: read diff --git a/.github/workflows/image.yaml b/.github/workflows/image.yaml new file mode 100644 index 0000000..2dec9cc --- /dev/null +++ b/.github/workflows/image.yaml @@ -0,0 +1,143 @@ +name: Build and Publish Rocks + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + ghcr-upload: ${{ steps.read-ci-config.outputs.ghcr-upload }} + build-matrix: ${{ steps.read-ci-config.outputs.build-matrix }} + upload-matrix: ${{ steps.read-ci-config.outputs.upload-matrix }} + arch-map: ${{ steps.set-map.outputs.arch-map }} + steps: + - name: Checkout Repository + uses: actions/checkout@v5 + + - name: Read .github/ci.yaml + id: read-ci-config + uses: canonical/rocks-template-actions/actions/read-ci-config@v1 + + - name: Set Architecture Map + id: set-map + run: | + if [[ "${{ github.repository_owner }}" != "canonical" ]]; then + echo 'arch-map={"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' >> $GITHUB_OUTPUT + fi + + + build: + needs: [prepare] + strategy: + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@main + with: + rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + rock-repo-commit: ${{ github.head_ref || github.ref_name }} + rockfile-directory: ${{ matrix.directory }} + oci-archive-name: ${{ matrix.artifact-name }} + arch-map: ${{ needs.prepare.outputs.arch-map }} + rockcraft-test: ${{ matrix.run-tests }} + pro-services: ${{ matrix.pro-services }} + secrets: + source-github-token: ${{ secrets.REPO_CLONER_TOKEN }} + pro-token: ${{ secrets[matrix.pro-token] }} + pro-artifact-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} + + + test: + needs: [prepare, build] + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@main + with: + oci-archive-name: ${{ matrix.artifact-name }} + secrets: + pro-artifact-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} + + + upload-ghcr: + needs: [prepare, test] + runs-on: ubuntu-latest + if: | + needs.prepare.outputs.ghcr-upload == 'true' && + github.event_name != 'pull_request' && + github.ref == 'refs/heads/main' + strategy: + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + fail-fast: false + permissions: + packages: write + steps: + - name: Pre-Check Pro Enabled Rocks + if: ${{ github.event.repository.visibility == 'public' && matrix.pro-services != '' }} + run: | + echo "::warning::Uploading Pro enabled rocks to GHCR is not allowed for public repositories." + + - name: Upload Rock to GHCR + if: ${{ github.event.repository.visibility != 'public' || matrix.pro-services == '' }} + uses: canonical/oci-factory/.github/actions/upload-rock@main + with: + artifact_name: ${{ matrix.artifact-name }} + tags: ${{ matrix.tag }} + name: ${{ matrix.name }} + registry: ghcr.io/${{ github.repository }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + decrypt-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} + + + upload-registries: + needs: [prepare, test] + runs-on: ubuntu-latest + if: | + needs.prepare.outputs.upload-matrix != '{"include": []}' && + github.event_name != 'pull_request' && + github.ref == 'refs/heads/main' + strategy: + matrix: ${{ fromJSON(needs.prepare.outputs.upload-matrix) }} + fail-fast: false + steps: + - name: Prepare ECR Session Token + if: ${{ contains(matrix.registry-auth-method, 'ecr') }} + id: get-ecr-token + env: + AWS_ACCESS_KEY_ID: ${{ secrets[matrix.registry-auth-username] }} + AWS_SECRET_ACCESS_KEY: ${{ secrets[matrix.registry-auth-password] }} + run: | + session_token=$(aws ${{ matrix.registry-auth-method }} \ + get-login-password \ + --region ${{ matrix.registry-auth-region }} \ + ) + echo "::add-mask::$session_token" + echo "aws-session-token=$session_token" >> $GITHUB_OUTPUT + + - name: Upload Rock to ECR + uses: canonical/oci-factory/.github/actions/upload-rock@main + if: ${{ contains(matrix.registry-auth-method, 'ecr') }} + with: + artifact_name: ${{ matrix.artifact-name }} + tags: ${{ matrix.tag }} + name: ${{ matrix.name }} + registry: ${{ matrix.registry-uri }} + username: AWS + password: ${{ steps.get-ecr-token.outputs.aws-session-token }} + decrypt-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} + + - name: Upload Rock to Registry + uses: canonical/oci-factory/.github/actions/upload-rock@main + if: matrix.registry-auth-method == 'basic' + with: + artifact_name: ${{ matrix.artifact-name }} + tags: ${{ matrix.tag }} + name: ${{ matrix.name }} + registry: ${{ matrix.registry-uri }} + username: ${{ secrets[matrix.registry-auth-username] }} + password: ${{ secrets[matrix.registry-auth-password] }} + decrypt-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} From 887b13e1588b54d0c31f444b2dd40a1137d6ef95 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 22 Jun 2026 11:14:12 +0800 Subject: [PATCH 22/54] remove old build workflow --- .github/workflows/build.yaml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/build.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index c06b2ba..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: Build Rock - -on: - push: - tags: - - '*' - workflow_dispatch: - pull_request: - branches: - - master - -permissions: - contents: read - -jobs: - build-rock: - uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@631b6dde575075c167b77f71619777e27210926a - with: - rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - rock-repo-commit: ${{ github.head_ref || github.ref_name }} - rockfile-directory: . - oci-archive-name: api-demo-server-${{ github.head_ref || github.ref_name }} From 52db383df396576831874f691b0c1abd6604b04f Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 22 Jun 2026 11:17:11 +0800 Subject: [PATCH 23/54] main --> master --- .github/workflows/image.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/image.yaml b/.github/workflows/image.yaml index 2dec9cc..522584d 100644 --- a/.github/workflows/image.yaml +++ b/.github/workflows/image.yaml @@ -2,9 +2,9 @@ name: Build and Publish Rocks on: push: - branches: [main] + branches: [master] pull_request: - branches: [main] + branches: [master] workflow_dispatch: jobs: @@ -68,7 +68,7 @@ jobs: if: | needs.prepare.outputs.ghcr-upload == 'true' && github.event_name != 'pull_request' && - github.ref == 'refs/heads/main' + github.ref == 'refs/heads/master' strategy: matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} fail-fast: false @@ -99,7 +99,7 @@ jobs: if: | needs.prepare.outputs.upload-matrix != '{"include": []}' && github.event_name != 'pull_request' && - github.ref == 'refs/heads/main' + github.ref == 'refs/heads/master' strategy: matrix: ${{ fromJSON(needs.prepare.outputs.upload-matrix) }} fail-fast: false From 06f1b1cef80531b09ce050f2ed36658a0eb5907b Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 22 Jun 2026 20:08:53 +0800 Subject: [PATCH 24/54] fix directory in CI --- .github/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci.yaml b/.github/ci.yaml index ae472b2..dc3d3d7 100644 --- a/.github/ci.yaml +++ b/.github/ci.yaml @@ -22,7 +22,7 @@ registries: # password: secrets.AWS_SECRET_ACCESS_KEY # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ images: - - directory: "*" + - directory: "." ## Example of specifying different registries for a specific rock # - directory: "my-rock-name/0.1-24.04" From fd11b1af0ae09f502e7097b2711bf07602cd7dda Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 22 Jun 2026 22:42:49 +0800 Subject: [PATCH 25/54] simplify rock CI --- .github/ci.yaml | 43 ---------- .github/workflows/build.yaml | 75 +++++++++++++++++ .github/workflows/cve-scan.yaml | 35 -------- .github/workflows/image.yaml | 143 -------------------------------- 4 files changed, 75 insertions(+), 221 deletions(-) delete mode 100644 .github/ci.yaml create mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/cve-scan.yaml delete mode 100644 .github/workflows/image.yaml diff --git a/.github/ci.yaml b/.github/ci.yaml deleted file mode 100644 index dc3d3d7..0000000 --- a/.github/ci.yaml +++ /dev/null @@ -1,43 +0,0 @@ -version: 1 -ghcr: - upload: true - cve-scan: true - -registries: - ## Example of specifying registries - # docker.io: - # uri: docker.io/ubuntu - # auth: - # - method: basic - # config: - # username: secrets.DOCKERHUB_USERNAME # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ - # password: secrets.DOCKERHUB_TOKEN # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ - # rocksdev-ecr-public: - # uri: public.ecr.aws/rocksdev - # auth: - # - method: ecr-public - # config: - # region: us-east-1 - # username: secrets.AWS_ACCESS_KEY_ID # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ - # password: secrets.AWS_SECRET_ACCESS_KEY # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ - -images: - - directory: "." - - ## Example of specifying different registries for a specific rock - # - directory: "my-rock-name/0.1-24.04" - # registries: - # - docker.io - # - rocksdev-ecr-public - - ## Example of specifying pro-services with custom config - # - directory: "my-rock-name/0.1-24.04" - # pro: - # services: - # - esm-apps - # - esm-infra - # config: - # token: secrets.MY_PRO_TOKEN # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ - # artifact-passphrase: secrets.MY_PASSPHRASE # @@@@@@ DO NOT PUT ACTUAL CREDENTIALS HERE @@@@@@ - # registries: - # ... diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..e082ace --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,75 @@ +name: Build and publish image + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + ghcr-upload: 'true' + build-matrix: ${{ steps.set-matrix.outputs.build-matrix }} + arch-map: ${{ steps.set-map.outputs.arch-map }} + steps: + - name: Set Build Matrix + id: set-matrix + run: | + echo 'build-matrix={"include":[{"name":"api-demo-server","tag":"2.0.0.dev0-24.04_edge","directory":".","artifact-name":"api-demo-server","run-tests":true,"lfs":false,"lfs-include":""}]}' >> $GITHUB_OUTPUT + + - name: Set Architecture Map + id: set-map + run: | + if [[ "${{ github.repository_owner }}" != "canonical" ]]; then + echo 'arch-map={"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' >> $GITHUB_OUTPUT + fi + + build: + needs: [prepare] + strategy: + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@main + with: + rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + rock-repo-commit: ${{ github.head_ref || github.ref_name }} + rockfile-directory: ${{ matrix.directory }} + oci-archive-name: ${{ matrix.artifact-name }} + arch-map: ${{ needs.prepare.outputs.arch-map }} + rockcraft-test: ${{ matrix.run-tests }} + secrets: + source-github-token: ${{ secrets.REPO_CLONER_TOKEN }} + + test: + needs: [prepare, build] + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@main + with: + oci-archive-name: ${{ matrix.artifact-name }} + + upload-ghcr: + needs: [prepare, test] + runs-on: ubuntu-latest + if: | + needs.prepare.outputs.ghcr-upload == 'true' && + github.event_name != 'pull_request' && + github.ref == 'refs/heads/master' + strategy: + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + fail-fast: false + permissions: + packages: write + steps: + - name: Upload Rock to GHCR + uses: canonical/oci-factory/.github/actions/upload-rock@main + with: + artifact_name: ${{ matrix.artifact-name }} + tags: ${{ matrix.tag }} + name: ${{ matrix.name }} + registry: ghcr.io/${{ github.repository }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/cve-scan.yaml b/.github/workflows/cve-scan.yaml deleted file mode 100644 index 82dc9b7..0000000 --- a/.github/workflows/cve-scan.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: Scan CVEs dispatch - -on: - workflow_dispatch: - schedule: - - cron: "0 1 * * *" - -jobs: - - read-config: - runs-on: ubuntu-latest - outputs: - ghcr-scanning: ${{ steps.read-ci-config.outputs.ghcr-cve-scan }} - build-matrix: ${{ steps.read-ci-config.outputs.build-matrix }} - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Read .github/ci.yaml - id: read-ci-config - uses: canonical/rocks-template-actions/actions/read-ci-config@v1 - - run-scan: - name: Run scan for released images - needs: [read-config] - uses: canonical/oci-factory/.github/workflows/Vulnerability-Scan.yaml@main - strategy: - matrix: ${{ fromJson(needs.read-config.outputs.build-matrix) }} - with: - oci-image-name: ghcr.io/${{ github.repository }}/${{ matrix.name }}:${{ matrix.tag }} - create-issue: true - trivyignore-path: ${{ matrix.directory }}/.trivyignore - permissions: - contents: read - packages: read diff --git a/.github/workflows/image.yaml b/.github/workflows/image.yaml deleted file mode 100644 index 522584d..0000000 --- a/.github/workflows/image.yaml +++ /dev/null @@ -1,143 +0,0 @@ -name: Build and Publish Rocks - -on: - push: - branches: [master] - pull_request: - branches: [master] - workflow_dispatch: - -jobs: - prepare: - runs-on: ubuntu-latest - outputs: - ghcr-upload: ${{ steps.read-ci-config.outputs.ghcr-upload }} - build-matrix: ${{ steps.read-ci-config.outputs.build-matrix }} - upload-matrix: ${{ steps.read-ci-config.outputs.upload-matrix }} - arch-map: ${{ steps.set-map.outputs.arch-map }} - steps: - - name: Checkout Repository - uses: actions/checkout@v5 - - - name: Read .github/ci.yaml - id: read-ci-config - uses: canonical/rocks-template-actions/actions/read-ci-config@v1 - - - name: Set Architecture Map - id: set-map - run: | - if [[ "${{ github.repository_owner }}" != "canonical" ]]; then - echo 'arch-map={"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' >> $GITHUB_OUTPUT - fi - - - build: - needs: [prepare] - strategy: - matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} - uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@main - with: - rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - rock-repo-commit: ${{ github.head_ref || github.ref_name }} - rockfile-directory: ${{ matrix.directory }} - oci-archive-name: ${{ matrix.artifact-name }} - arch-map: ${{ needs.prepare.outputs.arch-map }} - rockcraft-test: ${{ matrix.run-tests }} - pro-services: ${{ matrix.pro-services }} - secrets: - source-github-token: ${{ secrets.REPO_CLONER_TOKEN }} - pro-token: ${{ secrets[matrix.pro-token] }} - pro-artifact-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} - - - test: - needs: [prepare, build] - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} - uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@main - with: - oci-archive-name: ${{ matrix.artifact-name }} - secrets: - pro-artifact-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} - - - upload-ghcr: - needs: [prepare, test] - runs-on: ubuntu-latest - if: | - needs.prepare.outputs.ghcr-upload == 'true' && - github.event_name != 'pull_request' && - github.ref == 'refs/heads/master' - strategy: - matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} - fail-fast: false - permissions: - packages: write - steps: - - name: Pre-Check Pro Enabled Rocks - if: ${{ github.event.repository.visibility == 'public' && matrix.pro-services != '' }} - run: | - echo "::warning::Uploading Pro enabled rocks to GHCR is not allowed for public repositories." - - - name: Upload Rock to GHCR - if: ${{ github.event.repository.visibility != 'public' || matrix.pro-services == '' }} - uses: canonical/oci-factory/.github/actions/upload-rock@main - with: - artifact_name: ${{ matrix.artifact-name }} - tags: ${{ matrix.tag }} - name: ${{ matrix.name }} - registry: ghcr.io/${{ github.repository }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - decrypt-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} - - - upload-registries: - needs: [prepare, test] - runs-on: ubuntu-latest - if: | - needs.prepare.outputs.upload-matrix != '{"include": []}' && - github.event_name != 'pull_request' && - github.ref == 'refs/heads/master' - strategy: - matrix: ${{ fromJSON(needs.prepare.outputs.upload-matrix) }} - fail-fast: false - steps: - - name: Prepare ECR Session Token - if: ${{ contains(matrix.registry-auth-method, 'ecr') }} - id: get-ecr-token - env: - AWS_ACCESS_KEY_ID: ${{ secrets[matrix.registry-auth-username] }} - AWS_SECRET_ACCESS_KEY: ${{ secrets[matrix.registry-auth-password] }} - run: | - session_token=$(aws ${{ matrix.registry-auth-method }} \ - get-login-password \ - --region ${{ matrix.registry-auth-region }} \ - ) - echo "::add-mask::$session_token" - echo "aws-session-token=$session_token" >> $GITHUB_OUTPUT - - - name: Upload Rock to ECR - uses: canonical/oci-factory/.github/actions/upload-rock@main - if: ${{ contains(matrix.registry-auth-method, 'ecr') }} - with: - artifact_name: ${{ matrix.artifact-name }} - tags: ${{ matrix.tag }} - name: ${{ matrix.name }} - registry: ${{ matrix.registry-uri }} - username: AWS - password: ${{ steps.get-ecr-token.outputs.aws-session-token }} - decrypt-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} - - - name: Upload Rock to Registry - uses: canonical/oci-factory/.github/actions/upload-rock@main - if: matrix.registry-auth-method == 'basic' - with: - artifact_name: ${{ matrix.artifact-name }} - tags: ${{ matrix.tag }} - name: ${{ matrix.name }} - registry: ${{ matrix.registry-uri }} - username: ${{ secrets[matrix.registry-auth-username] }} - password: ${{ secrets[matrix.registry-auth-password] }} - decrypt-passphrase: ${{ secrets[matrix.pro-artifact-passphrase] }} From b65ea1e5ffe0041864723dffb7e11553cfcf39e1 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 05:55:42 +0800 Subject: [PATCH 26/54] override OpenSSL from base --- rockcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/rockcraft.yaml b/rockcraft.yaml index 51e947e..4dd11d9 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -16,6 +16,7 @@ parts: - astral-uv stage-packages: - python3-venv + - libssl3t64 # Override package from base (because of CVE-2026-45447). services: api-demo-server: From 9da1803a85aa81b94c4e14b686b43e9c68ecd8f8 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 05:59:35 +0800 Subject: [PATCH 27/54] check then format in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9cf0522..060b7fa 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ MAKEFLAGS += --always-make format: - uv run ruff format uv run ruff check --fix + uv run ruff format lint: uv run ruff check From 25c0967db7e3aebca63d92c1320f1e1688046305 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 06:06:55 +0800 Subject: [PATCH 28/54] overlay instead of stage --- rockcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/rockcraft.yaml b/rockcraft.yaml index 4dd11d9..261494e 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -16,6 +16,7 @@ parts: - astral-uv stage-packages: - python3-venv + overlay-packages: - libssl3t64 # Override package from base (because of CVE-2026-45447). services: From 6f62047c0c20deecf8a41d10031195da802bf54a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 06:56:38 +0800 Subject: [PATCH 29/54] ignore vulnerability instead --- .github/workflows/build.yaml | 2 +- rockcraft.yaml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e082ace..bb7a768 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,7 +19,6 @@ jobs: id: set-matrix run: | echo 'build-matrix={"include":[{"name":"api-demo-server","tag":"2.0.0.dev0-24.04_edge","directory":".","artifact-name":"api-demo-server","run-tests":true,"lfs":false,"lfs-include":""}]}' >> $GITHUB_OUTPUT - - name: Set Architecture Map id: set-map run: | @@ -50,6 +49,7 @@ jobs: uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@main with: oci-archive-name: ${{ matrix.artifact-name }} + ignored-vulnerabilities: CVE-2026-45447 # CVE affects OpenSSL version in Ubuntu base. upload-ghcr: needs: [prepare, test] diff --git a/rockcraft.yaml b/rockcraft.yaml index 261494e..51e947e 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -16,8 +16,6 @@ parts: - astral-uv stage-packages: - python3-venv - overlay-packages: - - libssl3t64 # Override package from base (because of CVE-2026-45447). services: api-demo-server: From af7abe65797912603096ee869098cc9b78e4d640 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 07:13:32 +0800 Subject: [PATCH 30/54] fix build workflow linting --- .github/workflows/build.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bb7a768..baeda7c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -7,6 +7,8 @@ on: branches: [master] workflow_dispatch: +permissions: {} + jobs: prepare: runs-on: ubuntu-latest @@ -28,9 +30,10 @@ jobs: build: needs: [prepare] + permissions: {} strategy: matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} - uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@main + uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de with: rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} rock-repo-commit: ${{ github.head_ref || github.ref_name }} @@ -46,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} - uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@main + uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de with: oci-archive-name: ${{ matrix.artifact-name }} ignored-vulnerabilities: CVE-2026-45447 # CVE affects OpenSSL version in Ubuntu base. @@ -65,7 +68,7 @@ jobs: packages: write steps: - name: Upload Rock to GHCR - uses: canonical/oci-factory/.github/actions/upload-rock@main + uses: canonical/oci-factory/.github/actions/upload-rock@43f2710f69c29e43fd90f9e600896a7e32de96de with: artifact_name: ${{ matrix.artifact-name }} tags: ${{ matrix.tag }} From 9296f07e89281329ae4dcc79c29cb22e96c8ae86 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 07:21:34 +0800 Subject: [PATCH 31/54] switch build workflow back to tag push --- .github/workflows/build.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index baeda7c..aebf709 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,10 +2,8 @@ name: Build and publish image on: push: - branches: [master] - pull_request: - branches: [master] - workflow_dispatch: + tags: + - '*' permissions: {} @@ -20,7 +18,7 @@ jobs: - name: Set Build Matrix id: set-matrix run: | - echo 'build-matrix={"include":[{"name":"api-demo-server","tag":"2.0.0.dev0-24.04_edge","directory":".","artifact-name":"api-demo-server","run-tests":true,"lfs":false,"lfs-include":""}]}' >> $GITHUB_OUTPUT + echo 'build-matrix={"include":[{"name":"api-demo-server","tag":"${{ github.ref_name }}-24.04","directory":".","artifact-name":"api-demo-server","run-tests":true,"lfs":false,"lfs-include":""}]}' >> $GITHUB_OUTPUT - name: Set Architecture Map id: set-map run: | @@ -58,9 +56,7 @@ jobs: needs: [prepare, test] runs-on: ubuntu-latest if: | - needs.prepare.outputs.ghcr-upload == 'true' && - github.event_name != 'pull_request' && - github.ref == 'refs/heads/master' + needs.prepare.outputs.ghcr-upload == 'true' strategy: matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} fail-fast: false From a3fe3efe043c07d3ab31a07c1f01c719c775affd Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 07:30:59 +0800 Subject: [PATCH 32/54] improve JSON building in workflow --- .github/workflows/build.yaml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index aebf709..5e94ac6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,8 +17,23 @@ jobs: steps: - name: Set Build Matrix id: set-matrix + env: + TAG: ${{ github.ref_name }} run: | - echo 'build-matrix={"include":[{"name":"api-demo-server","tag":"${{ github.ref_name }}-24.04","directory":".","artifact-name":"api-demo-server","run-tests":true,"lfs":false,"lfs-include":""}]}' >> $GITHUB_OUTPUT + matrix=$(jq --null-input --compact-output \ + --arg tag "${TAG}-24.04" \ + '{ + include: [{ + name: "api-demo-server", + tag: $tag, + directory: ".", + "artifact-name": "api-demo-server", + "run-tests": true, + lfs: false, + "lfs-include": "" + }] + }') + echo "build-matrix=${matrix}" >> $GITHUB_OUTPUT - name: Set Architecture Map id: set-map run: | From bd01d5cb835c42679276bb8f351290457a47fa15 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 23 Jun 2026 18:34:36 +0800 Subject: [PATCH 33/54] switch to bare base --- rockcraft.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rockcraft.yaml b/rockcraft.yaml index 51e947e..6e22114 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -1,5 +1,6 @@ name: api-demo-server -base: ubuntu@24.04 +base: bare +build-base: ubuntu@24.04 version: "2.0.0.dev0" summary: FastAPI demo server description: A web server that can be integrated with a PostgreSQL database. @@ -15,13 +16,13 @@ parts: build-snaps: - astral-uv stage-packages: - - python3-venv + - python3.12-venv_ensurepip services: api-demo-server: override: replace summary: FastAPI demo server - command: /bin/uvicorn api_demo_server.app:app --host 0.0.0.0 --port 8000 + command: /usr/bin/uvicorn api_demo_server.app:app --host 0.0.0.0 --port 8000 startup: enabled on-check-failure: api-demo-server-up: restart From 76e55bfd9a30c6ea620120a1bf77bedbd1f53b70 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 24 Jun 2026 06:56:34 +0800 Subject: [PATCH 34/54] fix symlink handling --- rockcraft.yaml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/rockcraft.yaml b/rockcraft.yaml index 6e22114..1c580aa 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -10,19 +10,38 @@ platforms: arm64: parts: + python-runtime: + plugin: nil + stage-packages: + - python3.12-venv_ensurepip + # Exclude symlinks lib -> usr/lib and lib64 -> usr/lib64 so they don't interfere + # with uv's venv creation and console scripts. + stage: + - -lib/ + - -lib64/ api-demo-server: + after: + - python-runtime source: . plugin: uv build-snaps: - astral-uv - stage-packages: - - python3.12-venv_ensurepip + fix-symlinks: + plugin: nil + after: + - api-demo-server + override-prime: | + # Fix symlinks so the dynamic linker resolves. + cp --archive --no-clobber ${CRAFT_PRIME}/lib/* ${CRAFT_PRIME}/usr/lib/ + rm -rf ${CRAFT_PRIME}/lib/ ${CRAFT_PRIME}/lib64/ + ln -s usr/lib ${CRAFT_PRIME}/lib + ln -s usr/lib64 ${CRAFT_PRIME}/lib64 services: api-demo-server: override: replace summary: FastAPI demo server - command: /usr/bin/uvicorn api_demo_server.app:app --host 0.0.0.0 --port 8000 + command: /bin/uvicorn api_demo_server.app:app --host 0.0.0.0 --port 8000 startup: enabled on-check-failure: api-demo-server-up: restart From 2a7b6f0c413de0974859623d6f3cfc9bf59cf0a4 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 24 Jun 2026 07:01:48 +0800 Subject: [PATCH 35/54] remove ignored CVE --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5e94ac6..43166d9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -65,7 +65,6 @@ jobs: uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de with: oci-archive-name: ${{ matrix.artifact-name }} - ignored-vulnerabilities: CVE-2026-45447 # CVE affects OpenSSL version in Ubuntu base. upload-ghcr: needs: [prepare, test] From 8cc1708c694fd0ba0ce00c643f97b28cd9867473 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 24 Jun 2026 07:03:37 +0800 Subject: [PATCH 36/54] add dry run build workflow --- .github/workflows/build-dry-run.yaml | 67 ++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/build-dry-run.yaml diff --git a/.github/workflows/build-dry-run.yaml b/.github/workflows/build-dry-run.yaml new file mode 100644 index 0000000..4d35142 --- /dev/null +++ b/.github/workflows/build-dry-run.yaml @@ -0,0 +1,67 @@ +name: Build image (dry run) + +on: + pull_request: + branches: + - master + +permissions: {} + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + ghcr-upload: 'true' + build-matrix: ${{ steps.set-matrix.outputs.build-matrix }} + arch-map: ${{ steps.set-map.outputs.arch-map }} + steps: + - name: Set Build Matrix + id: set-matrix + env: + TAG: 2.0.0.dev0 + run: | + matrix=$(jq --null-input --compact-output \ + --arg tag "${TAG}-24.04" \ + '{ + include: [{ + name: "api-demo-server", + tag: $tag, + directory: ".", + "artifact-name": "api-demo-server", + "run-tests": true, + lfs: false, + "lfs-include": "" + }] + }') + echo "build-matrix=${matrix}" >> $GITHUB_OUTPUT + - name: Set Architecture Map + id: set-map + run: | + if [[ "${{ github.repository_owner }}" != "canonical" ]]; then + echo 'arch-map={"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' >> $GITHUB_OUTPUT + fi + + build: + needs: [prepare] + permissions: {} + strategy: + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de + with: + rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + rock-repo-commit: ${{ github.head_ref || github.ref_name }} + rockfile-directory: ${{ matrix.directory }} + oci-archive-name: ${{ matrix.artifact-name }} + arch-map: ${{ needs.prepare.outputs.arch-map }} + rockcraft-test: ${{ matrix.run-tests }} + secrets: + source-github-token: ${{ secrets.REPO_CLONER_TOKEN }} + + test: + needs: [prepare, build] + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} + uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de + with: + oci-archive-name: ${{ matrix.artifact-name }} From 44c727c78b8c50f32b35127e5d3e40804a96b0e5 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Sun, 28 Jun 2026 09:25:46 +0800 Subject: [PATCH 37/54] adjust parts after feedback --- rockcraft.yaml | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/rockcraft.yaml b/rockcraft.yaml index 1c580aa..77a25cc 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -1,6 +1,7 @@ name: api-demo-server base: bare build-base: ubuntu@24.04 +run-user: _daemon_ version: "2.0.0.dev0" summary: FastAPI demo server description: A web server that can be integrated with a PostgreSQL database. @@ -14,8 +15,8 @@ parts: plugin: nil stage-packages: - python3.12-venv_ensurepip - # Exclude symlinks lib -> usr/lib and lib64 -> usr/lib64 so they don't interfere - # with uv's venv creation and console scripts. + # ^ This sets up symlinks from lib and lib64 to usr/. + # Exclude the symlinks (for now) so they don't interfere with uv. stage: - -lib/ - -lib64/ @@ -26,16 +27,33 @@ parts: plugin: uv build-snaps: - astral-uv - fix-symlinks: + # Copy the venv's lib dir to usr/lib, then exclude lib and lib64. + # The next part will create symlinks from lib and lib64 to usr/. + override-prime: | + craftctl default + cp --archive --no-clobber ${CRAFT_PART_INSTALL}/lib/* ${CRAFT_PRIME}/usr/lib/ + rm -rf ${CRAFT_PRIME}/lib/ ${CRAFT_PRIME}/lib64/ + stage: + - -lib/ + - -lib64/ + runtime-base: plugin: nil after: - api-demo-server - override-prime: | - # Fix symlinks so the dynamic linker resolves. - cp --archive --no-clobber ${CRAFT_PRIME}/lib/* ${CRAFT_PRIME}/usr/lib/ - rm -rf ${CRAFT_PRIME}/lib/ ${CRAFT_PRIME}/lib64/ - ln -s usr/lib ${CRAFT_PRIME}/lib - ln -s usr/lib64 ${CRAFT_PRIME}/lib64 + stage-packages: + # Create symlinks from bin, lib, and lib64 to usr/. + # Also create definitions for the _daemon_ user. + - base-files_base + # Create a chisel manifest for the deb-security-manifest part. + - base-files_chisel + stage: + - -bin/ # We want the venv's bin dir insead of a symlink to usr/bin. + deb-security-manifest: + plugin: make + source: https://github.com/canonical/rocks-security-manifest + source-type: git + source-branch: main + override-prime: gen_manifest services: api-demo-server: @@ -43,6 +61,8 @@ services: summary: FastAPI demo server command: /bin/uvicorn api_demo_server.app:app --host 0.0.0.0 --port 8000 startup: enabled + environment: + DEMO_SERVER_LOGFILE: /tmp/demo_server.log on-check-failure: api-demo-server-up: restart From 639890678e8f33b7a92ce6da644194e4a06b6ea4 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Sun, 28 Jun 2026 09:28:04 +0800 Subject: [PATCH 38/54] adjust service and check names --- rockcraft.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rockcraft.yaml b/rockcraft.yaml index 77a25cc..647e427 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -56,7 +56,7 @@ parts: override-prime: gen_manifest services: - api-demo-server: + fastapi: override: replace summary: FastAPI demo server command: /bin/uvicorn api_demo_server.app:app --host 0.0.0.0 --port 8000 @@ -64,12 +64,12 @@ services: environment: DEMO_SERVER_LOGFILE: /tmp/demo_server.log on-check-failure: - api-demo-server-up: restart + server-up: restart # This check is internal to the container. # It doesn't mean the container is ready to process external requests. checks: - api-demo-server-up: + server-up: override: replace period: 1s timeout: 5s From c932cd581abd29d9e6703436c1319346af4af951 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 29 Jun 2026 10:56:09 +0800 Subject: [PATCH 39/54] remove dry-run workflow --- .github/workflows/build-dry-run.yaml | 67 ---------------------------- 1 file changed, 67 deletions(-) delete mode 100644 .github/workflows/build-dry-run.yaml diff --git a/.github/workflows/build-dry-run.yaml b/.github/workflows/build-dry-run.yaml deleted file mode 100644 index 4d35142..0000000 --- a/.github/workflows/build-dry-run.yaml +++ /dev/null @@ -1,67 +0,0 @@ -name: Build image (dry run) - -on: - pull_request: - branches: - - master - -permissions: {} - -jobs: - prepare: - runs-on: ubuntu-latest - outputs: - ghcr-upload: 'true' - build-matrix: ${{ steps.set-matrix.outputs.build-matrix }} - arch-map: ${{ steps.set-map.outputs.arch-map }} - steps: - - name: Set Build Matrix - id: set-matrix - env: - TAG: 2.0.0.dev0 - run: | - matrix=$(jq --null-input --compact-output \ - --arg tag "${TAG}-24.04" \ - '{ - include: [{ - name: "api-demo-server", - tag: $tag, - directory: ".", - "artifact-name": "api-demo-server", - "run-tests": true, - lfs: false, - "lfs-include": "" - }] - }') - echo "build-matrix=${matrix}" >> $GITHUB_OUTPUT - - name: Set Architecture Map - id: set-map - run: | - if [[ "${{ github.repository_owner }}" != "canonical" ]]; then - echo 'arch-map={"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' >> $GITHUB_OUTPUT - fi - - build: - needs: [prepare] - permissions: {} - strategy: - matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} - uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de - with: - rock-repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - rock-repo-commit: ${{ github.head_ref || github.ref_name }} - rockfile-directory: ${{ matrix.directory }} - oci-archive-name: ${{ matrix.artifact-name }} - arch-map: ${{ needs.prepare.outputs.arch-map }} - rockcraft-test: ${{ matrix.run-tests }} - secrets: - source-github-token: ${{ secrets.REPO_CLONER_TOKEN }} - - test: - needs: [prepare, build] - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} - uses: canonical/oci-factory/.github/workflows/Test-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de - with: - oci-archive-name: ${{ matrix.artifact-name }} From e1803f7e25d458ab32b8c577aeb5f0a1b2981f05 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 29 Jun 2026 15:06:53 +0800 Subject: [PATCH 40/54] bump release version --- pyproject.toml | 2 +- rockcraft.yaml | 2 +- src/api_demo_server/__init__.py | 2 +- uv.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f2e08c9..772be62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "api_demo_server" -version = "2.0.0.dev0" +version = "2.0.0" description = "FastAPI demo server" authors = [ { name = "The Charm Tech team at Canonical Ltd." }, diff --git a/rockcraft.yaml b/rockcraft.yaml index 647e427..4afd6f0 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -2,7 +2,7 @@ name: api-demo-server base: bare build-base: ubuntu@24.04 run-user: _daemon_ -version: "2.0.0.dev0" +version: "2.0.0" summary: FastAPI demo server description: A web server that can be integrated with a PostgreSQL database. license: Apache-2.0 diff --git a/src/api_demo_server/__init__.py b/src/api_demo_server/__init__.py index 9fc20a7..8c0d5d5 100644 --- a/src/api_demo_server/__init__.py +++ b/src/api_demo_server/__init__.py @@ -1 +1 @@ -__version__ = "2.0.0.dev0" +__version__ = "2.0.0" diff --git a/uv.lock b/uv.lock index 62bff94..4f616f3 100644 --- a/uv.lock +++ b/uv.lock @@ -36,7 +36,7 @@ wheels = [ [[package]] name = "api-demo-server" -version = "2.0.0.dev0" +version = "2.0.0" source = { editable = "." } dependencies = [ { name = "fastapi" }, From 0c436ef958d74e5a9f3604f1fd1d6526b1ae09db Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 13:28:58 +0800 Subject: [PATCH 41/54] remove redundant permissions block --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 43166d9..e20d34a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -43,7 +43,6 @@ jobs: build: needs: [prepare] - permissions: {} strategy: matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} uses: canonical/oci-factory/.github/workflows/Build-Rock.yaml@43f2710f69c29e43fd90f9e600896a7e32de96de From a4adb0cceb6ccbc0cbd250d81e81326367c05f0e Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 13:33:23 +0800 Subject: [PATCH 42/54] rename 'build' workflow to 'publish' --- .github/workflows/{build.yaml => publish.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{build.yaml => publish.yaml} (100%) diff --git a/.github/workflows/build.yaml b/.github/workflows/publish.yaml similarity index 100% rename from .github/workflows/build.yaml rename to .github/workflows/publish.yaml From cbc429ad0f36e482042506b2b9ef9edf830fe281 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 13:34:16 +0800 Subject: [PATCH 43/54] remove flit config --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 772be62..6083579 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,8 +30,5 @@ requires = [ ] build-backend = "uv_build" -[tool.flit.module] -name = "api_demo_server" - [tool.ruff] line-length = 99 From 0373f866aff27aea66f49adad3a43ca6f6ebcb3d Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 13:45:05 +0800 Subject: [PATCH 44/54] remove ghcr conditional in publish workflow --- .github/workflows/publish.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index e20d34a..78c7e40 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -11,7 +11,6 @@ jobs: prepare: runs-on: ubuntu-latest outputs: - ghcr-upload: 'true' build-matrix: ${{ steps.set-matrix.outputs.build-matrix }} arch-map: ${{ steps.set-map.outputs.arch-map }} steps: @@ -68,8 +67,6 @@ jobs: upload-ghcr: needs: [prepare, test] runs-on: ubuntu-latest - if: | - needs.prepare.outputs.ghcr-upload == 'true' strategy: matrix: ${{ fromJSON(needs.prepare.outputs.build-matrix) }} fail-fast: false From bc1e086b16f3de2f9f340ef8d70de0e65fe192a0 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 13:46:13 +0800 Subject: [PATCH 45/54] switch to tag for rocks-security-manifest --- rockcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rockcraft.yaml b/rockcraft.yaml index 4afd6f0..134b904 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -52,7 +52,7 @@ parts: plugin: make source: https://github.com/canonical/rocks-security-manifest source-type: git - source-branch: main + source-tag: v0.1.0 override-prime: gen_manifest services: From 5ffc420e53ebc7c468ab11ee522c785a4f69c0e7 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 13:57:28 +0800 Subject: [PATCH 46/54] add note about Docker needing sudo --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a4ca7e0..325db5d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ The server is packaged as a "rock" using Canonical's OCI-compliant format for co You'll need [Rockcraft](https://documentation.ubuntu.com/rockcraft/stable/) and [Docker](https://docs.docker.com/). +By default, Docker requires `sudo`. To use Docker as a regular user, run `sudo usermod -aG docker $USER` then log out and log back in again. + 1. Build the container image from source: ```text From b4d807a6446db70bb32f4a53b53079053b9c941e Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 14:40:52 +0800 Subject: [PATCH 47/54] remove Ubuntu base from tag --- .github/workflows/publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 78c7e40..0b5340a 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,7 +20,7 @@ jobs: TAG: ${{ github.ref_name }} run: | matrix=$(jq --null-input --compact-output \ - --arg tag "${TAG}-24.04" \ + --arg tag "${TAG}" \ '{ include: [{ name: "api-demo-server", From abd1ec84e3f3eacb0f7263d0e57b01385198ac39 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 14:50:23 +0800 Subject: [PATCH 48/54] remove image from Docker after testing --- .scripts/integration-test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.scripts/integration-test.sh b/.scripts/integration-test.sh index 701a100..89c6d96 100755 --- a/.scripts/integration-test.sh +++ b/.scripts/integration-test.sh @@ -3,6 +3,7 @@ set -euo pipefail cleanup_docker() { docker compose down --volumes --remove-orphans + docker image rm api-demo-server:integration } rock_path="${ROCK_PATH:-}" From 89c21931d0b411b56d856c034e15d1df182350ac Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 15:04:47 +0800 Subject: [PATCH 49/54] add comment about runner selection --- .github/workflows/publish.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 0b5340a..be7778e 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -36,6 +36,9 @@ jobs: - name: Set Architecture Map id: set-map run: | + # We prefer to schedule the builds on Canonical's self-hosted runners, bypassing the + # queue for GitHub runners. But if someone forks our repo and tries to build images from + # their fork, they won't be able to access Canonical runners, so use GitHub runners. if [[ "${{ github.repository_owner }}" != "canonical" ]]; then echo 'arch-map={"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' >> $GITHUB_OUTPUT fi From e6373127264987ac864e8cabdd3fd0ef328225cd Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 1 Jul 2026 15:17:59 +0800 Subject: [PATCH 50/54] bump deps and uv action --- .github/workflows/lint.yaml | 2 +- .github/workflows/security.yaml | 2 +- .github/workflows/zizmor.yaml | 2 +- pyproject.toml | 4 ++-- uv.lock | 18 +++++++++--------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 121ae5f..bee8225 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -16,6 +16,6 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - name: Lint the code run: make lint diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index 4149454..aa30a22 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false - - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - name: Export locked dependencies (excluding this project) run: uv export --frozen --no-emit-project --no-hashes --format requirements-txt -o requirements.txt - name: Run pip-audit diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index c7f071c..16c9b83 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -20,7 +20,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - name: Run zizmor run: uvx zizmor@v1.23.1 --format=sarif . > results.sarif diff --git a/pyproject.toml b/pyproject.toml index 6083579..198a4bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,8 @@ license-files = ["LICENSE"] requires-python = ">=3.10" dependencies = [ "fastapi==0.136.3", - "uvicorn==0.48.0", - "python-multipart==0.0.31", + "uvicorn==0.49.0", + "python-multipart==0.0.32", "psycopg2-binary==2.9.12", "starlette==1.3.1", "starlette_exporter==0.23.0" diff --git a/uv.lock b/uv.lock index 4f616f3..a6d6bdd 100644 --- a/uv.lock +++ b/uv.lock @@ -58,10 +58,10 @@ dev = [ requires-dist = [ { name = "fastapi", specifier = "==0.136.3" }, { name = "psycopg2-binary", specifier = "==2.9.12" }, - { name = "python-multipart", specifier = "==0.0.31" }, + { name = "python-multipart", specifier = "==0.0.32" }, { name = "starlette", specifier = "==1.3.1" }, { name = "starlette-exporter", specifier = "==0.23.0" }, - { name = "uvicorn", specifier = "==0.48.0" }, + { name = "uvicorn", specifier = "==0.49.0" }, ] [package.metadata.requires-dev] @@ -263,7 +263,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -736,11 +736,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.31" +version = "0.0.32" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/7e/9b35ad8f3d9ca680f7c87a88f19612fdd8da9796c4d3b46e560ac79dcc4a/python_multipart-0.0.31.tar.gz", hash = "sha256:fc631183bb13e56db3158a4909908dfb2e23565286744e798241e63750e5d680", size = 46689, upload-time = "2026-06-04T08:27:49.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/1e/7f7f299527a5a8ad90acd5f2f78dfa6c8495c6301a3205106ea68a84de96/python_multipart-0.0.31-py3-none-any.whl", hash = "sha256:8408153d68a9773291fc1da39a8b85a50044bddbabd2dd72e9229776b7b15e28", size = 29996, upload-time = "2026-06-04T08:27:47.804Z" }, + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] [[package]] @@ -951,14 +951,14 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.48.0" +version = "0.49.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, ] From 9d0dc8371eb4d9795f17ccc6ae4f0ff4cfdc882c Mon Sep 17 00:00:00 2001 From: David Wilding Date: Thu, 2 Jul 2026 09:57:47 +0800 Subject: [PATCH 51/54] always use GitHub runners --- .github/workflows/publish.yaml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index be7778e..cfe0418 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -12,7 +12,6 @@ jobs: runs-on: ubuntu-latest outputs: build-matrix: ${{ steps.set-matrix.outputs.build-matrix }} - arch-map: ${{ steps.set-map.outputs.arch-map }} steps: - name: Set Build Matrix id: set-matrix @@ -33,14 +32,6 @@ jobs: }] }') echo "build-matrix=${matrix}" >> $GITHUB_OUTPUT - - name: Set Architecture Map - id: set-map - run: | - # We prefer to schedule the builds on Canonical's self-hosted runners, bypassing the - # queue for GitHub runners. But if someone forks our repo and tries to build images from - # their fork, they won't be able to access Canonical runners, so use GitHub runners. - if [[ "${{ github.repository_owner }}" != "canonical" ]]; then - echo 'arch-map={"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' >> $GITHUB_OUTPUT fi build: @@ -53,7 +44,7 @@ jobs: rock-repo-commit: ${{ github.head_ref || github.ref_name }} rockfile-directory: ${{ matrix.directory }} oci-archive-name: ${{ matrix.artifact-name }} - arch-map: ${{ needs.prepare.outputs.arch-map }} + arch-map: '{"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' rockcraft-test: ${{ matrix.run-tests }} secrets: source-github-token: ${{ secrets.REPO_CLONER_TOKEN }} From c51789c9a0aa8b45eb74b651487c179ba1bf1489 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Thu, 2 Jul 2026 10:00:09 +0800 Subject: [PATCH 52/54] add comment about runners --- .github/workflows/publish.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index cfe0418..5e8b58f 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -44,6 +44,8 @@ jobs: rock-repo-commit: ${{ github.head_ref || github.ref_name }} rockfile-directory: ${{ matrix.directory }} oci-archive-name: ${{ matrix.artifact-name }} + # We don't have any special architecture or performance requirements, + # so use GitHub's runners instead of Canonical's self-hosted runners. arch-map: '{"amd64":["ubuntu-24.04"],"arm64":["ubuntu-24.04-arm"]}' rockcraft-test: ${{ matrix.run-tests }} secrets: From 8cf1eefbb987d0910fcadd471da95437242bd6ed Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 7 Jul 2026 09:25:45 +0800 Subject: [PATCH 53/54] for Spread setup, list images instead of running an image --- spread.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spread.yaml b/spread.yaml index d29a6c0..f21ac32 100644 --- a/spread.yaml +++ b/spread.yaml @@ -18,7 +18,7 @@ suites: # Wait for docker daemon to come online sudo apt install --yes retry - retry --times=10 --delay 2 -- docker run hello-world + retry --times=10 --delay 2 -- docker images sudo apt remove --yes retry exclude: From 67db9552129f22d91b96e181ede6fab0db634bd0 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Wed, 8 Jul 2026 07:41:07 +0800 Subject: [PATCH 54/54] bump fastapi to 0.138.1 --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cdb2be9..cc51889 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ license = "Apache-2.0" license-files = ["LICENSE"] requires-python = ">=3.10" dependencies = [ - "fastapi==0.136.3", + "fastapi==0.138.1", "uvicorn==0.49.0", "python-multipart==0.0.32", "psycopg2-binary==2.9.12", diff --git a/uv.lock b/uv.lock index 01da9ec..bb9fe41 100644 --- a/uv.lock +++ b/uv.lock @@ -60,7 +60,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "fastapi", specifier = "==0.136.3" }, + { name = "fastapi", specifier = "==0.138.1" }, { name = "psycopg2-binary", specifier = "==2.9.12" }, { name = "python-multipart", specifier = "==0.0.32" }, { name = "starlette", specifier = "==1.3.1" }, @@ -276,7 +276,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.136.3" +version = "0.138.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -285,9 +285,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/c9/5e8defe249899c0dc900643695fc07829a67fc88b4ff2cdb03fcbdbf5a4b/fastapi-0.138.1.tar.gz", hash = "sha256:96e3702dce09ee0dce48856135620d3d865ca684a79fe7513fd7b13a12f82862", size = 419646, upload-time = "2026-06-25T15:40:42.115Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/38/a9/69a6924f645eb4dd8cd625bf255b3625990eb3e14e073438a53c405dcd3e/fastapi-0.138.1-py3-none-any.whl", hash = "sha256:b994cae7ba8b82c976a728b544244de31333fa5f7d261f9a1dffe526444cae23", size = 129182, upload-time = "2026-06-25T15:40:40.771Z" }, ] [[package]]