mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
A theme change compiled Bootswatch with libsass (34 MB, upstream deprecated) into two tracked files, which updates, rollbacks and new Docker containers then reset. The 27 themes now ship as static/css/themes/<theme>.min.css, built by "manage.py build_themes"; the Settings page only saves the theme name and every page links that file (zephyr when no settings are in context, as on the 500 page). libsass moves to dev/requirements.txt; a test checks that each shipped file is its theme compiled from dev/scss. The "Theme SASS Path" setting is removed, and the theme default "flaty", a typo that never applied, becomes zephyr, the theme those installs showed. The update and rollback steps restore an older version's rewritten theme files only where they are modified.
184 lines
6.9 KiB
YAML
184 lines
6.9 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
|
|
|
|
- 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
|
|
|
|
- name: Audit Installed Packages
|
|
run: pip-audit
|
|
|
|
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; }
|