Skip to content

Managing Entities with Backing Units

A backing Unit holds the configuration of one ConfigHub entity — a Trigger, Filter, View, Invocation, Attribute, Link, ChangeWorkflow, Target, Space, or Component — as a YAML document, so that changes to the entity have Revisions, can be diffed and restored, and can be validated by Triggers before they take effect. This guide shows how to give entities backing Units, create and update entities from documents with cub apply, edit them with cub <entity> edit, change the documents and then the entities, restore earlier Revisions, and validate the documents. Read the concept page first for what a backing Unit holds and how it is kept in step with its entity.

The examples use a Space named platform, holding a platform team's Filters, Triggers, and Views. The commands are the same for the other types, with filter, view, invocation, attribute, link, changeworkflow, target, space, or component in place of trigger. A Space or a Component is in no Space, so its commands take no --space, and a Component can't be created from a backing Unit, only updated from one.

You can also check out the simple demo script.

Giving entities backing Units

--with-backing-units gives an entity a backing Unit when it is created:

cub filter create --space platform web-units Unit \
    --where-field "Labels.tier = 'web'" --with-backing-units

To give existing entities backing Units, pass it to a patch. It creates a backing Unit for each selected entity that has none, from the entity's current configuration, and leaves the rest alone, so you can run it again as new entities appear:

cub filter update --space platform --patch web-units --with-backing-units
cub trigger update --space platform --patch --where "BackingUnitID IS NULL" --with-backing-units

A Space or a Component is in no Space, so its backing Unit goes in the Space --backing-unit-space names, which you need permission to create Units in:

cub space update --patch --where "Slug = 'platform'" --with-backing-units --backing-unit-space platform-home
cub component update --patch --where "Slug = 'checkout'" --with-backing-units --backing-unit-space platform-home

Finding a backing Unit

cub <entity> get names an entity's backing Unit:

$ cub filter get --space platform web-units
ID                 14153142-27a4-4292-8a4f-7878bd097fc5
Name               web-units
Space              platform
...
Backing Unit       platform/id14153142-27a4-4292-8a4f-7878bd097fc5

The Unit is named id followed by the entity's ID, and is displayed as Filter web-units. Backing Units are hidden, so cub unit list leaves them out; --include-hidden=BackingUnit lists them, and cub unit get shows the entity a backing Unit backs:

$ cub unit list --space platform --include-hidden=BackingUnit
NAME                                      SPACE       ...    LAST-CHANGE-DESCRIPTION
id14153142-27a4-4292-8a4f-7878bd097fc5    platform    ...    Backing Unit of Filter

$ cub unit get --space platform id14153142-27a4-4292-8a4f-7878bd097fc5
...
Display Name                  Filter web-units
Toolchain Type                ConfigHub/YAML
Backs                         Filter platform/web-units
Hidden Reason                 BackingUnit

Since a backing Unit is a Unit, the Unit commands read it: cub unit data shows the document, cub revision list the history of the entity's configuration, and cub unit diff the changes between Revisions:

$ cub unit data --space platform id14153142-27a4-4292-8a4f-7878bd097fc5
DisplayName: web-units
EntityType: Filter
From: Unit
Permissions:
  Manage:
    UserIDs:
      0dcb63e7-c13e-4fe1-8bbf-9f637e3689d4: true
ResourceType: ""
Slug: web-units
Where: Labels.tier = 'web'
WhereData: ""

To select the backing Units of one type, use the confighub.com/EntityType label each one carries:

cub unit list --space platform --include-hidden=BackingUnit --where "Labels.confighub.com/EntityType = 'Trigger'"

Creating and updating entities from documents with cub apply

cub apply creates or updates entities from documents, as kubectl apply does for Kubernetes objects. Keep the documents in files, in source control or wherever you author configuration. A document has the shape cub <entity> edit shows and a backing Unit holds: an EntityType, a Slug, and the fields you want to set. A reference names its entity by slug, unqualified for an entity in the same Space and as <space>/<slug> for one in another. A file may hold several documents separated by ---.

