cub variant upload
cub variant upload
Upload rendered Kubernetes resources into a Space as Units
Synopsis
Upload already-rendered Kubernetes manifests into a ConfigHub Space.
The input is a stream of rendered resources — from the installer, "kustomize build", or "helm template" — supplied as files, directories (walked for .yaml/.yml), "-" for stdin, or an "oci://" reference to a manifest bundle. This command does not render anything; it ingests what you give it.
An oci:// input is pulled and its YAML extracted before ingestion. The bundle is a standard OCI image artifact (a tar or tar+gzip layer of YAML, as "cub release publish" and Flux produce, or individual file layers as "oras push" produces); registry credentials are reused from your Docker config.
Every input is recorded on the Space as a "confighub.com/external-source" annotation (a JSON array), together with the --granularity and --namespace the upload ran with, so the plan can be reproduced from the Space alone. For an oci:// input the resolved digest is recorded too, making the exact bundle installed auditable.
An upload into a Space recorded with a different --granularity or --namespace is refused. Granularity decides the Unit slugs, so changing it would replace that Space's Units rather than update them; namespace decides the synthesized Namespace resource and what AppConfig placeholders carry, so changing it would rewrite those resources. Both have to be repeated on a re-upload — --granularity has a default, so omitting it is itself a change. Re-run with the recorded values, or use --space to upload elsewhere.
Resources become Units in one of three granularities (--granularity): minimal one Unit for everything, with CRDs split into their own Unit and each AppConfig file split into its own Unit set (the default). per-resource one Unit per resource. per-file one Unit per source file, named from the file's stem — so the input's file layout defines the Unit set (matches how "cub helm" groups a chart's template files). Useful with an oci:// bundle of named files.
In minimal mode the resources in the combined Unit are ordered by install priority (Namespaces, RBAC, config, then workloads) and by their references to one another. A Namespace resource is synthesized if --namespace is given and none is present. AppConfig ConfigMaps (carrying installer.confighub.com annotations) are expanded into an AppConfig data Unit, a render-configmap Invocation, a placeholder Unit, and an Upsert link. Rendered Secrets are never uploaded — apply them out-of-band.
Links between Units are inferred from references, label selectors, and custom-resource → CRD relationships. Because ConfigHub does not break dependency cycles, any cycle found in the ordering or the links is broken here — the weakest edge is dropped (a selector before a reference; a cross-scope reference before a same-namespace one) and the broken edge is reported.
The Space is created if missing and stamped with the well-known labels from --component, --variant, --stage, --environment, --region, --layer, and --owner. --component and --variant are required. The Space slug comes from --space-pattern (a Go template over .Labels), or from --space to set it explicitly.
Each created Unit records the input it came from — the oci:// ref, file, or directory as named on the command line — as its external source, so its change description reads "from oci://ghcr.io/org/bundle". Use --change-desc to prefix your own description.
Re-uploading: Running this command again against a Space it already populated is a re-upload, not a second create. Each Unit is merged rather than replaced: the new content is 3-way merged against the last upload, so changes made in ConfigHub after the first upload — a set- function, a hand edit, a needs/provides binding — survive, while everything the source actually changed lands. Resources new to the input become new Units. A re-upload whose input has not changed does nothing.
The updates are recorded in a ChangeSet, so an entire re-upload can be rolled back with
the "cub unit update --restore Before:ChangeSet:
Units in the Space that the input no longer produces are left alone and reported. Pass --prune to empty them instead: that merges empty content, withdrawing only the resources this source contributed and leaving post-upload additions in place, so their deployed resources are removed by the next apply. Units guarded by a DestroyGate refuse. Nothing is ever deleted — the Unit record, its target binding, and its metadata survive.
Use --dry-run to see what would be created, updated, or emptied, including the per-field merge as the server would resolve it, without changing anything.
Examples:
# Minimal upload of a kustomize build into a derived Space slug "web-base".
kustomize build overlays/base | cub variant upload --component web --variant base -
# One Unit per resource, into an explicit Space, bound to a target.
cub variant upload --component web --variant prod --space web-prod \
--granularity per-resource --target web-prod/cluster ./rendered/
# Helm output, ensuring a namespace and a regional label.
helm template myapp ./chart | cub variant upload --component myapp --variant prod \
--environment Prod --region us-east1 --namespace myapp -
# Seed a base from a published OCI manifest bundle, one Unit per bundled file.
cub variant upload --component cubbychat --variant base --granularity per-file \
oci://ghcr.io/confighub/configs/cubbychat
# Re-upload: same command, newer bundle. Changed Units are merged, preserving
# edits made in ConfigHub since the first upload.
cub variant upload --component cubbychat --variant base --granularity per-file \
oci://ghcr.io/confighub/configs/cubbychat:v2
# Preview that re-upload first, including the per-field merge.
cub variant upload --dry-run --component cubbychat --variant base \
--granularity per-file oci://ghcr.io/confighub/configs/cubbychat:v2
# Re-upload and withdraw the resources the new render no longer contains.
cub variant upload --prune --component web --variant base ./rendered/
cub variant upload [flags] <file|dir|oci://ref|-> [<file|dir|oci://ref> ...]
Options
--allow-exists tolerate Spaces, Units, Invocations, and Links that already exist (retry a partial upload)
--annotation strings annotation key=value to set on every created Unit (repeatable)
--change-desc string change description recorded on each created Unit
--component string value for the well-known "Component" Space label (required)
--dry-run report what the upload would create, update, or empty, and exit without changing anything
--environment string value for the well-known "Environment" Space label (e.g. Prod)
--granularity string how resources map to Units: minimal, per-resource, or per-file (default "minimal")
-h, --help help for upload
--label strings label key=value to set on every created Unit (repeatable)
--layer string value for the well-known "Layer" Space label (e.g. App)
--namespace string ensure a Namespace resource with this name exists (unless "default")
--owner string value for the well-known "Owner" Space label (e.g. Engineering)
--prune on a re-upload, empty Units in the Space that this input no longer produces
--region string value for the well-known "Region" Space label (e.g. us-east1)
--space string explicit Space slug; overrides --space-pattern
--space-pattern string Go template (prefix 'template:') for the Space slug, evaluated over .Labels (default "template:{{.Labels.Component}}-{{.Labels.Variant}}")
--stage string value for the well-known "Stage" Space label (e.g. Canary)
--target string target for the created Units, in <target-slug> or <space-slug>/<target-slug> form
--variant string value for the well-known "Variant" Space label (required)
Options inherited from parent commands
--context string The context to use for this command
--debug Debug output
SEE ALSO
- cub variant - Variant commands