Skip to content

Working with variants at the unit level

The cub variant commands operate on a whole space at once and are the recommended way to work with variants. They build on per-unit upstream relationships, and you can work with those relationships directly. That is what this guide covers, and it is what you need for partial scopes, cross-space fleets, and reshaping upstream links after the fact.

ConfigHub offers several merge-based mechanisms for keeping related units in sync. Pick the one that matches your goal:

Goal Mechanism
Inherit config properties Clone/upgrade, UpgradeUnit Links
Repeatedly render or generate config data MergeUnits Links
Combine or split config units, with updates MergeUnits Links
Repeatedly upload config data --merge-external-source
Merge a ChangeSet into other variants or upstream --merge-source

Cloning units individually

In ConfigHub you can copy (known as clone in the UI) config units in a way that tracks their relationship in order to help you keep them in sync. The new copied unit is said to be downstream from the original upstream unit.

Different deployment environments would have different spaces corresponding to them, so typically one would clone a unit to one or more other spaces.

You can copy one unit to another space with the CLI like this:

cub unit create --space prod --upstream-space dev --upstream-unit backend

You could copy multiple units to multiple spaces also. To copy all units from dev into multiple prod spaces (e.g., corresponding to multiple clusters or regions), you could do something like:

cub unit create --space dev --where-space "Labels.Environment = 'prod'"

When a unit is cloned, ConfigHub records the upstream unit and the upstream revision the clone was created from on the new downstream unit (UpstreamUnitID and UpstreamRevisionNum), and automatically creates an UpgradeUnit Link from the clone (downstream) to the original (upstream). The Link tracks which upstream revision was last merged. Which downstream changes a future upgrade may overwrite is protection's question, recorded per path on the downstream unit itself.

Establishing or changing the upstream relationship after creation

A unit's UpstreamUnitID is read-only and cannot be set or modified directly with cub unit update. To establish, change, or remove the upstream relationship after a unit has been created, manipulate the unit's UpgradeUnit Link.

To establish a relationship between two units that already exist, create an UpgradeUnit Link with --make-current. --make-current pins the Link's UpstreamLastMergedRevisionNum and DownstreamLastMergedRevisionNum to the current head revisions of the two units, so the next upgrade only merges changes made after this point — it does not retroactively merge the upstream's full history into the downstream:

cub link create --space prod --update-type UpgradeUnit --make-current upgrade-mydown mydown myupstream

To change which unit is upstream, delete the existing UpgradeUnit Link and create a new one. Deleting an UpgradeUnit Link clears the downstream unit's UpstreamUnitID and UpstreamRevisionNum. Without --make-current, creating a new UpgradeUnit Link performs an initial merge using the new upstream's full history.

Note: a unit can have at most one outgoing UpgradeUnit Link.

Reversing the upstream relationship

You may want to invert which unit is upstream. A common case: you started with a running app, then decided to copy it to a base unit so you can derive other variants from the base. The original (now an arbitrary variant) should become downstream of the base.

Reverse an UpgradeUnit Link with cub link update --reverse:

cub link update --space prod --patch --reverse upgrade-mydown

When the downstream and upstream units are in the same space, the Link is reversed in place. For cross-space Links, ConfigHub creates new reversed Link copies in the other space and deletes the originals (because a Link must live in the same space as its FromUnit).

Reversing also clears the Link's WhereMutation if it has one, since a filter written against the old downstream's mutation history means nothing once the direction flips.

For bulk reversal — for example, after copying every unit in a dev space to a new base space and wanting the dev units to become downstream of the base — use cub link create --reverse against the original UpgradeUnit Links, then delete the originals:

# Copy every unit from app-dev to app-base; this auto-creates UpgradeUnit
# links in app-base pointing back to app-dev.
cub unit create --space app-dev --where "Slug LIKE '%'" --dest-space app-base

# Reverse: create reversed UpgradeUnit links in app-dev pointing to app-base.
cub link create --space app-base --where "UpdateType = 'UpgradeUnit'" --reverse

