Skip to content

v1.1.0: pgrundev/ci-action/database — disposable PostgreSQL for any stack - #3

Merged
alexshapalov merged 5 commits into
mainfrom
feat/database-action
Oct 17, 2026
Merged

alexshapalov merged 5 commits into
mainfrom
feat/database-action

Conversation

@alexshapalov

@alexshapalov alexshapalov commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Adds pgrundev/ci-action/database, which provides disposable PostgreSQL for any stack. The action provisions the database, and the customer decides what runs against it.

      - uses: pgrundev/ci-action/database@v1
        with:
          token: ${{ secrets.PGRUN_API_TOKEN }}
      - run: npm ci && npx prisma migrate deploy && npm test   # anything; DATABASE_URL is set

It is a dependency-free Node 24 action using GitHub's main/post lifecycle (post-if: always()). Its only jobs:

  1. Gates: the same rules as v1. Protect must be active, the Safe Copy usable and not revoked, and PGX eligible when it is required.
  2. Creates 1–16 branches, either sanitized (from the Safe Copy) or schema-only (from a CI template; a CI token never sets the seed). It waits for quota, gives back a partial hold, and never reuses a branch name.
  3. Masks the token, every URL, and every password in both its raw and percent-encoded forms. It exports DATABASE_URL (and DATABASE_URL_1..N) through GITHUB_ENV, using a random heredoc delimiter, plus metadata outputs. export-env: false gives outputs only.
  4. Deletes exactly the branches it created at the end of the job, on success, failure and cancellation. Each name is recorded in a job-local ledger before its create call. If the runner dies, the TTL (1h by default) cleans up.

There is no framework detection, Ruby, migration or test logic in it. The root action (Rails preset) is unchanged: action.yml and lib/ are byte-identical to v1.0.0. No server change is needed: the action uses the existing ci/context, branches and templates API, with the same CI-token scoping.

Docs: the README now leads with the any-stack action; docs/database.md holds the full reference plus Rails, Django, Prisma and Go examples (documentation only, no integrations).

Acceptance on real GitHub runners (final SHA 5b1d8ce, plus earlier iterations)

Scenario Result
Sanitized branch: DATABASE_URL in a later step, psql connects, data present (6 tables, 117 rows), writes allowed ✅ ready in 2.0–3.0 s
Schema-only ×3: DATABASE_URL_1..3 are 3 distinct databases with schema, isolated writes ✅
export-env: false: env unset, outputs work ✅
3 concurrent matrix jobs × 2 branches under quota pressure (10/10): waited 13–39 s, each job saw only its own databases and deleted only its own ✅
Customer command fails → post deletes ✅ 222 ms
Run cancelled during the customer's command (8 branches) → post deletes ✅ 8 deleted in 497 ms
Run cancelled during provisioning (quota wait) → post runs, nothing held ✅
Runner killed (kill -9 on Runner.Worker/Listener; no post) → branch removed at its 1h TTL ✅ expired 06:18:16Z, reaped 06:20:00Z
Orphan audit 05:00–06:30Z ✅ 47 ci-db branches created, 47 deleted, 0 live
Public repo (this repo's CI, database-guards job): sanitized refused; no-token refused; schema-only passes the guard ✅
Fork PR: the acceptance repo has fork-PR workflows disabled, so fork PR #10 started no run. The action's own fork refusal (including a deleted fork) and pull_request_target refusal are covered by unit tests ✅
Leak scan of every log (unmasked postgres://, user:password@, pgrun_ci_…) ✅ 0
Rails preset at the candidate SHA, production-like + fast ✅
Ekomspace (pins v1.0.0, unchanged): rerun of #269 — production-like 11/0, Fast on 7 PGX branches green ✅

Unit tests: node --test tests/database.test.js, 17 tests against a stub API (masking, exports, outputs, gates, refusals, quota, SIGINT mid-provisioning, concurrency, planted ledgers, no POST retry).

Security review (independent)

  • High: none.
  • Medium: all four fixed in 5b1d8ce:
    • (1) post trusted an apiUrl read back from the ledger, so a later step could redirect the token. Post now uses only its own validated api-url, acts only on ledgers carrying its own token+URL hash, and only on ci-db-… names.
    • (2) a retried POST could orphan a branch. Creates are never retried now.
    • (3) a failed main dropped names it could not delete. It now keeps them for post.
    • (4) cross-token 404s, resolved by the same owner hash.
  • Low, fixed:
    • server text is collapsed to one line, so it can't inject a workflow command;
    • ledger writes are atomic;
    • sanitized data fails closed without a readable event payload;
    • a deleted fork is still treated as a fork;
    • the env-name reserved list is case-insensitive and wider.
  • Accepted by design: an exported DATABASE_URL is visible to later steps of the job (export-env: false exists), and api-url is author-controlled (documented).

Release (not done — needs owner approval)

Merge (rebase) → re-run the acceptance suite on the merged SHA → annotated tag v1.1.0 → move v1 → GitHub release. Then deploy pgrundev/pgrun#39, which switches the CI page and the CLI page to this workflow.

🤖 Generated with Claude Code

alexshapalov and others added 5 commits October 16, 2026 22:12
A dependency-free Node action (main/post): isolated sanitized or
schema-only PGRun branches, DATABASE_URL (and DATABASE_URL_1..N) for the
job's later steps, metadata outputs, and deletion of exactly the branches
it created when the job ends — on success, failure and cancellation; the
branch TTL covers a dead runner. Same gates and token scoping as v1. No
framework detection, Ruby, migrations or test execution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te or a given-back branch is dropped

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d Go examples; the root action is the Rails preset

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-token refusals on a real runner

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- post acts only on ledgers written with its own token and api-url (a hash,
  never the token) and calls only its own validated api-url — a ledger a
  later step rewrites can neither redirect the token nor name other branches
- a create is never retried (a lost response would orphan a branch)
- a failed main keeps the names it could not delete for post
- server text collapsed to one line: no workflow-command injection
- atomic ledger writes; sanitized data fails closed without a readable event;
  a deleted fork is still a fork; env-name reserved list case-insensitive and wider

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alexshapalov
alexshapalov marked this pull request as ready for review October 17, 2026 08:10
@alexshapalov
alexshapalov merged commit 96038ad into main Oct 17, 2026
2 checks passed
@alexshapalov
alexshapalov deleted the feat/database-action branch October 17, 2026 08:10
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