Skip to content

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, .json, .env, .properties, .toml, and .ini), "-" 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 by the server, which records the digest it read on the Space. The bundle is a standard OCI image artifact (a tar or tar+gzip layer of configuration files, as "cub release publish" and Flux produce, or individual file layers as "oras push" produces). The pull is anonymous, so the bundle has to be public. An oci:// input pulled by the server must be the only input. With --client-pull, cub pulls it instead, for a registry this machine can reach and the server cannot, such as one on a private network; it can then be combined with other inputs.

The server does the work: it splits the bundle into resources and makes the Space's Units, Links, and Invocations match it. Every resource becomes its own Unit, named after the resource rather than the file it came from — a workload keeps its bare name ("backend"), and everything else takes its kind as a suffix ("backend-service").

Uploading is create-or-update, always. The first upload and every later one go through the same path, so there is no separate "re-upload" mode and nothing to remember between runs:

create the resource is new, so a Unit is created for it. update the source changed since it last wrote that Unit, so the new content is 3-way merged into it. Changes made in ConfigHub since — a set- function, a hand edit, a needs/provides binding — survive, and anything the merge had to withhold is reported as a conflict. unchanged the source has not changed, so nothing is written. empty the resource is gone from the bundle, so the Unit's data is emptied. revive an emptied resource is back, so the whole resource is applied to the same Unit. adopt a Unit that already held exactly this resource is taken over, keeping its UnitID, its slug, and its history.

Nothing is ever deleted. A resource the bundle no longer contains empties its Unit instead, so the Unit keeps its identity, its links, and its history, and the next Release withdraws the object from the cluster. Emptying is what "--yes" confirms: without it, an upload whose plan empties Units asks first.

Ownership is a label. Every Unit, Link, and Invocation an upload writes is labeled with its source name (--source-name, defaulting to --component), and a Unit belonging to another source, or to no source, is never written or emptied — so one Space can hold several sources and hand-written Units side by side.

Rendered Secrets are never uploaded — apply them out-of-band. 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. A file holding application configuration rather than Kubernetes resources — a .properties, .env, .toml, or .ini file, or YAML or JSON with no apiVersion and kind — becomes one untargeted AppConfig Unit, named by its configHub.configName or else by its path (config/app.properties becomes config-app). A YAML file mixing the two is refused.

Links between Units are inferred from references, label selectors, and custom-resource → CRD relationships. Because ConfigHub does not break dependency cycles, any cycle in the inferred links is broken — the weakest edge is dropped (a selector before a reference; a cross-scope reference before a same-namespace one) — and reported.

The Space is created if missing, in the Component --component names, and stamped with the well-known labels from --variant, --stage, --environment, --region, and --layer, and any other labels given with --space-label. --component is required; --variant defaults to "base", since an upload normally seeds the base that variants are created from. The Space slug comes from --space-pattern (a Go template over .Labels), or from --space to set it explicitly. --unit-label and --unit-annotation set labels and annotations on every written Unit.

--owner sets the Owner label of the Component, not of the Space, once the upload has succeeded. A Component's owner is its own Owner label, or else the Owner label all of its Spaces share. When the Component already has a different owner, the upload is refused before anything is written, and the error gives the "cub component update --patch --label Owner=" command that changes it. When it has the same owner, the Component's label is written only if it is missing. An owner the server would refuse, for its value or for your permissions, is also refused before anything is written. With --dry-run, --owner is checked and the label it would set is printed.

--namespace is the release namespace, as in "helm template -n": where namespaced resources that name no namespace, and cluster-scoped resources, belong. It has no default. Charts write the namespace into places set-namespace cannot reach — ConfigMap data, flags, annotations, webhook references — so render with the real namespace rather than substituting one afterwards. --create-namespace synthesizes the Namespace resource when the bundle lacks it; it is off by default, because a bare Namespace has none of the pod-security labels, NetworkPolicy, ResourceQuota, or LimitRange that make a namespace usable, and the platform normally provisions those together.

The writes are recorded in a ChangeSet, so an entire upload can be rolled back with the "cub unit update --restore Before:ChangeSet:" command printed at the end.

The writes take the options a unit update does, and every Unit write of the upload carries them: creates, merges, revivals, adoptions, and empties. --clearance names the guarded reasons the writes are cleared for, and a guarded path it does not cover is withheld and reported as a conflict; --subgroup is recorded on each write's mutations. --tag marks the revision each Unit the upload writes is left at, created Units included, and leaves Units it does not change untagged. An upload with a Unit to write that the tag already marks is refused before anything is written.

