Files
FabledSteward/.forgejo/workflows/ci.yml
T
bvandeusen fb579bcf97
CI / lint (push) Successful in 4s
CI / unit (push) Successful in 9s
CI / integration (push) Failing after 2m22s
feat(ansible): ship ansible in image + allow system-triggered runs (task 545)
Foundation for the Ansible automation milestone (#37) — makes the existing
manual playbook runner actually executable and the schema automation-ready.

- pyproject: [ansible] extra (full ansible package, batteries-included, pinned)
- Dockerfile: pip install .[ansible]; add openssh-client for remote runs
- models/ansible.py + migration 0012: AnsibleRun.triggered_by now nullable so
  automated (alert/schedule) runs need no human actor
- ansible/routes.py + run_detail.html: show 'Triggered by' (username or 'system')
- CI: integration lane installs .[dev,ansible]; new tests/integration/
  test_ansible_foundation.py runs a real connection:local playbook end-to-end,
  asserts success+output, and round-trips a NULL-triggered run

No extra-vars/limit/credentials/scheduling here — those are their own #37 tasks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 11:31:43 -04:00

93 lines
3.6 KiB
YAML

name: CI
# CI lanes per FabledRulebook forgejo.md "CI philosophy":
# - lint: ruff only, no dep install — fast-fail for the common lint bounce.
# - unit: pytest -m "not integration"; no service containers, no DB.
# - integration: postgres service container; boots the real app + runs all
# (core + bundled-plugin) migrations.
# Single repo: first-party plugins are bundled in-tree under plugins/, so the
# unit lane imports them directly — no cross-repo checkout.
on:
push:
branches: [dev, main]
# pull_request intentionally absent — push on [dev, main] already fires CI for
# every dev commit and dev→main PRs. Single-operator repo, no fork PRs.
jobs:
# Fast-fail lint lane. ruff is pre-installed in the ci-python image, so this
# runs with NO dependency install and surfaces lint bounces in seconds.
lint:
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
steps:
- uses: actions/checkout@v4
- name: Ruff lint
run: ruff check steward/ plugins/ tests/
unit:
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
steps:
- uses: actions/checkout@v4
- name: Install Steward + test deps
# uv: 5-10x faster wheel resolve than pip on cold caches. Falls back to
# pip on uv-missing runners. Steward declares its deps in pyproject; the
# [dev] extra adds pytest + pytest-asyncio.
run: |
if command -v uv >/dev/null 2>&1; then
uv pip install --system -e '.[dev]'
else
pip install -e '.[dev]'
fi
- name: Pytest (unit only — integration runs in its own lane)
run: pytest tests/ -v -m "not integration"
integration:
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
env:
STEWARD_SECRET_KEY: ci_integration_placeholder
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: steward
POSTGRES_PASSWORD: steward
POSTGRES_DB: steward
options: >-
--health-cmd "pg_isready -U steward"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v4
- name: Boot + migrate against Postgres
# act_runner (swarm-runner) puts service containers on the default bridge
# with no embedded DNS, so the hostname `postgres` won't resolve — we
# discover the service container's bridge IP via the docker socket. The
# job name `integration` (no underscore) is the docker-ps name filter.
run: |
set -euxo pipefail
echo "=== container landscape (diagnostic for filter scoping) ==="
docker ps -a --format '{{.ID}} {{.Image}} -> {{.Names}}'
echo "=== end landscape ==="
PG=$(docker ps --filter "name=integration" --filter "ancestor=postgres:16-alpine" -q | head -n1)
test -n "$PG"
PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$PG")
test -n "$PG_IP"
export STEWARD_DATABASE_URL="postgresql+asyncpg://steward:steward@$PG_IP:5432/steward"
for i in $(seq 1 60); do
(echo > "/dev/tcp/$PG_IP/5432") >/dev/null 2>&1 && break
sleep 2
done
if command -v uv >/dev/null 2>&1; then
uv pip install --system -e '.[dev,ansible]'
else
pip install -e '.[dev,ansible]'
fi
pytest tests/integration -v -m integration --durations=10