Appearance
plexctl domain
Synopsis
plexctl domain is the operator surface for the Domain aggregate, the tenancy root of a plexsphere deployment. It wraps the typed /v1/domains operations (CreateDomain, ListDomains, GetDomain, PatchDomain, DeleteDomain) so an operator can manage Domains (create, list, get, update, delete) without hand-rolling curl calls .
The Domain aggregate is the tenancy root: every Project, Identity, Group, and Resource is owned by exactly one Domain. The CRUD surface documented here operates on the Domain aggregate body itself; the per-Domain sub-surfaces (identities, invitations, IdP bindings) live under the dedicated parent commands identity.md and domain-idp.md. Two wire-shape decisions in particular surface at the CLI layer: --mesh-cidr is a required flag on create because DomainCreateRequest.MeshCidr is a non-pointer string and the server's aggregate invariants insist on a canonical RFC 4632 pool at construction time, and the slug is immutable after creation — there is no --slug flag on update.
Invocation
text
plexctl domain <subcommand> [flags]The five subcommands are enumerated below. Every subcommand is a private cobra constructor; flags are validated at parse time so an invalid UUID or a missing required flag surfaces as exit 2 instead of a wire round-trip.
plexctl domain create
Creates a Domain via POST /v1/domains. The handler enforces --slug, --display-name, and --mesh-cidr as required flags so an operator cannot omit the canonical mesh-IP pool by mistake — the server would reject the empty value with invalid_body, but pushing the constraint into the cobra layer turns a one-line typo into a fast local error rather than a round-trip. --description is optional and omitted from the wire body when unset.
plexctl domain list
Lists Domains via GET /v1/domains. Supports keyset pagination via --cursor and an opt-in --all flag that walks every page until the next_cursor field is empty. The --all accumulator is capped at 100 000 items as defence-in-depth against a runaway server. The --slug flag is filtered client-side: the OpenAPI schema does not expose a slug query parameter on GET /v1/domains, so the CLI fetches the page and trims it after the fact.
plexctl domain get
Returns a single Domain via GET /v1/domains/{id}. The positional argument accepts either a UUID or a slug: a successful uuid.Parse short-circuits to the by-ID path, otherwise the subcommand walks ListDomains pages until the slug matches. The slug-resolution scan honours the same 100 000-item safety cap as list --all; an unmatched slug after the walk surfaces as exit 1 with the message domain get: no Domain with slug "<value>" .
plexctl domain update
Patches a Domain via PATCH /v1/domains/{id}. The update is sparse: only the flags that were explicitly set on the command line (cmd.Flags().Changed) are forwarded in the PATCH body. There is no --slug flag because the OpenAPI DomainPatchRequest schema documents the slug as immutable; the server rejects any PATCH body carrying a slug key with 400 slug_immutable. An invocation that sets none of --display-name, --description, or --mesh-cidr surfaces as exit 2 with a clear message rather than a 400 empty_patch round-trip .
plexctl domain delete
Deletes a Domain via DELETE /v1/domains/{id}. The destructive operation is gated behind the root persistent --yes flag — not a per-command --force flag — to keep muscle memory consistent with group delete, label define delete, and audit erase-identity. A single typo of the Domain id is unacceptable on a tenancy root, so the RunE refuses to call the API without explicit confirmation. The server enforces the empty-aggregate guard (409 domain_not_empty) when child Projects, Identities, Groups, or Resources still exist .
Flags
plexctl domain create
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--slug | string | yes | — | kebab-case URL handle, unique within the deployment. Frozen after creation. |
--display-name | string | yes | — | Human-readable Domain name. Maps to the JSON field name. |
--description | string | no | (empty) | Optional free-form description. Omitted from the wire body when unset. |
--mesh-cidr | string | yes | — | Canonical RFC 4632 mesh-IP pool (e.g. 10.42.0.0/16). Required at the CLI layer because the wire schema marks it required and the aggregate invariants insist on a canonical pool at construction. |
plexctl domain list
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--limit | int | no | server default | Maximum items per page. 0 lets the server pick. |
--cursor | string | no | — | Continuation token from a previous call's next_cursor. Mutually exclusive with --all. |
--all | bool | no | false | Follow next_cursor until exhausted. Capped at 100 000 items as defence-in-depth against a runaway server. |
--slug | string | no | — | Client-side slug filter. The wire ListDomainsParams does not expose a slug query parameter; the trim runs after the page is fetched. |
plexctl domain get
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
<id-or-slug> | string | yes | — | Domain UUID (parsed first) or slug (resolved by walking ListDomains pages). |
plexctl domain update
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
<id> (positional) | UUID | yes | — | Domain UUID. |
--display-name | string | conditional | — | New human-readable Domain name. Maps to the JSON field name. |
--description | string | conditional | — | New free-form description. An explicit empty string clears the field. |
--mesh-cidr | string | conditional | — | New canonical RFC 4632 mesh-IP pool. The server runs an extra reachability check on retarget. |
At least one of the three mutable flags must be set; an empty patch surfaces locally as exit 2 rather than a 400 empty_patch round-trip.
plexctl domain delete
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
<id> (positional) | UUID | yes | — | Domain UUID. |
--yes (persistent) | bool | yes | false | Required confirmation for the destructive operation. The CLI refuses to call DeleteDomain without --yes. |
Persistent flags inherited from root
--server, --profile, --token-file, --output, --yes, --reveal-secrets. The --yes flag is consumed by delete; the others apply unchanged. See ../plexctl.md for the canonical list.
Output JSON schema reference
JSON and YAML output mirror the typed wire shapes verbatim:
- Single-Domain operations (
create,get,update) renderDomainResponse— see schema definition in../../../api/openapi/plexsphere-v1.yaml. - The
listoperation rendersDomainList, withnext_cursorpreserved verbatim so a caller can resume pagination from the response shape.
The text output mode emits a tabwriter grid with the columns ID, SLUG, DISPLAY_NAME, CREATED_AT. Long-form fields (description, mesh_cidr, reachability, updated_at) are deliberately omitted from the text view; reach for --output json when those fields matter.
Exit codes
plexctl collapses every failure into the taxonomy below; the single source of truth is exitCodeFor in ../../../cmd/plexctl/app.go.
| Code | Reachable | Meaning |
|---|---|---|
0 | yes | The API returned the expected status (201 for create, 200 for list / get / update, 204 for delete). |
1 | yes | Runtime / API error: transport failure, unexpected status code, malformed response body, slug-resolution scan exhausted without a match, the --all safety cap fired, or delete invoked without --yes (the destructive-confirmation refusal is a plain errors.New which falls through exitCodeFor's default branch, mirroring group delete / audit erase-identity). |
2 | yes | Flag-parse / misconfiguration: missing required flag (--slug, --display-name, --mesh-cidr on create), malformed UUID, mutually exclusive --all / --cursor, or an empty patch on update. |
3 | yes | Missing or insecure credentials, or 401 Unauthorized from the API. |
4 | yes | Permission denied or resource not addressable: 403 Forbidden (non-ReBAC denial path) or 404 Not Found from the API. |
64 | no | Not reachable — domain is fully wired and never returns *NotImplementedError. |
77 | yes | ReBAC denial: 403 Forbidden with the typed *output.APIError carrying IsRebacDenied() == true. The dispatcher branches on this BEFORE the generic apiErr.ExitCode() mapping so callers can distinguish "you lack the relation" from "your token is for the wrong audience" without parsing the body. |
Examples
Create a Domain
shell
export PLEXSPHERE_URL="${PLEXSPHERE_URL:-https://localhost:8080}"
plexctl domain create \
--server "${PLEXSPHERE_URL}" \
--slug acme-prod \
--display-name "Acme Production" \
--mesh-cidr 10.42.0.0/16 \
--description "Production tenancy root for the Acme account"Page through every Domain with --all
shell
plexctl domain list \
--server "${PLEXSPHERE_URL}" \
--all \
--output jsonThe walk follows next_cursor until empty and aborts with exit 1 if the accumulator passes 100 000 items.
Get by slug (slug-resolution path)
shell
plexctl domain get acme-prod \
--server "${PLEXSPHERE_URL}" \
--output jsonThe argument is not a UUID, so the subcommand walks ListDomains pages until a Domain with slug == "acme-prod" is returned, then renders the typed DomainResponse.
Sparse update (only --display-name)
shell
plexctl domain update 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--server "${PLEXSPHERE_URL}" \
--display-name "Acme Production (renamed)"Only name is forwarded in the PATCH body; description and mesh_cidr are untouched. An invocation with no mutable flags set exits 2:
shell
plexctl domain update 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--server "${PLEXSPHERE_URL}"
# stderr: plexctl: domain update: at least one of --display-name, --description, --mesh-cidr must be set (PX-0031, REQ-001)
echo $? # 2Delete with --yes
shell
plexctl domain delete 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--server "${PLEXSPHERE_URL}" \
--yes
echo $? # 0The server returns 204 No Content on success; readDomainBody treats the empty body as a no-op and the dispatcher reports exit 0.
ReBAC denial (exit 77)
shell
plexctl domain delete 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
--server "${PLEXSPHERE_URL}" \
--yes
# stderr: plexctl: 403 Forbidden: rebac_denied (need domain:manage)
echo $? # 77The 403 is decoded by output.DecodeProblem into a typed *output.APIError whose IsRebacDenied() returns true; the dispatcher routes it to exit 77 BEFORE the generic apiErr.ExitCode() mapping that would otherwise emit 4.
Cross-references
../../../api/openapi/plexsphere-v1.yaml—CreateDomain,ListDomains,GetDomain,PatchDomain,DeleteDomainoperation definitions and theDomainResponse,DomainListschemas.project.md— sibling reference for the per-Domain Project aggregate.identity.md— sibling reference for the per-Domain Identity surface (users, service identities, invitations).../../../cmd/plexctl/commands/domain.go— source of truth for the cobra command, flag wiring, the--allcap, the slug-resolution path, and the table projection.