B29: E2E CI workflow on release branches#29
Merged
Conversation
turegjorup
force-pushed
the
feature/e2e-workflow
branch
from
May 6, 2026 19:45
145d67e to
0239b50
Compare
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
force-pushed
the
feature/e2e-workflow
branch
from
May 6, 2026 20:00
0239b50 to
64f2783
Compare
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.yamlwalks the localhost-cert install recipe end to end on a Linux runner:task env:init(domain prompt piped:os2display.localhost)APP_ENV=prodworkaround (until upstream display-api-service PR #435 ships in a tagged release — the image's bundled.envdefaults todevmode but the image is built--no-dev)task env:traefik(cert-file branch, prompts piped)task dev:certinstall-secrets.sh+compose pull+compose up --waitbin/console app:update+lexik:jwt:generate-keypair --skip-if-existsbin/console app:tenant:add+app:user:add(direct compose exec —task install's nestedtask: <subtask>calls don't pipe stdin reliably, per B23 caveat)curl -fv --cacert traefik/ssl/dev.crt --resolve os2display.localhost:443:127.0.0.1 https://os2display.localhost/admin/asserts HTTP 200compose down --volumes --remove-orphans(always-runs cleanup)Triggers
pull_requestagainstrelease/**pushtorelease/**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=devupstream bug: drop thesedstep once a fixed image tag is inOS2DISPLAY_VERSION_API.task installstdin 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_requestagainstrelease/**); review the run for green/red.🤖 Generated with Claude Code