Skip to content

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

FlagTypeRequiredDefaultDescription
--slugstringyeskebab-case URL handle, unique within the deployment. Frozen after creation.
--display-namestringyesHuman-readable Domain name. Maps to the JSON field name.
--descriptionstringno(empty)Optional free-form description. Omitted from the wire body when unset.
--mesh-cidrstringyesCanonical 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

FlagTypeRequiredDefaultDescription
--limitintnoserver defaultMaximum items per page. 0 lets the server pick.
--cursorstringnoContinuation token from a previous call's next_cursor. Mutually exclusive with --all.
--allboolnofalseFollow next_cursor until exhausted. Capped at 100 000 items as defence-in-depth against a runaway server.
--slugstringnoClient-side slug filter. The wire ListDomainsParams does not expose a slug query parameter; the trim runs after the page is fetched.

plexctl domain get

ArgumentTypeRequiredDefaultDescription
<id-or-slug>stringyesDomain UUID (parsed first) or slug (resolved by walking ListDomains pages).

plexctl domain update

FlagTypeRequiredDefaultDescription
<id> (positional)UUIDyesDomain UUID.
--display-namestringconditionalNew human-readable Domain name. Maps to the JSON field name.
--descriptionstringconditionalNew free-form description. An explicit empty string clears the field.
--mesh-cidrstringconditionalNew 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

FlagTypeRequiredDefaultDescription
<id> (positional)UUIDyesDomain UUID.
--yes (persistent)boolyesfalseRequired 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) render DomainResponse — see schema definition in ../../../api/openapi/plexsphere-v1.yaml.
  • The list operation renders DomainList, with next_cursor preserved 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.

CodeReachableMeaning
0yesThe API returned the expected status (201 for create, 200 for list / get / update, 204 for delete).
1yesRuntime / 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).
2yesFlag-parse / misconfiguration: missing required flag (--slug, --display-name, --mesh-cidr on create), malformed UUID, mutually exclusive --all / --cursor, or an empty patch on update.
3yesMissing or insecure credentials, or 401 Unauthorized from the API.
4yesPermission denied or resource not addressable: 403 Forbidden (non-ReBAC denial path) or 404 Not Found from the API.
64noNot reachable — domain is fully wired and never returns *NotImplementedError.
77yesReBAC 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 json

The 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 json

The 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 $?  # 2

Delete with --yes

shell
plexctl domain delete 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0a0 \
  --server "${PLEXSPHERE_URL}" \
  --yes
echo $?  # 0

The 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 $?  # 77

The 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.yamlCreateDomain, ListDomains, GetDomain, PatchDomain, DeleteDomain operation definitions and the DomainResponse, DomainList schemas.
  • 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 --all cap, the slug-resolution path, and the table projection.