# platform/filters.yaml
EntityType: Filter
Slug: prod-units
From: Unit
Where: Labels.Environment = 'prod'
# platform/trigger.yaml
EntityType: Trigger
Slug: ha-replicas
Event: Mutation
ToolchainType: Kubernetes/YAML
FunctionName: vet-celexpr
Arguments:
- ParameterName: validation-expr
  Value: r.kind != "Deployment" || r.spec.replicas > 1
UnitFilter: prod-units
# platform/view.yaml
EntityType: View
Slug: prod-units
Filter: prod-units
Of: Unit
Columns:
- Name: Unit.Slug
- Name: Unit.HeadRevisionNum

-f takes files or directories, and --space names the Space the documents go in, which must exist. --dry-run reports what would happen and writes nothing:

$ cub apply --space platform -f platform/ --dry-run
Dry run: nothing was written.
Create    Filter prod-units (backing Unit  create)
Create    Trigger ha-replicas (backing Unit  create)
Create    View prod-units (backing Unit  create)

$ cub apply --space platform -f platform/
Create    Filter prod-units (backing Unit id7f00f343-8daa-4002-b8eb-d381cc32c3af create)
Create    Trigger ha-replicas (backing Unit ide594ce26-6123-4bab-b0f9-034769646b4c create)
Create    View prod-units (backing Unit id6b318e40-c89f-433c-a1b9-8fac8f30b8e7 create)

Each document is written to a backing Unit, the Unit's Triggers run, and the entity is created or updated from it. Documents are written in the order their references require: the Filter first, since the Trigger and the View name it. A dry run checks that every field exists, that every name resolves, and that no field that can't change is changed, but leaves the rest of the checks to the writes. If a write fails, the document stays in its backing Unit, and applying again after you fix the document picks up where it left off.

Applying the same documents again changes nothing:

$ cub apply --space platform -f platform/
Unchanged Filter prod-units (backing Unit id7f00f343-8daa-4002-b8eb-d381cc32c3af unchanged)
Unchanged Trigger ha-replicas (backing Unit ide594ce26-6123-4bab-b0f9-034769646b4c unchanged)
Unchanged View prod-units (backing Unit id6b318e40-c89f-433c-a1b9-8fac8f30b8e7 unchanged)

Changing documents

Change a document and apply it again to change the entity. Apply changes what the document changed since it was last applied, and leaves everything else as it is, including changes made in ConfigHub. Here a label added to the Trigger in ConfigHub survives the document's change to Warn:

$ cub trigger update --space platform --patch --where "Slug = 'ha-replicas'" --label owner=platform
$ echo "Warn: true" >> platform/trigger.yaml
$ cub apply --space platform -f platform/trigger.yaml
Update    Trigger ha-replicas (backing Unit ide594ce26-6123-4bab-b0f9-034769646b4c update)
$ cub trigger get --space platform ha-replicas -o jq='.Trigger | {Warn, Labels}'
{
  "Labels": {
    "owner": "platform"
  },
  "Warn": true
}

A field you delete from a document is removed from the entity on the next apply. A field you never stated keeps whatever value the entity has. To set a value back after it was changed in ConfigHub, state it in the document, changed, or restore the backing Unit.

Sources and adoption

The documents you apply belong to a source, cub-apply unless you name another with --source, such as one per repository or pipeline. Applying a document for an entity that another source applied is refused, unless you pass --adopt, which makes it yours:

cub apply --space platform --source platform-repo --adopt -f platform/

An entity that exists without a backing Unit, created with cub or the UI, is adopted by the first document you apply to it: it gets a backing Unit holding its current configuration, and then the fields your document states.

Removing entities

Leaving a document out of an apply removes nothing. With --prune, the documents you give are all of the source's documents for the Space, and any entity the source applied before whose document is missing is deleted. Its backing Unit is emptied and kept, with the entity's history. Try it with --dry-run first:

