# shdctl release commands

> Pull, generate, deploy, and uninstall shdctl release commands

Use the `release` command group to manage the deployment lifecycle. Pull a release archive from the Unity registry, render Helm chart values from your manifest, deploy to your cluster or hand off to ArgoCD, and uninstall.

## Pull a release

Download a release from the registry. If you use a manifest file, the tool reads the version from the manifest automatically. By default, the release is extracted to `./extracted-release`.

```sh
shdctl release pull --clean-output
```

**Parameters:**

* `--extract-dir`: Directory to extract the archive to (default: `./extracted-release`)
* `--skip-extract`: Skip extracting the downloaded archive (default: extracts to `./extracted-release`)
* `--clean-output`: Empty the extraction directory before extracting. Pass it whenever that directory already holds a release: without it, extraction stops at the first file that already exists (`file already exists`), after the archive has been saved
* `--version`: Release version to pull (optional, defaults to value from manifest)
* `--registry`: Registry URL (default: `uccmpprivatecloud.azurecr.io`). The manifest's `artifactSync.sourceRepository` is not read here
* `--output`: Output file path for the archive (default: `unity-private-cloud-shd-<version>.tar.gz` in the working directory, kept after extraction)

It authenticates with the credentials `shdctl configure` stored for that registry — refer to [Configure registry credentials](../install#configure-registry-credentials).

It pulls `<registry>/releases/shd-configuration:<version>`, and falls back to `releases/onprem-configuration`, where releases up to 0.16.0 live, only when the version is not found there. A mirror you name with `--registry` has to use the same repository paths.

Example with manifest:

```sh
shdctl release pull --clean-output
```

Example without manifest, specify version:

```sh
shdctl release pull --version 2.0.0 --clean-output
```

Example with custom extraction directory:

```sh
shdctl release pull --extract-dir ./my-release --clean-output
```

Example skipping extraction:

```sh
shdctl release pull --skip-extract
```

## Generate and deploy charts

Choose your deployment method: **Helm** or **ArgoCD**.

Whichever format you generate, values from the manifest's `configuration.overrides` are merged into each chart's generated `values.yaml` last, after every layer the release ships. Chart names there must be `helm_charts` keys of the release's `common/versions.yaml` or its `platform/shd/versions.yaml` overlay, which the release merges; an unknown name fails the command before it writes anything, and the error lists the valid names. Refer to [Per-chart Helm value overrides](../manifest#per-chart-helm-value-overrides) for the merge semantics and the caveats that come with them.

### Generate Helm charts

```sh
shdctl release generate --clean-output
```

**Parameters:**

* `--clean-output`: Empty the output directory before generating — everything in it, hidden files included, so point `--output` at a directory that holds nothing else (cannot be combined with `--name`)
* `--format`: `argocd` adds the app-of-apps chart structure; omit it (or pass `helm`) for standard Helm charts. Any other value is accepted and treated as `helm`, so check that a run meant for ArgoCD wrote `app-of-apps-application.yaml`
* `--extracted-release`: Path to extracted release (defaults to `./extracted-release`)
* `--output`: Output directory (defaults to `generated-charts`)
* `--name`: Regenerate only the chart with exactly this name: its directory and the release's chart index are rewritten, and no other chart is. The output keeps describing the whole release, so `release deploy` and `release uninstall` still see every chart
* `--skip-version-check`: Skip shdctl version compatibility check against the release package

Example:

```sh
shdctl release generate --extracted-release ./extracted-release --output ./my-charts --clean-output
```

### Deploy with Helm

Preview deployment (recommended):

```sh
shdctl release deploy --dry-run
```

The dry run prints the `helm upgrade --install` command for every chart, in the order the deploy runs them, and contacts nothing. Charts are grouped by wave, lowest first, and within a wave the largest generated chart comes first. Each group sits under a header naming the command that deploys that wave alone:

```text
# Wave -3 (2 chart(s))
# Deploy this wave with: shdctl release deploy --wave -3
```

If you're deploying Asset Manager for the first time, deploy the release in waves. Start with the lowest wave by passing the `--wave` flag, for example `-3`, then validate the deployment before deploying the next wave. This ensures the release deploys in the correct order and all dependencies are satisfied.

Execute deployment:

```sh
shdctl release deploy
```

Like `uninstall`, `deploy` takes its chart list from the index `release generate` writes (`charts-metadata.yaml`; a tree an earlier version generated, with `vpctl-metadata.yaml`, still deploys, with a warning). It refuses a directory without one, and a directory missing a chart the index lists — unless `--name` or `--wave` narrows the run to charts that are present.

**Common options:**

* `--dry-run`: Show commands without executing
* `--format`: Deployment format (`helm` or `argocd`; defaults to `helm`)
* `--charts-dir`: Directory containing charts (defaults to `generated-charts`)
* `--wave`: Deploy only a specific wave (Helm format only). Waves deploy lowest first; in this release they run from `-3` to `2`, and the dry run lists them
* `--name`: Deploy only the chart with exactly this name
* `--wait`: Wait for each Helm release to complete before moving on (default: `true`; disable with `--wait=false`)
* `--timeout`: Timeout per Helm release when `--wait=true` (default: `10m`; ignored when `--wait=false`)
* `--retries`: Number of additional attempts after a failed Helm command (default: `0`). Useful for the CRD-not-yet-visible race that sometimes resolves on a second attempt.
* `--retry-delay`: Delay between retry attempts (default: `5s`; only meaningful when `--retries > 0`)
* `--dependency-update`: Update chart dependencies before deployment (default: `true`). In parallel mode (`--concurrency` greater than `1`), the update runs as a separate `helm dependency update --skip-refresh` step for each chart instead of inline through `helm upgrade --dependency-update`: refer to [Parallel deployment](#parallel-deployment).
* `--helm-flags`: Additional Helm flags appended to every `helm upgrade --install` command. The command line runs through `sh -c`, so shell quoting applies — `--helm-flags "--set-string key='a b'"` passes one value containing a space — and so do shell metacharacters. With Helm 4, `--helm-flags "--force-conflicts"` lets an upgrade take over fields another field manager last wrote — an earlier `kubectl apply`, say — instead of failing on the conflict
* `--concurrency`: Number of charts to deploy in parallel within a wave (default: `1`, sequential; Helm format only). When you omit the flag, the manifest value `deployment.helm.concurrency` applies. Refer to [Parallel deployment](#parallel-deployment).

Example deploying a specific wave:

```sh
shdctl release deploy --wave -3
```

#### Parallel deployment

Set `--concurrency N` (where `N` is greater than `1`) to deploy the charts within the same wave in parallel. You can also set `deployment.helm.concurrency` in the manifest to make it the default for your environment; the CLI flag takes precedence when both are set.

* Charts within the same wave deploy concurrently through a bounded worker pool of size `N`. Waves remain sequential: the next wave starts only after every chart in the current wave succeeds.
* Within a wave, charts start largest first, by the size of their generated directory: the largest take the longest, and a wave lasts as long as its slowest chart.
* The per-chart `helm dependency update` calls run with `--skip-refresh`, and — when any Helm repositories are registered locally — `helm repo update` runs once at the start of the deployment instead. This avoids races between workers writing to the shared `~/.cache/helm/repository` index. A `--dry-run` prints the sequential plan, which shows neither step — and its `helm upgrade` lines lose `--dependency-update` too, so read it as a preview rather than a script to run.
* Each chart's Helm output is buffered and printed as a single block when that chart finishes, for example `==> [chart-name] done in 12.3s`, so the line ordering differs from sequential mode and there's no live progress for an in-flight chart.
* A summary line at the end of every wave and at the end of the deployment reports the total, succeeded, and failed counts with the elapsed time, as in a sequential deployment: `Wave 0 complete: 36 total, 36 succeeded, 0 failed (elapsed 4m12.318s)` and `Deploy complete: 59 total across 6 wave(s), 59 succeeded, 0 failed (elapsed 17m3.524s)`. On failure, the error lists the failing chart names.
* Parallel deployment applies to the Helm format only: with `--format argocd`, any `--concurrency` value greater than `1` is rejected, because the ArgoCD path applies a single bootstrap `Application` and does no per-chart Helm work.

Example deploying with four workers:

```sh
shdctl release deploy --format helm --concurrency 4
```

### Manifest defaults for ArgoCD

You can define ArgoCD parameters in the manifest under `deployment.argocd` so you don't need to pass them every time. CLI flags always take precedence over manifest values when both are provided.

```yaml
# manifest.yaml
deployment:
  argocd:
    repoURL: "https://github.com/org/repo.git"
    pathPrefix: "shd/cluster1"
    destinationServer: "https://kubernetes.default.svc"
    targetRevision: "main"
```

When the tool reads a value from the manifest instead of a CLI flag, it prints a log message, for example, `Using argocd-repo-url from manifest: ...`.

### Generate ArgoCD application

If you set `deployment.argocd` in the manifest, you only need to pass flags you want to override:

```sh
shdctl release generate --format argocd --clean-output
```

Or override specific values for each run:

```sh
shdctl release generate --format argocd --argocd-path-prefix <path-prefix> --argocd-target-revision <branch-or-tag> --clean-output
```

You can still pass all flags explicitly; they take precedence over the manifest:

```sh
shdctl release generate --format argocd --argocd-repo-url <your-git-repo-url> --argocd-destination "https://kubernetes.default.svc" --argocd-target-revision "main" --argocd-path-prefix <path-prefix> --clean-output
```

**Parameters:**

* `--format argocd`: Generate ArgoCD format
* `--argocd-repo-url`: Git repository URL where charts are stored (required; can be set in manifest `deployment.argocd.repoURL`)
* `--argocd-destination`: Kubernetes server URL (required; can be set in manifest `deployment.argocd.destinationServer`). `shdctl manifest schema` lists a default for the field, but it is not applied: generation fails when neither is set
* `--argocd-target-revision`: Git branch or tag ArgoCD reads the charts from (default: `main`; can be set in manifest `deployment.argocd.targetRevision`)
* `--argocd-path-prefix`: The directory, relative to the repository root, that holds the generated charts (can be set in manifest `deployment.argocd.pathPrefix`). Keep it non-empty: an empty prefix renders every child application's path as `/<chart>`, which ArgoCD rejects as absolute
* `--clean-output`: Empty the output directory before generating (cannot be combined with `--name`)

This command creates an app-of-apps chart structure in the output directory. Before deploying, commit the output directory's contents to the repository at `--argocd-path-prefix`, on the `--argocd-target-revision` branch: every application reads its chart and its `values.yaml` from that path, so charts committed anywhere else leave the applications pointing at nothing.

A full run writes one ArgoCD `Application` per chart your manifest enables and removes any other application file from the app-of-apps chart, so an application belonging to a chart that has left the release, or that your manifest disables, stops being part of the deployment's desired state — expect ArgoCD to delete it and its workloads on the next sync. A `--name <chart>` run regenerates that chart and rewrites the application files the same way, so the application set stays whole.

The top-level `asset-solutions` application carries no cascading-delete finalizer: deleting it leaves the child applications and their workloads running, and re-applying `app-of-apps-application.yaml` adopts them again. To remove a deployment, delete the top-level application first, then the child applications, whose own finalizers remove their workloads. The order matters: while the top-level application exists it syncs automatically with self-heal, so it recreates a deleted child from Git — after that child's workloads have already been removed.

```sh
kubectl -n <namespace> delete application asset-solutions
kubectl -n <namespace> delete applications --all   # the children, named after their charts; --all only if the namespace holds no other applications
```

### Deploy ArgoCD application

Preview deployment (recommended):

```sh
shdctl release deploy --format argocd --dry-run
```

Execute deployment:

```sh
shdctl release deploy --format argocd
```

**Common options:**

* `--format argocd`: Use ArgoCD deployment
* `--dry-run`: Preview kubectl command without executing
* `--charts-dir`: Directory containing charts (defaults to `generated-charts`)

This runs one `kubectl apply` of `app-of-apps-application.yaml` from `--charts-dir` into `configuration.kubernetes.namespace`, so the kubeconfig needs `get`, `create` and `patch` on `applications.argoproj.io` there, and nothing cluster-wide. `--wave` and a `--concurrency` above 1 are Helm-only and refused with this format.

Every application, the top-level one included, is created in that namespace and in ArgoCD's `default` project, so ArgoCD must reconcile Applications there: install it in the deployment namespace, or configure it to watch that namespace (ArgoCD's "Applications in any namespace", which needs the namespace in both its `application.namespaces` setting and the `default` project's `sourceNamespaces`). An ArgoCD watching only its own namespace leaves them unsynced, with nothing reporting an error.

## Uninstall a release

To remove a deployed release, use the uninstall command. The tool uninstalls charts in reverse wave order with the highest wave first to ensure dependencies are removed correctly.

It runs `helm uninstall` for what `release deploy --format helm` installed, and removes nothing else — so what Helm keeps stays: the CRDs charts ship in their `crds/` directory, resources annotated `helm.sh/resource-policy: keep`, and the volumes StatefulSets claimed. An ArgoCD deployment has no Helm releases to remove — remove it through its applications instead, the top-level one first (refer to [Generate ArgoCD application](#generate-argocd-application)).

Which charts are uninstalled comes from the release the `--charts-dir` directory was generated from, not from the directories present in it, so a partially generated directory is refused rather than treated as the whole release. Narrow the scope with `--name` or `--wave`.

Preview uninstall commands (recommended):

```sh
shdctl release uninstall --dry-run
```

Execute uninstall:

```sh
shdctl release uninstall
```

**Common options:**

* `--dry-run`: Show commands without executing
* `--charts-dir`: Directory containing charts (defaults to `generated-charts`)
* `--wave`: Uninstall only charts in the specified wave (in this release, waves run from `-3` to `2`)
* `--name`: Uninstall only the chart with exactly this name

A chart that fails to uninstall is logged as a warning and the command carries on with the rest, so it can exit 0 with releases still installed. Read its output, or check what is left with `helm list -n <namespace>`.

Example uninstalling a specific wave:

```sh
shdctl release uninstall --wave 0 --dry-run
```

Example uninstalling a specific chart:

```sh
shdctl release uninstall --name my-chart --dry-run
```

For every chart in the release, the uninstall command runs `helm uninstall`.

> **Note:**
>
> `uninstall` covers the charts the current manifest **enables**, which is the same set `deploy` installs. A chart a manifest change has since switched off is not in that set, so it is neither redeployed nor removed — on the Helm path its release is simply left running. Switching `configuration.objectStore.provider` to `s3` is the case to watch: run `helm -n <namespace> uninstall garage` yourself to remove the in-cluster object store. With ArgoCD the application is pruned on the next sync. Either way, its volumes (`data-garage-<n>`, `meta-garage-<n>`) stay until you delete them, once you no longer need the data they hold.
