Skip to content

B29: E2E CI workflow on release branches#29

Merged
turegjorup merged 4 commits into
release/3.0.0from
feature/e2e-workflow
May 6, 2026
Merged

B29: E2E CI workflow on release branches#29
turegjorup merged 4 commits into
release/3.0.0from
feature/e2e-workflow

Conversation

@turegjorup

Copy link
Copy Markdown
Contributor

Summary

Addresses Questions.md item 4. Static checks (markdown, yaml, sh, compose, tasks) catch a lot but can't catch "does the stack actually boot, run migrations, and serve /admin/?". This PR adds a GitHub Actions workflow that does exactly that, on release-branch PRs.

What it does

.github/workflows/e2e.yaml walks the localhost-cert install recipe end to end on a Linux runner:

  1. task env:init (domain prompt piped: os2display.localhost)
  2. APP_ENV=prod workaround (until upstream display-api-service PR #435 ships in a tagged release — the image's bundled .env defaults to dev mode but the image is built --no-dev)
  3. task env:traefik (cert-file branch, prompts piped)
  4. task dev:cert
  5. install-secrets.sh + compose pull + compose up --wait
  6. bin/console app:update + lexik:jwt:generate-keypair --skip-if-exists
  7. bin/console app:tenant:add + app:user:add (direct compose exec — task install's nested task: <subtask> calls don't pipe stdin reliably, per B23 caveat)
  8. curl -fv --cacert traefik/ssl/dev.crt --resolve os2display.localhost:443:127.0.0.1 https://os2display.localhost/admin/ asserts HTTP 200
  9. compose down --volumes --remove-orphans (always-runs cleanup)

Triggers

  • pull_request against release/**
  • push to release/**

Not on every PR — too slow (~5 min wall-clock) and too much docker churn for the casual review cycle. Acts as the release-cut quality bar.

Known fragility points

Documented inline in the workflow comments for the next operator who has to debug a flake:

  • APP_ENV=dev upstream bug: drop the sed step once a fixed image tag is in OS2DISPLAY_VERSION_API.
  • GHCR pull quota: bot quota, can flake on heavy CI days.
  • task install stdin piping: known limitation (B23). The workflow sidesteps by stepping through the install manually.

Test plan

The PR itself triggers the workflow (it's a pull_request against release/**); review the run for green/red.

🤖 Generated with Claude Code

@turegjorup
turegjorup force-pushed the feature/e2e-workflow branch from 145d67e to 0239b50 Compare May 6, 2026 19:45
Static lint + config-sanity checks (markdown, yaml, sh, compose, tasks)
catch a lot, but not "does the stack actually boot, run migrations, and
serve /admin/?". Adds a GitHub Actions workflow that does exactly that
on release-branch PRs.

The workflow walks the localhost-cert install recipe end to end:

1. task env:init  (domain prompt piped: os2display.localhost)
2. APP_ENV=prod sed (until upstream display-api-service ships PR #435
   in a tagged release; the image's bundled .env defaults to dev mode
   but the image is built --no-dev)
3. task env:traefik (cert-file branch, prompts piped)
4. task dev:cert
5. install-secrets.sh + compose pull + compose up --wait
6. bin/console app:update + lexik:jwt:generate-keypair
7. bin/console app:tenant:add + app:user:add (direct compose exec — task
   install's nested task: subtasks don't pipe stdin reliably per B23)
8. curl /admin/ via --cacert traefik/ssl/dev.crt + --resolve. Asserts 200.
9. compose down --volumes (always-runs cleanup)

Triggers on pull_request against release/** and push to release/**.
Doesn't run on every PR — too slow (~5 min) and too much docker churn
for the casual review cycle. Acts as the release-cut quality bar.

Known fragility points documented in the workflow comments for the
next operator who has to debug a flake: the APP_ENV workaround,
GHCR rate limits, and the task-install stdin-piping caveat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turegjorup
turegjorup force-pushed the feature/e2e-workflow branch from 0239b50 to 64f2783 Compare May 6, 2026 20:00
turegjorup and others added 3 commits May 6, 2026 22:07
PR #29's e2e workflow surfaced this: when invoked directly (not via
`task install`), the script silently no-ops and the stack comes up
with mariadb running on the literal CHANGE_ME password while
DATABASE_URL has the would-be-random hex. Doctrine: access denied,
exit code 1045.

Root cause: the sentinel check reads `$MARIADB_PASSWORD` from the env,
which is populated by Taskfile's `dotenv:` directive — but only when
the script runs as part of a `task` invocation. Direct invocation
(CI workflow, operator running from a fresh shell) leaves both
$MARIADB_PASSWORD and $MARIADB_ROOT_PASSWORD empty. The condition
"if NEITHER is CHANGE_ME, skip" then matches because both are "" —
not "CHANGE_ME" — and the script exits 0 without doing anything.

Fix: source .env.mariadb at the top of the script. Idempotent when
the env is already populated; load-bearing for direct invocation.
Same pattern env-init.sh uses for .env.

Verified: `env -i ./install-secrets.sh` (empty env) now correctly
detects the CHANGE_ME sentinel, generates random passwords, and
syncs DATABASE_URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three threads, all on the same install-time correctness theme:

(1) install-secrets.sh standalone-invocation fix.

When invoked via `task install`, the script reads
$MARIADB_PASSWORD from Taskfile's `dotenv:`-loaded env. When invoked
directly (CI workflow, manual operator use), the env is empty —
"${VAR:-}" != "CHANGE_ME" matches because both sides are empty
strings, the script silently exits 0, and mariadb starts on the
literal CHANGE_ME password while DATABASE_URL has the would-be-random
hex. Surfaced by PR #29's first e2e run with Doctrine reporting access
denied. Fix: source .env.mariadb at the top of the script.

(2) `compose up --wait` + `depends_on` graph.

Post-install migrations need a connectable database. Previously
`task install` fired `compose up -d` then `sleep 20` to paper over a
race; `task up` returned immediately with no wait at all. B6 wired
healthchecks on every service but only one `depends_on`
(nginx-api → os2display: service_healthy). Now:

- task up / install / update use `compose up -d --wait` — blocks until
  every healthcheck passes. install drops its sleep 20 hack.
- os2display.depends_on adds mariadb (service_healthy, required: false
  so the dep skips cleanly when an operator runs external db / drops
  the mariadb profile) and redis (service_healthy).
- traefik.depends_on adds socket-proxy (service_healthy) — traefik
  queries it for docker-label discovery; no point asking before it's
  reachable.

(3) E2E workflow rewritten to drive everything through tasks.

Previously the workflow bypassed Taskfile for the install steps and
called docker compose exec ... bin/console directly, because
`task install`'s nested `task: <subtask>` invocations don't pipe
stdin reliably to interactive Symfony prompts (B23 caveat). Now:

- task env:init / env:traefik / dev:cert (was: same)
- ./scripts/install-secrets.sh (script, not a standalone task)
- task up (waits for healthchecks via above)
- task console -- --no-interaction app:update
- task console -- lexik:jwt:generate-keypair --skip-if-exists
- task console -- app:tenant:add e2e 'E2E Tenant' CI
- task console -- app:user:add admin@os2display.localhost ...
- task templates:install
- curl smoke test against the dev cert
- task -y dev:teardown

`task console -- ARGS` proxies bin/console with explicit args, which
sidesteps the chained-subtask stdin issue for tenant:add / user:add.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous run got past install-secrets and migrations but failed at
JWT keypair generation: container writes as `deploy` (UID 1042) per
the README's deploy-host UID/GID contract, but CI runners aren't an
operator's 1042-group host, so the bind-mounted ./jwt is owned by the
runner user and `chmod` semantics give EACCES.

`sudo chown -R 1042:1042 jwt media backup` after install-secrets and
before `task up`. Mirrors what an operator's `useradd -u 1042 deploy`
does on a Linux host — just at workflow scope rather than as a host
boot step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turegjorup
turegjorup merged commit 16909e3 into release/3.0.0 May 6, 2026
9 checks passed
@turegjorup
turegjorup deleted the feature/e2e-workflow branch May 6, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant