Commit graph

2 commits

Author SHA1 Message Date
claude-hdb
bbca3cfff0 feat(service): set and diff per-container service hostnames via urls (#72)
Services could not carry hostnames through cast: `desiredFromManifest`
dropped a service's `domains` and warned they were a manual Coolify UI act,
citing a re-checked "no flat `domains` on a 4.1.2 service, on any route."
The audit (#72) disproved that — the same failure mode #51 corrected for
backup schedules. The FLAT shape genuinely has no route; the per-container
CAPABILITY was there at 4.1.2 all along.

`POST /services` and `PATCH /services/{uuid}` both take a `urls` list
([{name, url}], url comma-joined) that `applyServiceUrls` matches to a
`ServiceApplication` by name and stores as its `fqdn`; `GET /services/{uuid}`
loads `applications` and returns each `fqdn` (verified against
ServicesController v4.1.2). So services now speak the SAME per-container
vocabulary a dockercompose app does:

- **Manifest:** `service_domains: { <container>: [url] }` replaces the flat,
  unhonorable `domains` on a service (a flat list cannot name which container
  a hostname belongs to — exactly what `urls` requires). Canonicalized (keys
  and each URL array sorted) so container order never false-drifts.
- **Write:** `serviceApiFields` builds `urls` on create and update.
- **Read/diff:** a supplementary `GET /services/{uuid}` per service
  (`attachServiceDomains`, gated to `diff`/`apply` like backups) projects
  `applications[].fqdn` back into `service_domains`, so a declared hostname is
  compared every run — no perpetual drift, no manual UI step.
- **Pre-flight:** a service create's `service_domains` joins
  `desiredDomainsOfCreate`, the more important because a service create whose
  domain conflicts is DELETED server-side before the 409 (rollback).

Two limits stated out loud: the read is fail-closed (an unreachable/
unrecognized `GET /services/{uuid}` aborts rather than projecting empty and
re-PATCHing forever), and `inventory --emit-draft` does not yet make the
per-service GET, so a drafted service's hostnames are still declared by hand
(same as backups) — draft/semantics say so.

`npm run check` clean · 514 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:14:29 +00:00
claude-hdb
2e201fb58a fix(apply): pre-flight domain uniqueness, and translate Coolify's 409 (#44)
Coolify enforces domain uniqueness across the whole instance; cast plans
inside one project + one environment. So apply could produce a plan that
was internally consistent, correct against everything cast can observe,
and still be refused — by a resource in a project cast never queries,
arriving as a raw 409 mid-apply, after the project and the environment
had already been created.

- Pre-flight the create plan: before the first write (project and
  environment are created lazily, by the first create), check the domains
  the plan is about to claim against GET /applications. A conflict is now
  a refusal that costs nothing, not a half-applied run. One GET, and only
  on a plan that creates an application with a domain — a first apply.
  Covers both live shapes: fqdn, and per-service docker_compose_domains.
- Translate the 409 when one gets through anyway (a conflict with a
  service fqdn or the instance fqdn is not visible in GET /applications,
  so the pre-flight is a strict subset of Coolify's check). Names the
  domain, the resource, its uuid — and whether it is outside the applied
  project, which is the part the operator cannot get from Coolify.
- Never send force_domain_override=true. Coolify suggests it in the error
  text; two resources on one domain is a routing coin-flip, and Coolify
  says so in the same response.

Closes #44.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:30:34 +00:00