mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
pip-audit audited the whole CI environment, so on Python 3.10 it failed on filelock 4.1.0, a dependency of pip-audit itself: six CVEs published on 2026-10-23 are fixed only in filelock 4.2.1, which needs Python 3.11. No installation gets filelock. The step now audits conf/requirements.txt and conf/requirements-ldap.txt, resolved for the leg's Python, which is what the audit is for.
203 lines
7.8 KiB
YAML
203 lines
7.8 KiB
YAML
name: Test Suite
|
|
|
|
on:
|
|
push:
|
|
branches: ['**']
|
|
pull_request:
|
|
branches: [master]
|
|
|
|
# the jobs only read the code
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
test:
|
|
name: Python ${{ matrix.python-version }} / ${{ matrix.database }}
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
python-version: ['3.10', '3.11', '3.12', '3.13']
|
|
database: [sqlite]
|
|
include:
|
|
- python-version: '3.12'
|
|
database: postgresql
|
|
|
|
# Used by the postgresql leg only; settings fall back to sqlite when
|
|
# WEBVIRTCLOUD_DB_HOST is empty.
|
|
services:
|
|
postgres:
|
|
image: postgres:17-alpine
|
|
env:
|
|
POSTGRES_USER: webvirtcloud
|
|
POSTGRES_PASSWORD: webvirtcloud
|
|
POSTGRES_DB: webvirtcloud
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
|
|
env:
|
|
WEBVIRTCLOUD_DB_HOST: ${{ matrix.database == 'postgresql' && 'localhost' || '' }}
|
|
WEBVIRTCLOUD_DB_PASSWORD: webvirtcloud
|
|
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Python ${{ matrix.python-version }}
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: 'pip'
|
|
cache-dependency-path: |
|
|
conf/requirements.txt
|
|
dev/requirements.txt
|
|
|
|
# Build headers for the sdist-only bindings (libvirt-python, python-ldap)
|
|
# A runner's package mirror sometimes stops answering: time out and
|
|
# retry instead of hanging until the job's 6-hour limit
|
|
- name: Install System Dependencies
|
|
timeout-minutes: 10
|
|
run: |
|
|
APT="-o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30"
|
|
sudo apt-get $APT update -qq
|
|
sudo apt-get $APT install -y --no-install-recommends \
|
|
libvirt-dev \
|
|
libldap2-dev \
|
|
libsasl2-dev \
|
|
pkg-config \
|
|
gcc
|
|
|
|
- name: Install Python Dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip setuptools
|
|
pip install -r dev/requirements.txt
|
|
|
|
- name: Configure WebVirtCloud Settings
|
|
run: |
|
|
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
|
|
SECRET_KEY=$(python -c 'import secrets; print(secrets.token_urlsafe(50))')
|
|
sed -i "s|^SECRET_KEY = .*|SECRET_KEY = \"${SECRET_KEY}\"|" webvirtcloud/settings.py
|
|
|
|
- name: Check For Missing Migrations
|
|
run: python manage.py makemigrations --check --dry-run
|
|
|
|
- name: Run Django Migrations
|
|
run: python manage.py migrate --noinput
|
|
|
|
# Includes the vrtManager tests; fails below the coverage floor in pyproject.toml
|
|
- name: Run Test Suite
|
|
run: |
|
|
coverage run manage.py test
|
|
coverage report --skip-covered
|
|
|
|
# The project test runner (webvirtcloud/test_runner.py) on one leg
|
|
- name: Run Test Suite In Parallel
|
|
if: matrix.python-version == '3.13' && matrix.database == 'sqlite'
|
|
run: python manage.py test --parallel
|
|
|
|
# The per-file report of one leg, to see what a change left untested
|
|
- name: Store Coverage Report
|
|
if: always() && matrix.python-version == '3.12' && matrix.database == 'sqlite'
|
|
run: coverage html -d coverage-html --fail-under=0
|
|
|
|
- name: Upload Coverage Report
|
|
if: always() && matrix.python-version == '3.12' && matrix.database == 'sqlite'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: coverage-html
|
|
path: coverage-html
|
|
|
|
- name: Validate OpenAPI Schema
|
|
run: python manage.py spectacular --validate --fail-on-warn --file /dev/null
|
|
|
|
# Security warnings (HSTS, secure cookies) depend on the deployment;
|
|
# gate on errors only.
|
|
- name: Deployment Checks
|
|
run: python manage.py check --deploy --fail-level ERROR
|
|
|
|
# What installations get, resolved for this Python; not the CI tools
|
|
# (their fixes can need a newer Python than the leg's, as filelock's did)
|
|
- name: Audit Shipped Packages
|
|
run: pip-audit -r conf/requirements.txt -r conf/requirements-ldap.txt
|
|
|
|
docker:
|
|
name: Docker Image
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v4
|
|
|
|
# Files that exist in a used checkout and must never enter the image
|
|
- name: Plant Secret Sentinels
|
|
run: |
|
|
mkdir -p data
|
|
for f in data/secret_key data/admin_password webvirtcloud/settings.py.bak \
|
|
'webvirtcloud/settings.py~' webvirtcloud/.settings.py.swp; do
|
|
echo sentinel > "$f"
|
|
done
|
|
|
|
- name: Build Image
|
|
run: docker build -t webvirtcloud:ci .
|
|
|
|
- name: No Runtime Data In Image
|
|
run: |
|
|
docker run --rm --entrypoint sh webvirtcloud:ci -c '
|
|
cd /srv/webvirtcloud
|
|
for f in data/secret_key data/admin_password webvirtcloud/settings.py.bak \
|
|
"webvirtcloud/settings.py~" webvirtcloud/.settings.py.swp; do
|
|
if [ -e "$f" ]; then echo "$f is in the image"; exit 1; fi
|
|
done'
|
|
|
|
# The default profile, and the HTTPS profile behind a TLS proxy
|
|
- name: Start Containers
|
|
run: |
|
|
docker run -d --name wvc -p 127.0.0.1:8080:80 webvirtcloud:ci
|
|
docker run -d --name wvc-https -e WEBVIRTCLOUD_HTTPS=1 webvirtcloud:ci
|
|
|
|
- name: Wait Until Healthy
|
|
run: |
|
|
for c in wvc wvc-https; do
|
|
for i in $(seq 40); do
|
|
[ "$(docker inspect -f '{{.State.Health.Status}}' $c)" = healthy ] && continue 2
|
|
sleep 5
|
|
done
|
|
docker inspect -f '{{json .State.Health}}' $c || true
|
|
docker exec $c sv status /etc/service/nginx /etc/service/webvirtcloud /etc/service/novnc || true
|
|
docker logs $c || true
|
|
exit 1
|
|
done
|
|
|
|
- name: Import Compiled Dependencies
|
|
run: docker exec wvc /srv/webvirtcloud/venv/bin/python3 -c "import libvirt, ldap, lxml.etree, psycopg"
|
|
|
|
# Static URLs carry no version: browsers must revalidate them
|
|
- name: Static Files Revalidated
|
|
run: |
|
|
url=http://127.0.0.1:8080/static/css/themes/zephyr.min.css
|
|
header() { tr -d '\r' | awk -v h="$1" 'tolower($0) ~ "^" h ":" {sub(/^[^:]*: */, ""); print}'; }
|
|
h=$(curl -s -m 5 -D - -o /dev/null "$url")
|
|
[ "$(echo "$h" | header cache-control)" = no-cache ] || { echo "$h"; exit 1; }
|
|
etag=$(echo "$h" | header etag)
|
|
[ -n "$etag" ]
|
|
h=$(curl -s -m 5 -D - -o /dev/null -H "If-None-Match: $etag" "$url")
|
|
echo "$h" | head -1 | grep -q " 304" || { echo "$h"; exit 1; }
|
|
[ "$(echo "$h" | header cache-control)" = no-cache ]
|
|
docker exec wvc sh -c 'echo "/* changed */" >> /srv/webvirtcloud/staticfiles/css/themes/zephyr.min.css'
|
|
h=$(curl -s -m 5 -D - -o /dev/null -H "If-None-Match: $etag" "$url")
|
|
echo "$h" | head -1 | grep -q " 200" || { echo "$h"; exit 1; }
|
|
new=$(echo "$h" | header etag)
|
|
[ -n "$new" ] && [ "$new" != "$etag" ]
|
|
|
|
# The browser reaches the console through nginx on the panel's port
|
|
- name: Console Through Nginx Only
|
|
run: |
|
|
[ "$(docker inspect -f '{{json .Config.ExposedPorts}}' webvirtcloud:ci)" = '{"80/tcp":{}}' ]
|
|
docker exec wvc grep -qx 'WS_PUBLIC_PORT = 80' /srv/webvirtcloud/webvirtcloud/settings.py
|
|
code=$(curl -s -m 5 -o /dev/null -w '%{http_code}' -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
|
|
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGVzdGtleXRlc3RrZXk9PQ==' http://127.0.0.1:8080/novncd/ || true)
|
|
[ "$code" = 101 ] || { echo "websocket upgrade through nginx: $code"; exit 1; }
|