Appearance
plexctl sink
Synopsis
plexctl sink is the operator surface for the telemetry sinks a Domain declares and for the grants that make one of them usable in a Project. It spans three /v1 surfaces under a single cobra parent:
/v1/domains/{domain_id}/sinks(create, list) and/v1/sinks/{id}(get, update, delete): the tenant sinks one Domain owns./v1/sinks/built-in(list): the platform's own backends. They are read-only, belong to no Domain, and a Telemetry Route may target one without a grant./v1/projects/{project_id}/sink-enablements(request, list),/v1/sinks/{id}/sink-enablements(grant) and/v1/sink-enablements/{id}/revoke: the enablement lifecycle under the nestedplexctl sink enablementsubgroup. Approving and rejecting a request belongs toplexctl approval, not here.
text
plexctl sink <subcommand> [flags]
plexctl sink enablement <subcommand> [flags]Subcommands
plexctl sink create
POSTs /v1/domains/{domain_id}/sinks. --domain-id is required, and so are the four fields every write states: --slug, --display-name, --type and --endpoint. The type decides which endpoint grammar holds (otlp takes an https URL, syslog a host and port pair), and an invalid pairing is refused by the server with 422 sink_invalid naming the field. A slug another sink of the same Domain already holds fails with 409 sink_slug_taken.
--id declares the sink under an identifier the caller mints. That is what a caller who stores connection material needs: the KV path is derived from the owning Domain and the sink id, so the material is written before the sink exists. Omit the flag and the server mints the id.
An otlp sink that states no --dataset files its telemetry under the owning Domain's slug.
plexctl sink list
Reads either the tenant sinks of one Domain (--domain-id, GETs /v1/domains/{domain_id}/sinks) or the platform's built-in sinks (--built-in, GETs /v1/sinks/built-in). Exactly one of the two arms must be set; naming both, or neither, fails locally with exit 2 before the request is sent. Both sets are bounded, so neither is paginated.
plexctl sink get <id>
GETs /v1/sinks/{id}. Resolves a tenant sink and a built-in sink alike. The projection carries the sink's credential coordinates and never the material stored at them.
plexctl sink update <id>
PUTs /v1/sinks/{id} with the full post-image, so a field the command does not state is cleared rather than left untouched: an update that omits --ca-pem-file drops the stored bundle. The owning Domain, the identifier and the built-in marker are immutable and carry no flags; a write addressing a built-in sink fails with 409 sink_conflict.
--expected-updated-at is required and states the updated_at you read the sink at, which plexctl sink get prints. It is the compare-and-swap token: because the body replaces the whole sink, an update written from a stale read would restore every field you did not state, including the TLS posture and the credential reference. A sink that changed since that read fails with 409 sink_cas_conflict and nothing is written, so the fix is to re-read the sink and restate the post-image.
plexctl sink delete <id>
DELETEs /v1/sinks/{id}. Requires --yes. A Telemetry Route that still targets the sink is what makes this destructive, so the confirmation is fail-closed and decided locally.
plexctl sink enablement request
POSTs /v1/projects/{project_id}/sink-enablements. Both --project-id and --sink-id are required. The enablement opens in the requested state and is decided on the approvals queue. The sink and the Project must belong to one Domain, otherwise the request fails with 422 sink_domain_mismatch; a built-in sink needs no enablement at all and is refused with 422 sink_not_enableable.
plexctl sink enablement grant
POSTs /v1/sinks/{id}/sink-enablements. This is the owner's push, the counterpart to request. Both --sink-id and --project-id are required, and the server gates the call on sink#assign over the named sink. The enablement lands already approved, so the sink is usable in the Project the moment the command returns. That is deliberate rather than a shortcut: requesting and then approving one's own request is refused by the self-approval guard, so without this route a sink's owner could not place it in a Project at all.
A second live enablement for the same (Project, sink) pair fails with 409 duplicate_live_sink_enablement.
plexctl sink enablement list
GETs /v1/projects/{project_id}/sink-enablements (the --project-id flag is required). Pagination is keyset-based via --cursor / --limit; --all follows next_cursor until exhausted and is mutually exclusive with --cursor.
plexctl sink enablement revoke <id>
POSTs /v1/sink-enablements/{id}/revoke. Requires --yes and a --reason audit-rationale string. Either side may revoke: the server first checks assign on the sink, and on that denial falls back to deploy on the consuming Project. Revocation is legal from approved only; any other source state answers 409 illegal_transition.
A 502 revocation_sync_pending is not a completed revocation. The row moved to revoked while the grant is still live in the authorization graph, so the Project keeps delivering. Retrying is refused; the removal replays through the committed event.
Deciding a request
plexctl sink enablement has no approve or reject subcommand. Deciding a filed request happens on the dual-control queue: plexctl approval list --kind sink_enablement shows the rows the caller may decide, and plexctl approval approve or plexctl approval reject takes the enablement id directly. See approval.md.
Connection material
A sink states where its connection material lives, never the material itself. --credential-kv-mount names the KV mount and --credential-kv-version the revision to read (the latest one when the flag is omitted). The path under the mount is derived server-side from the owning Domain and the sink id as domains/<domain-id>/sinks/<sink-id>, so no flag carries it and a reference cannot address another tenant's material.
Write the material at that path before the sink exists, which is why --id is worth minting client-side. With --credential-kv-mount secret:
shell
bao kv put secret/domains/<domain-id>/sinks/<sink-id> token='<auth-token>'--credential-kv-version without --credential-kv-mount fails locally with exit 2: a sink that names no mount reads no material, so there is nothing for a version to select.
--ca-pem-file is the one flag that carries bytes rather than coordinates. The request body holds the PEM bundle itself, not a path the server could open, so the file is read client-side.
Flags
plexctl sink create
| Flag | Type | Required | Description |
|---|---|---|---|
--domain-id | UUID | yes | Owning Domain UUID. |
--id | UUID | no | Identifier to declare the sink under (UUIDv7); server-minted when omitted. |
--slug | string | yes | Kebab-case handle, unique among the Domain's sinks. |
--display-name | string | yes | Human-readable name the sink is listed under. |
--type | string | yes | Protocol to deliver with: otlp or syslog. |
--endpoint | string | yes | Address to deliver to: an https URL for otlp, a host and port pair for syslog. |
--ca-pem-file | path | no | Path to the PEM bundle the destination's certificate is verified against. Read client-side. |
--insecure-skip-verify | bool | no | Connect without verifying the destination's certificate. |
--credential-kv-mount | string | no | KV mount the connection material lives under. |
--credential-kv-version | int64 | no | KV version to read; latest when omitted. Requires --credential-kv-mount. |
--dataset | string | no | Logical stream an otlp destination files the delivered telemetry under. Defaults to the Domain slug. |
plexctl sink list
| Flag | Type | Required | Description |
|---|---|---|---|
--domain-id | UUID | conditional | Domain UUID whose tenant sinks to list. Mutually exclusive with --built-in. |
--built-in | bool | conditional | List the platform's built-in sinks instead of a Domain's. Mutually exclusive with --domain-id. |
plexctl sink update <id>
Takes the same field flags as plexctl sink create minus --domain-id and --id, both of which address immutable state. The four required ones stay required, because the body is the full post-image, and one flag is added:
| Flag | Type | Required | Description |
|---|---|---|---|
--expected-updated-at | RFC 3339 timestamp | yes | The updated_at the sink was read at. A sink changed since then fails with 409 sink_cas_conflict. |
plexctl sink delete <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--yes (persistent) | bool | yes | Required confirmation for the destructive operation. |
plexctl sink enablement request
| Flag | Type | Required | Description |
|---|---|---|---|
--project-id | UUID | yes | Requesting Project UUID. |
--sink-id | UUID | yes | Sink UUID to request delivery to. |
plexctl sink enablement grant
| Flag | Type | Required | Description |
|---|---|---|---|
--sink-id | UUID | yes | Sink UUID to grant. |
--project-id | UUID | yes | Project UUID to enable the sink in. |
plexctl sink enablement list
| Flag | Type | Required | Description |
|---|---|---|---|
--project-id | UUID | yes | Owning Project UUID. |
--limit | int | no | Maximum items per page (server default when zero). |
--cursor | string | no | Continuation token from a previous call's next_cursor. |
--all | bool | no | Follow next_cursor until exhausted. Mutually exclusive with --cursor. |
plexctl sink enablement revoke <id>
| Flag | Type | Required | Description |
|---|---|---|---|
--reason | string | yes | Audit-recorded revocation rationale. |
--yes (persistent) | bool | yes | Required confirmation for the destructive operation. |
Destructive subcommands
plexctl sink delete and plexctl sink enablement revoke consume the persistent --yes flag. Both reject the call without explicit confirmation, and the refusal is decided locally with exit 2 before any request is sent.
Persistent flags inherited from root
--server, --profile, --token-file, --output, --yes, --reveal-secrets. See ../plexctl.md.
Exit codes
See ../plexctl.md#exit-code-taxonomy.
Examples
Declare a tenant sink with connection material
Mint the id first, write the material at the derived path, then declare the sink against the mount:
shell
plexctl sink create \
--server "${PLEXSPHERE_URL}" \
--domain-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0d0 \
--id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0e0 \
--slug central-otlp \
--display-name "Central OTLP" \
--type otlp \
--endpoint https://otlp.example.com:4318 \
--credential-kv-mount secretGrant a sink to a Project and read the enablements back
shell
plexctl sink enablement grant \
--server "${PLEXSPHERE_URL}" \
--sink-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0e0 \
--project-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0c0
plexctl sink enablement list \
--server "${PLEXSPHERE_URL}" \
--project-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0c0 \
--all