$ rm platform/view.yaml
$ cub apply --space platform -f platform/ --prune --dry-run
Dry run: nothing was written.
Prune     View prod-units (backing Unit id6b318e40-c89f-433c-a1b9-8fac8f30b8e7 empty)
Unchanged Filter prod-units (backing Unit id7f00f343-8daa-4002-b8eb-d381cc32c3af unchanged)
Unchanged Trigger ha-replicas (backing Unit ide594ce26-6123-4bab-b0f9-034769646b4c unchanged)

$ cub apply --space platform -f platform/ --prune
Prune     View prod-units (backing Unit id6b318e40-c89f-433c-a1b9-8fac8f30b8e7 empty)
Unchanged Filter prod-units (backing Unit id7f00f343-8daa-4002-b8eb-d381cc32c3af unchanged)
Unchanged Trigger ha-replicas (backing Unit ide594ce26-6123-4bab-b0f9-034769646b4c unchanged)
Pruned    View prod-units

If the document comes back, applying it revives the empty Unit and creates a new View from it.

Space and Component documents

A Space document describes the Space --space names, and may leave out its Slug. Its backing Unit goes in the Space --backing-unit-space names. A Space document can name entities that the same apply creates in the Space: here the Space selects its Triggers with a Filter that the apply also creates, and cub apply creates the Filter before writing the Space's reference to it.

# space/space.yaml
EntityType: Space
Labels:
  team: platform
TriggerFilter: platform-triggers
# space/filter.yaml
EntityType: Filter
Slug: platform-triggers
From: Trigger
FromSpace: platform-policies
$ cub apply --space platform --backing-unit-space platform-home -f space/
Update    Space platform (backing Unit iddf0f6bc6-e196-4224-bc03-3e820ab23404 adopt)
Create    Filter platform-triggers (backing Unit id5b9ae64b-31df-4818-a94b-00b29393232f create)

A Component document describes the Component of a cub variant upload, which takes --backing-unit-space too.

Creating entities from Units you write yourself

cub apply creates the backing Units for you. You can also create a ConfigHub/YAML Unit from a document yourself, and then create its entity from it with --from-backing-units, which takes the Units --where-unit selects:

cub unit create --space platform --toolchain ConfigHub/YAML filter-db-units db-units.yaml
cub filter create --space platform --from-backing-units --where-unit "Slug = 'filter-db-units'"

The Unit becomes the Filter's backing Unit, and keeps the name you gave it. A ConfigHub/YAML Unit is hidden when it is created, as a backing Unit is. --where-unit can select many Units at once, and the Units that describe another type of entity are passed over, so one expression can select every backing Unit in a Space and each type's create creates its own. A Unit that already backs an entity is reported as a conflict, unless you pass --patch-existing, which updates the entity from the Unit instead, so the same command creates the new entities and updates the existing ones.

Editing entities with cub <entity> edit

cub <entity> edit opens an entity's document in the editor EDITOR names, as kubectl edit does for a Kubernetes object, and updates the entity with what you changed when the editor exits. The document names references by slug, and holds only the fields you can change.

cub trigger edit --space platform ha-replicas
  • Without a backing Unit, the entity is updated directly. A field you delete is cleared, and a field you leave alone keeps its value. If the entity changed while you were editing it, the edit is refused, and left in a file whose name is reported; edit it again.
  • With a backing Unit, the edit goes through the Unit. It is saved as a Revision of the Unit, described as Edit of trigger ha-replicas, the Unit's Triggers run, and the entity is then updated from the Unit. If the Triggers report Validation Errors, the edit stays in the Unit and the entity does not take it.
$ cub revision list --space platform ide594ce26-6123-4bab-b0f9-034769646b4c
NUM    UNIT                                      SOURCE           ...    DESCRIPTION
7      ide594ce26-6123-4bab-b0f9-034769646b4c    UpdateUnit       ...    Edit of trigger ha-replicas
6      ide594ce26-6123-4bab-b0f9-034769646b4c    MergeExternal    ...    MergeExternal; from platform/trigger.yaml

An edit starts from the Unit's head, so it is refused while the Unit holds a change the entity has not taken, which would otherwise be applied along with your edit without your seeing it. Take or discard that change first, as the next two sections show, or pass --force to edit the Unit anyway and apply both.

