Skip to content

Complete SDK resource coverage to enable an IaC package #32

@fbarrento

Description

@fbarrento

Problem Statement

I want to build a separate PHP package that manages Laravel Forge infrastructure declaratively (infrastructure-as-code: a manifest of desired servers, databases, users, sites, deploys, etc. that's planned and applied/reconciled). When I reach for phpdevkits/forge-sdk as the engine, it covers the provision + site + deploy layer (servers incl. database/cache types, SSH keys, sites, deployments, daemons) — but the moment a manifest says "create database app with user app_user", or "issue an SSL cert", or "resolve my Hetzner credential by name", I hit a wall: those Forge resources exist in the API but aren't wrapped by the SDK. Today an IaC tool can stand up machines and deploy code, but can't manage databases, database users, firewall, scheduled jobs, env, certs, or look up credentials/VPCs by name.

Solution

Round out the SDK's resource coverage so every Forge resource an IaC reconciler needs is available as an imperative primitive with the established list / get / create / update / delete shape (plus filters for lookup-by-name). The SDK stays imperative; it does not gain plan/apply/state/reconcile logic — that lives in the downstream IaC package. This PRD is the dependency map for that package, delivered in tiers.

Tier 1 — IaC-blocking

  • Databases (schemas)GET/POST /database/schemas, GET/DELETE /database/schemas/{database}, POST /database/schemas/synchronizations.
  • Database UsersGET/POST /database/users, GET/PUT/DELETE /database/users/{databaseUser}.
  • Database PasswordPUT /database/password.
  • Server Credentials + VPCsGET /server-credentials, GET /server-credentials/{credential}, GET/POST /server-credentials/{credential}/regions/{region}/vpcs, GET .../vpcs/{vpcId} — so the IaC layer resolves credential_id / network_id by name instead of hardcoded numbers (closes the gap noted in docs/FINDINGS.md for server create).
  • Certificates + Domains — tracked in SSL certificates resource (under site domains) #25 (SSL); referenced here as a Tier-1 dependency, not duplicated.

Tier 2 — common infra

  • Firewall RulesGET/POST /firewall-rules, GET/DELETE /firewall-rules/{rule}.
  • Scheduled Jobs — server-level /scheduled-jobs (+ /output) and site-level /sites/{site}/scheduled-jobs (+ /output): list/get/create/delete.
  • Site Environment (.env)GET/PUT /sites/{site}/environment.
  • MonitorsGET/POST /monitors, GET/DELETE /monitors/{monitor}.

Tier 3 — config / ops

  • Nginx — site config GET/PUT /sites/{site}/nginx, server templates /nginx/templates CRUD, POST /services/nginx/actions.
  • Redirect RulesGET/POST /sites/{site}/redirect-rules, GET/DELETE .../{redirectRule}.
  • Database Backups — backup configurations, instances, restores under /database/backups/....
  • Logs (read-only) — site application / nginx-access / nginx-error, server /logs/{key}.

User Stories

  1. As an IaC package author, I want to create a database schema on a server, so that a manifest's database: block provisions it.
  2. As an IaC package author, I want to list/get database schemas, so that I can diff desired vs actual and avoid recreating existing ones.
  3. As an IaC package author, I want to delete a database schema, so that removing it from the manifest tears it down.
  4. As an IaC package author, I want to trigger a schema synchronization, so that the server's schema list reflects reality.
  5. As an IaC package author, I want to create a database user (with privileges/databases), so that a users: block is provisioned.
  6. As an IaC package author, I want to list/get/update/delete database users, so that I can reconcile them idempotently.
  7. As an IaC package author, I want to rotate the database password, so that secrets rotation is automatable.
  8. As an IaC package author, I want to list server credentials and get one by id, so that I can resolve a credential by name for server creation.
  9. As an IaC package author, I want to list a credential's VPCs for a region, so that I can resolve network_id by name (required for Hetzner creates).
  10. As an IaC package author, I want to create a VPC, so that a manifest can declare its own private network.
  11. As an IaC package author, I want to manage SSL certificates on a site domain (see SSL certificates resource (under site domains) #25), so that HTTPS is part of the desired state.
  12. As an IaC package author, I want to create/list/get/delete firewall rules, so that a firewall: block is reconciled.
  13. As an IaC package author, I want to create/list/get/delete scheduled jobs at server and site scope, so that cron is declarative.
  14. As an IaC package author, I want to read a scheduled job's output, so that I can surface run results.
  15. As an IaC package author, I want to get and update a site's environment file, so that .env is managed as desired state.
  16. As an IaC package author, I want to create/list/get/delete monitors, so that health checks are declarative.
  17. As an IaC package author, I want to get/update a site's nginx config and manage server nginx templates, so that custom server config is reproducible.
  18. As an IaC package author, I want to manage redirect rules on a site, so that redirects are declarative.
  19. As an IaC package author, I want to restart nginx (service action), so that config changes take effect within a reconcile.
  20. As an IaC package author, I want to manage database backup configurations, list/get instances, and trigger restores, so that backup policy is declarative.
  21. As an IaC package author, I want to read site and server logs, so that the IaC tool can report on apply failures.
  22. As an IaC package author, I want every managed resource to expose list + get + create/update/delete with filters, so that my reconciler can converge desired state with minimal custom code.
  23. As an IaC package author, I want list endpoints to support filtering by name/identifier, so that lookups don't require paging the whole collection.
  24. As an IaC package author, I want typed, hydrated DTOs for every resource, so that planning/diffing works against real fields, not arrays.
  25. As an IaC package author, I want consistent typed exceptions across all new resources, so that apply errors are catchable and actionable.
  26. As an SDK maintainer, I want each new resource to follow the existing Resource + Request + DTO + Enum + factory shape, so that the SDK stays internally consistent and learnable.
  27. As an SDK maintainer, I want spec-vs-runtime divergences for the new resources recorded in docs/FINDINGS.md, so that the next person isn't surprised.

Implementation Decisions

  • The SDK stays imperative. No plan/apply/diff/state/reconcile logic lands here — that's the downstream IaC package. This PRD only adds resource primitives.
  • Resource shape (uniform): every new resource gets a collection resource (all/iterate/create) and/or an item resource (get/update/delete/actions), matching the existing servers/sites/deployments/daemons resources, so a reconciler can rely on a consistent CRUD surface.
  • Nesting / accessors mirror the URL tree:
    • server($id)->databases() / ->database($name); server($id)->databaseUsers() / ->databaseUser($id); server($id)->database()->updatePassword(...).
    • server($id)->firewallRules(), ->monitors(), ->scheduledJobs(), ->nginxTemplates().
    • site($id)->environment() (get/update), ->scheduledJobs(), ->redirectRules(), ->nginx() (get/update), ->domain($id)->certificates() (SSL certificates resource (under site domains) #25), ->logs().
    • org()->serverCredentials() / ->serverCredential($id)->region($r)->vpcs().
  • DTOs: final readonly, JsonSerializable, ::from(array) hydration with the require/optional helper pattern; input DTOs (Create*Data, Update*Data) with toArray() that strips nulls; list-options DTOs with toQuery(). Lenient on spec-vs-runtime nullability per existing precedent.
  • Enums only for spec-closed sets (e.g. firewall rule type, scheduled-job frequency, backup provider — TBD per spec).
  • Pagination via the existing Page<T> + ParsesPage trait for all new collections.
  • Exceptions reuse the existing typed hierarchy; no new exception types unless a new client-side guard is needed.
  • Tiering: Tier 1 ships first (unblocks the IaC package's core); Tiers 2–3 follow. Each resource is an independently shippable PR/slice (tracer-bullet, like Tracer bullet: $forge->me() end-to-end + credential factories #2Daemons / Background Processes (under server) #9). Tier 1 may be split into per-resource sub-issues.
  • Credentials/VPC ergonomics: expose enough read surface that the IaC layer can map human names → credential_id/network_id, removing the magic-number requirement currently documented in FINDINGS.

Testing Decisions

  • Same standard as the SDK to date. A good test asserts external behavior — DTO hydration from a recorded response, request URL/method/body composition, and resource method return types/contracts — never internal implementation details.
  • Per resource: unit tests for DTOs (::from() happy path + each validation throw), enums, list-options toQuery(), and request resolveEndpoint()/method/body; plus a live-recorded lifecycle test (create → list → get → update → delete/actions) with fixtures captured against the real Forge API and scrubbed via per-domain Fixture subclasses.
  • Coverage: maintain the 100% pest --exactly=100 gate; new resources must not drop it.
  • Prior art: tests/Unit/Resources/*ResourceTest.php, the *WriteLifecycleTest files, the *Fixture redaction classes, and the recording workflow in the saloon-integration skill / docs/FINDINGS.md.
  • Fixtures: recorded only (no hand-crafted JSON, no MockResponse::make()), per the established convention; redact all PII and account-identifying ids/hostnames (the centralized URL-id redaction in ForgeFixture already covers new nested segments — extend the alternation when adding a new URL segment).

Out of Scope

  • The IaC package itself — manifest parsing, plan/diff, apply, dependency ordering, state tracking. This PRD only delivers SDK primitives it will consume.
  • SSL certificates / domains — tracked separately in SSL certificates resource (under site domains) #25 (a Tier-1 dependency, referenced not duplicated).
  • sudo_password surfacing — tracked in Surface server create's one-time sudo_password to callers #30.
  • Teams, recipes, and any non-infrastructure endpoints not needed for IaC reconciliation.
  • Reconcile-oriented conveniences (e.g. upsert-by-name helpers) — if useful, they belong in the IaC package, not the imperative SDK.

Further Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions