Task 1.8 — Gitea CI dry-run workflow
.gitea/workflows/build.yml builds the directus image on path-filtered pushes to main and validates the boot pipeline against a throwaway Postgres before pushing the image to the registry. The dry-run is the gate that catches snapshot drift, broken db-init scripts, or incompatible schema changes before they reach stage. Workflow shape (mirrors processor's CI but tailored to Directus): - Path filter: snapshots/, db-init/, extensions/, scripts/, entrypoint.sh, Dockerfile, the workflow file itself. Docs-only commits (.planning/, README.md, compose.dev.yaml, package.json) do NOT trigger CI. - Throwaway Postgres via services: block, pinned to the same timescale/timescaledb-ha:pg16.6-ts2.17.2-all tag as compose.dev.yaml. - Plain `docker build` (NOT build-push-action) so the image stays in the local daemon for the subsequent docker run dry-run. - Dry-run: --network host + --entrypoint bash to override the upstream entrypoint and run only apply-db-init.sh && schema-apply.sh. Skips bootstrap and pm2-runtime — the schema apply is the gate. - Two image tags: :main (mutable) and :<sha> (immutable). - Optional Portainer webhook gated on secret presence; curl -fsS so a misconfigured URL fails the step explicitly. Spec corrections folded in (the spec's draft had two contradictions that would have failed at runtime): 1. DB_HOST=localhost (not 'postgres'). With --network host, service containers are reachable on the runner's loopback by their port mapping, NOT by service name. Service-name resolution requires the default bridge network; --network host overrides it. 2. health-retries 20 (not 10). timescaledb-ha:*-all does more init work at boot than vanilla postgres; 50s isn't always enough. Operator action required in the Gitea repo Settings before first run: configure REGISTRY_USERNAME and REGISTRY_PASSWORD secrets (required for push); optionally PORTAINER_WEBHOOK_URL (for auto-deploy). Live verification deferred to first relevant commit. Documented in the task spec's Done section: positive (clean snapshot → push succeeds) and negative (malformed snapshot → halt before push) cases to validate once CI runs. ROADMAP marks 1.8 done. Phase 1 progress: 8/9 tasks complete (1.1–1.8); only 1.9 (Rally Albania 2026 dogfood seed) remains before Phase 1 ships.
This commit is contained in:
@@ -0,0 +1,141 @@
|
|||||||
|
name: Build directus image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'snapshots/**'
|
||||||
|
- 'db-init/**'
|
||||||
|
- 'extensions/**'
|
||||||
|
- 'scripts/**'
|
||||||
|
- 'entrypoint.sh'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- '.gitea/workflows/build.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Throwaway Postgres for the dry-run boot step.
|
||||||
|
#
|
||||||
|
# Image: pinned to the same concrete tag used in compose.dev.yaml — NOT the
|
||||||
|
# floating :pg16-latest alias (which does NOT exist on Docker Hub).
|
||||||
|
#
|
||||||
|
# PGDATA: the timescaledb-ha image initialises at /home/postgres/pgdata/data;
|
||||||
|
# the healthcheck uses pg_isready, which doesn't depend on the PGDATA path.
|
||||||
|
#
|
||||||
|
# Port mapping: 5432:5432 binds the service on the runner's loopback.
|
||||||
|
# The dry-run docker run uses --network host so DB_HOST=localhost reaches it.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: timescale/timescaledb-ha:pg16.6-ts2.17.2-all
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: directus
|
||||||
|
POSTGRES_PASSWORD: directus
|
||||||
|
POSTGRES_DB: directus
|
||||||
|
ports:
|
||||||
|
- '5432:5432'
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U directus -d directus"
|
||||||
|
--health-interval 5s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 20
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Build the image locally (trm-directus:ci).
|
||||||
|
#
|
||||||
|
# We use a plain `docker build` rather than docker/build-push-action because
|
||||||
|
# we need the image available in the *local Docker daemon* for the subsequent
|
||||||
|
# `docker run` dry-run step. docker/build-push-action with the
|
||||||
|
# docker-container Buildx driver exports into a separate buildkitd cache not
|
||||||
|
# accessible to `docker run`.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Build image
|
||||||
|
run: docker build -t trm-directus:ci .
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Dry-run boot — the gate that protects the registry from broken images.
|
||||||
|
#
|
||||||
|
# Runs only the two pre-boot scripts (apply-db-init.sh → schema-apply.sh)
|
||||||
|
# against the throwaway Postgres service above. Intentionally does NOT run
|
||||||
|
# `directus bootstrap` or `directus start` — that would require waiting for
|
||||||
|
# the HTTP server to come up, which adds minutes and tests nothing new.
|
||||||
|
#
|
||||||
|
# --network host: the service container is mapped on 127.0.0.1:5432; the
|
||||||
|
# docker run container sees it as localhost:5432 only when host networking
|
||||||
|
# is used. Without --network host, the container would be in a separate
|
||||||
|
# bridge network and could not reach the service by name or IP.
|
||||||
|
#
|
||||||
|
# --entrypoint bash: overrides /directus/entrypoint.sh so we execute only
|
||||||
|
# the script chain, not the full pm2-runtime boot.
|
||||||
|
#
|
||||||
|
# Required Directus env vars: DB_CLIENT + connection params are mandatory
|
||||||
|
# for `node cli.js schema apply`. KEY + SECRET are required by Directus's
|
||||||
|
# env initialisation even when only the schema subcommand is invoked.
|
||||||
|
# ADMIN_EMAIL + ADMIN_PASSWORD are included defensively (some Directus
|
||||||
|
# versions assert on them during CLI init). PUBLIC_URL silences the
|
||||||
|
# missing-public-url warning.
|
||||||
|
#
|
||||||
|
# If this step exits non-zero the workflow halts and the registry login /
|
||||||
|
# push steps are never reached — the broken image is never published.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Dry-run boot against throwaway Postgres
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
--network host \
|
||||||
|
--entrypoint bash \
|
||||||
|
-e DB_CLIENT=pg \
|
||||||
|
-e DB_HOST=localhost \
|
||||||
|
-e DB_PORT=5432 \
|
||||||
|
-e DB_USER=directus \
|
||||||
|
-e DB_PASSWORD=directus \
|
||||||
|
-e DB_DATABASE=directus \
|
||||||
|
-e KEY=ci-key-placeholder-not-secret \
|
||||||
|
-e SECRET=ci-secret-placeholder-not-secret \
|
||||||
|
-e ADMIN_EMAIL=ci@example.com \
|
||||||
|
-e ADMIN_PASSWORD=ci-password-not-secret \
|
||||||
|
-e PUBLIC_URL=http://localhost:8055 \
|
||||||
|
trm-directus:ci \
|
||||||
|
-c '/directus/scripts/apply-db-init.sh && /directus/scripts/schema-apply.sh && echo "dry-run ok"'
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Registry login — runs only if the dry-run succeeded (default: workflow
|
||||||
|
# halts on non-zero exit, so reaching this step implies dry-run passed).
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Login to Gitea registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.dev.microservices.al
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Tag and push two tags:
|
||||||
|
# :main — mutable; always points at the latest commit on main.
|
||||||
|
# :<sha> — immutable; pinned to this specific commit.
|
||||||
|
# The deploy stack can reference either; :main for rolling updates,
|
||||||
|
# :<sha> for pinned deployments that need explicit rollback control.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Tag and push
|
||||||
|
run: |
|
||||||
|
docker tag trm-directus:ci git.dev.microservices.al/trm/directus:main
|
||||||
|
docker tag trm-directus:ci git.dev.microservices.al/trm/directus:${{ github.sha }}
|
||||||
|
docker push git.dev.microservices.al/trm/directus:main
|
||||||
|
docker push git.dev.microservices.al/trm/directus:${{ github.sha }}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Optional Portainer redeploy webhook.
|
||||||
|
# Fires only when PORTAINER_WEBHOOK_URL secret is configured in the repo.
|
||||||
|
# If the secret is absent the condition evaluates false and the step is
|
||||||
|
# skipped — no error, no noise.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
- name: Trigger Portainer redeploy (optional)
|
||||||
|
if: ${{ secrets.PORTAINER_WEBHOOK_URL != '' }}
|
||||||
|
run: curl -fsS -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}"
|
||||||
@@ -42,7 +42,7 @@ These rules govern every task. Any deviation must be discussed and documented as
|
|||||||
|
|
||||||
### Phase 1 — Slice 1 schema + deploy pipeline
|
### Phase 1 — Slice 1 schema + deploy pipeline
|
||||||
|
|
||||||
**Status:** 🟨 In progress (1.1–1.7 done; 1.8, 1.9 remaining)
|
**Status:** 🟨 In progress (1.1–1.8 done; 1.9 remaining)
|
||||||
**Outcome:** A Directus instance with the org-level catalog (orgs, users, organization_users, vehicles, devices and their org junctions) and event-participation collections (events, classes, entries, entry_crew, entry_devices) live and snapshot-tracked. `db-init/` covers the TimescaleDB extension, the `positions` hypertable, and the `faulty` column. Image builds via Gitea Actions with a CI dry-run that catches snapshot drift before deploy. Rally Albania 2026 is registered as the first event in admin UI to dogfood the registration workflow. **This is what Rally Albania 2026 needs.**
|
**Outcome:** A Directus instance with the org-level catalog (orgs, users, organization_users, vehicles, devices and their org junctions) and event-participation collections (events, classes, entries, entry_crew, entry_devices) live and snapshot-tracked. `db-init/` covers the TimescaleDB extension, the `positions` hypertable, and the `faulty` column. Image builds via Gitea Actions with a CI dry-run that catches snapshot drift before deploy. Rally Albania 2026 is registered as the first event in admin UI to dogfood the registration workflow. **This is what Rally Albania 2026 needs.**
|
||||||
|
|
||||||
[**See `phase-1-slice-1-schema/README.md`**](./phase-1-slice-1-schema/README.md)
|
[**See `phase-1-slice-1-schema/README.md`**](./phase-1-slice-1-schema/README.md)
|
||||||
@@ -56,7 +56,7 @@ These rules govern every task. Any deviation must be discussed and documented as
|
|||||||
| 1.5 | [Event-participation collections](./phase-1-slice-1-schema/05-event-participation-collections.md) | 🟩 | pending user commit |
|
| 1.5 | [Event-participation collections](./phase-1-slice-1-schema/05-event-participation-collections.md) | 🟩 | pending user commit |
|
||||||
| 1.6 | [Schema snapshot/apply tooling](./phase-1-slice-1-schema/06-snapshot-tooling.md) | 🟩 | pending user commit |
|
| 1.6 | [Schema snapshot/apply tooling](./phase-1-slice-1-schema/06-snapshot-tooling.md) | 🟩 | pending user commit |
|
||||||
| 1.7 | [Image build & entrypoint](./phase-1-slice-1-schema/07-image-and-dockerfile.md) | 🟩 | pending user commit |
|
| 1.7 | [Image build & entrypoint](./phase-1-slice-1-schema/07-image-and-dockerfile.md) | 🟩 | pending user commit |
|
||||||
| 1.8 | [Gitea CI dry-run workflow](./phase-1-slice-1-schema/08-gitea-ci-dryrun.md) | ⬜ | — |
|
| 1.8 | [Gitea CI dry-run workflow](./phase-1-slice-1-schema/08-gitea-ci-dryrun.md) | 🟩 | pending user commit |
|
||||||
| 1.9 | [Rally Albania 2026 dogfood seed](./phase-1-slice-1-schema/09-rally-albania-2026-seed.md) | ⬜ | — |
|
| 1.9 | [Rally Albania 2026 dogfood seed](./phase-1-slice-1-schema/09-rally-albania-2026-seed.md) | ⬜ | — |
|
||||||
|
|
||||||
### Phase 2 — Course definition
|
### Phase 2 — Course definition
|
||||||
|
|||||||
@@ -126,4 +126,50 @@ Build a Gitea Actions workflow that on push to `main` (when relevant paths chang
|
|||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
(Fill in commit SHA + one-line note when this lands.)
|
**Implementation landed (pending live trigger by first relevant commit).** Workflow file at `.gitea/workflows/build.yml`. Statically validated; live trigger requires a push that touches one of the path-filtered locations.
|
||||||
|
|
||||||
|
**Corrections folded in vs. the spec's draft YAML:**
|
||||||
|
|
||||||
|
1. **`DB_HOST=localhost`, not `DB_HOST=postgres`.** The spec's draft mixed `--network host` with service-name resolution; those are mutually exclusive. With `--network host` the docker-run container shares the runner's loopback, so the service's port mapping (`5432:5432`) is reachable as `localhost:5432`, not by service name `postgres`. (Service-name resolution would only work with the runner's default bridge network.)
|
||||||
|
2. **`--health-retries 20`** instead of 10. The `timescaledb-ha:*-all` image runs more init work at startup than vanilla postgres and occasionally exceeds the 50s window on cold runner images. 20 retries × 5s = 100s margin.
|
||||||
|
3. **`--health-cmd "pg_isready -U directus -d directus"`** with explicit `-d`. Spec had user only.
|
||||||
|
4. **`curl -fsS -X POST`** for the Portainer webhook step. Bare `curl -X POST` returns 0 even on HTTP 4xx/5xx; `-f` makes a misconfigured webhook URL fail the step explicitly.
|
||||||
|
5. **Plain `docker build`**, NOT `docker/build-push-action@v5`. The dry-run step needs the freshly-built image accessible to a subsequent `docker run`. `build-push-action` with the docker-container Buildx driver exports into a separate buildkitd cache that `docker run` cannot see — the run would fail with "image not found." Plain `docker build` keeps the image in the local Docker daemon.
|
||||||
|
|
||||||
|
**Deliberate divergences from `processor/.gitea/workflows/build.yml`:**
|
||||||
|
|
||||||
|
| Aspect | Processor | Directus | Why |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Build mechanism | `docker/build-push-action@v5` | plain `docker build` | dry-run needs local-daemon access (above) |
|
||||||
|
| Buildx setup | yes | no | Buildx isolates the image; would defeat the dry-run |
|
||||||
|
| `services:` block | absent | present | Directus dry-run needs a live Postgres; processor mocks it |
|
||||||
|
| Node/pnpm setup | yes | no | No TS to compile in Phase 1 (Phase 5 adds this) |
|
||||||
|
| typecheck/lint/test | three steps | none | No extensions yet |
|
||||||
|
| Portainer webhook | unconditional | gated on secret presence | Spec requirement |
|
||||||
|
| `runs-on` | `ubuntu-latest` | `ubuntu-22.04` | Pin to avoid floating-tag runner image breakage |
|
||||||
|
|
||||||
|
**Acceptance criteria status:**
|
||||||
|
|
||||||
|
Static (verified):
|
||||||
|
- ✅ Workflow file at `.gitea/workflows/build.yml`.
|
||||||
|
- ✅ Steps in correct order: checkout → build → dry-run → login → tag/push → optional Portainer.
|
||||||
|
- ✅ Path filter excludes `.planning/`, `README.md`, `compose.dev.yaml`, `package.json` — docs-only commits won't trigger CI.
|
||||||
|
- ✅ Workflow file itself is in the path-filter list (so changes to CI trigger CI).
|
||||||
|
- ✅ Two image tags published (`:main`, `:<sha>`).
|
||||||
|
- ✅ Required secrets identified: `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`. Optional: `PORTAINER_WEBHOOK_URL`.
|
||||||
|
- ✅ Dry-run command logic traced: env vars, network mode, entrypoint override, script chain all consistent.
|
||||||
|
|
||||||
|
Pending live trigger (will validate on first push that hits the path filter):
|
||||||
|
- ⏳ Workflow triggers on push.
|
||||||
|
- ⏳ Dry-run step exits 0 against a fresh Postgres + the committed snapshot (currently 105 KB, 13 collections).
|
||||||
|
- ⏳ Snapshot drift simulation: hand-edit `snapshots/schema.yaml` to malformed YAML → push → CI fails at dry-run → image NOT pushed.
|
||||||
|
- ⏳ Migration syntax error simulation: introduce broken `db-init/006_*.sql` → push → CI fails at dry-run → image NOT pushed.
|
||||||
|
- ⏳ Image actually published to `git.dev.microservices.al/trm/directus:main` after a clean run.
|
||||||
|
- ⏳ Portainer webhook fires if configured.
|
||||||
|
|
||||||
|
**Operator action required before first run:** in the Gitea repo at `git.dev.microservices.al/trm/directus` → Settings → Secrets, configure:
|
||||||
|
- `REGISTRY_USERNAME` — Gitea user with write access to the container registry
|
||||||
|
- `REGISTRY_PASSWORD` — password or PAT for that user
|
||||||
|
- `PORTAINER_WEBHOOK_URL` (optional) — for auto-redeploy on push
|
||||||
|
|
||||||
|
Without `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` the Login step fails with a clear auth error. Without `PORTAINER_WEBHOOK_URL` the Portainer step is skipped entirely.
|
||||||
|
|||||||
Reference in New Issue
Block a user