diff mbox series

[layerindex-web] Add GitHub Actions CI running the test suite in Docker

Message ID 20261017183411.91297-1-tim.orling@konsulko.com
State New
Headers show
Series [layerindex-web] Add GitHub Actions CI running the test suite in Docker | expand

Commit Message

Tim Orling Oct. 17, 2026, 6:34 p.m. UTC
docker/run-tests.sh builds the production image, starts a throwaway
MariaDB 10.11 on a private network and runs the full test suite inside
the image, so tests/test_update.py can run bitbake on Linux. Test data
comes only from migrations and public git repositories; the database
password is the public default from docker/settings.py. The script can
also be run locally from the repository root.

A separate Safety workflow runs 'safety scan' on 'pip freeze' output
from the image, so version ranges in requirements.txt (e.g. Django)
cannot hide vulnerable releases. It runs on push, weekly and on demand,
so newly published vulnerabilities are reported without a code change,
and is skipped when SAFETY_API_KEY is not available.

Also keep local keys, logs, patches and virtualenvs out of the Docker
build context.

AI-Generated: Claude Opus 5.5
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
 .dockerignore                |  7 ++++++
 .github/workflows/ci.yml     | 22 +++++++++++++++++++
 .github/workflows/safety.yml | 40 +++++++++++++++++++++++++++++++++++
 .safety-project.ini          |  5 +++++
 docker/run-tests.sh          | 41 ++++++++++++++++++++++++++++++++++++
 5 files changed, 115 insertions(+)
 create mode 100644 .github/workflows/ci.yml
 create mode 100644 .github/workflows/safety.yml
 create mode 100644 .safety-project.ini
 create mode 100755 docker/run-tests.sh
diff mbox series

Patch

diff --git a/.dockerignore b/.dockerignore
index b9ab463..3b82016 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,3 +12,10 @@  Dockerfile
 Dockerfile.web
 docker-compose.yml
 dockersetup.py
+*.pem
+*.log
+*.patch
+*.zst
+.venv
+.idea
+.DS_Store
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..74405ac
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,22 @@ 
+# SPDX-License-Identifier: MIT
+
+name: CI
+
+on:
+  push:
+    branches: [master,master-next]
+  pull_request:
+    branches: [master]
+
+permissions:
+  contents: read
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    timeout-minutes: 30
+    steps:
+      - uses: actions/checkout@v5
+
+      - name: Build image and run tests
+        run: docker/run-tests.sh
diff --git a/.github/workflows/safety.yml b/.github/workflows/safety.yml
new file mode 100644
index 0000000..4840a08
--- /dev/null
+++ b/.github/workflows/safety.yml
@@ -0,0 +1,40 @@ 
+# SPDX-License-Identifier: MIT
+
+name: Safety
+
+on:
+  push:
+    branches: [master,master-next]
+  schedule:
+    - cron: '0 6 * * 1'
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+jobs:
+  scan:
+    runs-on: ubuntu-latest
+    timeout-minutes: 15
+    env:
+      SAFETY_API_KEY: ${{ secrets.SAFETY_API_KEY }}
+    # Steps are skipped where the secret isn't set, e.g. other forks.
+    steps:
+      - uses: actions/checkout@v5
+
+      - name: Build image
+        if: env.SAFETY_API_KEY != ''
+        run: docker build -t layerindex-test .
+
+      # Scans the versions actually installed in the image, so ranges in
+      # requirements.txt (e.g. Django) can't hide vulnerable releases.
+      - name: Safety scan
+        if: env.SAFETY_API_KEY != ''
+        run: |
+          mkdir safety-scan
+          cp .safety-project.ini safety-scan/
+          docker run --rm layerindex-test pip3 freeze > safety-scan/requirements.txt
+          # typer pinned: newer releases break safety 3.7.0 ("no active click context")
+          python3 -m venv /tmp/safety
+          /tmp/safety/bin/pip install -q safety==3.7.0 typer==0.23.0
+          /tmp/safety/bin/safety scan --target safety-scan --detailed-output
diff --git a/.safety-project.ini b/.safety-project.ini
new file mode 100644
index 0000000..e7ae822
--- /dev/null
+++ b/.safety-project.ini
@@ -0,0 +1,5 @@ 
+[project]
+id = layerindex-web
+url = /codebases/layerindex-web/findings
+name = layerindex-web
+
diff --git a/docker/run-tests.sh b/docker/run-tests.sh
new file mode 100755
index 0000000..e655611
--- /dev/null
+++ b/docker/run-tests.sh
@@ -0,0 +1,41 @@ 
+#!/bin/sh
+# layerindex-web - run the test suite in the production image against a
+# throwaway MariaDB. Used by CI; can also be run locally from the repo root.
+#
+# SPDX-License-Identifier: MIT
+
+set -e
+
+IMAGE=${IMAGE:-layerindex-test}
+NET=layerindex-test
+DB=layerindex-test-db
+
+docker build -t "$IMAGE" .
+
+docker network create $NET >/dev/null
+trap 'docker rm -f $DB >/dev/null; docker network rm $NET >/dev/null' EXIT
+
+# Alias "layersdb" matches the default DATABASE_HOST in docker/settings.py;
+# testingpw is that file's default password, not a real secret.
+docker run -d --name $DB --network $NET --network-alias layersdb \
+    -e MARIADB_ROOT_PASSWORD=testingpw mariadb:10.11 \
+    --character-set-server=utf8mb4 >/dev/null
+until docker exec $DB healthcheck.sh --connect --innodb_initialized >/dev/null 2>&1; do
+    sleep 1
+done
+
+# Runs as root only to prepare the container: tests/test_update.py rewrites
+# settings.py and bitbake writes its lock file next to it, the update scripts
+# expect bitbake already fetched, and the tests commit to git and call tree.
+docker run --rm --network $NET -u root \
+    -e SECRET_KEY=ci-only \
+    -e GIT_AUTHOR_NAME=ci -e GIT_AUTHOR_EMAIL=ci@example.com \
+    -e GIT_COMMITTER_NAME=ci -e GIT_COMMITTER_EMAIL=ci@example.com \
+    -v "$PWD/tests:/src/tests:ro" -w /opt/layerindex "$IMAGE" sh -ec '
+        apt-get update -qq && apt-get install -y -qq tree >/dev/null
+        cp -r /src/tests /opt/layerindex/
+        chown -R layers /opt/layerindex
+        su layers -s /bin/sh -c "set -e
+            git clone -q https://git.openembedded.org/bitbake /opt/workdir/https___git_openembedded_org_bitbake
+            pip3 install -q --user --no-warn-script-location pytest==9.0.3 pytest-django==4.12.0
+            python3 -m pytest -rfE -p no:cacheprovider tests/"'