From a088f1a610bbce7701048173cd3a3e3e96841a34 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Thu, 30 Jul 2026 18:09:47 +0300 Subject: [PATCH 1/2] fix mirror docs Signed-off-by: Pavel Okhlopkov --- docs/mirror-pull-dry-run.md | 30 ++-- internal/mirror/PROXY-REGISTRY.md | 14 +- internal/mirror/README.MD | 229 +++++++++++++++++++++++++----- 3 files changed, 220 insertions(+), 53 deletions(-) diff --git a/docs/mirror-pull-dry-run.md b/docs/mirror-pull-dry-run.md index e4e46ec99..38ceb118b 100644 --- a/docs/mirror-pull-dry-run.md +++ b/docs/mirror-pull-dry-run.md @@ -2,10 +2,9 @@ ## What dry-run does -`--dry-run` runs the full planning pipeline of `d8 mirror pull` — version resolution, -release-channel discovery, module filtering, installer tag lookup — then **prints the -complete list of images that would be downloaded** and exits without writing any bundle -output to the bundle directory. +`--dry-run` runs the full planning pipeline of `d8 mirror pull` — version resolution, release-channel discovery, module and package filtering, installer tag lookup — then **prints the images that would be downloaded** and exits without writing any bundle output to the bundle directory. + +> The **platform** plan is complete (the full component digest list is streamed from the installer image). For **modules** and **packages**, the plan lists release-channel refs and per-version images, but does **not** resolve `extra_images.json` extra images or `.att` VEX attestations — those require a real pull, so the plan and its counts undercount what a real pull downloads for each module/package. The key distinction from a no-op: @@ -14,16 +13,16 @@ The key distinction from a no-op: | Validate registry access | yes | yes | | Resolve versions / channels | yes | yes | | Read installer `images_digests.json` (platform) | via OCI layout in tmpDir | **streamed from the registry** (no layout) | -| Stage installer/security/module OCI layout dirs in tmpDir | yes (with blobs) | **scaffolding only** (no image blobs) | +| Stage installer/security/module/package OCI layout dirs in tmpDir | yes (with blobs) | **scaffolding only** (no image blobs) | | Pull release-channel metadata | yes | yes | | Download platform/module/security blobs | yes | **no** | -| Write `platform.tar`, `security.tar`, module tarballs | yes | **no** | +| Write `platform.tar`, `security.tar`, module/package tarballs, `package-versions.tar` | yes | **no** | | Write `deckhousereleases.yaml` | yes | **no** | | Compute GOST digests | yes | **no** | In dry-run the **platform** service streams the built-in image digest list (`images_tags.json` / `images_digests.json`) straight from the remote installer image, -layer by layer, without writing an OCI layout. The `installer`, `security` and `modules` +layer by layer, without writing an OCI layout. The `installer`, `security`, `modules` and `packages` services still create their OCI layout directories under `--tmp-dir` (or `/.tmp`), but in dry-run they pull no image blobs (only layout scaffolding), so `--tmp-dir` ends up non-empty while the **bundle directory** (first positional @@ -230,10 +229,14 @@ Puller.Execute() │ ├─ validateSecurityAccess() │ ├─ downloadList.FillSecurityImages() │ └─ [dry-run guard] print plan → return nil - └─ modules.Service.PullModules() [DryRun=true] - ├─ discover modules (ListRepositories) - ├─ per module: extractVersionsFromReleaseChannels() - └─ [dry-run guard] print plan → return nil + ├─ modules.Service.PullModules() [DryRun=true] + │ ├─ discover modules (ListTags) + │ ├─ per module: extractVersionsFromReleaseChannels() + │ └─ [dry-run guard] print plan → return nil + ├─ packages.Service.PullPackages() [DryRun=true] + │ └─ [dry-run guard] print per-package plan → return nil + └─ packages.Service.PullPackageVersions() [DryRun=true, ALWAYS runs] + └─ [dry-run guard] print package-versions plan → return nil After Pull() returns: if DryRun → print summary "No images were downloaded (dry-run)." → return nil @@ -255,10 +258,11 @@ what a real pull actually downloads. |----------|---------------------|-------------| | `/platform/...` | **no** | Platform digests are streamed; no platform OCI layout is written | | `/installer/` | **yes** | Installer OCI layout dir (scaffolding only, no blobs) | -| `/security*/`, `/modules*/` | **yes** | Security / module OCI layout dirs (scaffolding only, no blobs) | +| `/security*/`, `/modules*/`, `/packages*/` | **yes** | Security / module / package OCI layout dirs (scaffolding only, no blobs) | | `/platform.tar` | no | Not created | | `/security.tar` | no | Not created | -| `/modules-*.tar` | no | Not created | +| `/module-*.tar` | no | Not created | +| `/package-*.tar`, `package-versions.tar` | no | Not created | | `/deckhousereleases.yaml` | no | Not created | `tmpDir` is cleaned up by a subsequent normal pull or can be removed manually. diff --git a/internal/mirror/PROXY-REGISTRY.md b/internal/mirror/PROXY-REGISTRY.md index 31da955ca..161436847 100644 --- a/internal/mirror/PROXY-REGISTRY.md +++ b/internal/mirror/PROXY-REGISTRY.md @@ -104,6 +104,8 @@ The "(probe)" steps are the only network behaviour that changes — they're the The walk never invents tags: only versions that the registry confirmed are written to the bundle, and the latest-patch-per-minor / inclusive-anchor rules described in [README.MD: Platform Version Filtering](./README.MD#platform-version-filtering) and [README.MD: Module Filtering](./README.MD#module-filtering) are applied to the result the same way they would be after a normal `ListTags`. +For **modules** whose version constraint is an OR of several ranges (e.g. `>=1 <2 || >=3 <4`), the walk above runs once per sub-range independently. Platform constraints are always a single range, so this applies to modules only. + --- ## Worked example @@ -143,10 +145,10 @@ The platform probe issues the following HEAD requests in order. The right column | 10 | `release-channel:v1.67.0` | 404 | new-minor probe failed; jump to next major (2.0.0) | | 11 | (skipped) `v2.0.0` is outside `<=1.68.0` | n/a | constraint excludes 2.0.0; **probe terminates** | -After the probe finishes, the downstream pipeline keeps only the highest patch per `(major, minor)` (so `v1.64.0` and `v1.64.1` are dropped because `v1.64.2` is newer in the same minor). The final platform set written to `platform.tar` is therefore: +After the probe finishes, the downstream pipeline keeps only the highest patch per `(major, minor)`, so `v1.64.1` is dropped in favour of `v1.64.2`. However, the lower bound `>=1.64.0` is an **inclusive anchor**: because the probe confirmed `v1.64.0` exists (step 1), it is preserved even though `v1.64.2` is a newer patch in the same minor — see the inclusive-anchor rule in [README.MD: Platform Version Filtering](./README.MD#platform-version-filtering). The final platform set written to `platform.tar` is therefore: ``` -v1.64.2, v1.65.0, v1.66.1 +v1.64.0, v1.64.2, v1.65.0, v1.66.1 ``` …plus any version pinned by an existing release channel snapshot (alpha/beta/etc.) that also satisfies the constraint. @@ -157,10 +159,10 @@ Note that `v1.67.x` and `v1.68.x` would have been pulled too if the proxy regist ## What "exists" and "not found" mean on the wire -The probe relies on the standard registry-v2 manifest endpoint: +The probe uses the standard registry-v2 manifest endpoint. It issues a `HEAD` first and, if the registry answers the `HEAD` with anything other than a clean success or a `404`, it retries the same tag with a `GET` before deciding: ``` -HEAD /v2//manifests/ +HEAD /v2//manifests/ # retried as GET /v2//manifests/ on a non-404 error ``` The mapping from HTTP response to probe action is: @@ -172,7 +174,7 @@ The mapping from HTTP response to probe action is: | `401 Unauthorized`, `403 Forbidden` | Auth failure | Abort the entire pull with the error | | `5xx`, network error, timeout | Real failure | Abort the entire pull with the error | -In other words: only an unambiguous "the registry does not have this tag" stops the probe — everything else is propagated so a transient network blip never gets silently mistaken for "release series ended". This is the same error policy used by `CheckImageExists` in the rest of the pull pipeline. +In other words: only an unambiguous "the registry does not have this tag" stops the probe — everything else is propagated so a transient network blip never gets silently mistaken for "release series ended". This is the same error policy used by `CheckImageExists` in the rest of the pull pipeline. Because a `HEAD` that fails with a non-404 error is retried as a `GET`, a proxy that refuses `HEAD` requests but serves `GET` still works. If a proxy registry returns `200 OK` for tags it later refuses to serve the manifest of, the per-tag GET in the normal pull step (step 3d / 6d of the flow) will surface a clear error against that exact tag. @@ -205,7 +207,7 @@ The reason this works: every operation other than the three `ListTags` calls is | `--no-platform` is set | `--include-platform` is **not** required | | modules are being pulled (default) or `--only-extra-images` | At least one `--include-module @`. Every entry **must** include `@` — `--include-module foo` alone is rejected because the probe would otherwise start at `v0.0.0` and silently miss every real tag | | `--no-modules` is set | `--include-module` is **not** required | -| `--exclude-module` | Honoured (subtracts from the include list) | +| `--exclude-module` | Cannot be used with the required `--include-module` (the two are mutually exclusive). Accepted only together with `--no-modules`, where it has no effect | | `--deckhouse-tag` | **Conflict**: a single pinned tag is already a direct check; do not combine with `--proxy-registry` | | `--since-version` | **Conflict**: `--since-version` has no upper bound and the probe cannot terminate. Use `--include-platform` with an explicit range instead | | `--dry-run` | Honoured — runs the probe and prints the plan without downloading any blobs | diff --git a/internal/mirror/README.MD b/internal/mirror/README.MD index 995bd4b5f..34e869e4e 100644 --- a/internal/mirror/README.MD +++ b/internal/mirror/README.MD @@ -54,7 +54,8 @@ d8 mirror pull [flags] |------|-------------| | `--since-version` | Minimal Deckhouse release to pull (lower bound, inclusive). Ignored if above current Rock Solid release. Conflicts with `--deckhouse-tag` and `--include-platform` | | `--include-platform` | Select platform releases by semver constraint (e.g. `">=1.64 <=1.68"`). Uses the same constraint dialect as `--include-module`. Conflicts with `--deckhouse-tag` and `--since-version`. See [Platform Version Filtering](#platform-version-filtering) | -| `--deckhouse-tag` | Specific Deckhouse build tag to pull. Conflicts with `--since-version` and `--include-platform`. If the registry contains a release channel image for the specified tag, all release channels in the bundle will point to it | +| `--deckhouse-tag` | Specific Deckhouse build to pull. Accepts a **release channel name** (`alpha`, `beta`, `early-access`, `stable`, `rock-solid`, `lts`), a **semver version** (`v1.69.3`), or **any custom tag** (`pr12345`, dev builds). Conflicts with `--since-version` and `--include-platform`. If the registry contains a release channel image for the value, all release channels in the bundle will point to it. See [Release Channel Selection](#release-channel-selection) | +| `--ignore-suspend` | Mirror even when a release channel the request resolves to is marked suspended upstream. Use with caution. See [Suspended Release Channels](#suspended-release-channels) | #### Module Filtering @@ -64,25 +65,38 @@ d8 mirror pull [flags] | `--exclude-module` | `-e` | Blacklist specific modules. Format: `module-name[@version]`. Use one flag per module. Overridden by `--include-module` | | `--modules-path-suffix` | | Suffix to append to source repo path to locate modules (default: `/modules`) | +#### Package Filtering + +Packages are mirrored with the same `name[@version-constraint]` dialect as modules (see [Module Filtering](#module-filtering)). See [Package Mirroring](#package-mirroring) for the full behavior. + +| Flag | Description | +|------|-------------| +| `--include-package` | Whitelist specific packages. Use one flag per package. Disables `--exclude-package` | +| `--exclude-package` | Blacklist specific packages. Format: `package-name[@version]`. Use one flag per package. Overridden by `--include-package` | + #### Component Selection | Flag | Description | |------|-------------| | `--no-platform` | Do not pull Deckhouse Kubernetes Platform into bundle | -| `--no-security-db` | Do not pull security databases into bundle | -| `--no-modules` | Do not pull Deckhouse modules into bundle | -| `--only-extra-images` | Pull only extra images for modules (security databases, scanners, etc.) without main module images | -| `--no-installer` | Do not pull Deckhouse installer into bundle | -| `--installer-tag` | Specific Deckhouse installer build tag to pull. | +| `--no-security-db` | Do not pull security databases into bundle. No effect on editions that ship no security databases. See [Security Databases](#security-databases) | +| `--no-modules` | Do not pull Deckhouse modules into bundle. Overridden by `--include-module` (a whitelist wins; a warning is printed to stderr) | +| `--no-packages` | Do not pull Deckhouse packages into bundle. Note: `package-versions.tar` is produced regardless. See [Package Mirroring](#package-mirroring) | +| `--only-extra-images` | Pull only the extra images (scanners, etc.) of modules **and packages**, without their main images. Also re-enables the modules/packages phase even when `--no-modules`/`--no-packages` is set | +| `--no-installer` | Do not pull the standalone Deckhouse installer (`installer.tar`). Does not affect the installer images embedded in `platform.tar`, which are governed by `--no-platform` | +| `--installer-tag` | Specific Deckhouse installer build tag to pull (default: `latest`) | +| `--skip-vex-images` | Do not pull VEX vulnerability-attestation (`.att`) images. Applies to platform, modules and packages. See [VEX Images](#vex-vulnerability-attestation-images) | #### Bundle Options | Flag | Short | Description | |------|-------|-------------| -| `--images-bundle-chunk-size` | `-c` | Split resulting bundle into chunks of at most N gigabytes (default: 0 = no splitting) | -| `--gost-digest` | | Calculate GOST R 34.11-2012 STREEBOG digest for downloaded bundle | +| `--images-bundle-chunk-size` | `-c` | Split resulting bundle into chunks of at most N gigabytes (decimal GB = 1000³ bytes; default: 0 = no splitting). Chunk files are named `.tar.NNNN.chunk` | +| `--gost-digest` | | Calculate GOST R 34.11-2012 Streebog-256 digest for each downloaded `.tar` and `.chunk` (writes `.gostsum` files) | | `--force` | | Overwrite existing bundle packages if they conflict with current pull operation | -| `--no-pull-resume` | | Do not continue last unfinished pull operation; start from scratch | +| `--no-pull-resume` | | Do not continue last unfinished pull operation; start from scratch. Otherwise an unfinished pull is resumed automatically for 24 hours (resume state is kept per source registry) | +| `--dry-run` | | Print what would be pulled without downloading any images or writing a bundle. See [Dry Run](../../docs/mirror-pull-dry-run.md) | +| `--verbose-summary` | | List every module and package (with resolved versions and VEX counts) in the end-of-pull summary instead of only totals. Output only; does not change what is pulled | #### Proxy/Cache Registry Discovery @@ -145,6 +159,59 @@ d8 mirror pull /tmp/d8-bundle \ --- +### Release Channel Selection + +`--deckhouse-tag` is described above as a way to pin a specific build, but the value it accepts has three forms — and two of them are how you mirror **by release channel** instead of by version. + +| Value form | Example | What is pulled | +|------------|---------|----------------| +| Release channel name | `--deckhouse-tag stable` | Only the version the named channel currently points at. Valid channels: `alpha`, `beta`, `early-access`, `stable`, `rock-solid`, `lts` | +| Semver version | `--deckhouse-tag v1.69.3` | Only that exact release | +| Any other tag | `--deckhouse-tag pr12345` | That tag as-is (dev/PR builds); it is matched against no channel | + +#### Pull a single release channel + +To mirror only the current `stable` release — the smallest platform bundle for a single channel: + +```bash +d8 mirror pull /tmp/d8-bundle \ + --license $LICENSE_TOKEN \ + --deckhouse-tag stable +``` + +A default pull (no version flags) discovers every release between the `rock-solid` and `alpha` channels — see [Platform Release Discovery](#platform-release-discovery) — and downloads all of them. Selecting a channel by name downloads the images of one version only. Channel metadata is still read over the network for every channel, but only the selected channel's images are pulled. + +#### Behaviour of tag-pinned pulls + +Any `--deckhouse-tag ` (and the exact-tag form `--include-platform "=vX.Y.Z"`) changes two things beyond narrowing the version set: + +- **All release channels in the bundle are re-pointed to the pinned build.** If the source registry serves a release-channel image for the value, every default channel (`alpha`, `beta`, `early-access`, `stable`, `rock-solid`) in the resulting bundle is aliased to it. After push, a cluster on any channel receives the pinned version. This aliasing only re-tags an already-downloaded image — it pulls no extra data. For a channel-name value this is what makes "`stable` only" work end to end; for a custom tag it happens only when such a release-channel image exists. +- **No `deckhousereleases.yaml` is generated.** The per-version `DeckhouseRelease` manifests are produced only for range/discovery pulls. A tag-pinned bundle relies on the channel aliases above instead of the release manifest. + +> The `+channel` suffix of the exact-tag form (e.g. `--include-platform "=v1.65.3+stable"`) restricts channel propagation **for modules**, but has no effect for the platform: a pinned platform tag is always propagated to all default channels, exactly like `--deckhouse-tag`. + +#### Suspended Release Channels + +A release channel can be marked `suspend: true` in its `version.json` on the source registry (for example while a release is being retracted). By default: + +- A pull that **resolves to** a suspended channel is refused with an error, so you never mirror a version upstream has pulled back. +- A channel that is suspended but that your request does **not** resolve to is ignored. A suspended `alpha` does not block `--deckhouse-tag stable`, and a specific `--deckhouse-tag vX.Y.Z` (or a custom tag) that matches no channel is unaffected. + +Pass `--ignore-suspend` to mirror regardless. Use with caution — you may distribute a version that is actively being retracted upstream. + +#### LTS Channel and CSE Editions + +`lts` is a valid release channel in addition to the five default channels, and is the channel the CSE edition ships: + +- `--deckhouse-tag lts` mirrors the current LTS release, exactly like any other channel name. +- On editions that ship only `lts` (CSE), the absence of the standard channels (`alpha` … `rock-solid`) is not an error. Registry access validation also falls back from `stable` to `lts` automatically, so a plain `d8 mirror pull` works against a CSE source without extra flags. + +#### VEX (Vulnerability Attestation) Images + +For each platform image, the pull looks up a matching VEX attestation — an `.att` tag carrying Vulnerability-Exploitability-eXchange data — and, when present, adds it to the bundle. This happens by default. Pass `--skip-vex-images` to exclude these attestations and produce a smaller bundle. + +--- + ### Proxy Registry Mode `--proxy-registry` adapts the pull command to source registries that act as a transparent proxy or cache in front of another registry and do **not** implement the registry catalog API (`/v2/_catalog`, `/v2//tags/list`). Instead of calling `ListTags`, the pull walks individual semver tags forward from a starting point you supply via `--include-platform` / `--include-module` and probes each one with a single manifest `HEAD` request. @@ -183,13 +250,13 @@ module-name[@version-constraint] --include-module mymodule ``` -2. **Semver caret constraint (^)** - Compatible versions (default when version is specified without operator) +2. **Bare version / caret (^)** - a version given without an operator, or with a caret ```bash - --include-module mymodule@1.3.0 - --include-module mymodule@^1.3.0 # equivalent + --include-module mymodule@1.3.0 # bare version: >=1.3.0 <2.0.0 (same major line) + --include-module mymodule@^1.3.0 # caret: >=1.3.0 <2.0.0 for major >= 1 ``` - - Example: For available versions `v1.0.0, v1.1.0, v1.2.0, v1.3.0, v1.3.3, v1.4.1` - - Constraint `^1.3.0` includes: `v1.3.3, v1.4.1` + - Example: For available versions `v1.0.0, v1.1.0, v1.2.0, v1.3.0, v1.3.3, v1.4.1`, both include `v1.3.3, v1.4.1`. + - **The bare form is NOT identical to caret for `0.x`:** `mymodule@0.4.0` expands to `>=0.4.0 <1.0.0` (the whole `0.x` line), whereas `^0.4.0` locks the minor (`>=0.4.0 <0.5.0`). For major ≥ 1 the two coincide. Prefer the bare form for step-by-step upgrades that must capture every intermediate minor. - Only the highest patch in each `(major, minor)` series is kept, matching the platform release discovery rule. Use the exact-tag form (`=`) to pin a specific older patch. - Versions currently pinned by release channels (alpha, beta, early-access, stable, rock-solid, lts) are pulled in addition, regardless of the patch filter. - Also pulls current versions from all release channels @@ -219,7 +286,7 @@ module-name[@version-constraint] --include-module mymodule@=v1.3.0 ``` - Includes only `v1.3.0` - - Publishes to all release channels (alpha, beta, early-access, stable, rock-solid) + - Publishes to all release channels (alpha, beta, early-access, stable, rock-solid, **and `lts`**) 6. **Exact tag with custom build identifier** ```bash @@ -234,6 +301,17 @@ module-name[@version-constraint] ``` - Includes only `v1.3.0` - Publishes only to the `stable` channel + - An unrecognized channel in the suffix (e.g. `+bogus`) is ignored: the pin falls back to publishing to all channels, as in items 5–6. + +#### Pinning several versions at once + +Repeating `--include-module` for the **same** module OR-combines the constraints, so you can pin multiple versions in one pull: + +```bash +--include-module mymodule@=v1.3.0 --include-module mymodule@=v1.4.0 # pulls both +``` + +When more than one exact tag is pinned for the same module, the "publish to all channels" behavior of a single pin no longer applies — only tags that name their own `+channel` suffix are aliased to a channel; the rest are pulled as plain tags. The same rule applies to `--include-package`. #### Examples @@ -280,6 +358,53 @@ d8 mirror pull /tmp/d8-bundle \ --deckhouse-tag v1.59.0 ``` +--- + +### Package Mirroring + +Packages are a component mirrored much like modules, but under the `packages/` registry segment. They are selected with `--include-package` / `--exclude-package` (same `name[@version-constraint]` dialect as [Module Filtering](#module-filtering)) and skipped with `--no-packages`. A default pull mirrors **all** packages the source registry exposes. + +Registry layout (under the edition root, e.g. `registry.deckhouse.ru/deckhouse/fe`): + +| Path | Contents | +|------|----------| +| `/packages/:` | Main package image | +| `/packages//version:` | Release-channel and per-version release metadata (segment is `version`, not modules' `release`) | +| `/packages//extra/:` | Extra images (listed in the release image's `extra_images.json`) | + +**Bundle output:** one `package-.tar` per package that pulled at least one image, plus a single `package-versions.tar`. + +**`package-versions.tar` is produced on every pull** — regardless of `--no-packages` or the include/exclude filter. It clones the full package release-metadata catalog (all package names, every `version:` tag) so the bundle's package release metadata stays in sync. Do not rely on `--no-packages` to suppress it. + +Differences from modules: + +- There is no `--packages-path-suffix`; the `packages/` segment is fixed. +- `--only-extra-images`, `--skip-vex-images`, `--dry-run` and `--verbose-summary` apply to packages exactly as to modules. +- Exact-tag pins and multi-version pinning follow the same channel-alias rules as modules (see [Pinning several versions at once](#pinning-several-versions-at-once)). + +If the source registry has no `packages` repository (some public/CE registries), the package phase and `package-versions.tar` are skipped with a warning instead of failing the pull. + +--- + +### Security Databases + +The security phase mirrors four Trivy databases into `security.tar`, each pinned at a fixed schema tag under `//security/`: + +| Database | Tag | Contents | +|----------|-----|----------| +| `trivy-db` | `2` | Main Trivy vulnerability database | +| `trivy-bdu` | `1` | Russian BDU (FSTEC) vulnerability feed | +| `trivy-java-db` | `1` | Java package index | +| `trivy-checks` | `0` | Misconfiguration policy checks | + +These tags are rolling schema pointers, not release versions: a pull always fetches their current contents, and the set is **not** affected by `--deckhouse-tag`, `--include-platform`, `--since-version` or any release channel. Security-database freshness is therefore independent of which platform versions you mirror. + +**Edition availability.** Availability is detected automatically by probing `trivy-db:2`. Editions that do not ship security databases (e.g. CE, BE, SE) return "not found", so the whole phase is skipped silently — no `security.tar` is written, no error is raised, and `--no-security-db` has nothing to skip. EE/FE ship the databases. + +**Partial sets.** Only `trivy-db` gates the phase; the other three are best-effort, so a missing `trivy-bdu`/`trivy-java-db`/`trivy-checks` tag is skipped rather than failing the pull. The end-of-pull summary reports `N/4 databases` and flags the line when `N < 4`. + +--- + ### Environment Variables Additional configuration options are available through environment variables: @@ -291,6 +416,7 @@ Additional configuration options are available through environment variables: | `HTTP_PROXY` | URL of proxy server for HTTP requests to hosts not listed in `NO_PROXY` | | `HTTPS_PROXY` | URL of proxy server for HTTPS requests to hosts not listed in `NO_PROXY` | | `NO_PROXY` | Comma-separated list of hosts to exclude from proxying. Supports IP addresses, CIDR notations, domains, and asterisk. Domains match name and subdomains. A single `*` disables all proxying | +| `D8_MIRROR_TIMEOUT` | Per-request timeout for registry operations, as a Go duration (e.g. `30s`, `5m`). Applies to all pull operations, including platform. A negative or unset value leaves the client default in place | ### Bundle Structure @@ -298,13 +424,18 @@ The pull command creates a bundle with the following structure: ``` / -├── platform.tar # Deckhouse platform images (if not --no-platform) -├── security.tar # Security databases (if not --no-security-db) -├── module-.tar # Individual module bundles (if not --no-modules) -└── module--.chunk # Chunked bundles (if --images-bundle-chunk-size is set) +├── platform.tar # Platform images incl. embedded install/install-standalone (if not --no-platform) +├── installer.tar # Standalone Deckhouse installer (if not --no-installer) +├── security.tar # Security databases: trivy-db/bdu/java-db/checks (if available for the edition) +├── module-.tar # One archive per module (if not --no-modules) +├── package-.tar # One archive per package (if not --no-packages) +├── package-versions.tar # Package release-metadata catalog (always produced) +└── .tar.NNNN.chunk # Chunk parts, when --images-bundle-chunk-size is set (NNNN = 0000, 0001, …) ``` -If `--gost-digest` is specified, `.gostsum` files are created alongside each `.tar` file. +When a component is chunked, its `.tar` is written as a sequence of `.tar.NNNN.chunk` parts (4-digit, zero-padded, starting at `0000`) instead of a single `.tar`; `d8 mirror push` reassembles them transparently. + +If `--gost-digest` is specified, a GOST R 34.11-2012 Streebog-256 `.gostsum` file is written next to every `.tar` **and** every `.chunk`. --- @@ -314,16 +445,21 @@ If `--gost-digest` is specified, `.gostsum` files are created alongside each `.t Uploads a previously downloaded Deckhouse Kubernetes Platform distribution bundle to a third-party container registry. This is typically used to populate an air-gapped registry with Deckhouse images. +Before uploading, push runs a write-access check against the target repository (15-second timeout, or `D8_MIRROR_TIMEOUT`); set `MIRROR_BYPASS_ACCESS_CHECKS=1` to proceed even if that check fails. Chunked packages (`.tar.NNNN.chunk`) are reassembled transparently, and each image upload is retried up to 4 times. After the layouts are pushed, small discovery-index tags are created at `/modules:` and `/packages:` so the mirrored modules and packages can be enumerated by tag listing. + ### Synopsis ```bash -d8 mirror push [flags] +d8 mirror push [images-bundle-path] [flags] + +# Push individual package files instead of a bundle directory: +d8 mirror push --file platform.tar --file module-foo.tar [flags] ``` ### Arguments -- `` - Path to the directory containing the bundle created by `d8 mirror pull` -- `` - Target registry address (format: `registry-host[:port]/path`, e.g. `registry.corp.local:5000/deckhouse`) +- `` - Target registry address, always the **last** positional argument (format: `registry-host[:port]/path`, e.g. `registry.corp.local:5000/deckhouse`). `http://`/`https://` prefixes are stripped; a bare registry root with no path is rejected; the repository path must be 2–255 characters. +- `[images-bundle-path]` - **Optional** path to the bundle directory (or a single tar/chunked package) created by `d8 mirror pull`. May be omitted when packages are supplied via `--file`. ### Flags @@ -332,7 +468,7 @@ d8 mirror push [flags] | Flag | Short | Environment Variable | Description | |------|-------|---------------------|-------------| | `--registry-login` | `-u` | `D8_MIRROR_REGISTRY_LOGIN` | Username to log into the target registry | -| `--registry-password` | `-p` | `D8_MIRROR_REGISTRY_PASSWORD` | Password to log into the target registry | +| `--registry-password` | `-p` | `D8_MIRROR_REGISTRY_PASSWORD` | Password to log into the target registry. Requires `--registry-login` to be set as well | #### Connection Options @@ -342,6 +478,12 @@ d8 mirror push [flags] | `--insecure` | Interact with registries over HTTP | | `--tmp-dir` | Path to temporary directory for processing. Ensure sufficient disk space for the entire bundle | +#### Package Selection + +| Flag | Description | +|------|-------------| +| `--file` | Path to a single tar or chunked package to push. May be repeated. Can replace the `[images-bundle-path]` argument or be combined with it | + #### Other Options | Flag | Description | @@ -378,6 +520,8 @@ The same environment variables used by `d8 mirror pull` are also supported: | `HTTP_PROXY` | HTTP proxy server URL | | `HTTPS_PROXY` | HTTPS proxy server URL | | `NO_PROXY` | Comma-separated list of hosts to exclude from proxying | +| `D8_MIRROR_TIMEOUT` | Per-request timeout for registry operations, as a Go duration (e.g. `30s`, `5m`). Also caps the pre-push access check | +| `MIRROR_BYPASS_ACCESS_CHECKS` | Set to `1` to continue the push even when the registry write-access pre-check fails | --- @@ -425,7 +569,7 @@ The same environment variables used by `d8 mirror pull` are also supported: **Problem:** Pull operation fails due to lack of disk space. **Solution:** -- Use `--tmp-dir` to specify a location with more space +- Use `--tmp-dir` to specify a location with more space. By default the temp directory is `/.tmp`, i.e. on the **same volume as the bundle** — point `--tmp-dir` elsewhere if that volume is small - Use `--images-bundle-chunk-size` to split the bundle into smaller chunks - Use component flags (`--no-modules`, `--no-security-db`) to reduce bundle size @@ -459,15 +603,24 @@ The same environment variables used by `d8 mirror pull` are also supported: ### Debug Logging -Enable debug logging to troubleshoot issues: +Enable debug logging to troubleshoot issues via the `MIRROR_DEBUG_LOG` environment variable (an integer, higher = more verbose): + +| Level | Output | +|-------|--------| +| `0` | No debug logging (default) | +| `1` | Image-transfer progress to stderr | +| `2` | Level 1 + registry-client warnings to stderr | +| `3` | Level 2 + mirror debug messages to stdout | +| `4` | Level 3 + full registry requests/responses to stderr | ```bash -# Set debug level (1-4, higher = more verbose) -export D8_LOG_LEVEL=3 +export MIRROR_DEBUG_LOG=3 d8 mirror pull /tmp/bundle --license $LICENSE_TOKEN ``` +> Note: `D8_LOG_LEVEL` does **not** control mirror verbosity — use `MIRROR_DEBUG_LOG`. + --- ## Best Practices @@ -492,7 +645,7 @@ d8 mirror pull /tmp/bundle --license $LICENSE_TOKEN 2. **Use `--include-platform`:** To pull a bounded window of releases (e.g. `">=1.64 <=1.68"`) — useful for staged incremental updates where you want to move up a few minors at a time without pulling the entire channel history 3. **Specific Versions:** Use `--deckhouse-tag` (or `--include-platform "=vX.Y.Z"`) for fully controlled, single-release deployments 4. **Module Versioning:** Leverage semver constraints in `--include-module` for flexible module version management; `--include-platform` speaks the same dialect -5. **Release Channels:** Pulling respects all configured release channels (alpha, beta, early-access, stable, rock-solid); channels whose snapshot falls outside an `--include-platform` constraint are automatically excluded from the bundle +5. **Release Channels:** Pulling respects all configured release channels (alpha, beta, early-access, stable, rock-solid, lts); channels whose snapshot falls outside an `--include-platform` constraint are automatically excluded from the bundle ### Bundle Management @@ -507,15 +660,19 @@ d8 mirror pull /tmp/bundle --license $LICENSE_TOKEN ### Bundle Components -The mirror system handles three primary component types: +The mirror system handles these component types, each packed into its own archive(s): + +1. **Platform images** (`platform.tar`) — core platform containers, plus the edition-scoped `install` and `install-standalone` installer images. +2. **Standalone installer** (`installer.tar`) — the non-edition-scoped `installer` image, controlled by `--installer-tag` / `--no-installer`. +3. **Modules** (`module-.tar`) — optional modules with versioned releases; see [Module Filtering](#module-filtering). +4. **Packages** (`package-.tar`, plus the always-present `package-versions.tar`) — see [Package Mirroring](#package-mirroring). +5. **Security databases** (`security.tar`) — four Trivy databases; see [Security Databases](#security-databases). -1. **Platform Images:** Core Deckhouse Kubernetes Platform containers -2. **Modules:** Optional Deckhouse modules with versioned releases -3. **Security Databases:** Vulnerability scanning databases and related data +For each module and package version, the pull additionally fetches release-metadata images, images referenced by the release image's `images_digests.json`, extra images listed in `extra_images.json`, and (unless `--skip-vex-images` is set) `.att` VEX attestation images. ### Platform Release Discovery -By default, the platform service discovers releases between the current `rock-solid` channel version (lower bound) and the current `alpha` channel version (upper bound), keeping the highest patch per `(major, minor)`. +By default, the platform service discovers releases between the current `rock-solid` channel version (lower bound) and the current `alpha` channel version (upper bound), keeping the highest patch per `(major, minor)`. To narrow this to a single channel, a single version, or a custom build, use `--deckhouse-tag` — see [Release Channel Selection](#release-channel-selection). Suspended channels, the `lts`/CSE special cases, and VEX attestation images are covered there as well. `--since-version` raises the lower bound above `rock-solid` when the user wants to skip older minors. @@ -530,6 +687,10 @@ By default, the platform service discovers releases between the current `rock-so - **Version Constraints:** Semver-based filtering for granular control (same dialect as `--include-platform`) - **Release Channels:** Each module can have multiple release channel versions +### Editions + +The last path segment of `--source` selects the Deckhouse edition and is stripped to form the *edition root* that scopes the deckhouse, modules, packages and security repositories. Recognized editions: `ee`, `fe`, `se`, `se-plus`, `be`, `ce`. A source whose last segment is not one of these is treated as a plain path with no edition (the end-of-pull summary then omits the Edition line). The standalone `installer` image is always pulled from the non-edition root. Editions differ in which components exist — for example, only EE/FE ship [security databases](#security-databases). + ### Storage Layout Internally, the tool uses OCI-compliant image layouts for organizing container images and manifests. This ensures compatibility with standard container tools and registries. From 2c63be235f94bfe71912108f44c2de051832eb95 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Thu, 30 Jul 2026 19:20:58 +0300 Subject: [PATCH 2/2] bump Signed-off-by: Pavel Okhlopkov --- README.md | 3 +- docs/plugins.md | 88 +++++++--- internal/plugins/README.md | 53 ++++-- internal/status/README.md | 123 ++++++++++++++ internal/system/README.md | 332 +++++++++++++++++++++++++++++++++++++ 5 files changed, 560 insertions(+), 39 deletions(-) create mode 100644 internal/status/README.md create mode 100644 internal/system/README.md diff --git a/README.md b/README.md index 64f2b9cc4..39e5aeb92 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ D8 provides comprehensive cluster management capabilities: | [**backup**](internal/backup/) | Backup operations | ETCD snapshots, configuration backups, data export | | [**cr**](internal/cr/) | Container registry tool | Pull/push images, list tags & repos, inspect manifests/configs, browse image filesystems | | [**mirror**](internal/mirror/) | Module mirroring | Registry operations, image synchronization, air-gapped deployments | -| [**system**](internal/system/) | System diagnostics | Debug info collection, logs analysis, troubleshooting | +| [**status**](internal/status/README.md) | Cluster status report | Read-only health snapshot: nodes, Deckhouse pods, releases, edition, settings, registry, alerts, CNI, queue | +| [**system**](internal/system/README.md) | Platform operations | Config get/edit, module lifecycle, package scans, queue dumps, controller logs, debug archives | | **user-operation** | Local user operations | Request `UserOperation` in `user-authn` (ResetPassword/Reset2FA/Lock/Unlock) | ### 🚀 Module Management diff --git a/docs/plugins.md b/docs/plugins.md index 4b1f5d282..bc6f38645 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -7,7 +7,7 @@ Plugins are versioned binaries distributed through the cluster registry. [Versions & majors](#versions-majors-and-switching) · [Requirements](#requirements) · [Flags & env](#flags-and-environment-variables) · -[Troubleshooting](#troubleshooting) +[Troubleshooting](#troubleshooting) · [Advanced](#advanced-hidden-flags) > [!NOTE] > The `d8 plugins` command group is hidden from the root `--help` while the @@ -16,19 +16,26 @@ Plugins are versioned binaries distributed through the cluster registry. ## Plugin source Plugins are pulled from the in-cluster **registry-packages-proxy**, the same -channel as d8 self-update. There is no direct-registry path: every `d8 plugins` +channel as d8 self-update. This is the only supported path: every `d8 plugins` command reaches the registry through the proxy, so a reachable cluster is -required. The access model: +required. (A hidden, temporary `--source` flag pulls straight from a registry +repo instead - see [Advanced](#advanced-hidden-flags) - but it bypasses the +cluster and is not the intended flow.) The access model: - Authentication: the **Bearer token** from your kubeconfig (client certificates do not work). -- Authorization: the ClusterRole `d8:registry-packages-proxy:cli-download`, - bound by the cluster administrator. -- Endpoint: discovered automatically; override with `--rpp-endpoint` / - `D8_RPP_ENDPOINT`, pass a private CA with `--rpp-ca-file`. - -See [self-update.md - How access works](self-update.md#how-access-works) for -the full picture (RBAC binding example, OIDC kubeconfig, endpoint discovery). +- Authorization: the ClusterRole + `d8:registry-packages-proxy:packages-download`, bound by the cluster + administrator. Authorization is cached for about 5 minutes, so after the + binding is created, retry with a fresh token. +- Endpoint: discovered automatically through your kubeconfig's API server; + override with `--rpp-endpoint` / `D8_RPP_ENDPOINT`, pass a private CA with + `--rpp-ca-file`. + +The access model is shared with d8 self-update (see +[self-update.md - How access works](self-update.md#how-access-works) for the +OIDC-kubeconfig and endpoint-discovery details), but the ClusterRole differs: +plugins need `packages-download`, CLI self-update needs `cli-download`. ## Commands @@ -78,13 +85,17 @@ Rules that follow from this layout: ## Requirements -A plugin's contract may declare requirements: - -- other plugins; -- Kubernetes / Deckhouse versions; -- enabled modules. +A plugin's contract may declare requirements, all validated **before** anything +is downloaded or switched: -They are validated **before** anything is downloaded or switched: +- **Kubernetes / Deckhouse version** constraints (semver). A cluster whose + Deckhouse version is not a release semver - e.g. a `dev` build - skips the + Deckhouse check with a warning instead of failing. +- **Modules**: required-enabled (optionally with a version constraint), + conditional (checked only if the module is enabled), any-of (at least one of + a group must be enabled), and forbidden (a module that must *not* be enabled). +- **Other plugins**: mandatory dependencies, and conditional ones that are + enforced only if that plugin is already installed. ```console $ d8 plugins install package @@ -92,10 +103,13 @@ $ d8 plugins install package Error: plugin requirements not satisfied # e.g. requires plugin delivery-kit ``` -Plugins this one depends on are installed/upgraded automatically. +Mandatory plugin dependencies are installed and upgraded automatically during +`install` / `update`. Cluster-side requirements (Kubernetes / Deckhouse / +modules) are only *verified* - d8 never changes the cluster for you. -- `--skip-cluster-checks` (or `D8_PLUGINS_SKIP_CLUSTER_CHECKS=1`) - skip - cluster-side checks, e.g. in air-gapped scenarios. +- `--skip-cluster-checks` (or `D8_PLUGINS_SKIP_CLUSTER_CHECKS=1`) downgrades the + cluster-side checks to a warning - useful when the cluster is unreachable or + air-gapped. Plugin-to-plugin requirements are still enforced. ## Flags and environment variables @@ -107,11 +121,41 @@ Plugins this one depends on are installed/upgraded automatically. | `--rpp-endpoint` | `D8_RPP_ENDPOINT` | proxy base URL; discovered from the cluster when empty | | `--rpp-ca-file` | `D8_RPP_CA_FILE` | PEM CA bundle to verify the proxy TLS certificate | | `--rpp-insecure-skip-tls-verify` | - | skip proxy TLS verification (debugging only) | +| `--version X` *(install only)* | - | install an exact version; may be a pre-release | +| `--use-major N` *(install, update)* | - | cross to major `N`; by default operations stay within the installed major | +| `--force` *(install only)* | - | reinstall even if already current (re-pull and re-verify) | + +The persistent flags above are shared by every `d8 plugins` subcommand; the +`--source*` family is hidden - see [Advanced](#advanced-hidden-flags). ## Troubleshooting | Symptom | Cause | Fix | |---|---|---| -| `image or tag not found` (404) on a plugin | the plugin is not published in this cluster's registry | check with `d8 plugins versions `; publishing is the plugin CI's job | -| `plugin requirements not satisfied` | the contract requires other plugins or cluster versions/modules | see `d8 plugins contract `; plugin deps are auto-installed, cluster requirements are not | -| 401 / 403 / `x509: ...` reaching the proxy | access or TLS issue with the registry-packages-proxy | see [self-update.md - Troubleshooting](self-update.md#troubleshooting) - the access model is shared | +| `image or tag not found` (404) | that plugin - or that specific version - is not published in this cluster's registry | check with `d8 plugins versions `; publishing is the plugin CI's job | +| `... unauthorized (401)` | no accepted Bearer token (a client-certificate kubeconfig is not enough) | use an OIDC-token kubeconfig (Kubeconfig Generator or `d8 login`) | +| `... forbidden (403)` | your identity may not download plugins | ask an admin to bind the ClusterRole `d8:registry-packages-proxy:packages-download`; authorization is cached ~5 min, so retry with a fresh token | +| `... requirements not satisfied` | mandatory **plugin** dependencies are missing or version-incompatible | run `d8 plugins contract `; on `install` deps auto-install, but at plugin *run* time install them manually as the hint says (`d8 plugins install `) | +| `... requires Kubernetes/Deckhouse/module ...` | a **cluster-side** requirement is unmet (a different message from the row above) | upgrade the cluster/module, or pass `--skip-cluster-checks` to bypass verification | +| `... upstream error (5xx)` | the proxy could not reach the backing registry | retry shortly, or check the `registry-packages-proxy` pods in `d8-cloud-instance-manager` | +| `endpoint discovery ... failed`, `x509:` to the API server | endpoint discovery goes through your kubeconfig's **API server** (not the proxy), which was unreachable or had an invalid certificate | confirm the API server is reachable with a valid cert, or skip discovery with `--rpp-endpoint https://registry-packages-proxy.` (`D8_RPP_ENDPOINT`) | +| `cannot reach the cluster to ...` | the cluster is needed to verify requirements or select a version, but is unreachable | pass `--skip-cluster-checks` (`D8_PLUGINS_SKIP_CLUSTER_CHECKS=1`) | + +The access model is shared with d8 self-update; see +[self-update.md - Troubleshooting](self-update.md#troubleshooting) for the +registry-packages-proxy side. + +## Advanced (hidden flags) + +These flags are hidden from `--help` and exist as a temporary escape hatch; +prefer the proxy flow above. + +- `--source ` pulls plugins **directly from a registry + repository, bypassing the cluster and the proxy**. It automatically enables + `--skip-cluster-checks`, so cluster-side requirements are not verified. + Credentials come from `--source-login` / `--source-password`, or `--license` + (a shortcut for `--source-login=license-token`), or your + `~/.docker/config.json` - in that order. `--tls-skip-verify` and `--insecure` + relax TLS / allow HTTP for that registry. +- `--rpp-insecure-skip-tls-verify` skips registry-packages-proxy TLS + verification (debugging only). diff --git a/internal/plugins/README.md b/internal/plugins/README.md index c010b11d6..4a791065e 100644 --- a/internal/plugins/README.md +++ b/internal/plugins/README.md @@ -30,13 +30,23 @@ split `internal/selfupdate` / `internal/selfupdate/cmd` uses. ## Plugin source -`rppPluginSource` (`rpp_source.go`) implements the `PluginSource` interface -(`source.go`) and is the only source: plugins are pulled through the in-cluster -registry-packages-proxy using the **kubeconfig identity**, with no registry -credentials on the user side (ADR: deckhouse-cli reaches the registry -exclusively through the proxy, so every command needs a reachable cluster). -See `internal/selfupdate/README.md` for what RPP is and how authorization works; -the plugin routes are `/v1/images/deckhouse-cli/plugins//...`. +The `pluginSource` interface (`source.go`) has two implementations, chosen in +`InitPluginServices` (`init.go`) by whether the hidden `--source` flag is set: + +- **`rppPluginSource` (`rpp_source.go`) - the default and only supported + source.** Plugins are pulled through the in-cluster registry-packages-proxy + using the **kubeconfig identity**, with no registry credentials on the user + side (ADR #386: deckhouse-cli reaches the registry exclusively through the + proxy, so every command needs a reachable cluster). See + `internal/selfupdate/README.md` for what RPP is and how authorization works - + plugin download is gated by the `d8:registry-packages-proxy:packages-download` + ClusterRole, distinct from self-update's `cli-download`. The plugin routes are + `/v1/images/deckhouse-cli/plugins//{tags,manifests/,images/}`. +- **`registryPluginSource` (`source_legacy.go`) - a temporary, hidden `--source` + bypass.** It pulls straight from a registry repo with go-containerregistry, + skipping the proxy and the cluster, and force-sets `--skip-cluster-checks`. It + exists for pre-#386 workflows and is documented for removal (grep marker + `legacy --source`). ## What a plugin image contains @@ -105,18 +115,25 @@ A failure at any step leaves the previous version installed and working. ## Requirements enforcement -- **Cluster-side** (`kubernetes`, `deckhouse`, `modules` incl. - mandatory/conditional/anyOf): verified against a one-shot cluster snapshot - (the `requirements/` package); the cluster is queried only when the plugin - actually declares such requirements, so contract-less plugins install offline. +- **Cluster-side** (`kubernetes`, `deckhouse`, and `modules` - + mandatory/conditional/anyOf/**noneOf**, the last *forbidding* a module): + verified against a one-shot cluster snapshot (the `requirements/` package) + built from three reads - the API-server version, the `deckhouse` deployment's + `core.deckhouse.io/version` annotation, and a `modules.deckhouse.io` list. The + snapshot is lazy (built only when the plugin declares such requirements, so + contract-less plugins install offline), cached once per run, and bounded by a + 30s probe timeout. A non-release Deckhouse version (e.g. `dev`) skips the + Deckhouse check with a warning rather than failing. - **Plugin-to-plugin**: a plugin's mandatory dependencies are installed and upgraded automatically (the resolution planner: constraint-aware, newest satisfying version, within each dependency's own major - or across it when `--use-major` cascades). Conflicts with already-installed plugins skip a - candidate during selection. -- **At runtime**: the wrapper re-validates requirements before EVERY plugin run - (the gate is skipped for purely local queries: `--help`, `--version`, - `completion`). + candidate during selection. Conditional dependencies are enforced only when + that plugin is already installed and are never auto-installed. +- **At runtime**: the wrapper re-validates requirements before EVERY plugin run. + The gate is skipped for local-only invocations - `--help`/`-h` (anywhere + before a `--`), `--version`/`-v` or `help`/`completion` as the first arg, and + cobra's `__complete*` requests - and when the plugin ships no contract. - Escape hatch for air-gapped setups: `--skip-cluster-checks` / `D8_PLUGINS_SKIP_CLUSTER_CHECKS=1` (downgrades the check to a warning). @@ -156,16 +173,20 @@ A failure at any step leaves the previous version installed and working. | `plugins.go` | the `Manager`: shared state of the plugin machinery | | `install.go` | the install pipeline: lock, staged download, smoke, atomic swap, idempotency | | `select.go` | newest-compatible version selection, contract memoization | +| `planner.go` | plugin-to-plugin dependency resolution: constraint-aware planning, conflict/cycle/depth guards, upgrade-only | | `update.go` | `UpdateAll`, installed-plugin discovery, home-fallback switch | | `remove.go` | `Remove` / `RemoveAll` | | `validators.go` | plugin-to-plugin requirement checks + the Manager glue over `requirements/` (snapshot cache, kubeconfig clients, `--skip-cluster-checks`) | | `requirements/` | cluster-side requirements: the one-shot cluster snapshot (k8s / Deckhouse / modules) and the named checks against it | | `run.go` | running an installed plugin: requirement gate, env injection, exec | | `list.go` / `versions.go` | data for the `list` / `versions` commands | -| `source.go` / `rpp_source.go` / `init.go` | the `PluginSource` interface, its RPP implementation, source wiring | +| `source.go` / `rpp_source.go` / `init.go` | the `pluginSource` interface, its default RPP implementation, and source selection | +| `source_legacy.go` | the hidden `--source` direct-registry bypass (temporary, pre-#386; force-enables `--skip-cluster-checks`) | +| `builtins.go` | built-in command names (`delivery-kit`, `package`) that satisfy a same-named plugin dependency by presence - no version check, no registry lookup | | `layout/` | on-disk path layout | | `flags/` | the `d8 plugins` flag set | | `cmd/` | the `d8 plugins ...` command tree and the per-plugin wrapper command, one file per command | +| `cmd/errdetect/` | maps registry-packages-proxy errors (401/403/404/5xx/endpoint-discovery) to actionable hints | Related: `internal/rpp` (proxy HTTP client), `internal/lockfile` (install lock), `internal/selfupdate` (the same store-and-symlink update pattern for the d8 diff --git a/internal/status/README.md b/internal/status/README.md new file mode 100644 index 000000000..a9b7662c0 --- /dev/null +++ b/internal/status/README.md @@ -0,0 +1,123 @@ +# `d8 status` - Cluster Status Report + +`d8 status` prints a single, read-only health snapshot of a running Deckhouse Kubernetes Platform (DKP) cluster. One invocation queries the Kubernetes API (and, for the queue section, execs the Deckhouse controller once) and renders a colorized report covering the control-plane nodes, the Deckhouse pods, and the platform's releases, edition, settings, registry, alerts, CNI modules, and reconciliation queue. + +It takes no subcommands and no arguments - it is a dashboard you read, not a tool you drive. Because it only reads, it is safe to run against any cluster you can reach with a kubeconfig. + +© Flant JSC 2025 + +--- + +## Table of contents + +- [Synopsis](#synopsis) +- [Global flags](#global-flags) +- [The report](#the-report) +- [Report sections](#report-sections) +- [Data sources and prerequisites](#data-sources-and-prerequisites) +- [Examples](#examples) +- [Notes](#notes) + +--- + +## Synopsis + +```bash +d8 status [flags] +``` + +No positional arguments, no subcommands, no aliases. + +## Global flags + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--kubeconfig` | `-k` | string | `$KUBECONFIG`, else the OS-default kubeconfig (e.g. `~/.kube/config`) | Path to the kubeconfig file. | +| `--context` | | string | current-context of the kubeconfig | Name of the kubeconfig context to use. | + +Unlike `d8 system`, `d8 status` does not pre-validate the kubeconfig path; an unreachable cluster or a bad path surfaces as an error when the report runs. + +--- + +## The report + +Output is written to stdout and always has the same shape: + +``` +Generated by Deckhouse CLI v1.x.y (faint byline: the d8 version that produced the report) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Cluster Status Report ┃ (cyan header) +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +┌ Master Nodes Status: (nine sections, in the order listed below, +├ ... each drawn with ┌ ├ └ │ box characters) +... +``` + +A few properties are worth knowing: + +- **Colors auto-strip when piped.** Colorization uses `fatih/color`, which detects a non-terminal stdout, so `d8 status > report.txt` (or piping into `less`/`tee`) yields clean plain text suitable for tickets and CI logs. +- **Sections are independent and best-effort.** Each section is collected on its own; if one fails it prints a red `Error getting
: ...` line and the rest of the report still renders. A single unreachable resource never aborts the whole report. +- **Read-only.** Every section performs `get`/`list` calls (plus one controller `queue list` exec); nothing in the cluster is modified. +- **Empty states are marked, not errored.** A section with no matching objects prints a yellow notice (`❗ No ... found`, or `✅ No Cluster Alerts found`) rather than a red error. + +--- + +## Report sections + +The report always contains these nine sections, in this order: + +| # | Section | What it reads | What it reports | +|---|---|---|---| +| 1 | Master Nodes | `Nodes` labeled `node-role.kubernetes.io/master` (cluster-scoped) | `NAME`, `STATUS` (Ready / NotReady), `VERSION` (kubelet), `IP-ADDRESS` (first internal IP) | +| 2 | Deckhouse Pods | `Pods` labeled `app=deckhouse` in `d8-system` | `NAME`, `READY` (ready/total containers), `STATUS` (phase), `RESTARTS` (summed), `AGE` | +| 3 | Deckhouse Releases | `deckhousereleases.deckhouse.io/v1alpha1` (cluster-scoped) | `NAME`, `PHASE`, `TRANSITIONTIME` (as age), `MESSAGE` | +| 4 | Deckhouse Edition | `Deployment/deckhouse` in `d8-system`, annotation `core.deckhouse.io/edition` | The edition string (e.g. `CE`, `EE`, `CSE`) | +| 5 | Deckhouse ModuleConfig | `ModuleConfig/deckhouse` (`deckhouse.io/v1alpha1`), field `spec.settings` | The platform settings as a sorted, nested tree (credentials masked - see below) | +| 6 | Deckhouse Registry | `Secret/deckhouse-registry` in `d8-system` | `Registry` (host/path) and `Scheme` (from the secret's `imagesRegistry` / `scheme` keys) | +| 7 | Cluster Alerts | `clusteralerts.deckhouse.io/v1alpha1` (cluster-scoped) | `SEVERITY`, `ALERT`, `SUM` - identical alerts aggregated into a count, sorted by severity then name | +| 8 | Modules | `modules.deckhouse.io/v1alpha1` (cluster-scoped) | `NAME`, `WEIGHT`, `SOURCE`, `PHASE`, `ENABLED`, `READY` for **CNI** modules only | +| 9 | Deckhouse Queue | `deckhouse-controller queue list`, exec'd in the leader pod | The controller's task queue - header line, per-task lines, and the summary | + +Section behaviors that are easy to miss: + +- **Edition** reads a single annotation on the `deckhouse` Deployment. If the annotation is absent, the section renders a red error rather than an empty notice. +- **ModuleConfig settings** dumps `spec.settings` verbatim as an indented tree, **masking credentials**: any value under a key whose name contains `password`, `secret`, `token`, `license`, `dockercfg`, `apikey`, `accesskey`, or `credential` (case-insensitive) is replaced with `***`, and that masking is inherited by everything nested beneath such a key. When `spec.settings` is empty the section prints nothing at all - no header, no placeholder. +- **Cluster Alerts** groups duplicate alerts (same severity + name) into a single row with a `SUM` count; the per-alert phase is read but not shown. +- **Modules** is filtered client-side to entries whose name contains `cni`, and its `ENABLED` / `READY` columns are derived from the module's `status.conditions` (`EnabledByModuleConfig` / `EnabledByModuleManager`, and `IsReady`). A cluster whose CNI module is named differently may show `❗ No CNI modules found`. +- **Deckhouse Queue** is the only section that leaves the API server: it opens an exec stream into the pod labeled `app=deckhouse,leader=true` in `d8-system` (container `deckhouse`) and runs `deckhouse-controller queue list`. Note this differs from [`d8 system queue`](../system/README.md#queues-queue), which curls the controller's HTTP self-API instead. + +--- + +## Data sources and prerequisites + +- **Namespaced reads (`d8-system`):** Deckhouse Pods, Edition (the `deckhouse` Deployment), Registry (the `deckhouse-registry` Secret), and the Queue leader pod. +- **Cluster-scoped reads:** Master Nodes, Deckhouse Releases, Cluster Alerts, Modules, and the `deckhouse` ModuleConfig. +- **RBAC:** you need read access (`get`/`list`) to the resources above, plus - for the queue section specifically - permission to `create pods/exec` in `d8-system`. The `deckhouse-controller` binary must exist in the `deckhouse` container (it does in a normal install). If there is no `leader=true` pod, only the queue section errors; the rest of the report is unaffected. +- **Sensitivity:** although ModuleConfig credentials are masked, a saved report still contains the registry host, node IP addresses, and alert text. Treat it accordingly when attaching it to a ticket. + +--- + +## Examples + +```bash +# Full status report +d8 status + +# Plain text (colors are auto-stripped when redirected) for a ticket or CI log +d8 status > cluster-status.txt + +# Run against a specific cluster / context +d8 status --kubeconfig ~/.kube/prod.config --context prod + +# Quick control-plane glance +d8 status | sed -n '/Master Nodes/,/Deckhouse Pods/p' +``` + +--- + +## Notes + +- **Age formatting** (the pod `AGE` and release `TRANSITIONTIME` columns) is humanized to the two largest non-zero units, e.g. `2d 3h`, `3h 4m`, `5m 7s`, `9s`. An unset or zero timestamp shows ``; a release timestamp that cannot be parsed shows `Parse Error`. +- **Column widths adapt** to the longest node/alert name, and over-long names are truncated with a `...` suffix so rows stay aligned. +- The report is generated fresh on every run; there is no caching, no watch mode, and no machine-readable (JSON/YAML) output format - it is a human-facing snapshot. For scriptable queue output use [`d8 system queue`](../system/README.md#queues-queue); for module state use [`d8 system module list`](../system/README.md#d8-system-module-list). diff --git a/internal/system/README.md b/internal/system/README.md new file mode 100644 index 000000000..e972efc4b --- /dev/null +++ b/internal/system/README.md @@ -0,0 +1,332 @@ +# `d8 system` - Platform Operations + +`d8 system` is the cluster-side operations subtree of the Deckhouse CLI. Where `d8 mirror` and `d8 cr` work against registries, `d8 system` talks to a **running** Deckhouse Kubernetes Platform (DKP) cluster: it reads and edits bootstrap configuration, drives the module lifecycle (enable/disable, maintenance, release approvals), triggers package-repository scans, dumps the controller's reconciliation queues, streams controller logs, and packages a full debug archive. + +It is aimed at cluster administrators and SREs operating a live DKP installation. Every subcommand authenticates with the standard kubeconfig, so it works anywhere `kubectl` does. + +© Flant JSC 2025 + +--- + +## Table of contents + +- [Command map](#command-map) +- [Global flags](#global-flags) +- [How `d8 system` reaches the cluster](#how-d8-system-reaches-the-cluster) +- [Configuration: `get` and `edit`](#configuration-get-and-edit) +- [Modules: `module`](#modules-module) +- [Packages: `package`](#packages-package) +- [Queues: `queue`](#queues-queue) +- [Logs: `logs`](#logs-logs) +- [Debug archive: `collect-debug-info`](#debug-archive-collect-debug-info) +- [Examples](#examples) +- [Behavior and safety notes](#behavior-and-safety-notes) + +--- + +## Command map + +``` +d8 system (aliases: s, p, platform) +├── get Read bootstrap configuration Secrets (kube-system) +│ ├── cluster-configuration +│ ├── provider-cluster-configuration +│ └── static-cluster-configuration +├── edit Edit those Secrets in $EDITOR and patch them back +│ ├── cluster-configuration +│ ├── provider-cluster-configuration +│ └── static-cluster-configuration +├── module Operate DKP modules +│ ├── list List enabled modules +│ ├── enable Set ModuleConfig spec.enabled=true +│ ├── disable Set ModuleConfig spec.enabled=false +│ ├── maintenance enable|disable Toggle spec.maintenance +│ ├── approve Approve a Manual-policy ModuleRelease +│ ├── apply-now Deploy a ModuleRelease now, ignoring update windows +│ ├── values Dump the module's computed hook values +│ └── snapshots Dump the module's hook snapshots +├── package Operate DKP packages +│ └── scan Create a PackageRepositoryOperation scan task +├── queue Dump the controller reconciliation queues +│ ├── list Dump all queues (optionally watch) +│ └── main Dump the main queue +├── logs Stream deckhouse-controller logs +└── collect-debug-info Stream a gzipped debug tarball to stdout +``` + +The `s` alias is the recommended short form (`d8 s module list`). `p` and `platform` are legacy aliases kept for backward compatibility with older documentation. + +> **Availability:** the built-in `system` command is registered only when the environment variable `DECKHOUSE_PLUGINS_ENABLED` is **not** `true`. When plugins are enabled, `d8 system` is served by a plugin shim instead, and the exact surface may differ from what is documented here. + +--- + +## Global flags + +These persistent flags are declared on `d8 system` and inherited by **every** subcommand below. There are no other cluster-wide flags for this subtree. + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--kubeconfig` | `-k` | string | `$KUBECONFIG`, else the OS-default kubeconfig (e.g. `~/.kube/config`) | Path to the kubeconfig file. Supports the OS path-list form (e.g. colon-separated on Linux/macOS). | +| `--context` | | string | current-context of the kubeconfig | Name of the kubeconfig context to use. | + +Before any subcommand runs, `d8 system` validates that `--kubeconfig` points at an **existing regular file** and fails fast otherwise (`Invalid --kubeconfig: ...`). It does not validate connectivity at this stage - that surfaces when the subcommand actually calls the cluster. + +--- + +## How `d8 system` reaches the cluster + +Commands in this subtree use one of three access paths. Knowing which a command uses explains its prerequisites and its failure modes. + +1. **Direct Kubernetes API.** The command reads or writes a specific resource through the API server using your kubeconfig credentials. Used by `get`/`edit` (Secrets in `kube-system`), `module enable`/`disable`/`maintenance` (`ModuleConfig`), `module approve`/`apply-now` (`ModuleRelease`), and `package scan` (`PackageRepositoryOperation`). Requires the corresponding RBAC (get/patch/create on those resources). + +2. **Exec into the Deckhouse leader pod.** The command shells into the running controller and either curls the controller's internal self-API at `http://127.0.0.1:9652/...` (`module list`/`values`/`snapshots`, `queue list`/`main`) or runs a battery of diagnostic commands (`collect-debug-info`). The leader pod is located in namespace `d8-system` by the label selector `leader=true`, container `deckhouse`. This path needs RBAC to `create pods/exec` in `d8-system`, and the relevant tools (`curl`, `kubectl`, `deckhouse-controller`, ...) must exist inside that container. If no leader pod is present the command fails with `no pods deckhouse available in namespace d8-system`. + +3. **Pod log stream.** `logs` reads the leader pod's `deckhouse` container log through the Kubernetes log API (not an exec). + +--- + +## Configuration: `get` and `edit` + +`get` and `edit` operate on the three DKP bootstrap-configuration Secrets stored in the `kube-system` namespace. Both the namespace and the Secret/data-key names are fixed - there is no flag to point them elsewhere. + +| Subcommand | Secret (`kube-system`) | Data key | +|---|---|---| +| `cluster-configuration` | `d8-cluster-configuration` | `cluster-configuration.yaml` | +| `provider-cluster-configuration` | `d8-provider-cluster-configuration` | `cloud-provider-cluster-configuration.yaml` | +| `static-cluster-configuration` | `d8-static-cluster-configuration` | `static-cluster-configuration.yaml` | + +Note that the provider key is `cloud-provider-cluster-configuration.yaml`, which does not match the Secret-name stem. + +### `d8 system get ` + +Reads the Secret and prints the decoded YAML to stdout verbatim - no re-formatting, no highlighting, no filtering. Pipe it into `yq`/`grep` to slice it. This is read-only; it makes no cluster changes and writes no files. Neither positional args nor local flags apply. + +### `d8 system edit ` + +Dumps the decoded YAML into a temporary file, opens it in your editor, and - **only if the content changed** - base64-encodes the result and patches it straight back onto the live Secret with a JSON merge patch. + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--editor` | `-e` | string | `$EDITOR`, else `vi` | Editor to launch. | + +Behavior worth knowing before you use it: + +- Change detection is a SHA-256 comparison of the file bytes. Identical content prints `Configurations are equal. Nothing to update.` and makes no API call; a real change prints `Secret updated successfully`. +- There is **no YAML or schema validation and no diff/confirmation prompt.** Whatever you save is written to the live Secret as-is, taking effect immediately. Malformed YAML will be stored unchanged. +- If the editor exits non-zero the command aborts before patching, so quitting your editor with an error is a safe way to cancel. +- The temporary file holds plaintext cluster configuration while you edit; it is removed on exit. Requires RBAC to get and patch the named Secret in `kube-system`. + +--- + +## Modules: `module` + +Manage the DKP module lifecycle. The state-changing commands (`enable`, `disable`, `maintenance`, `approve`, `apply-now`) edit `ModuleConfig`/`ModuleRelease` resources through the Kubernetes API; the read commands (`list`, `values`, `snapshots`) dump data from the controller's in-pod self-API and therefore need a running leader pod. + +Output convention across the group: lines reporting an **applied change** go to **stdout**; "already in that state" notices, warnings, and errors go to **stderr**. Message prefixes are colorized - `[INFO]` green, `[WARN]` yellow, `[ERROR]` red. + +### `d8 system module list` + +Lists the enabled modules by querying the controller. The payload is rendered by DKP and printed raw (there are no client-side columns). + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--output` | `-o` | string | `yaml` | Output format: `yaml` or `json`. | + +### `d8 system module enable ` / `disable ` + +Sets `spec.enabled` on the module's `ModuleConfig` (`deckhouse.io/v1alpha1`, cluster-scoped) to `true` / `false`. Takes exactly one argument, the module name. + +- If the `ModuleConfig` does not exist, **both** commands create it with the corresponding `spec.enabled` value - `disable` on an unknown module does not error, it creates a disabled config. +- If the module is already in the requested state, the command reports it on stderr and makes no change. +- `enable` has a dedicated hint path: when the admission webhook rejects a module as experimental, it prints a ready-to-run `kubectl patch` that sets `allowExperimentalModules: true` on the `deckhouse` ModuleConfig, then exits with an error. + +### `d8 system module maintenance enable|disable ` + +Toggles maintenance mode by setting or clearing `spec.maintenance` on the module's `ModuleConfig` (takes exactly one argument). While maintenance is on, Deckhouse stops reconciling that module's resources, which lets you hand-edit them. + +- `enable` sets `spec.maintenance: "NoResourceReconciliation"`; `disable` removes the field (restoring normal reconciliation). +- Unlike `module enable`/`disable`, this **does not create** the `ModuleConfig`. If it is missing the command prints `[ERROR] ModuleConfig '' does not exist.` and points you at `d8 system module enable ` first. + +### `d8 system module approve ` + +Approves a pending `ModuleRelease` for a module whose update policy is **Manual**, by adding the annotation `modules.deckhouse.io/approved="true"`. Requires exactly two arguments; a missing `v` prefix on the version is added automatically (`0.3.10` -> `v0.3.10`). + +- Only releases in the `Pending` phase can be approved. If the release is already approved, or is not in `Pending`, the command prints a notice and **exits 0** without changing anything - it does not treat these as errors. +- If the release is not found, it suggests the nearest versions and lists the pending releases available for that module. + +### `d8 system module apply-now ` + +Forces immediate deployment of a `ModuleRelease` (for modules on the **Auto** policy that have update windows or a future `applyAfter`), by adding the annotation `modules.deckhouse.io/apply-now="true"`. Same argument rules, phase checks, exit-0-on-noop behavior, and not-found suggestions as `approve`. + +### `d8 system module values ` / `snapshots ` + +Dump, respectively, the module's computed hook **values** and its hook **snapshots** (cached hook objects) from the controller's in-pod API. Each takes exactly one argument, the module name. + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--output` | `-o` | string | `yaml` | Output format: `yaml` or `json`. | + +--- + +## Packages: `package` + +### `d8 system package scan ` + +Triggers a full scan of a `PackageRepository` by creating a `PackageRepositoryOperation` resource (`deckhouse.io/v1alpha1`) with `spec.type: Update` and `spec.update.fullScan: true`. This is **fire-and-forget**: the command returns once the operation resource is created and does not wait for, or report, scan results. The repository name argument is required (shell completion offers existing `PackageRepository` names). + +| Flag | Type | Default | Description | +|---|---|---|---| +| `--timeout` | duration | `5m` | Scan timeout embedded into the created resource (`spec.update.timeout`). This is the **scan-side** timeout, not the CLI's API timeout. | +| `--name` | string | auto-generated | Name for the `PackageRepositoryOperation`. If omitted, a name is generated (`-scan-manual-...`). An explicit name that already exists makes creation fail (no upsert). | +| `--dry-run` | bool | `false` | Print the resource that would be created (as YAML) without creating it. | + +Note that `--dry-run` still contacts the cluster: the target `PackageRepository` is fetched (and validated to exist) *before* the dry-run branch, so dry-run needs connectivity and an existing repository. + +--- + +## Queues: `queue` + +Dump the controller's reconciliation queues. Both leaves exec into the leader pod and curl the controller self-API, then print its response. + +### `d8 system queue list` + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--output` | `-o` | string | `text` | Output format: `text`, `yaml`, or `json`. | +| `--show-empty` | `-e` | bool | `false` | Include empty queues. | +| `--watch` | `-w` | bool | `false` | Continuously re-render the queue in place. | + +`--watch` is a full-screen view that refreshes about once a second until you press `Ctrl+C`; it is only valid with `--output text` (combining it with `json`/`yaml` is rejected up front). + +### `d8 system queue main` + +Dumps only the main queue. Supports `--output` (`text`/`yaml`/`json`, default `text`) - it has no `--show-empty` or `--watch`. + +--- + +## Logs: `logs` + +Streams the `deckhouse` container log from the leader pod (`d8-system`) through the Kubernetes log API and copies it to stdout verbatim (no JSON parsing or reformatting). There is no per-module filter - it streams the whole controller log. + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--tail` | | int | `-1` | Limit output to the last N lines. `-1` means no limit (must be `>= -1`; `0` is also treated as no limit). | +| `--follow` | `-f` | bool | `false` | Stream new log lines as they arrive. | +| `--since` | | string | | Show logs newer than a relative duration, e.g. `5s`, `2m`, `1h`. | +| `--since-time` | | string | | Show logs after a timestamp, e.g. `2025-05-19 12:00:00` (interpreted as UTC). | + +`--since` and `--since-time` are mutually exclusive. Logs come from the current leader pod's live container instance only (there is no `--previous` and no multi-replica aggregation). + +--- + +## Debug archive: `collect-debug-info` + +Collects a wide cluster snapshot into a **gzipped tar streamed to stdout**, so you always redirect it to a file: + +```bash +d8 system collect-debug-info > deckhouse-debug-$(date +"%Y_%m_%d").tar.gz +``` + +It refuses to run when stdout is a terminal (to avoid dumping binary to your screen) unless you pass `--list-exclude`. The collection runs **inside** the leader pod: it executes on the order of ~60 diagnostic commands there (`deckhouse-controller queue list`, redacted global values, module/source/release inventories, cluster-wide `kubectl get` snapshots, and controller/etcd/apiserver/VPA/Prometheus logs, plus cloud-provider/cert-manager/istio/cni-cilium extras when those modules are Ready), writing each result as a file in the archive. + +| Flag | Short | Type | Default | Description | +|---|---|---|---|---| +| `--exclude` | | string list | (none) | Comma-separated list of elements to leave out of the archive. Matches by base name, so e.g. `ccm-logs` also drops the per-cloud `ccm-logs-.txt`. | +| `--list-exclude` | `-l` | bool | `false` | Print the names of everything that can be excluded, then exit. This path makes no cluster calls. | +| `--command-timeout` | | duration | `2m` | Timeout applied to each individual in-pod command. | +| `--request-interval` | | duration | `0` | Minimum gap between commands to avoid overloading the cluster (e.g. `200ms`, `1s`). `0` disables rate limiting. | + +**Handle the archive as sensitive.** Only `global-values.json` is redacted (its `kubeRBACProxyCA` and registry `dockercfg`); container logs and the raw `audit-policy` Secret are included unredacted. Also note that a file is written even when its source command fails or times out, so an entry may be empty rather than absent. + +--- + +## Examples + +```bash +# --- Configuration --- + +# View the cluster configuration +d8 system get cluster-configuration + +# Extract one field with yq +d8 system get provider-cluster-configuration | yq '.masterNodeGroup.replicas' + +# Edit the static cluster configuration in a specific editor +d8 system edit static-cluster-configuration --editor nano + + +# --- Modules --- + +# List enabled modules as JSON +d8 system module list -o json + +# Enable / disable a module +d8 system module enable cert-manager +d8 system module disable cni-cilium + +# Put a module into maintenance mode to hand-edit its resources, then release it +d8 system module maintenance enable my-module +d8 system module maintenance disable my-module + +# Approve a Manual-policy release, or force an Auto-policy release out the door now +d8 system module approve csi-hpe v0.3.10 +d8 system module apply-now csi-hpe 0.3.10 # 'v' prefix added automatically + +# Inspect a module's computed values and hook snapshots +d8 system module values prometheus -o json +d8 system module snapshots node-manager + + +# --- Packages --- + +# Trigger a repository scan; preview first with --dry-run +d8 system package scan my-repo --dry-run +d8 system package scan my-repo --timeout 10m + + +# --- Queues --- + +# Dump all queues, including empty ones, as YAML +d8 system queue list -o yaml --show-empty + +# Live-watch the queues (text only, Ctrl+C to stop) +d8 system queue list --watch + +# Just the main queue +d8 system queue main + + +# --- Logs --- + +# Follow the controller log, last 100 lines to start +d8 system logs --tail 100 --follow + +# Everything from the last 15 minutes +d8 system logs --since 15m + + +# --- Debug archive --- + +# List what can be excluded, then collect a trimmed archive +d8 system collect-debug-info --list-exclude +d8 system collect-debug-info --exclude ccm-logs,csi-controller-logs \ + > deckhouse-debug-$(date +"%Y_%m_%d").tar.gz + + +# --- Global flags --- + +# Target a specific cluster/context +d8 system --kubeconfig ~/.kube/prod.config --context prod module list +``` + +--- + +## Behavior and safety notes + +- **`edit` writes live, unvalidated.** No schema check, no diff, no confirmation - the patch lands on the `kube-system` Secret the moment you save a changed file. Quit the editor with a non-zero status to cancel. +- **`disable` creates a `ModuleConfig`** when none exists (as a disabled config), whereas **`maintenance` requires** the `ModuleConfig` to pre-exist. `enable`/`disable` auto-create; `maintenance` does not. +- **`approve` / `apply-now` are annotation-only and idempotent.** They never error on an already-annotated or non-`Pending` release; they print a notice and exit 0. +- **`package scan` does not scan locally and does not wait.** It creates a `PackageRepositoryOperation` and returns; results are reported by the platform, not the CLI. +- **In-pod commands need a leader pod.** `module list`/`values`/`snapshots`, `queue`, and `collect-debug-info` exec into the pod labeled `leader=true` in `d8-system`; without it they fail with `no pods deckhouse available in namespace d8-system`. +- **The debug archive is sensitive** (unredacted logs and the raw audit-policy Secret) and must be redirected to a file. +- **stdout vs stderr:** `module` state changes print to stdout while notices/warnings/errors print to stderr, which makes it easy to script against applied changes only.