ci: migrate workflows to Gitea Actions
This commit is contained in:
@@ -8,38 +8,38 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Protected runs get their own group; publication is queued at job level.
|
||||
# Publication runs are not canceled by newer checks; pushes serialize per job.
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}-${{ github.event_name != 'pull_request' && github.ref_protected && github.run_id || 'checks' }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || !github.ref_protected }}
|
||||
group: ci-${{ gitea.workflow }}-${{ gitea.ref }}-${{ gitea.event_name != 'pull_request' && vars.PUBLISH_IMAGES == 'true' && gitea.run_id || 'checks' }}
|
||||
cancel-in-progress: ${{ gitea.event_name == 'pull_request' || vars.PUBLISH_IMAGES != 'true' }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
DEFAULT_BRANCH: ${{ gitea.event.repository.default_branch }}
|
||||
PUBLISH_IMAGES: ${{ vars.PUBLISH_IMAGES }}
|
||||
REGISTRY_HOST: ${{ vars.REGISTRY_HOST || 'gitlab.bartelluis.de' }}
|
||||
|
||||
jobs:
|
||||
python-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ${{ vars.AIS_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
- uses: https://github.com/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
- uses: https://github.com/actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
- name: Run Python tests
|
||||
run: sh ci/python-tests.sh
|
||||
- name: Upload JUnit report
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: https://gitea.com/actions/gitea-upload-artifact@62ac910c5d3dfa85c7cb2df15afe2e342b2407c2
|
||||
with:
|
||||
name: python-test-results
|
||||
path: reports/pytest.xml
|
||||
@@ -47,13 +47,13 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
|
||||
javascript-check:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ${{ vars.AIS_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
- uses: https://github.com/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
- uses: https://github.com/actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: "24"
|
||||
package-manager-cache: false
|
||||
@@ -61,37 +61,31 @@ jobs:
|
||||
run: node --check provisioner/static/app.js
|
||||
|
||||
container-policy:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ${{ vars.AIS_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
outputs:
|
||||
publish: ${{ steps.policy.outputs.publish }}
|
||||
steps:
|
||||
# Ref names are read from the runner environment, never inserted into shell code.
|
||||
# The container script independently enforces these publication rules.
|
||||
- uses: https://github.com/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Gitea supplies GITHUB_* compatibility variables. Keep ref names out of
|
||||
# shell expressions and use the same policy in the container script.
|
||||
- name: Select verification or publication
|
||||
id: policy
|
||||
run: |
|
||||
publish=false
|
||||
if [[ "$GITHUB_REF_PROTECTED" == true ]] &&
|
||||
[[ "$GITHUB_EVENT_NAME" == push || "$GITHUB_EVENT_NAME" == workflow_dispatch ]]; then
|
||||
if [[ "$GITHUB_REF_TYPE" == branch && -n "$DEFAULT_BRANCH" && "$GITHUB_REF_NAME" == "$DEFAULT_BRANCH" ]] ||
|
||||
[[ "$GITHUB_REF_TYPE" == tag && "$GITHUB_REF_NAME" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
|
||||
publish=true
|
||||
fi
|
||||
fi
|
||||
publish=$(sh ci/publish-policy.sh)
|
||||
printf 'publish=%s\n' "$publish" >> "$GITHUB_OUTPUT"
|
||||
|
||||
container-verify:
|
||||
needs: [python-tests, javascript-check, container-policy]
|
||||
if: ${{ needs.container-policy.outputs.publish == 'false' }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ${{ vars.AIS_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DOCKER_HOST: unix:///var/run/docker.sock
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
- uses: https://github.com/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check Docker tools
|
||||
@@ -100,7 +94,7 @@ jobs:
|
||||
docker buildx version
|
||||
- name: Build and smoke-test image
|
||||
run: sh ci/container.sh verify
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
- uses: https://gitea.com/actions/gitea-upload-artifact@62ac910c5d3dfa85c7cb2df15afe2e342b2407c2
|
||||
with:
|
||||
name: container-build
|
||||
path: build.env
|
||||
@@ -110,20 +104,17 @@ jobs:
|
||||
container-publish:
|
||||
needs: [python-tests, javascript-check, container-policy]
|
||||
if: ${{ needs.container-policy.outputs.publish == 'true' }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ${{ vars.AIS_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
concurrency:
|
||||
group: ghcr-container-publish
|
||||
group: container-publish
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
env:
|
||||
DOCKER_HOST: unix:///var/run/docker.sock
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
- uses: https://github.com/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check Docker tools
|
||||
@@ -132,9 +123,10 @@ jobs:
|
||||
docker buildx version
|
||||
- name: Build, smoke-test and publish image
|
||||
env:
|
||||
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REGISTRY_USERNAME: ${{ vars.REGISTRY_USERNAME }}
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: sh ci/container.sh publish
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
- uses: https://gitea.com/actions/gitea-upload-artifact@62ac910c5d3dfa85c7cb2df15afe2e342b2407c2
|
||||
with:
|
||||
name: container-deploy
|
||||
path: |
|
||||
@@ -55,7 +55,7 @@ und TLS-Konfiguration stehen in der [Betriebsanleitung](docs/operations.md).
|
||||
|
||||
Auf dem Zielserver werden Docker Engine, das Compose-Plugin und ein
|
||||
HTTPS-Reverse-Proxy benötigt. Die Anwendung wird als fertiges Image aus der
|
||||
GitHub Container Registry (GHCR) heruntergeladen. Quellcode und Build-Werkzeuge werden
|
||||
Container Registry der Gitea-Instanz heruntergeladen. Quellcode und Build-Werkzeuge werden
|
||||
für das Deployment nicht benötigt.
|
||||
|
||||
[compose.yaml](compose.yaml) und die [Umgebungsvorlage](config/deployment.env.example)
|
||||
@@ -67,14 +67,14 @@ einen bereits veröffentlichten Versionstag aus der Projektregistry verwenden.
|
||||
Compose benötigt ausdrücklich `PROVISIONER_IMAGE`; ein lokaler Build ist nicht
|
||||
Teil dieser Deployment-Konfiguration.
|
||||
|
||||
Im Deployment-Verzeichnis ausführen. Für ein privates GHCR-Paket beim Login den
|
||||
eigenen GitHub-Benutzernamen und einen Personal Access Token (classic) mit
|
||||
`read:packages` als Passwort verwenden. Bei einem öffentlichen Paket entfällt
|
||||
Im Deployment-Verzeichnis ausführen. Für ein privates Gitea-Paket beim Login den
|
||||
eigenen Gitea-Benutzernamen und einen Personal Access Token mit
|
||||
`read:package` als Passwort verwenden. Bei einem öffentlichen Paket entfällt
|
||||
der Login. Details stehen unter [Registry-Anmeldung](docs/deployment.md#an-der-registry-anmelden-und-image-laden).
|
||||
|
||||
```bash
|
||||
# Nur für ein privates Paket; GITHUB_USERNAME ersetzen:
|
||||
docker login ghcr.io --username GITHUB_USERNAME
|
||||
# Nur für ein privates Paket; GITEA_USERNAME ersetzen:
|
||||
docker login gitlab.bartelluis.de --username GITEA_USERNAME
|
||||
docker compose config --quiet
|
||||
docker compose pull provisioner
|
||||
# Nur bei der ersten Inbetriebnahme:
|
||||
@@ -91,20 +91,25 @@ Master-Key getrennt in `proxmox-ais-keys`.
|
||||
Die [Deployment-Anleitung](docs/deployment.md) beschreibt Image-Auswahl,
|
||||
Erstinitialisierung, Updates, Rollback und die Prüfung des laufenden Containers.
|
||||
|
||||
## GitHub Actions CI/CD
|
||||
## Gitea Actions CI/CD
|
||||
|
||||
Der [GitHub-Actions-Workflow](.github/workflows/ci.yml) prüft Python und JavaScript, baut das Image
|
||||
Das Projekt liegt auf [Gitea](https://gitlab.bartelluis.de/BartelLuis/Proxmox-AIS-Server).
|
||||
Der [Gitea-Actions-Workflow](.gitea/workflows/ci.yml) prüft Python und JavaScript, baut das Image
|
||||
und testet HTTPS, Anmeldung und Datenerhalt beim Neustart im gehärteten Container.
|
||||
Geschützte Standardbranch-Pushes veröffentlichen `sha-<Commit>` und `edge` in
|
||||
`ghcr.io/netivra/proxmox-ais`; geschützte Release-Tags wie `v0.1.0` zusätzlich
|
||||
die passende Versionsnummer. Feature-Branches und Pull Requests werden ohne Push geprüft.
|
||||
Bei aktivierter Veröffentlichung (`PUBLISH_IMAGES=true`) veröffentlichen
|
||||
Standardbranch-Pushes `sha-<Commit>` und `edge` in
|
||||
`gitlab.bartelluis.de/bartelluis/proxmox-ais-server`; Release-Tags wie `v0.1.0`
|
||||
zusätzlich die passende Versionsnummer. Feature-Branches und Pull Requests
|
||||
werden ohne Registry-Push geprüft.
|
||||
|
||||
Die Jobs verwenden GitHub-gehostete Runner mit `ubuntu-24.04`, Python 3.13,
|
||||
Node.js 24 und Docker/Buildx. Ein eigener CI-Runner ist nicht erforderlich.
|
||||
Für die Veröffentlichung müssen der Standardbranch und die Release-Tags durch
|
||||
aktive GitHub-Regeln geschützt sein. GHCR erhält das automatisch bereitgestellte
|
||||
`GITHUB_TOKEN`; ein eigenes CI-Secret wird nicht benötigt. Einrichtung,
|
||||
Schutzregeln und Artefakte stehen in der [GitHub-Anleitung](docs/github-actions.md).
|
||||
Gitea benötigt einen registrierten, erreichbaren Linux-amd64-Runner. Das
|
||||
Runner-Label ist über `AIS_RUNNER_LABEL` wählbar und lautet standardmäßig
|
||||
`ubuntu-latest`. Die Build-Umgebung benötigt Python 3.13, Node.js 24 und
|
||||
Docker/Buildx. Für die Veröffentlichung werden die Variable `REGISTRY_USERNAME`
|
||||
und das Secret `REGISTRY_TOKEN` mit `write:package` eingerichtet. Standardbranch
|
||||
und Release-Tags in Gitea schützen, bevor `PUBLISH_IMAGES` aktiviert wird.
|
||||
Runner-Einrichtung, Schutzregeln und Artefakte stehen in der
|
||||
[Gitea-Anleitung](docs/gitea-actions.md).
|
||||
|
||||
Die Pipeline liefert das getestete Image und dessen Digest in `deploy.env`.
|
||||
Die Installation auf dem Zielserver folgt der [Deployment-Anleitung](docs/deployment.md).
|
||||
|
||||
+14
-8
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
# Build and smoke-test the same image that is optionally published to GHCR.
|
||||
# Build and smoke-test the same image that is optionally published to Gitea.
|
||||
# Gitea Actions supplies the GitHub-compatible GITHUB_* runner variables.
|
||||
set -eu
|
||||
|
||||
fail() {
|
||||
@@ -19,17 +20,22 @@ esac
|
||||
: "${GITHUB_JOB:?GITHUB_JOB is required}"
|
||||
: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}"
|
||||
: "${GITHUB_SERVER_URL:?GITHUB_SERVER_URL is required}"
|
||||
: "${REGISTRY_HOST:?REGISTRY_HOST is required (hostname, optionally with port)}"
|
||||
printf '%s\n' "$GITHUB_SHA" | grep -Eq '^[0-9a-f]{40}$' || fail 'Expected a full Git commit SHA'
|
||||
for run_identifier in "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT"; do
|
||||
case "$run_identifier" in
|
||||
''|*[!0-9]*) fail 'GitHub run ID and attempt must be numeric' ;;
|
||||
''|*[!0-9]*) fail 'Actions run ID and attempt must be numeric' ;;
|
||||
esac
|
||||
done
|
||||
case "$GITHUB_JOB" in
|
||||
''|[!A-Za-z_]*|*[!A-Za-z0-9_-]*) fail 'GITHUB_JOB must be a safe job identifier' ;;
|
||||
esac
|
||||
printf '%s\n' "$GITHUB_REPOSITORY" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9-]*/[A-Za-z0-9_][A-Za-z0-9_.-]*$' || fail 'Expected GITHUB_REPOSITORY in owner/repository form'
|
||||
registry_image="ghcr.io/$(printf '%s' "$GITHUB_REPOSITORY" | LC_ALL=C tr '[:upper:]' '[:lower:]')"
|
||||
case "$REGISTRY_HOST" in
|
||||
''|*[!a-z0-9.:-]*) fail 'REGISTRY_HOST must be a lowercase hostname, optionally with port' ;;
|
||||
esac
|
||||
printf '%s\n' "$REGISTRY_HOST" | grep -Eq '^[a-z0-9][a-z0-9.-]*(:[0-9]+)?$' || fail 'Invalid REGISTRY_HOST'
|
||||
registry_image="$REGISTRY_HOST/$(printf '%s' "$GITHUB_REPOSITORY" | LC_ALL=C tr '[:upper:]' '[:lower:]')"
|
||||
project_url="${GITHUB_SERVER_URL%/}/${GITHUB_REPOSITORY}"
|
||||
job_identifier="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${GITHUB_JOB}"
|
||||
|
||||
@@ -50,7 +56,7 @@ project_version=$(awk '
|
||||
|
||||
publish_tag=
|
||||
if [ "$mode" = publish ]; then
|
||||
[ "${GITHUB_REF_PROTECTED:-}" = true ] || fail 'Publication requires a protected branch or tag'
|
||||
[ "$(sh ci/publish-policy.sh)" = true ] || fail 'Publication requires PUBLISH_IMAGES=true and a default-branch or release-tag push/manual run'
|
||||
case "${GITHUB_EVENT_NAME:-}" in
|
||||
push|workflow_dispatch) ;;
|
||||
*) fail 'Publication is allowed only from push or workflow_dispatch events' ;;
|
||||
@@ -67,8 +73,8 @@ if [ "$mode" = publish ]; then
|
||||
;;
|
||||
*) fail 'Publication requires a branch or tag ref' ;;
|
||||
esac
|
||||
: "${GITHUB_ACTOR:?GITHUB_ACTOR is required for publication}"
|
||||
: "${GHCR_TOKEN:?GHCR_TOKEN is required for publication}"
|
||||
: "${REGISTRY_USERNAME:?REGISTRY_USERNAME must be the package token owner}"
|
||||
: "${REGISTRY_TOKEN:?REGISTRY_TOKEN requires write:package permission}"
|
||||
fi
|
||||
|
||||
BUILD_IMAGE="${registry_image}:ci-${job_identifier}"
|
||||
@@ -119,8 +125,8 @@ if [ "$mode" = publish ]; then
|
||||
docker_config_dir=$(mktemp -d "${TMPDIR:-/tmp}/ais-ci-docker.XXXXXXXX")
|
||||
DOCKER_CONFIG=$docker_config_dir
|
||||
export DOCKER_CONFIG
|
||||
printf '%s' "$GHCR_TOKEN" | docker login ghcr.io \
|
||||
--username "$GITHUB_ACTOR" --password-stdin
|
||||
printf '%s' "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" \
|
||||
--username "$REGISTRY_USERNAME" --password-stdin
|
||||
|
||||
commit_image="${registry_image}:sha-${GITHUB_SHA}"
|
||||
channel_image="${registry_image}:${publish_tag}"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
# Gitea 1.27 does not populate ref_protected. Repository administrators opt in
|
||||
# after configuring branch/tag protection and package credentials in Gitea.
|
||||
set -eu
|
||||
|
||||
publish=false
|
||||
if [ "${PUBLISH_IMAGES:-}" = true ]; then
|
||||
case "${GITHUB_EVENT_NAME:-}" in
|
||||
push|workflow_dispatch)
|
||||
case "${GITHUB_REF_TYPE:-}" in
|
||||
branch)
|
||||
if [ -n "${DEFAULT_BRANCH:-}" ] && [ "${GITHUB_REF_NAME:-}" = "$DEFAULT_BRANCH" ]; then
|
||||
publish=true
|
||||
fi
|
||||
;;
|
||||
tag)
|
||||
case "${GITHUB_REF_NAME:-}" in
|
||||
''|*[!v0-9.]*) ;;
|
||||
*)
|
||||
if printf '%s\n' "$GITHUB_REF_NAME" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'; then
|
||||
publish=true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
printf '%s\n' "$publish"
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
# Run tests in an isolated, disposable environment on a Linux Actions runner.
|
||||
# Gitea Actions supplies the GitHub-compatible GITHUB_* runner variables.
|
||||
set -eu
|
||||
|
||||
fail() {
|
||||
@@ -13,7 +14,7 @@ fail() {
|
||||
: "${GITHUB_JOB:?GITHUB_JOB is required}"
|
||||
for run_identifier in "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT"; do
|
||||
case "$run_identifier" in
|
||||
''|*[!0-9]*) fail 'GitHub run ID and attempt must be numeric' ;;
|
||||
''|*[!0-9]*) fail 'Actions run ID and attempt must be numeric' ;;
|
||||
esac
|
||||
done
|
||||
case "$GITHUB_JOB" in
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
provisioner:
|
||||
image: ${PROVISIONER_IMAGE:?PROVISIONER_IMAGE in .env auf das Image aus der GitHub Container Registry setzen}
|
||||
image: ${PROVISIONER_IMAGE:?PROVISIONER_IMAGE in .env auf das Image aus der Gitea Container Registry setzen}
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
ports:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
PUBLIC_URL=https://provision.example.net
|
||||
MAINTENANCE=false
|
||||
|
||||
# Durch die vollstaendige PROVISIONER_IMAGE-Zeile aus deploy.env im GitHub-
|
||||
# Durch die vollstaendige PROVISIONER_IMAGE-Zeile aus deploy.env im Gitea-
|
||||
# Actions-Artefakt container-deploy ersetzen. Alternativ einen vorhandenen
|
||||
# Versionstag aus GHCR verwenden. Der Beispieltag muss veroeffentlicht sein.
|
||||
PROVISIONER_IMAGE=ghcr.io/netivra/proxmox-ais:0.1.0
|
||||
# Versionstag aus der Gitea-Registry verwenden. Der Beispieltag muss veroeffentlicht sein.
|
||||
PROVISIONER_IMAGE=gitlab.bartelluis.de/bartelluis/proxmox-ais-server:0.1.0
|
||||
|
||||
+17
-14
@@ -1,7 +1,7 @@
|
||||
# Deployment mit einem Image aus der Container Registry
|
||||
|
||||
Proxmox AIS wird auf dem Zielserver aus einem bereits veröffentlichten
|
||||
Containerimage gestartet. Der [GitHub-Actions-Workflow](github-actions.md) erstellt dieses Image; auf dem
|
||||
Containerimage gestartet. Der [Gitea-Actions-Workflow](gitea-actions.md) erstellt dieses Image; auf dem
|
||||
Zielserver werden nur Docker Compose und die Betriebskonfiguration benötigt.
|
||||
Diese Anleitung setzt einen erfolgreichen `container-publish`-Job und ein
|
||||
weiterhin abrufbares Image voraus.
|
||||
@@ -42,20 +42,20 @@ Wert führt bereits bei der Konfigurationsprüfung zu einem Fehler.
|
||||
|
||||
## Image und öffentliche Adresse festlegen
|
||||
|
||||
In GitHub unter **Actions** den erfolgreichen Workflow-Lauf mit
|
||||
In Gitea unter **Actions** den erfolgreichen Workflow-Lauf mit
|
||||
`container-publish` öffnen, unter **Artifacts** das Archiv `container-deploy`
|
||||
herunterladen und entpacken. Es enthält `build.env` und `deploy.env`.
|
||||
Die vollständige Zeile `PROVISIONER_IMAGE=…@sha256:…` aus `deploy.env` in die
|
||||
`.env` auf dem Zielserver übernehmen. Der Digest legt genau das veröffentlichte
|
||||
Image fest. Compose liest `deploy.env` nicht automatisch ein.
|
||||
[GitHub-Artefakte herunterladen](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts).
|
||||
|
||||
Alternativ auf GitHub das zugehörige **Packages**-Paket öffnen und den vollständigen
|
||||
Alternativ in Gitea beim Benutzer `BartelLuis` das zugehörige **Packages**-Paket
|
||||
öffnen und den vollständigen
|
||||
Imagepfad mit einem tatsächlich vorhandenen Versions-Tag kopieren. Der aktuelle
|
||||
Imagepfad der Beispieldatei sieht so aus:
|
||||
|
||||
```dotenv
|
||||
PROVISIONER_IMAGE=ghcr.io/netivra/proxmox-ais:0.1.0
|
||||
PROVISIONER_IMAGE=gitlab.bartelluis.de/bartelluis/proxmox-ais-server:0.1.0
|
||||
PUBLIC_URL=https://provision.example.net
|
||||
MAINTENANCE=false
|
||||
```
|
||||
@@ -64,7 +64,9 @@ MAINTENANCE=false
|
||||
Imagepfad entspricht dem aktuellen Wert der Vorlage; die Verfügbarkeit des
|
||||
Tags `0.1.0` wird damit nicht vorausgesetzt. Maßgeblich sind das
|
||||
Pipeline-Artefakt oder die Registry-Anzeige deines Projekts.
|
||||
Der Registry-Endpunkt ist `ghcr.io`. Für reproduzierbare Deployments den Digest
|
||||
Der Registry-Endpunkt der Vorlage ist `gitlab.bartelluis.de`. Bei einer abweichenden
|
||||
CI-Registry den Host aus `deploy.env` auch für `docker login` verwenden.
|
||||
Für reproduzierbare Deployments den Digest
|
||||
bevorzugen und die bisher verwendeten Digests für spätere Updates aufbewahren.
|
||||
|
||||
`PUBLIC_URL` ist die vollständige HTTPS-Basisadresse ohne zusätzlichen Pfad,
|
||||
@@ -78,19 +80,20 @@ einrichten; `127.0.0.1` bezeichnet dort jeweils das eigene System.
|
||||
|
||||
## An der Registry anmelden und Image laden
|
||||
|
||||
Öffentliche GHCR-Pakete lassen sich ohne Anmeldung herunterladen. Für ein
|
||||
privates Paket einen GitHub Personal Access Token **(classic)** mit dem Scope
|
||||
`read:packages` verwenden. Das zugehörige GitHub-Konto benötigt Lesezugriff auf
|
||||
das Paket; bei Organisations-SSO den Token dafür autorisieren. Der Token wird
|
||||
Öffentliche Gitea-Pakete lassen sich ohne Anmeldung herunterladen. Für ein
|
||||
privates Paket einen Gitea Personal Access Token mit dem Scope
|
||||
`read:package` verwenden. Das zugehörige Gitea-Konto benötigt Lesezugriff auf
|
||||
das Paket. Der Token wird
|
||||
bei der Anmeldung als Passwort verdeckt eingegeben und gehört nicht in `.env`.
|
||||
Das kurzlebige `GITHUB_TOKEN` des CI-Jobs ist kein Deployment-Zugang.
|
||||
[GHCR-Authentifizierung](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic).
|
||||
Das kurzlebige `GITEA_TOKEN` des CI-Jobs ist kein Deployment-Zugang.
|
||||
[Gitea-Container-Registry](https://docs.gitea.com/usage/packages/container/),
|
||||
[Paketberechtigungen](https://docs.gitea.com/usage/packages/overview/#access-restrictions).
|
||||
|
||||
Nur für private Pakete anmelden und `GITHUB_USERNAME` durch den GitHub-Namen
|
||||
Nur für private Pakete anmelden und `GITEA_USERNAME` durch den Gitea-Namen
|
||||
des Token-Inhabers ersetzen:
|
||||
|
||||
```bash
|
||||
docker login ghcr.io --username GITHUB_USERNAME
|
||||
docker login gitlab.bartelluis.de --username GITEA_USERNAME
|
||||
```
|
||||
|
||||
Konfiguration prüfen und das gewählte Image herunterladen:
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# Gitea Actions und Container Registry
|
||||
|
||||
Das Projekt liegt auf
|
||||
[Gitea](https://gitlab.bartelluis.de/BartelLuis/Proxmox-AIS-Server).
|
||||
Der [Workflow](../.gitea/workflows/ci.yml) prüft Python und JavaScript, baut
|
||||
das Image und führt den Container-Smoke-Test aus. Bei aktivierter
|
||||
Veröffentlichung lädt er dasselbe getestete Image in die Gitea Container
|
||||
Registry. Der Workflow endet beim Image-Push; die Schritte auf dem Zielserver
|
||||
stehen in der [Deployment-Anleitung](deployment.md).
|
||||
|
||||
## Gitea und Runner einrichten
|
||||
|
||||
In den Repository-Einstellungen **Actions** aktivieren und unter
|
||||
**Settings → Actions → Runners** einen registrierten Runner prüfen oder
|
||||
einrichten. Gitea benötigt einen eigenen Runner-Dienst; ein GitHub-Runner-Label
|
||||
stellt keine Rechenkapazität bereit.
|
||||
[Gitea Actions einrichten](https://docs.gitea.com/usage/actions/quickstart/).
|
||||
|
||||
Alle Jobs verwenden `vars.AIS_RUNNER_LABEL` oder, wenn die Variable nicht gesetzt
|
||||
ist, `ubuntu-latest`. Das ausgewählte Label muss einem für dieses Repository
|
||||
verfügbaren, erreichbaren Linux-amd64-Runner entsprechen. Die Angabe
|
||||
`ubuntu-latest` wählt ein Runner-Label; sie installiert kein Ubuntu-Image.
|
||||
Für ein anderes Label in **Settings → Actions → Variables** beispielsweise
|
||||
`AIS_RUNNER_LABEL=ais-ci` setzen und genau dieses Label am Runner konfigurieren.
|
||||
|
||||
Die tatsächliche Job-Umgebung benötigt Git, Bash, OpenSSL, `ssh-keygen`, Python
|
||||
3.13 mit `venv`, Node.js 24 sowie Docker CLI und Buildx mit Zugriff auf einen
|
||||
laufenden Docker-Daemon. Die Setup-Actions richten Python 3.13 und Node.js 24
|
||||
für ihre jeweiligen Jobs ein. Die Umgebung muss außerdem bereits die
|
||||
JavaScript-Laufzeit zum Ausführen der Actions unterstützen. Bei einem
|
||||
Container-Runner müssen Werkzeuge und Docker-Zugriff im Job-Container verfügbar
|
||||
sein, nicht nur auf dem Host. Ein Standard-Node-Image allein enthält nicht die
|
||||
gesamte Build-Umgebung. Runner-Konfiguration und Label-Zuordnung sind in der
|
||||
[Runner-Anleitung](https://docs.gitea.com/1.26/usage/actions/act-runner/#labels)
|
||||
beschrieben.
|
||||
|
||||
Der Runner muss Gitea, die Registry, die absoluten Action-URLs im Workflow und
|
||||
die Downloadquellen für Python, Node.js, Python-Pakete und das Docker-Basisimage
|
||||
erreichen. Die Actions sind auf vollständige Commit-SHAs festgelegt.
|
||||
Der Artefakt-Upload verwendet die Gitea-kompatible Action
|
||||
`https://gitea.com/actions/gitea-upload-artifact`.
|
||||
|
||||
Diese Build-Umgebung ist unabhängig vom AIS-Host-Runner für die
|
||||
Postinstallation auf Proxmox-Hosts. CI-Code mit Docker-Zugriff auf einer dafür
|
||||
vorgesehenen Build-Maschine ausführen; Änderungen an Workflow und CI-Skripten
|
||||
vor der Übernahme prüfen.
|
||||
|
||||
## Registry und Veröffentlichung einrichten
|
||||
|
||||
Ohne `PUBLISH_IMAGES=true` führen alle Refs ausschließlich Prüfungen durch.
|
||||
In **Settings → Actions → Variables** folgende Repository-Variablen setzen:
|
||||
|
||||
| Variable | Wert und Bedeutung |
|
||||
| --- | --- |
|
||||
| `AIS_RUNNER_LABEL` | Optional: Label eines verfügbaren Runners; Standard `ubuntu-latest` |
|
||||
| `REGISTRY_HOST` | Optional: Registry-Host ohne Schema oder Pfad; Standard `gitlab.bartelluis.de` |
|
||||
| `REGISTRY_USERNAME` | Gitea-Benutzername des Token-Inhabers; für Veröffentlichung erforderlich |
|
||||
| `PUBLISH_IMAGES` | Genau `true`, nachdem Registry-Zugang und Schutzregeln eingerichtet sind |
|
||||
|
||||
Im Gitea-Benutzerkonto einen Personal Access Token mit Paket-Schreibrecht
|
||||
(`write:package`, in der Oberfläche **package: Read & Write**) erstellen.
|
||||
Den Wert unter **Settings → Actions → Secrets** als `REGISTRY_TOKEN` speichern.
|
||||
Das zugehörige Konto muss beim Paketinhaber `BartelLuis` Schreibzugriff besitzen.
|
||||
Das kurzlebige `GITEA_TOKEN` des Jobs unterstützt die Paketveröffentlichung
|
||||
nicht; es ersetzt dieses Secret nicht.
|
||||
[Gitea-Secrets](https://docs.gitea.com/usage/actions/secrets/),
|
||||
[Paketberechtigungen](https://docs.gitea.com/usage/packages/overview/#access-restrictions),
|
||||
[Gitea-Paketautorisierung](https://docs.gitea.com/usage/actions/comparison/#package-repository-authorization).
|
||||
|
||||
Der Imagepfad ist `<REGISTRY_HOST>/<kleingeschriebener Repositoryname>`, hier
|
||||
`gitlab.bartelluis.de/bartelluis/proxmox-ais-server`. Bei einem Fork oder einer
|
||||
Umbenennung ändert sich der Pfad. Pakete gehören in Gitea einem Benutzer oder
|
||||
einer Organisation. Nach dem ersten Push kann das Paket in seinen Einstellungen
|
||||
mit dem Repository verknüpft werden, damit es auch in dessen Paketliste erscheint.
|
||||
[Gitea Container Registry](https://docs.gitea.com/usage/packages/container/).
|
||||
|
||||
## Standardbranch und Release-Tags schützen
|
||||
|
||||
Vor `PUBLISH_IMAGES=true` in den Gitea-Repository-Einstellungen den
|
||||
Standardbranch `main` und die Release-Tags schützen. Für `main` direkte Pushes
|
||||
deaktivieren, Reviews und erfolgreiche Statuschecks verlangen sowie Force-Push
|
||||
und Löschen einschränken.
|
||||
Für `v*` die Erstellung, Änderung und Löschung auf Release-Verantwortliche
|
||||
begrenzen. Release-Tags auf einen geprüften Commit mit passender Projektversion
|
||||
setzen. Die Schutzregeln werden durch den Workflow-Commit nicht angelegt.
|
||||
[Geschützte Branches](https://docs.gitea.com/usage/access-control/protected-branches/),
|
||||
[Geschützte Tags](https://docs.gitea.com/usage/access-control/protected-tags/).
|
||||
|
||||
Nach dem ersten Pull-Request-Lauf die tatsächlich gemeldeten Statuskontexte
|
||||
für `python-tests`, `javascript-check`, `container-policy` und `container-verify`
|
||||
als verpflichtend auswählen. `container-publish` wird bei Pull Requests übersprungen und ist
|
||||
deshalb keine Pflichtprüfung.
|
||||
|
||||
Die CI entscheidet anhand von `PUBLISH_IMAGES`, Ereignis und Ref. Sie fragt
|
||||
keinen Schutzstatus ab: Giteas kompatibler `ref_protected`-Wert ist dafür nicht
|
||||
verwendbar. Die Gitea-Schutzregeln und restriktive Schreibrechte bleiben daher
|
||||
ein eigener Einrichtungsschritt.
|
||||
|
||||
## Auslöser und Image-Tags
|
||||
|
||||
Der Workflow läuft bei Branch- und Tag-Pushes, bei Pull Requests sowie bei
|
||||
manuellem Start über `workflow_dispatch`.
|
||||
|
||||
| Ref und Ereignis | Container-Job | Veröffentlichung |
|
||||
| --- | --- | --- |
|
||||
| `PUBLISH_IMAGES` fehlt oder ist nicht `true` | `container-verify` | Keine |
|
||||
| Pull Request, Feature-Branch oder sonstiger Tag | `container-verify` | Keine |
|
||||
| Standardbranch, Push oder manueller Start, Veröffentlichung aktiviert | `container-publish` | `sha-<vollständiger Commit-SHA>` und `edge` |
|
||||
| Tag `vX.Y.Z`, Push oder manueller Start, Veröffentlichung aktiviert | `container-publish` | `sha-<vollständiger Commit-SHA>` und `X.Y.Z` |
|
||||
|
||||
Release-Tags müssen vollständig dem Muster
|
||||
`v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)` entsprechen. Führende
|
||||
Nullen, Vorabversionen und Build-Zusätze veröffentlichen kein Image.
|
||||
Bei einem zur Veröffentlichung ausgewählten Release muss die Version außerdem
|
||||
exakt `[project].version` in [pyproject.toml](../pyproject.toml) entsprechen;
|
||||
eine Abweichung bricht den Publish-Job ab.
|
||||
|
||||
`edge` ist ein beweglicher Tag. Ein erneuter Lauf eines älteren Commits kann
|
||||
ihn zurücksetzen. SHA-Tags enthalten alle 40 Zeichen des Commits; externe
|
||||
Build-Abhängigkeiten können bei erneuten Builds zu einem anderen Digest führen.
|
||||
Es gibt keinen `latest`-Tag. Im Betrieb den Digest aus `deploy.env` verwenden
|
||||
und bisherige Registry-Versionen für Updates und Rollback aufbewahren.
|
||||
|
||||
## Jobs und Artefakte
|
||||
|
||||
`python-tests` führt über [ci/python-tests.sh](../ci/python-tests.sh) Pytest in
|
||||
einer temporären virtuellen Umgebung aus. `javascript-check` prüft mit
|
||||
`node --check provisioner/static/app.js` die JavaScript-Syntax.
|
||||
`container-policy` wählt den Container-Job aus. Nach erfolgreichen
|
||||
Quellcodeprüfungen läuft genau einer der Jobs `container-verify` und
|
||||
`container-publish`. Beide verwenden [ci/container.sh](../ci/container.sh);
|
||||
das Skript prüft vor einer Veröffentlichung erneut Ereignis, Ref und Opt-in.
|
||||
|
||||
Der [Container-Smoke-Test](../tests/container_smoke.py) prüft das gebaute
|
||||
`linux/amd64`-Image auf TLS, Anmeldung, CSRF, Webdateien und Datenerhalt über
|
||||
einen Dienstneustart. Er läuft als UID 10001 mit schreibgeschütztem Dateisystem,
|
||||
entfernten Linux-Capabilities und temporären Testdaten. Eine Proxmox-Installation
|
||||
ist kein Bestandteil dieses Tests. Der Publish-Job lädt das getestete Image
|
||||
ohne zweiten Build hoch. OCI-Labels enthalten Quellrepository, Commit und
|
||||
Anwendungsversion; Provenance- und SBOM-Attestierungen werden nicht erzeugt.
|
||||
|
||||
| Artefakt im Gitea-Lauf | Inhalt | Angefragte Aufbewahrung |
|
||||
| --- | --- | --- |
|
||||
| `python-test-results` | `reports/pytest.xml`, auch bei fehlgeschlagenen Tests, sofern erzeugt | 7 Tage |
|
||||
| `container-build` | `build.env` nach erfolgreicher Verifikation | 7 Tage |
|
||||
| `container-deploy` | `build.env` und `deploy.env` nach erfolgreicher Veröffentlichung | 30 Tage |
|
||||
|
||||
Die tatsächliche Aufbewahrung unterliegt der Gitea-Instanzkonfiguration.
|
||||
Unter **Actions** den erfolgreichen Lauf öffnen und das gewünschte Artefakt
|
||||
herunterladen. `build.env` enthält den temporären lokalen Image-Tag, Commit
|
||||
und Anwendungsversion. Der lokale `ci-…`-Tag wird nicht veröffentlicht und am
|
||||
Jobende entfernt. `deploy.env` enthält ausschließlich
|
||||
`PROVISIONER_IMAGE=gitlab.bartelluis.de/bartelluis/proxmox-ais-server@sha256:…`
|
||||
beziehungsweise die konfigurierte Registry-Adresse. Beide Dateien enthalten
|
||||
keine Zugangsdaten. Die Image-Zeile in `.env` auf dem Zielserver übernehmen.
|
||||
|
||||
## Erster Lauf und Release
|
||||
|
||||
Nach dem Push unter **Actions** prüfen, dass der Lauf einen Runner erhält und
|
||||
Python-, JavaScript- und Containerprüfungen erfolgreich sind. Nach aktivierter
|
||||
Veröffentlichung muss ein Standardbranch-Lauf außerdem die Image-Tags
|
||||
`sha-<Commit>` und `edge` sowie `container-deploy` mit `deploy.env` liefern.
|
||||
Lokale Tests ersetzen diesen Lauf mit Registry-Anmeldung und Push nicht.
|
||||
|
||||
Für ein Release zuerst `[project].version` ändern und den geprüften Commit
|
||||
übernehmen. Anschließend mit einem berechtigten Konto den passenden Tag
|
||||
erstellen und an den Gitea-Remote pushen. Im vorhandenen Checkout heißt er
|
||||
`origin`; vor dem Push mit `git remote -v` prüfen. Für Projektversion `0.1.0`,
|
||||
sofern der Tag noch nicht existiert:
|
||||
|
||||
```bash
|
||||
git tag -a v0.1.0 -m "Release 0.1.0"
|
||||
git push origin v0.1.0
|
||||
```
|
||||
|
||||
Der Release-Lauf muss den Image-Tag `0.1.0` erzeugen. Für einen manuellen Lauf
|
||||
unter **Actions** den Workflow und den gewünschten Ref auswählen. Auch dabei
|
||||
gelten Opt-in, Standardbranch- beziehungsweise Tag-Regel und Versionsabgleich.
|
||||
Branch-Push und Pull Request können für denselben Quellstand getrennte Läufe
|
||||
erzeugen. Pull Requests veröffentlichen keine Images.
|
||||
|
||||
## Fehlersuche und Deployment
|
||||
|
||||
Bei **Waiting** beziehungsweise **no matching online runner** das angeforderte
|
||||
Label mit den erreichbaren Runnern unter **Settings → Actions → Runners**
|
||||
vergleichen. Der bisherige Workflow verlangte `ubuntu-24.04`; dafür war kein
|
||||
passender Online-Runner verfügbar. `AIS_RUNNER_LABEL` auf ein tatsächlich
|
||||
vorhandenes Label setzen oder einen geeigneten Runner registrieren und starten.
|
||||
Ein Label-Wechsel allein ersetzt keine fehlende Runner-Installation.
|
||||
|
||||
Bei fehlendem Docker-Zugriff `docker info` und `docker buildx version` in der
|
||||
Job-Umgebung prüfen. Bei DNS-, Checkout- oder Artefaktfehlern müssen Giteas
|
||||
öffentliche `ROOT_URL`, die Checkout-Adresse und die vom Runner erreichbare
|
||||
Adresse zusammenpassen. Das Projekt verwendet `gitlab.bartelluis.de`; eine
|
||||
von der Instanz zurückgegebene abweichende Adresse wie `git.bartelluis.de` muss
|
||||
ebenfalls auflösbar und erreichbar sein oder in der Gitea-Konfiguration
|
||||
korrigiert werden.
|
||||
|
||||
Bei übersprungenem Publish-Job `PUBLISH_IMAGES`, Ereignis, Standardbranch und
|
||||
Tagformat prüfen. Bei `denied` oder `unauthorized` Registry-Host, Benutzername,
|
||||
`REGISTRY_TOKEN`, dessen `write:package`-Scope und die Rechte am Paketinhaber
|
||||
kontrollieren. Bei Versionsfehlern Git-Tag und `pyproject.toml` abgleichen.
|
||||
Fehlt `container-deploy`, die Build-, Smoke-, Push- und Upload-Logs prüfen.
|
||||
|
||||
Zum Deployment [compose.yaml](../compose.yaml) und
|
||||
[config/deployment.env.example](../config/deployment.env.example) auf den
|
||||
Zielserver kopieren. Die [Deployment-Anleitung](deployment.md) beschreibt
|
||||
Registry-Anmeldung, Erstinitialisierung, HTTPS-Prüfung, Updates und Rollback.
|
||||
Für private Pakete benötigt das Deploymentkonto einen eigenen Token mit
|
||||
`read:package`; öffentliche Pakete lassen sich anonym laden.
|
||||
+3
-202
@@ -1,203 +1,4 @@
|
||||
# GitHub Actions und Container Registry
|
||||
# CI/CD ist nach Gitea umgezogen
|
||||
|
||||
Der [Workflow](../.github/workflows/ci.yml) führt die Python-Tests aus, prüft die
|
||||
JavaScript-Syntax und baut und testet das Anwendungsimage. Freigegebene Builds
|
||||
werden in die GitHub Container Registry (GHCR) veröffentlicht. Der Workflow
|
||||
endet beim Image-Push und aktualisiert keinen Zielserver. Die anschließenden
|
||||
Betriebsschritte stehen in der [Deployment-Anleitung](deployment.md).
|
||||
|
||||
## Auslöser und Image-Tags
|
||||
|
||||
Der Workflow läuft bei Pushes auf alle Branches und Tags, bei Pull Requests
|
||||
und beim manuellen Start mit `workflow_dispatch`.
|
||||
|
||||
| Ref und Ereignis | Container-Job | Veröffentlichung |
|
||||
| --- | --- | --- |
|
||||
| Pull Request, auch aus einem Fork | `container-verify` | Keine |
|
||||
| Feature-Branch, ungeschützter Ref oder sonstiger Tag | `container-verify` | Keine |
|
||||
| Geschützter Standardbranch bei Push oder manuellem Start | `container-publish` | `sha-<vollständiger Commit-SHA>` und `edge` |
|
||||
| Geschützter Tag `vX.Y.Z` bei Push oder manuellem Start | `container-publish` | `sha-<vollständiger Commit-SHA>` und `X.Y.Z` |
|
||||
|
||||
Release-Tags müssen dem Muster `v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)`
|
||||
vollständig entsprechen. Führende Nullen, Vorabversionen wie `v1.0.0-rc.1` und
|
||||
Build-Zusätze veröffentlichen daher kein Image. Bei einem gültigen geschützten
|
||||
Release-Tag muss dessen Version außerdem exakt `[project].version` in
|
||||
[pyproject.toml](../pyproject.toml) entsprechen; eine Abweichung bricht den Lauf ab.
|
||||
|
||||
Das Image liegt für dieses Repository unter `ghcr.io/netivra/proxmox-ais`.
|
||||
Der Workflow leitet den Pfad aus dem kleingeschriebenen GitHub-Repositorynamen
|
||||
ab; bei einem Fork oder einer Umbenennung ändert sich deshalb der Imagepfad.
|
||||
`edge` bezeichnet den zuletzt veröffentlichten Standardbranch-Build. Ein
|
||||
erneuter Lauf eines älteren Commits kann `edge` zurücksetzen. SHA-Tags enthalten
|
||||
alle 40 Zeichen des Commits; erneute Builds desselben Quellstands können durch
|
||||
externe Build-Abhängigkeiten einen anderen Digest ergeben. Es gibt keinen
|
||||
`latest`-Tag. Im Betrieb den Digest aus `deploy.env` verwenden und die
|
||||
zugehörigen Registry-Versionen für Updates und Rollback aufbewahren.
|
||||
|
||||
## Jobs und Artefakte
|
||||
|
||||
`python-tests` und `javascript-check` prüfen den Quellstand. Das Skript
|
||||
[ci/python-tests.sh](../ci/python-tests.sh) installiert `.[dev]` in einer
|
||||
eigenen temporären virtuellen Umgebung, führt Pytest aus und entfernt die
|
||||
Umgebung anschließend. Die JavaScript-Prüfung verwendet
|
||||
`node --check provisioner/static/app.js`; sie ist kein Browsertest.
|
||||
`container-policy` bestimmt anhand des GitHub-Ereignisses, des Ref-Schutzes
|
||||
und des Branch- beziehungsweise Tag-Namens, welcher Container-Job laufen darf.
|
||||
|
||||
Nach erfolgreichen Python- und JavaScript-Prüfungen läuft genau einer der
|
||||
Jobs `container-verify` und `container-publish`. Beide bauen über
|
||||
[ci/container.sh](../ci/container.sh) ein `linux/amd64`-Image und prüfen es
|
||||
mit dem [Container-Smoke-Test](../tests/container_smoke.py). Der Publish-Job
|
||||
lädt genau das bereits getestete Image hoch, ohne einen zweiten Build.
|
||||
Die Veröffentlichung wird im Skript erneut auf Ereignis und Ref geprüft;
|
||||
dort erfolgt auch der Abgleich mit der Projektversion.
|
||||
|
||||
Publish-Jobs teilen eine Warteschlange mit `queue: max` und laufen einzeln.
|
||||
Bis zu 100 Jobs können warten; ein laufender Publish-Job wird durch neue
|
||||
Commits nicht abgebrochen. Die Reihenfolge richtet sich nach dem Eintritt in
|
||||
die Warteschlange, nicht zwingend nach dem Alter der Commits. Bei Pull Requests
|
||||
und ungeschützten Refs ersetzt ein neuer Lauf den vorherigen Lauf desselben Refs.
|
||||
[GitHub-Parallelität und Warteschlangen](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency).
|
||||
|
||||
Der Smoke-Test prüft TLS, Anmeldung, CSRF, ausgelieferte Webdateien und
|
||||
Datenerhalt über einen Dienstneustart. Er läuft als UID 10001 mit
|
||||
schreibgeschütztem Dateisystem, entfernten Linux-Capabilities und temporären
|
||||
Testdaten. Eine Proxmox-Installation ist kein Bestandteil dieses Tests.
|
||||
|
||||
Der Build verwendet `--provenance=false --sbom=false` und veröffentlicht
|
||||
keine Provenance- oder SBOM-Attestierungen. Die OCI-Labels für Quellrepository,
|
||||
Commit und Anwendungsversion bleiben erhalten.
|
||||
|
||||
| Artefakt in GitHub Actions | Inhalt | Aufbewahrung |
|
||||
| --- | --- | --- |
|
||||
| `python-test-results` | `reports/pytest.xml` als JUnit-Datei, auch nach fehlgeschlagenen Tests, sofern erzeugt | 7 Tage |
|
||||
| `container-build` | `build.env` nach erfolgreichem Verify-Job | 7 Tage |
|
||||
| `container-deploy` | `build.env` und `deploy.env` nach erfolgreichem Publish-Job | 30 Tage |
|
||||
|
||||
Die Aufbewahrung wird im Workflow mit `retention-days` gesetzt und ist durch
|
||||
die übergeordneten GitHub-Einstellungen begrenzt. Die JUnit-Datei wird als
|
||||
Download bereitgestellt; der Workflow installiert keinen zusätzlichen
|
||||
Testberichtsdienst. Auf der Übersicht des jeweiligen Laufs unter **Artifacts**
|
||||
das gewünschte Archiv herunterladen und entpacken.
|
||||
[Workflow-Artefakte](https://docs.github.com/en/actions/tutorials/store-and-share-data),
|
||||
[Artefakte herunterladen](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts).
|
||||
|
||||
`build.env` enthält den temporären lokalen Image-Tag, Commit und
|
||||
Anwendungsversion. Dieser `ci-…`-Tag wird nicht veröffentlicht und beim
|
||||
Jobende lokal entfernt. `deploy.env` enthält ausschließlich die
|
||||
Deploymentreferenz `PROVISIONER_IMAGE=ghcr.io/netivra/proxmox-ais@sha256:…`.
|
||||
Die Dateien enthalten keine Zugangsdaten. `deploy.env` wird auf dem Zielserver
|
||||
nicht automatisch eingelesen; seine Image-Zeile in die dortige `.env` übernehmen.
|
||||
|
||||
## GitHub einrichten
|
||||
|
||||
Ein eigener CI-Runner ist nicht erforderlich. Alle Jobs verwenden
|
||||
GitHub-gehostete Linux-amd64-Runner mit `ubuntu-24.04`. Der Workflow richtet
|
||||
Python 3.13 über `actions/setup-python` und Node.js 24 über `actions/setup-node`
|
||||
ein. Docker und Buildx werden vom Runner bereitgestellt und vor dem Build
|
||||
geprüft. Bash, OpenSSL und der OpenSSH-Client werden dort ebenfalls verwendet.
|
||||
Diese Build-Umgebung ist
|
||||
unabhängig vom AIS-Host-Runner für die Postinstallation auf Proxmox-Hosts.
|
||||
[GitHub-gehostete Runner](https://docs.github.com/en/actions/reference/runners/github-hosted-runners).
|
||||
|
||||
Im Repository unter **Settings → Actions → General** GitHub Actions aktivieren
|
||||
und die im Workflow verwendeten Actions von `actions/*` zulassen. Diese sind
|
||||
im Workflow auf vollständige Commit-SHAs festgelegt. Es werden
|
||||
keine selbst angelegten Repository-Secrets oder CI-Variablen benötigt.
|
||||
Der Workflow verwendet standardmäßig `contents: read`; nur
|
||||
`container-publish` erhält zusätzlich `packages: write` und verwendet das
|
||||
automatisch bereitgestellte `GITHUB_TOKEN` für die Anmeldung an `ghcr.io`.
|
||||
[Actions-Einstellungen](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository).
|
||||
|
||||
GHCR-Pakete sind bei der ersten Veröffentlichung standardmäßig privat. Ein
|
||||
bereits existierendes Paket muss mit diesem Repository verbunden sein und
|
||||
dessen Actions Schreibzugriff haben. In den Paketeinstellungen unter
|
||||
**Manage Actions access** gegebenenfalls `Netivra/Proxmox-AIS` mit Schreibrecht
|
||||
hinzufügen. Dort auch die gewünschte Paketsichtbarkeit festlegen.
|
||||
[GHCR und GITHUB_TOKEN](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-in-a-github-actions-workflow),
|
||||
[Paketzugriff und Sichtbarkeit](https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility).
|
||||
|
||||
## Standardbranch und Release-Tags schützen
|
||||
|
||||
**Vor der ersten Veröffentlichung müssen aktive Schutzregeln für die
|
||||
veröffentlichten Refs eingerichtet sein.** Der Workflow und das Skript
|
||||
verlangen `github.ref_protected == true`. GitHub setzt dieses Merkmal, wenn
|
||||
Branchschutz oder ein passendes Ruleset für den auslösenden Ref konfiguriert
|
||||
ist. Ohne Schutz wird nur geprüft.
|
||||
[GitHub-Ref-Kontext](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#github-context).
|
||||
|
||||
Unter den Repository-Einstellungen für Rulesets zwei Regeln anlegen und
|
||||
jeweils **Enforcement status: Active** wählen:
|
||||
|
||||
| Ruleset | Ziel | Vorgesehene Regeln |
|
||||
| --- | --- | --- |
|
||||
| Standardbranch | **Include default branch**, etwa `main` | Pull Request und Review verlangen; Löschen und Force-Push verhindern; erfolgreiche Statuschecks verlangen |
|
||||
| Releases | Tags mit dem Muster `v*` | Erstellung, Aktualisierung und Löschung beschränken; nur Release-Verantwortliche in die Bypass-Liste aufnehmen |
|
||||
|
||||
Für Pull Requests die Statuschecks `python-tests`, `javascript-check`,
|
||||
`container-policy` und `container-verify` nach ihrem ersten Lauf als
|
||||
verpflichtend auswählen. `container-publish` ist bei Pull Requests absichtlich
|
||||
übersprungen und wird deshalb nicht als Pflichtprüfung eingerichtet.
|
||||
Das Tag-Ruleset schützt alle `v*`-Tags; die engere Versionsprüfung übernimmt
|
||||
zusätzlich das CI-Skript. Release-Verantwortliche setzen Tags auf einen
|
||||
geprüften Commit mit der passenden Projektversion. Ein Tag auf einem Commit
|
||||
des geschützten Standardbranches ist selbst noch kein geschützter Tag.
|
||||
[Rulesets anlegen](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository),
|
||||
[Verfügbare Schutzregeln](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets).
|
||||
|
||||
Die Regeln und Paketberechtigungen im GitHub-Projekt sind Teil der Einrichtung;
|
||||
sie werden durch den Commit der Workflow-Datei nicht automatisch angelegt.
|
||||
Welche Rulesets verfügbar sind, hängt von Repository-Sichtbarkeit und
|
||||
GitHub-Tarif ab. Die CI-Abfragen ersetzen keine restriktiven Schreibrechte
|
||||
auf Repository und Paket; Änderungen an Workflow und CI-Skripten im Review prüfen.
|
||||
[Ruleset-Verfügbarkeit](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository).
|
||||
|
||||
## Erster Lauf und Release
|
||||
|
||||
Nach dem Push der Workflow-Datei unter **Actions** den Lauf öffnen.
|
||||
Python- und JavaScript-Prüfung sowie der passende Container-Job müssen
|
||||
erfolgreich sein. Für einen geschützten Standardbranch zusätzlich prüfen,
|
||||
dass das GHCR-Paket `sha-<Commit>` und `edge` enthält und das Artefakt
|
||||
`container-deploy` mit `deploy.env` bereitsteht. Ein lokaler Test ersetzt
|
||||
diesen ersten GitHub-Lauf samt GHCR-Anmeldung und Push nicht.
|
||||
|
||||
Für ein Release zuerst `[project].version` auf die gewünschte Version setzen
|
||||
und den geprüften Commit übernehmen. Anschließend durch ein für das
|
||||
Release-Ruleset berechtigtes Konto den passenden Tag erstellen und pushen.
|
||||
Im vorhandenen Checkout heißt der GitHub-Remote `GH-AIS`; in anderen Checkouts
|
||||
den Namen mit `git remote -v` prüfen und im Befehl entsprechend ersetzen.
|
||||
Für Projektversion `0.1.0`, sofern der Tag noch nicht existiert:
|
||||
|
||||
```bash
|
||||
git tag -a v0.1.0 -m "Release 0.1.0"
|
||||
git push GH-AIS v0.1.0
|
||||
```
|
||||
|
||||
Der Release-Lauf muss zusätzlich den Image-Tag `0.1.0` erzeugen. Für einen
|
||||
manuellen Branch-Lauf unter **Actions** den Workflow und **Run workflow**
|
||||
wählen. Dafür muss die Workflow-Datei bereits auf dem Standardbranch liegen.
|
||||
Ein manueller Lauf veröffentlicht nur, wenn dieselben Ref-Schutz- und
|
||||
Versionsbedingungen erfüllt sind.
|
||||
[Workflow manuell starten](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow).
|
||||
|
||||
Branch-Push und Pull Request können für denselben Quellstand getrennte Läufe
|
||||
erzeugen. Pull Requests werden im Merge-Kontext geprüft; ihre Tests führen
|
||||
keinen Registry-Push aus. Die Jobs laden keine produktiven Deployment-Zugänge.
|
||||
|
||||
## Fehlersuche und Deployment
|
||||
|
||||
Bei übersprungenem Publish-Job zuerst Ereignis, Standardbranch, Tagformat und
|
||||
aktive Schutzregeln prüfen. Bei einer Versionsabweichung müssen Git-Tag und
|
||||
`pyproject.toml` aufeinander abgestimmt werden. Bei `denied` oder `unauthorized`
|
||||
im Publish-Job die Organisationsrichtlinien, `packages: write` und den
|
||||
Actions-Zugriff auf das GHCR-Paket kontrollieren. Fehlt `container-deploy`,
|
||||
die Logs des Build-, Smoke- und Push-Schritts prüfen; vor erfolgreicher
|
||||
Veröffentlichung steht kein Deployment-Digest bereit.
|
||||
|
||||
Zum Deployment [compose.yaml](../compose.yaml) und
|
||||
[config/deployment.env.example](../config/deployment.env.example) auf den
|
||||
Zielserver kopieren und die Image-Zeile aus `deploy.env` verwenden.
|
||||
Die [Deployment-Anleitung](deployment.md) beschreibt GHCR-Anmeldung,
|
||||
Erstinitialisierung, HTTPS-Prüfung, Updates und Rollback. Für private Pakete
|
||||
benötigt das Deploymentkonto einen Personal Access Token (classic) mit
|
||||
`read:packages`; öffentliche Pakete lassen sich anonym laden.
|
||||
Die aktuelle Anleitung steht unter [Gitea Actions und Container Registry](gitea-actions.md).
|
||||
Der Workflow liegt in [.gitea/workflows/ci.yml](../.gitea/workflows/ci.yml).
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Betrieb und Wiederherstellung
|
||||
|
||||
Für die Installation aus der GitHub Container Registry (GHCR) siehe
|
||||
Für die Installation aus der Gitea Container Registry siehe
|
||||
[Deployment-Anleitung](deployment.md). Alle `docker compose`-Befehle unten
|
||||
werden im Deployment-Verzeichnis mit `compose.yaml` und `.env` ausgeführt.
|
||||
`PROVISIONER_IMAGE` verweist auf das veröffentlichte Image, vorzugsweise per Digest.
|
||||
|
||||
+23
-4
@@ -1,11 +1,30 @@
|
||||
# Softwaretestbericht
|
||||
|
||||
Stand: 13. September 2026. Die hier dokumentierten Softwaretests führen keine
|
||||
Die hier dokumentierten Softwaretests führen keine
|
||||
Proxmox-Installation aus und verändern keine Hostdatenträger, Paketquellen oder
|
||||
Produktivdienste. Die noch offenen praktischen Prüfungen stehen in der
|
||||
[Kompatibilitätsmatrix](compatibility.md).
|
||||
|
||||
## Automatisierte Prüfungen
|
||||
## Gitea-Migration: 14. September 2026
|
||||
|
||||
Der aktuelle [Workflow](../.gitea/workflows/ci.yml) und die
|
||||
[Einrichtungsanleitung](gitea-actions.md) verwenden Gitea Actions und die
|
||||
Gitea Container Registry. Die Prüfung unter Windows mit Python 3.13.15 ergab:
|
||||
|
||||
- 120 CI-Skripttests bestanden: Veröffentlichungs-Opt-in, Ereignis- und Ref-Regeln,
|
||||
Versionsabgleich, Registry-Adressen und Paket-Token sowie Build-, Smoke-,
|
||||
Push- und Aufräumfehler. Der konstante Gitea-Wert `ref_protected=false`
|
||||
verhindert bei gesetztem Opt-in keine Veröffentlichung mehr.
|
||||
- 47 Anwendungstests bestanden; zwei Tests zur nativen Linux-Prozessüberwachung
|
||||
wurden unter Windows übersprungen.
|
||||
- Workflow-YAML, Shell-Syntax und JavaScript-Syntax wurden erfolgreich geprüft.
|
||||
|
||||
Ein neuer Linux-Containerbuild und ein vollständiger Gitea-Lauf einschließlich
|
||||
Registry-Push sind noch offen. Der vorhandene Gitea-Lauf wartet auf einen
|
||||
Online-Runner mit passendem Label; die Repository-Runnerliste ist leer.
|
||||
Die folgenden Ergebnisse dokumentieren den Stand vor dieser Migration.
|
||||
|
||||
## Automatisierte Prüfungen: 13. September 2026
|
||||
|
||||
Die 49 Anwendungstests wurden unter Windows mit Python 3.14 ausgeführt:
|
||||
47 Tests bestanden; zwei Linux-spezifische Prozesstests wurden dort übersprungen.
|
||||
@@ -86,9 +105,9 @@ TLS-Reverse-Proxy, reale Provisionierungsnetze, Medienboot, Initialisierung und
|
||||
Wiederherstellung in der späteren Betriebsumgebung müssen zusätzlich geprüft werden.
|
||||
Ein erfolgreicher Image-Build allein ist keine Betriebsfreigabe.
|
||||
|
||||
## GitHub Actions und GHCR
|
||||
## GitHub Actions und GHCR: historischer Stand
|
||||
|
||||
Der [Workflow](../.github/workflows/ci.yml) verwendet GitHub-gehostete
|
||||
Der damalige Workflow verwendete GitHub-gehostete
|
||||
Ubuntu-24.04-Runner, Python 3.13 und Node.js 24. Python- und JavaScript-Prüfung
|
||||
müssen erfolgreich sein, bevor das Containerimage gebaut und getestet wird.
|
||||
Die [Einrichtungsanleitung](github-actions.md) beschreibt Schutzregeln,
|
||||
|
||||
+117
-15
@@ -12,7 +12,8 @@ import pytest
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
VERSION = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
|
||||
SHA = "1234567890abcdef1234567890abcdef12345678"
|
||||
REGISTRY_IMAGE = "ghcr.io/team/proxmox-ais"
|
||||
REGISTRY_HOST = "gitlab.bartelluis.de"
|
||||
REGISTRY_IMAGE = f"{REGISTRY_HOST}/team/proxmox-ais"
|
||||
DIGEST = f"{REGISTRY_IMAGE}@sha256:{'a' * 64}"
|
||||
PASSWORD = "dummy-ci-token-$()-do-not-print"
|
||||
|
||||
@@ -37,6 +38,7 @@ def container_ci(tmp_path):
|
||||
(tmp_path / "bin").mkdir()
|
||||
(tmp_path / "tmp").mkdir()
|
||||
shutil.copyfile(ROOT / "ci/container.sh", tmp_path / "ci/container.sh")
|
||||
shutil.copyfile(ROOT / "ci/publish-policy.sh", tmp_path / "ci/publish-policy.sh")
|
||||
shutil.copyfile(ROOT / "pyproject.toml", tmp_path / "pyproject.toml")
|
||||
smoke_source = (ROOT / "tests/container_smoke.py").read_bytes()
|
||||
(tmp_path / "tests/container_smoke.py").write_bytes(smoke_source)
|
||||
@@ -78,14 +80,17 @@ esac
|
||||
"GITHUB_RUN_ATTEMPT": "1",
|
||||
"GITHUB_JOB": "container_publish",
|
||||
"GITHUB_REPOSITORY": "Team/Proxmox-AIS",
|
||||
"GITHUB_SERVER_URL": "https://github.com",
|
||||
"GITHUB_SERVER_URL": "https://gitlab.bartelluis.de",
|
||||
"GITHUB_EVENT_NAME": "push",
|
||||
"GITHUB_REF_PROTECTED": "true",
|
||||
"GITHUB_REF_PROTECTED": "false",
|
||||
"GITHUB_REF_TYPE": "branch",
|
||||
"GITHUB_REF_NAME": "main",
|
||||
"DEFAULT_BRANCH": "main",
|
||||
"PUBLISH_IMAGES": "true",
|
||||
"GITHUB_ACTOR": "ci-user",
|
||||
"GHCR_TOKEN": PASSWORD,
|
||||
"REGISTRY_HOST": REGISTRY_HOST,
|
||||
"REGISTRY_USERNAME": "registry-owner",
|
||||
"REGISTRY_TOKEN": PASSWORD,
|
||||
"MOCK_DOCKER_LOG": (tmp_path / "docker.log").as_posix(),
|
||||
"MOCK_SMOKE_STDIN": (tmp_path / "smoke.stdin").as_posix(),
|
||||
"MOCK_LOGIN_STDIN": (tmp_path / "login.stdin").as_posix(),
|
||||
@@ -123,7 +128,7 @@ def test_default_branch_publishes_only_after_hardened_image_smoke(container_ci):
|
||||
assert build[build.index("--platform") + 1] == "linux/amd64"
|
||||
assert "--provenance=false" in build and "--sbom=false" in build
|
||||
assert f"org.opencontainers.image.revision={SHA}" in build
|
||||
assert "org.opencontainers.image.source=https://github.com/Team/Proxmox-AIS" in build
|
||||
assert "org.opencontainers.image.source=https://gitlab.bartelluis.de/Team/Proxmox-AIS" in build
|
||||
assert build[build.index("--tag") + 1] == build_image
|
||||
smoke = outcome.calls[1]
|
||||
for option in ["--read-only", "--cap-drop", "ALL", "no-new-privileges:true", "/tmp:rw,noexec,nosuid,size=128m"]:
|
||||
@@ -138,7 +143,7 @@ def test_default_branch_publishes_only_after_hardened_image_smoke(container_ci):
|
||||
]
|
||||
assert (outcome.root / "deploy.env").read_text() == f"PROVISIONER_IMAGE={DIGEST}\n"
|
||||
assert "ci-210-1-container_publish" in (outcome.root / "build.env").read_text()
|
||||
assert outcome.calls[2] == ["login", "ghcr.io", "--username", "ci-user", "--password-stdin"]
|
||||
assert outcome.calls[2] == ["login", REGISTRY_HOST, "--username", "registry-owner", "--password-stdin"]
|
||||
assert (outcome.root / "login.stdin").read_text() == PASSWORD
|
||||
assert PASSWORD not in outcome.result.stdout + outcome.result.stderr + (outcome.root / "docker.log").read_text()
|
||||
config_path = Path((outcome.root / "docker-config.path").read_text())
|
||||
@@ -155,8 +160,8 @@ def test_release_tag_matches_project_version_and_does_not_move_edge(container_ci
|
||||
|
||||
def test_pull_request_verifies_without_registry_credentials(container_ci):
|
||||
outcome = container_ci(
|
||||
"verify", GITHUB_EVENT_NAME="pull_request", GITHUB_REF_NAME="1/merge",
|
||||
GITHUB_REF_PROTECTED="false", GITHUB_ACTOR=None, GHCR_TOKEN=None,
|
||||
"verify", GITHUB_EVENT_NAME="pull_request", GITHUB_REF_NAME="1/head",
|
||||
PUBLISH_IMAGES=None, GITHUB_ACTOR=None, REGISTRY_USERNAME=None, REGISTRY_TOKEN=None,
|
||||
)
|
||||
assert outcome.result.returncode == 0, outcome.result.stderr
|
||||
assert [call[0] for call in outcome.calls] == ["build", "run", "rm", "image"]
|
||||
@@ -166,14 +171,49 @@ def test_pull_request_verifies_without_registry_credentials(container_ci):
|
||||
assert not (outcome.root / "deploy.env").exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("protected", ["false", None])
|
||||
def test_gitea_publication_opt_in_does_not_depend_on_ref_protected(container_ci, protected):
|
||||
outcome = container_ci(GITHUB_REF_PROTECTED=protected, GITHUB_ACTOR=None)
|
||||
assert outcome.result.returncode == 0, outcome.result.stderr
|
||||
assert outcome.calls[2] == ["login", REGISTRY_HOST, "--username", "registry-owner", "--password-stdin"]
|
||||
assert (outcome.root / "deploy.env").is_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("registry_host", [
|
||||
None, "", "https://registry.example.test", "registry.example.test/images",
|
||||
"registry.example.test\nother.example.test", "Registry.example.test",
|
||||
"registry.example.test:invalid", "registry.example.test:",
|
||||
"-registry.example.test", "user@registry.example.test",
|
||||
])
|
||||
def test_invalid_registry_host_fails_before_build(container_ci, registry_host):
|
||||
outcome = container_ci(REGISTRY_HOST=registry_host)
|
||||
assert outcome.result.returncode != 0
|
||||
assert not outcome.calls
|
||||
assert not (outcome.root / "deploy.env").exists()
|
||||
|
||||
|
||||
def test_registry_port_is_used_for_login_tags_and_deployment_digest(container_ci):
|
||||
registry_host = "registry.example.test:5000"
|
||||
registry_image = f"{registry_host}/team/proxmox-ais"
|
||||
digest = f"{registry_image}@sha256:{'b' * 64}"
|
||||
outcome = container_ci(REGISTRY_HOST=registry_host, MOCK_REPO_DIGESTS=digest)
|
||||
assert outcome.result.returncode == 0, outcome.result.stderr
|
||||
assert outcome.calls[2] == ["login", registry_host, "--username", "registry-owner", "--password-stdin"]
|
||||
assert [call[1] for call in outcome.calls if call[0] == "push"] == [
|
||||
f"{registry_image}:sha-{SHA}", f"{registry_image}:edge"
|
||||
]
|
||||
assert (outcome.root / "deploy.env").read_text() == f"PROVISIONER_IMAGE={digest}\n"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("overrides", [
|
||||
{"GITHUB_REF_NAME": "feature/ci"},
|
||||
{"GITHUB_REF_NAME": f"v{VERSION}"},
|
||||
{"GITHUB_REF_NAME": None},
|
||||
{"DEFAULT_BRANCH": ""},
|
||||
{"GITHUB_REF_PROTECTED": "false"},
|
||||
{"GITHUB_REF_PROTECTED": None},
|
||||
{"GITHUB_REF_PROTECTED": "TRUE"},
|
||||
{"PUBLISH_IMAGES": "false"},
|
||||
{"PUBLISH_IMAGES": None},
|
||||
{"PUBLISH_IMAGES": "TRUE"},
|
||||
{"PUBLISH_IMAGES": "1"},
|
||||
{"GITHUB_EVENT_NAME": "pull_request"},
|
||||
{"GITHUB_EVENT_NAME": "pull_request_target"},
|
||||
{"GITHUB_EVENT_NAME": "workflow_run"},
|
||||
@@ -191,9 +231,9 @@ def test_pull_request_verifies_without_registry_credentials(container_ci):
|
||||
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.1.00"},
|
||||
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.1"},
|
||||
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.1.0.0"},
|
||||
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": f"v{VERSION}", "GITHUB_REF_PROTECTED": "false"},
|
||||
{"GHCR_TOKEN": None},
|
||||
{"GITHUB_ACTOR": None},
|
||||
{"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": f"v{VERSION}", "PUBLISH_IMAGES": "false"},
|
||||
{"REGISTRY_TOKEN": None},
|
||||
{"REGISTRY_USERNAME": None},
|
||||
{"GITHUB_SHA": "1234"},
|
||||
{"GITHUB_RUN_ID": "invalid"},
|
||||
{"GITHUB_RUN_ATTEMPT": "../2"},
|
||||
@@ -214,7 +254,7 @@ def test_unauthorized_or_invalid_publication_fails_before_build(container_ci, ov
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ref_type, ref_name", [("branch", "main"), ("tag", f"v{VERSION}")])
|
||||
def test_manual_run_can_publish_protected_default_branch_or_release(container_ci, ref_type, ref_name):
|
||||
def test_manual_run_can_publish_opted_in_default_branch_or_release(container_ci, ref_type, ref_name):
|
||||
outcome = container_ci(
|
||||
GITHUB_EVENT_NAME="workflow_dispatch", GITHUB_REF_TYPE=ref_type, GITHUB_REF_NAME=ref_name,
|
||||
)
|
||||
@@ -269,6 +309,68 @@ def test_missing_or_invalid_registry_digest_fails_without_artifact(container_ci,
|
||||
assert not (outcome.root / "deploy.env").exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("overrides, expected", [
|
||||
({}, "true"),
|
||||
({"GITHUB_EVENT_NAME": "workflow_dispatch"}, "true"),
|
||||
({"GITHUB_REF_NAME": "trunk", "DEFAULT_BRANCH": "trunk"}, "true"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v0.0.0"}, "true"),
|
||||
({"GITHUB_EVENT_NAME": "workflow_dispatch", "GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v12.34.56"}, "true"),
|
||||
({"GITHUB_REF_PROTECTED": None}, "true"),
|
||||
({"PUBLISH_IMAGES": None}, "false"),
|
||||
({"PUBLISH_IMAGES": ""}, "false"),
|
||||
({"PUBLISH_IMAGES": "false"}, "false"),
|
||||
({"PUBLISH_IMAGES": "TRUE"}, "false"),
|
||||
({"PUBLISH_IMAGES": "1"}, "false"),
|
||||
({"PUBLISH_IMAGES": "true\n"}, "false"),
|
||||
({"PUBLISH_IMAGES": "false", "GITHUB_REF_PROTECTED": "true"}, "false"),
|
||||
({"GITHUB_EVENT_NAME": None}, "false"),
|
||||
({"GITHUB_EVENT_NAME": "pull_request"}, "false"),
|
||||
({"GITHUB_EVENT_NAME": "pull_request_target"}, "false"),
|
||||
({"GITHUB_EVENT_NAME": "workflow_run"}, "false"),
|
||||
({"GITHUB_EVENT_NAME": "schedule"}, "false"),
|
||||
({"GITHUB_REF_TYPE": None}, "false"),
|
||||
({"GITHUB_REF_TYPE": "pull_request"}, "false"),
|
||||
({"GITHUB_REF_NAME": "feature/ci"}, "false"),
|
||||
({"GITHUB_REF_NAME": None}, "false"),
|
||||
({"DEFAULT_BRANCH": None}, "false"),
|
||||
({"DEFAULT_BRANCH": "", "GITHUB_REF_NAME": ""}, "false"),
|
||||
({"GITHUB_REF_NAME": "main;touch injected"}, "false"),
|
||||
({"GITHUB_REF_NAME": "$(touch injected)"}, "false"),
|
||||
({"GITHUB_REF_NAME": "`touch injected`"}, "false"),
|
||||
({"GITHUB_REF_NAME": "release/$(touch${IFS}injected)", "DEFAULT_BRANCH": "release/$(touch${IFS}injected)"}, "true"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "main"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v01.2.3"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.02.3"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.03"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3.4"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3-rc1"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3+build"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3\nother"}, "false"),
|
||||
({"GITHUB_REF_TYPE": "tag", "GITHUB_REF_NAME": "v1.2.3$(touch injected)"}, "false"),
|
||||
])
|
||||
def test_publication_policy_matrix(tmp_path, overrides, expected):
|
||||
shutil.copyfile(ROOT / "ci/publish-policy.sh", tmp_path / "publish-policy.sh")
|
||||
environment = {
|
||||
**os.environ,
|
||||
"PUBLISH_IMAGES": "true",
|
||||
"GITHUB_EVENT_NAME": "push",
|
||||
"GITHUB_REF_TYPE": "branch",
|
||||
"GITHUB_REF_NAME": "main",
|
||||
"DEFAULT_BRANCH": "main",
|
||||
"GITHUB_REF_PROTECTED": "false",
|
||||
**overrides,
|
||||
}
|
||||
environment = {name: value for name, value in environment.items() if value is not None}
|
||||
result = subprocess.run(
|
||||
[posix_shell(), "publish-policy.sh"], cwd=tmp_path, env=environment,
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stdout == f"{expected}\n"
|
||||
assert not (tmp_path / "injected").exists(), "Ref names must never be evaluated as shell code"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def python_ci(tmp_path):
|
||||
shell = posix_shell()
|
||||
|
||||
Reference in New Issue
Block a user