An edit of an entity that was applied with cub apply is a change made in ConfigHub: applying the same documents again leaves it in place, and a later change to the same field in a document overwrites it.

Changing documents, then entities

A backing Unit is a Unit, so everything that changes a Unit's configuration changes the document: cub unit data-edit, cub unit update with a file, functions with cub function do, and merges. None of them changes the entity. The entity takes the change when you update it with --from-backing-units, which can be reviewed and validated in between.

Edit one document, and then update the entity from it:

cub unit data-edit --space platform ide594ce26-6123-4bab-b0f9-034769646b4c
cub trigger update --space platform --patch ha-replicas --from-backing-units

Or change many documents with a function, and then update every entity in the Space from its backing Unit. Here every Filter in the Space gets an owner label:

cub function do --space platform --toolchain ConfigHub/YAML \
    --where "HiddenReason = 'BackingUnit' AND Labels.confighub.com/EntityType = 'Filter'" \
    set-label owner platform
cub filter update --space platform --patch --where "BackingUnitID IS NOT NULL" --from-backing-units

Naming HiddenReason in --where selects the hidden backing Units without --include-hidden. An entity whose backing Unit has nothing new is left as it is, so --where can be as broad as you like. Flags that set fields, such as --label, are applied after the change from the Unit, and their changes are rendered back into the Unit.

The change taken is the difference between the Revision the entity last took, the Unit's LastReleasedRevisionNum, and the Unit's head. To find the backing Units with changes their entities have not taken:

cub unit list --space platform --where "HiddenReason = 'BackingUnit' AND HeadRevisionNum > LastReleasedRevisionNum"

Changing a Unit's data runs its Triggers, and the entity can't take the change until they have finished. The cub commands wait for them, but a script that writes a Unit through the API has to wait for the awaiting/triggers Validation Error to clear before updating the entity, as waiting for Triggers describes.

Restoring earlier Revisions

Every change to an entity with a backing Unit is a Revision of the Unit, whether it came from the Unit or from the entity. To return the entity to an earlier configuration, restore the Unit to an earlier Revision, and then update the entity from the Unit. cub revision list and cub unit diff show what each Revision changed:

$ cub revision list --space platform ide594ce26-6123-4bab-b0f9-034769646b4c
NUM    UNIT                                      SOURCE           ...    DESCRIPTION
8      ide594ce26-6123-4bab-b0f9-034769646b4c    UpdateUnit       ...    CLI edit
7      ide594ce26-6123-4bab-b0f9-034769646b4c    UpdateUnit       ...    Edit of trigger ha-replicas
...
$ cub unit diff --space platform ide594ce26-6123-4bab-b0f9-034769646b4c --from=-1
$ cub unit update --space platform ide594ce26-6123-4bab-b0f9-034769646b4c --restore -1 \
    --change-desc "Re-enable ha-replicas"
$ cub trigger update --space platform --patch ha-replicas --from-backing-units

--restore takes a Revision number, a negative number counting back from the head, a Tag, or a ChangeSet, as for any Unit; see making changes.

Discarding a change the entity has not taken

A change to a backing Unit that you decide against, such as an edit its Triggers refused, can be discarded by restoring the Revision the entity holds:

cub unit update --space platform ide594ce26-6123-4bab-b0f9-034769646b4c --restore LastReleasedRevisionNum \
    --change-desc "Discard the edits ha-replicas did not take"
cub trigger update --space platform --patch ha-replicas --from-backing-units

The restore writes a new Revision with the configuration the entity already has, so updating the entity from it changes nothing on the entity, and records that the entity holds the Unit's head again. Until then, cub <entity> edit refuses, since the Unit's head is ahead of the entity.

Reverting an apply

Each cub apply is recorded as a ChangeSet, upload-<timestamp>, which cub changeset list shows. To revert one, restore the backing Units to before the ChangeSet, update the entities whose documents it changed, and prune the entities it created, whose backing Units the restore emptied. Here the apply changed the prod-units Filter's Where and added an Invocation:

