4.7 KiB
Enrolling a recipe under cc-ci (D5)
Adding a recipe is a small, repeatable, no-harness-surgery operation:
1. Make the recipe available on the mirror
Recipes under test live on the private mirror git.autonomic.zone/recipe-maintainers/<recipe>,
synced from upstream git.coopcloud.tech. If not yet mirrored, mirror it (abra fetch + push to the
org) — see the recipe mirror+PR flow (plan §4.1). A recipe may ship its own tests/ dir in its repo;
those are discovered and run against the live app (D4 — see below).
2. Add the per-recipe test tree in this repo
tests/<recipe>/
├── recipe_meta.py # optional per-recipe harness config (see below)
├── install_steps.sh # optional custom install-steps hook (pre-deploy setup)
├── test_install.py # optional install overlay (else the generic install tier runs)
├── test_upgrade.py # optional upgrade overlay (else the generic upgrade tier runs)
├── test_backup.py # optional backup overlay (else the generic backup tier runs)
└── test_restore.py # optional restore overlay (else the generic restore tier runs)
A recipe is testable with ZERO config: with no overlay files, the generic lifecycle suite
runs (install/upgrade/backup/restore) against a single shared deployment — see docs/testing.md for
the full model (tiers, deploy-once, override-vs-extend, precedence, the install-steps hook). The
per-recipe dir only holds the bits where the recipe needs more than the generic.
To add recipe-specific coverage, drop a tests/<recipe>/test_<op>.py overlay (it OVERRIDES the
generic for that op; absent ⇒ generic runs). Overlays are assertion-only against the shared live
deployment (the live_app fixture; they never deploy), and reuse the generic op + serving check by
composition (from harness import generic; generic.do_upgrade(...) etc.), adding recipe-specific
assertions. Copy an existing overlay (tests/custom-html/ simple/volume marker; tests/keycloak/
admin-API; tests/matrix-synapse/ db-service psql marker). Do not edit the shared
tests/conftest.py / runner/harness/ to add a recipe — set per-recipe config in recipe_meta.py:
HEALTH_PATH = "/realms/master" # path that returns a healthy status (default "/")
HEALTH_OK = (200,) # acceptable status codes (default 200/301/302)
DEPLOY_TIMEOUT = 600 # seconds for services to converge (default 600)
HTTP_TIMEOUT = 600 # seconds for the app to answer (default 300)
BACKUP_CAPABLE = True # override backup-capability auto-detect (default: scan compose)
EXTRA_ENV = {"KEY": "value"} # or EXTRA_ENV(domain) -> dict; extra .env keys set at deploy
Useful harness.lifecycle helpers for overlays: http_get, http_fetch, http_body,
exec_in_app (use this for data markers — volume/DB, robust to the serving layer); the lifecycle ops
themselves come from harness.generic (assert_serving, do_upgrade, do_backup, do_restore).
The harness forces LETS_ENCRYPT_ENV="" (no ACME), a unique short domain per run, and guarantees
teardown.
3. Recipe-local tests (D4)
If the recipe's own repo contains tests/test_*.py, the runner snapshots them right after fetch and
runs them against the live deployment as a recipe-local stage. Contract: those tests receive
env CCCI_BASE_URL (e.g. https://<app>.ci.commoninternet.net/) and CCCI_APP_DOMAIN.
4. Add the repo to the bridge poll list
The trigger is polling (primary): add the repo's full name to the comment-bridge POLL_REPOS
csv (nix/modules/bridge.nix) and nixos-rebuild switch. The bridge then polls that repo's open PRs
every 30s and fires a run on a new !testme comment from an authorized org member. This needs only
read + comment access — no webhook, no repo-admin.
!testme on a PR runs install/upgrade/backup + any recipe-local tests, and reports back to the PR.
Optional: lower-latency webhook (admin-registered)
Polling already satisfies D1 (<60s). For lower latency an admin may optionally register a
Gitea issue_comment webhook (the bot does not self-register one — that needs repo-admin):
- URL
https://ci.commoninternet.net/hook, content-typeapplication/json, eventIssue Comment, secret = the shared webhook HMAC (secrets/secrets.yaml→webhook_hmac). - The Gitea instance must allow the host (admin: add
ci.commoninternet.netto the[webhook] ALLOWED_HOST_LIST).
The webhook and poller are deduped by comment id, so a comment seen by both fires only once.
Run locally
RECIPE=<recipe> PR=<n> REF=<sha-or-branch> SRC=recipe-maintainers/<recipe> \
STAGES=install,upgrade,backup cc-ci-run runner/run_recipe_ci.py