Skip to content
🛡️ adminadmin@example.com · Domain admin in Acme Corp

Chapter 6 — Labels

A Project to classify

bash
PROJECT=$(plexctl project create \
  --domain "$DOMAIN_ID" \
  --slug label-demo \
  --display-name "Label Demo" \
  --output json)
PROJECT_ID=$(echo "$PROJECT" | jq -r '.id')
echo "$PROJECT" | jq '{id, domain_id, slug, name}'

Labels are typed, not free-form strings

bash
DEF=$(plexctl label define create \
  --scope domain --scope-id "$DOMAIN_ID" \
  --key env --type enum --enum-values production,staging \
  --applicable-kinds project --on-delete cascade \
  --output json)
DEF_ID=$(echo "$DEF" | jq -r '.id')
echo "$DEF" | jq '{id, scope, qualified_key, applicable_kinds, on_delete}'

A definition first, then assignments against it.

--type enum with two allowed values. --applicable-kinds project — this Label may not be attached to a Node. --on-delete cascade decides what happens to assignments when the definition goes away.

The twist

Assigning a Label is a privileged act, and being Domain admin does not authorize it.

Two explicit relations are needed: maintainer on the target Project, and assigner on the Label definition. The admin relation you hold grants neither.

bash
plexctl rebac tuple add \
  --project "$PROJECT_ID" \
  --resource "project:$PROJECT_ID" \
  --relation maintainer \
  --subject "user:$USER_ID"

And the second relation

bash
plexctl rebac tuple add \
  --project "$PROJECT_ID" \
  --resource "labeldefinition:$DEF_ID" \
  --relation assigner \
  --subject "user:$USER_ID"

Why this matters: a Label often drives something — network policy targeting, which credential a Project may use, who gets paged.

If administering a Domain silently granted the right to attach labels, every one of those downstream decisions would inherit that authority.

Assign it

bash
plexctl label object set "project:$PROJECT_ID" \
  --definition-id "$DEF_ID" \
  --value production
text
OBJECT_KIND  OBJECT_ID                             QUALIFIED_KEY  VALUE       ASSIGNED_BY
project      019ecc6a-1a2b-7c3d-8e4f-0a1b2c3d4e5f  acme-corp/env  production  user

production is one of the declared enum values, so it is accepted. --value bogus is rejected — the schema is enforced at assignment time, not merely documented.

Read it back

bash
plexctl label object list "project:$PROJECT_ID"
text
OBJECT_KIND  OBJECT_ID                             QUALIFIED_KEY  VALUE       ASSIGNED_BY
project      019ecc6a-1a2b-7c3d-8e4f-0a1b2c3d4e5f  acme-corp/env  production  user

Note the qualified key: acme-corp/env. Labels are namespaced by the scope that defined them, so two Domains can both have an env and never collide.

Labels exist to be selected

bash
plexctl label selector preview --selector "acme-corp/env=production"
text
OP  KEY            VALUE       VALUES
eq  acme-corp/env  production

preview only parses — it touches no object. The grammar has equality, inequality, set membership, and existence, joined with commas for AND.

A Label you can only read back would be a sticky note. This is the bridge from "I tagged one thing" to "I can act on everything tagged this way."

Two axes in one query

bash
plexctl label object search \
  --selector "acme-corp/env=production" --relation maintainer
text
KIND     ID
project  019ecc6a-1a2b-7c3d-8e4f-0a1b2c3d4e5f

The Label axis (the selector) and the access axis (the relation), resolved together.

Objects you cannot reach through the relation are silently omitted. The result is exactly the set you are allowed to act on — not a tag list you then have to filter by hand.

This is the shape that network policy targeting and bulk actions consume.