Use --dry-run to see what would be created, updated, emptied, revived, or adopted without changing anything.

Examples:

  # Upload a kustomize build into a derived Space slug "web-base".
  kustomize build overlays/base | cub variant upload --component web --variant base -

  # Into an explicit Space, bound to a target.
  cub variant upload --component web --variant prod --space web-prod \
    --target web-prod/cluster ./rendered/

  # Helm output, rendered with the real namespace and uploaded into it.
  helm template myapp ./chart -n myapp | cub variant upload \
    --component myapp --variant prod --environment Prod --namespace myapp -

  # Seed a base from a published OCI manifest bundle.
  cub variant upload --component cubbychat --variant base \
    oci://ghcr.io/confighub/configs/cubbychat

  # Upload a newer bundle. Changed Units are merged, preserving edits made in
  # ConfigHub since; resources the bundle dropped have their Units emptied.
  cub variant upload --component cubbychat --variant base --yes \
    oci://ghcr.io/confighub/configs/cubbychat:v2

  # Pull from a registry only this machine can reach.
  cub variant upload --client-pull --component web --variant base \
    oci://registry.internal:5000/configs/web:1.2.0

  # Upload a release, tagging what it writes and taking guarded paths owned by the
  # platform team.
  cub variant upload --component cubbychat --variant base --yes \
    --tag cubbychat-base/v2 --clearance owner=platform \
    oci://ghcr.io/confighub/configs/cubbychat:v2

  # Preview that upload first.
  cub variant upload --dry-run --component cubbychat --variant base \
    oci://ghcr.io/confighub/configs/cubbychat:v2
cub variant upload [flags] <file|dir|oci://ref|-> [<file|dir|oci://ref> ...]

Options

      --backing-unit-space string   space, by slug, for the backing Units of the bundle's Space and Component documents, which are in no space of their own; required with either
      --change-desc string          change description recorded on each written Unit
      --clearance stringArray       class of guarded reason this change is cleared for, as KEY, KEY=VALUE[;VALUE...], KEY!=VALUE[;VALUE...], or !KEY to refuse any path carrying KEY (repeatable). A guarded path this does not cover is not written, and the withheld change is reported as a conflict
      --client-pull                 pull oci:// inputs on this machine rather than having the server pull them, for a registry the server cannot reach
      --component string            slug of the Component the Space is a Variant of, created if it does not exist (required)
      --create-namespace            synthesize the release Namespace resource if the bundle does not contain it
      --dry-run                     report what the upload would create, update, empty, revive, or adopt, and exit without changing anything
      --environment string          value for the well-known "Environment" Space label (e.g. Prod)
  -h, --help                        help for upload
      --layer string                value for the well-known "Layer" Space label (e.g. App)
      --namespace string            the release namespace: where namespaced resources that name no namespace, and cluster-scoped resources, belong
      --owner string                set the "Owner" label on the Component, not on the Space; refused when the Component already has a different owner (e.g. Engineering)
      --region string               value for the well-known "Region" Space label (e.g. us-east1)
      --source-name string          ownership name for the Units, Links, and Invocations this upload writes; defaults to --component
      --space string                explicit Space slug; overrides --space-pattern
      --space-label strings         label key=value to set on the Space (repeatable); it may not name a label another flag sets, such as Component
      --space-pattern string        Go template (prefix 'template:') for the Space slug, evaluated over .Component and .Labels (default "template:{{.Component.Slug}}-{{.Labels.Variant}}")
      --stage string                value for the well-known "Stage" Space label (e.g. Canary)
      --subgroup string             category recorded on the mutations of each Unit write of the upload: alphanumeric, at most 64 characters
      --tag string                  tag (slug, space/slug, or UUID) to put on the revision each Unit the upload writes is left at
      --target string               target for the created Units, in <target-slug> or <space-slug>/<target-slug> form
      --unit-annotation strings     annotation key=value to set on every written Unit (repeatable)
      --unit-label strings          label key=value to set on every written Unit (repeatable)
      --variant string              value for the well-known "Variant" Space label (default "base")
      --yes                         do not ask for confirmation when the upload would empty Units

Options inherited from parent commands

      --context string   The context to use for this command
      --debug            Debug output

SEE ALSO