$ cub changeset list --space platform
NAME                      SPACE       STATE     DESCRIPTION
upload-20261006-205050    platform    Closed    Upload from platform/
...
$ cub unit update --patch --space platform --where "HiddenReason = 'BackingUnit'" \
    --restore Before:ChangeSet:upload-20261006-205050 --change-desc "Revert the last apply"
$ cub filter update --space platform --patch --where "BackingUnitID IS NOT NULL" --from-backing-units
$ cub invocation delete --space platform --from-backing-units

cub <entity> delete --from-backing-units deletes only the entities whose backing Units are empty, and leaves the others alone, so it can select a whole Space. Updating an entity from an empty backing Unit is refused, with a message saying to prune it or restore the Unit.

Applying the same documents again does not undo a restore: as far as cub apply is concerned, they have not changed since they were last applied. Revert the documents in your source too, so the next change to them is made on top of the restored configuration.

Validating backing Unit documents

A Trigger whose toolchain is ConfigHub/YAML runs on the backing Units it applies to, and an entity can't take a change from its backing Unit while the Unit has Validation Errors. This applies however the change is made: with cub apply, cub <entity> edit, or --from-backing-units. Write the Triggers as for any other configuration; see validating configuration and enforcing policies. For example, in the Space whose entities they govern:

# Each document is valid for its entity type
cub trigger create --space platform valid-documents Mutation ConfigHub/YAML vet-schemas

# Each where expression parses, and names attributes its entity type has
cub trigger create --space platform valid-where Mutation ConfigHub/YAML vet-where-expressions

# No field that can't change after the entity is created was changed
cub trigger create --space platform immutable-fields Mutation ConfigHub/YAML \
    --other-data-source LastReleasedRevisionNum vet-immutable

# A policy of your own: no Trigger is disabled
cub trigger create --space platform no-disabled-triggers Mutation ConfigHub/YAML \
    --where-unit-field "Labels.confighub.com/EntityType = 'Trigger'" \
    vet-celexpr 'r.Disabled == false'

vet-immutable compares the document with the Revision the entity holds, which --other-data-source LastReleasedRevisionNum passes to it; without it, the function has nothing to compare with, and passes. --where-unit-field limits a Trigger to the backing Units of one type, by the confighub.com/EntityType label. In a CEL expression, r is the document, with the field names it uses.

A change the Triggers reject stays in the backing Unit, and the entity keeps its configuration. Here an edit set the Trigger's Warn to maybe:

$ cub trigger edit --space platform ha-replicas
Unit ide594ce26-6123-4bab-b0f9-034769646b4c (27c11c46-8aa2-4fc4-8559-2a3e6283e3c4) has validation errors: platform/valid-documents/vet-schemas
Failed: the edit is saved in the backing Unit platform/ide594ce26-6123-4bab-b0f9-034769646b4c, but the trigger did not take it; fix it with `cub unit data-edit`, then apply it with `cub trigger update --patch --from-backing-units`: HTTP 422 ...: the backing Unit ide594ce26-6123-4bab-b0f9-034769646b4c has outstanding ValidationErrors

To see why, run the function on the Unit with cub function vet, which reports each failing field. Pass --toolchain ConfigHub/YAML, since the default is Kubernetes/YAML:

$ cub function vet --space platform --toolchain ConfigHub/YAML \
    --unit ide594ce26-6123-4bab-b0f9-034769646b4c vet-schemas
...
  Passed: false  Function: vet-schemas
  Resource Trigger//ha-replicas: Warn: Invalid type. Expected: boolean, given: string

Then fix the document with cub unit data-edit and update the entity with --from-backing-units, or discard the change.

Triggers that select backing Units across Spaces work as any Triggers do: put them in a Space of their own and select them with each Space's WhereTrigger or TriggerFilter, as organizing Triggers across Spaces describes. Remember that a Space's TriggerFilter or WhereTrigger replaces the Triggers in the Space itself, including those validating its backing Units.