From 1c62ff84348892e4ee23ccf58576f8d1c728ddbe Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:00:30 +0100 Subject: [PATCH] feat(ci): opt-in PostgreSQL/ArangoDB for elixir-ci-reusable tests Ecto suites whose `mix test` alias runs `ecto.create` died before any test ran: the reusable started no database (bofig main: "The database for EvidenceGraph.Repo couldn't be created"). Adds two optional inputs, postgres-image and arangodb-image (default empty). When set, the database is started on the runner's loopback at its default port with auth off, and a readiness step fails by name if it never comes up. Started with `docker run` steps rather than a services: block so existing callers -- including tma-mark2 on a self-hosted runner -- run exactly the steps they ran before. Images reach the scripts through env, never interpolated. No new uses:, so actions.lock is unchanged (check-lock-sync.sh in sync; gh actions-lock --verify output identical to main). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Vwyfa8Eq6GYCqYnwo4fqGb --- .github/workflows/elixir-ci-reusable.yml | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/.github/workflows/elixir-ci-reusable.yml b/.github/workflows/elixir-ci-reusable.yml index 320e7d5ab..63e3e86c5 100644 --- a/.github/workflows/elixir-ci-reusable.yml +++ b/.github/workflows/elixir-ci-reusable.yml @@ -54,6 +54,20 @@ # uses: hyperpolymath/standards/.github/workflows/elixir-ci-reusable.yml@571cc734cd69fb846032ec77a662aa8ee4fc32cd # with: # working_directory: server +# +# Ecto / ArangoDB test suites (the `mix test` alias creates and migrates a +# database before any test runs, so without a server the job dies at +# `ecto.create` — bofig lived this). Pass a DIGEST-PINNED image; the +# database is started on the runner's loopback at its default port with +# authentication off, so whatever test credentials config/test.exs uses +# are accepted: +# +# jobs: +# elixir-ci: +# uses: hyperpolymath/standards/.github/workflows/elixir-ci-reusable.yml@ +# with: +# postgres-image: "postgres:16@sha256:" +# arangodb-image: "arangodb:3.12@sha256:" name: Elixir CI (reusable) @@ -116,6 +130,25 @@ on: type: string required: false default: "." + postgres-image: + description: >- + Optional PostgreSQL image (pin it by digest). When set, the server + runs on 127.0.0.1:5432 with trust authentication for the test job. + Empty (the default) starts nothing, so existing callers are + unchanged. Started with `docker run`, not a `services:` block, so + the job stays valid on runners that have no service-container + support (tma-mark2 calls this on a self-hosted runner). + type: string + required: false + default: "" + arangodb-image: + description: >- + Optional ArangoDB image (pin it by digest). When set, the server + runs on 127.0.0.1:8529 with authentication disabled for the test + job. Empty (the default) starts nothing. + type: string + required: false + default: "" # Only `contents: read` is requested. A reusable workflow may narrow the # caller's permissions but never widen them: requesting a permission the @@ -176,6 +209,21 @@ jobs: repository: ${{ github.repository }} ref: ${{ github.ref }} + # Opt-in databases start before the BEAM toolchain is installed so + # they warm up while deps compile. Images arrive through env, never + # interpolated into the script, so a caller value cannot inject shell. + - name: Start PostgreSQL (opt-in) + if: ${{ inputs.postgres-image != '' }} + env: + IMAGE: ${{ inputs.postgres-image }} + run: docker run --detach --name ci-postgres --publish 127.0.0.1:5432:5432 --env POSTGRES_HOST_AUTH_METHOD=trust "$IMAGE" + + - name: Start ArangoDB (opt-in) + if: ${{ inputs.arangodb-image != '' }} + env: + IMAGE: ${{ inputs.arangodb-image }} + run: docker run --detach --name ci-arangodb --publish 127.0.0.1:8529:8529 --env ARANGO_NO_AUTH=1 "$IMAGE" + - name: Guard — rebar3 pinning needs an authenticated lookup if: ${{ inputs.rebar3-version != '' && inputs.github-token == '' }} run: | @@ -222,5 +270,30 @@ jobs: if: ${{ inputs.enable_dialyzer }} run: mix dialyzer + # A database that never came up must fail here, by name, rather than + # as an opaque connection error inside the test alias. + - name: Wait for opt-in databases + if: ${{ inputs.postgres-image != '' || inputs.arangodb-image != '' }} + env: + WANT_PG: ${{ inputs.postgres-image != '' }} + WANT_ARANGO: ${{ inputs.arangodb-image != '' }} + run: | + for _ in $(seq 1 60); do + pg_ok=true; arango_ok=true + if [ "$WANT_PG" = true ]; then + docker exec ci-postgres pg_isready --host 127.0.0.1 --username postgres >/dev/null || pg_ok=false + fi + if [ "$WANT_ARANGO" = true ]; then + curl --silent --fail --output /dev/null http://127.0.0.1:8529/_api/version || arango_ok=false + fi + if $pg_ok && $arango_ok; then echo "databases ready"; exit 0; fi + sleep 2 + done + echo "::error title=database not ready::postgres ready=$pg_ok, arangodb ready=$arango_ok after 120s" + docker ps --all + [ "$WANT_PG" = true ] && docker logs ci-postgres | tail -n 40 + [ "$WANT_ARANGO" = true ] && docker logs ci-arangodb | tail -n 40 + exit 1 + - name: Run tests run: mix test --cover