# Delete the now-redundant original links in app-base.
cub link delete --space app-base --where "UpdateType = 'UpgradeUnit'"

Keeping variants in sync

The upstream and downstream units can all be modified independently, but if you want to propagate a change from the upstream unit to the downstream units, ConfigHub uses the UpgradeUnit Link's tracked revision to know which upstream changes still need to be merged.

To upgrade and copy more recent changes from the upstream unit, you can execute a command like this:

cub unit update --space "*" --patch --upgrade --where "UpstreamUnit.Slug = 'backend' AND Space.Labels.Environment = 'prod'"

Alternatively, since the relationship is represented as a Link, you can also upgrade via the resolve mechanism:

cub unit update --space prod --patch --resolve "Link:*" backend-clone

Both approaches produce the same result. The --upgrade flag applies the UpgradeUnit Link's WhereMutation filter if one is set. The --resolve approach works with any Link of type UpgradeUnit or MergeUnits.

Upgrading the units modifies the configuration data but does not automatically apply the changes. To preview the changes first, add --dry-run. To inspect the resulting diff, add -o mutations:

cub unit update --space prod --patch --upgrade --dry-run -o mutations backend-clone

The paths the variant has protected are left alone, and what the merge withheld to leave them alone is reported as a conflict.

Ad hoc merges across variants and from downstream to upstream

Upgrade propagates changes from the upstream unit to selected downstream units. For other directions — between sibling variants, from a downstream back to its upstream, or from a previous range of revisions of the same unit (for rebase-like flows) — use --merge-source with --merge-base and --merge-end. The base/end pair specifies a revision range on the source unit to merge into the target unit:

cub unit update \
    --patch \
    --space prod-west \
    --merge-source prod-east/backend \
    --merge-base Before:ChangeSet:prod-east/prodfix42 \
    --merge-end ChangeSet:prod-east/prodfix42 \
    backend

To apply a range of changes from a unit to itself (for rebase-like flows after a restore), use --merge-source Self:

cub unit update \
    --patch \
    --space acme-dev \
    --filter acme-home/acme-app \
    --merge-source Self \
    --merge-base Before:ChangeSet:acme-home/release-v452 \
    --merge-end ChangeSet:acme-home/release-v452 \
    --change-desc "Reapply release 452"

See merging and rebasing for additional examples.

Merging external sources into ConfigHub

When the base for a unit lives outside ConfigHub — for example, a YAML file in a Git repository, a chart from a registry, or a manifest produced by an external pipeline — use --merge-external-source with cub unit create and cub unit update to bring in changes from that source while preserving anything ConfigHub-side functions, links, or hand edits added on top:

# Create the unit, recording deployment.yaml as the external source.
cub unit create --space prod mydep deployment.yaml --merge-external-source deployment.yaml

# Apply ConfigHub-side defaults and customizations.
cub function set --space prod --unit mydep set-pod-container-security-context-defaults

# Later, after the upstream file changes, merge the new version in.
cub unit update --space prod mydep deployment-v2.yaml --merge-external-source deployment.yaml

The first invocation records deployment.yaml as the external source so subsequent updates with the same --merge-external-source are treated as continued merges from the same logical source rather than full replacements. Mutations made on the ConfigHub side (the security-context function above, link resolutions, etc.) are preserved through the merge.

By default the merge uses the latest revision whose source is MergeExternal as its base — i.e., the last externally-merged version of the file. Pass --merge-base to override that selection when you need to rebase the merge against a different revision of the unit (for example, to re-apply changes from an older external snapshot, or to reset the cursor after rewriting external history). It accepts the same revision syntax as --restore and --merge-base for --merge-source:

cub unit update --space prod mydep deployment-v2.yaml \
    --merge-external-source deployment.yaml \
    --merge-base Tag:initial-external

--merge-end is not used with --merge-external-source; the new external data passed on the command line plays that role.

Going further