Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
bffb43a
[deckhouse-cli] Add lockfile package for exclusive file locks
Glitchy-Sheep Jun 3, 2026
ba7cf8d
[deckhouse-cli] iam access: preallocate grant and summary slices
Glitchy-Sheep Jun 3, 2026
65c8fcf
[sig-migrate] Pad mutex sections with blank lines
Glitchy-Sheep Jun 3, 2026
adf25ac
[deckhouse-cli] Add registry-packages-proxy client (transport, routes…
Glitchy-Sheep Jun 3, 2026
596fb29
[deckhouse-cli] Add rpp endpoint discovery and tar.gz extraction
Glitchy-Sheep Jun 4, 2026
d2718a6
[deckhouse-cli] Test rpp client, endpoint and extraction
Glitchy-Sheep Jun 4, 2026
05a43af
[deckhouse-cli] Export the plugin contract decoder for reuse
Glitchy-Sheep Jun 4, 2026
62f46d3
[deckhouse-cli] Drop the direct-registry plugin service, keep the con…
Glitchy-Sheep Jun 4, 2026
5a101ce
[deckhouse-cli] Add plugins package scaffold (layout, flags, source)
Glitchy-Sheep Jun 5, 2026
d7ba99c
[deckhouse-cli] Add plugin requirements checks and cluster snapshot
Glitchy-Sheep Jun 5, 2026
893e1a9
[deckhouse-cli] Add plugin requirement validation and conflict resolu…
Glitchy-Sheep Jun 5, 2026
c519a31
[deckhouse-cli] Add requirements-aware version selection
Glitchy-Sheep Jun 5, 2026
3122ab6
[deckhouse-cli] Add atomic plugin install pipeline
Glitchy-Sheep Jun 8, 2026
275f428
[deckhouse-cli] Add rpp plugin source
Glitchy-Sheep Jun 8, 2026
9a795ba
[deckhouse-cli] Add plugin runtime gate and exec wrapper
Glitchy-Sheep Jun 8, 2026
e712736
[deckhouse-cli] Wire the rpp source and add list, versions, update, r…
Glitchy-Sheep Jun 8, 2026
5bb82b3
[deckhouse-cli] Add background plugin auto-update scheduler
Glitchy-Sheep Jun 9, 2026
7daa3f4
[deckhouse-cli] Restructure plugins cmd into thin cobra wrappers
Glitchy-Sheep Jun 9, 2026
bed5036
[deckhouse-cli] Add self-update version store
Glitchy-Sheep Jun 9, 2026
48fb008
[deckhouse-cli] Add atomic self-update via rpp
Glitchy-Sheep Jun 10, 2026
82298b0
[deckhouse-cli] Add synchronous self-update notice
Glitchy-Sheep Jun 10, 2026
b839a28
[deckhouse-cli] Add d8 cli command tree (check/update/use/versions/cron)
Glitchy-Sheep Jun 10, 2026
c259a1c
[deckhouse-cli] Wire plugins and self-update into root command
Glitchy-Sheep Jun 11, 2026
82640b4
[deckhouse-cli] Document plugins and self-update
Glitchy-Sheep Jun 11, 2026
52e1515
[deckhouse-cli] Update mirror pull --dry-run docs for digest streaming
Glitchy-Sheep Jun 11, 2026
cf7f464
[deckhouse-cli] Fix the self-update notice: resolve the default kubec…
Glitchy-Sheep Jun 12, 2026
0c55bd5
[deckhouse-cli] Remove automatic plugin and CLI update machinery
Glitchy-Sheep Jun 15, 2026
f159f87
[deckhouse-cli] Remove the unreachable plugins catalog listing
Glitchy-Sheep Jun 15, 2026
34ae5b5
[deckhouse-cli] Drop the unused PullImage descriptor
Glitchy-Sheep Jun 15, 2026
2619237
[deckhouse-cli] Drop the misleading deferred-verification notes
Glitchy-Sheep Jun 15, 2026
8e000b4
[deckhouse-cli] Fix comments left over from the removed background up…
Glitchy-Sheep Jun 15, 2026
7530d43
[deckhouse-cli] Unexport internal-only symbols
Glitchy-Sheep Jun 15, 2026
b7d86c2
[deckhouse-cli] Take the install lock when removing a plugin
Glitchy-Sheep Jun 15, 2026
648a3d2
[deckhouse-cli] Avoid a missing-binary window during plugin swap
Glitchy-Sheep Jun 15, 2026
a5ce891
[deckhouse-cli] Re-validate plugin requirements after conflict resolu…
Glitchy-Sheep Jun 16, 2026
bd446e9
[deckhouse-cli] Keep the plugin command when the install root is unav…
Glitchy-Sheep Jun 16, 2026
6987f11
[deckhouse-cli] Roll back current when the self-update PATH migration…
Glitchy-Sheep Jun 16, 2026
fc68b7b
[deckhouse-cli] Drop comments referencing removed update machinery
Glitchy-Sheep Jun 16, 2026
873142a
[deckhouse-cli] Remove the dead plugin .old backup
Glitchy-Sheep Jun 16, 2026
94a5ca8
[deckhouse-cli] Trim redundant indirection surfaced by review
Glitchy-Sheep Jun 16, 2026
e00359e
[deckhouse-cli] Make unsatisfied plugin-requirement errors actionable
Glitchy-Sheep Jun 16, 2026
116fed5
[deckhouse-cli] Document the on-disk store layout in package docs
Glitchy-Sheep Jun 16, 2026
f6186a9
[deckhouse-cli] Render unsatisfied plugin requirements as a bulleted …
Glitchy-Sheep Jun 16, 2026
0b37bee
[deckhouse-cli] Add semantic color to plugin install output
Glitchy-Sheep Jun 16, 2026
c127cef
[deckhouse-cli] Add semantic color and actionable errors to d8 cli ou…
Glitchy-Sheep Jun 16, 2026
c0a02e0
[deckhouse-cli] Map registry-packages-proxy errors to colored diagnos…
Glitchy-Sheep Jun 16, 2026
74fed04
[deckhouse-cli] Surface proxy endpoint discovery failures clearly
Glitchy-Sheep Jun 17, 2026
b4d0cea
[deckhouse-cli] Simplify and reflow comments for readability
Glitchy-Sheep Jun 17, 2026
01cd4c9
Resolve plugin dependencies automatically on install and update
Glitchy-Sheep Jun 19, 2026
439f9a0
[deckhouse-cli] Skip and name an unpublished plugin dependency
Glitchy-Sheep Jun 24, 2026
ace8faa
[deckhouse-cli] Render plugin install failures as actionable diagnostics
Glitchy-Sheep Jun 24, 2026
3a98b71
[deckhouse-cli] Name unresolved dependencies in plugin install errors
Glitchy-Sheep Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions cmd/d8/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ import (
mirror "github.com/deckhouse/deckhouse-cli/internal/mirror/cmd"
network "github.com/deckhouse/deckhouse-cli/internal/network"
packagecmd "github.com/deckhouse/deckhouse-cli/internal/packagecmd"
plugins "github.com/deckhouse/deckhouse-cli/internal/plugins/cmd"
"github.com/deckhouse/deckhouse-cli/internal/plugins/cmd/flags"
pluginscmd "github.com/deckhouse/deckhouse-cli/internal/plugins/cmd"
"github.com/deckhouse/deckhouse-cli/internal/plugins/flags"
selfupdatecmd "github.com/deckhouse/deckhouse-cli/internal/selfupdate/cmd"
status "github.com/deckhouse/deckhouse-cli/internal/status/cmd"
system "github.com/deckhouse/deckhouse-cli/internal/system/cmd"
"github.com/deckhouse/deckhouse-cli/internal/tools"
Expand Down Expand Up @@ -85,16 +86,11 @@ func NewRootCommand() *RootCommand {
},
}

envCliPath := os.Getenv("DECKHOUSE_CLI_PATH")
envCliPath := os.Getenv(flags.EnvPluginsDir)
if envCliPath != "" {
flags.DeckhousePluginsDir = envCliPath
}

envRegistryRepo := os.Getenv("DECKHOUSE_REGISTRY_REPO")
if envRegistryRepo != "" {
flags.SourceRegistryRepo = envRegistryRepo
}

rootCmd.registerCommands()
rootCmd.cmd.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)

Expand Down Expand Up @@ -128,12 +124,14 @@ func (r *RootCommand) registerCommands() {
if os.Getenv("DECKHOUSE_PLUGINS_ENABLED") != "true" {
r.cmd.AddCommand(system.NewCommand())
} else {
r.cmd.AddCommand(plugins.NewPluginCommand(plugins.SystemPluginName, "Operate system options in DKP", []string{"s", "p", "platform"}, r.logger.Named("system-command")))
r.cmd.AddCommand(pluginscmd.NewPluginCommand(pluginscmd.SystemPluginName, "Operate system options in DKP", []string{"s", "p", "platform"}, r.logger.Named("system-command")))
}

r.cmd.AddCommand(packagecmd.NewCommand())

r.cmd.AddCommand(plugins.NewCommand(r.logger.Named("plugins-command")))
r.cmd.AddCommand(pluginscmd.NewCommand(r.logger.Named("plugins-command")))

r.cmd.AddCommand(selfupdatecmd.NewCommand(r.logger.Named("cli-command")))
}

func (r *RootCommand) Execute() error {
Expand Down
84 changes: 48 additions & 36 deletions docs/mirror-pull-dry-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@ The key distinction from a no-op:
|------|-------------|---------|
| Validate registry access | yes | yes |
| Resolve versions / channels | yes | yes |
| Pull installer to tmpDir | yes | **yes** (needed to read `images_digests.json`) |
| 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) |
| Pull release-channel metadata | yes | yes |
| Download platform/module/security blobs | yes | **no** |
| Write `platform.tar`, `security.tar`, module tarballs | yes | **no** |
| Write `deckhousereleases.yaml` | yes | **no** |
| Compute GOST digests | yes | **no** |

Installer OCI layouts land in `--tmp-dir` (or `<bundle-path>/.tmp`) so the tool can
extract the built-in image digest list from `deckhouse/candi/images_digests.json`.
The **bundle directory** (first positional argument) remains empty.
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`
services still create their OCI layout directories under `--tmp-dir` (or
`<bundle-path>/.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
argument) remains empty.

---

Expand Down Expand Up @@ -51,18 +56,22 @@ Expected output (abbreviated):
```
INFO Skipped releases lookup as tag "v1.69.0" is specifically requested with --deckhouse-tag
INFO Deckhouse releases to pull: [1.69.0]
INFO ╔ Pull release channels and installers
INFO ║ [1 / 1] Pulling registry.deckhouse.io/deckhouse/install:v1.69.0
INFO ╚ Pull release channels and installers succeeded in …
INFO Extracting images digests from Deckhouse installer v1.69.0
INFO Searching for Deckhouse built-in modules digests
INFO [dry-run] Streaming installer metadata for v1.69.0 from registry
INFO Deckhouse digests found: 319
INFO Found 320 images
INFO [dry-run] Platform images that would be pulled:
INFO registry.deckhouse.io/deckhouse/fe@sha256:…
INFO registry.deckhouse.io/deckhouse/fe/release-channel:v1.69.0
INFO Deckhouse components: 319 images
INFO registry.deckhouse.io/deckhouse/fe@sha256:…
INFO Release channels: 1
INFO registry.deckhouse.io/deckhouse/fe/release-channel:v1.69.0
INFO Installer: 1
INFO registry.deckhouse.io/deckhouse/fe/install:v1.69.0
INFO Standalone installer: 1
INFO Total: 322 platform images
INFO [dry-run] Installer images that would be pulled:
INFO registry.deckhouse.io/deckhouse/fe/install:v1.69.0
INFO [dry-run] Done. No images were downloaded.
No images were downloaded (dry-run).
```

### All components
Expand Down Expand Up @@ -134,7 +143,7 @@ go test ./internal/mirror/... -run 'TestDryRun' -v -timeout 120s
# Pull-command level (flag registration, no bundle output, exit 0)
go test ./internal/mirror/cmd/pull/ -run 'TestDryRun' -v

# Platform service level (installer pulled to tmpDir, bundle stays empty)
# Platform service level (digests streamed, no platform OCI layout, bundle stays empty)
go test ./internal/mirror/platform/ -run 'TestDryRun' -v
```

Expand All @@ -148,7 +157,8 @@ go test ./internal/mirror/platform/ -run 'TestDryRun' -v
| `TestDryRunWithDeckhouseTag` | specific `--deckhouse-tag` works in dry-run |
| `TestDryRunExitsZeroOnSuccess` | `Execute()` returns `nil` |
| `TestDryRun_NoBundleFilesWritten` | platform service: bundleDir empty |
| `TestDryRun_InstallerPulledToTmpDir` | platform service: `<tmpDir>/platform/install/` exists |
| `TestDryRun_NoOCILayoutCreated` | platform service: `<tmpDir>/platform/install/` is **not** created (digests are streamed) |
| `TestDryRun_WorkingDirHasLayouts` | installer / security service: OCI layout dir staged in workingDir, bundleDir empty |

### Integration smoke test (real registry)

Expand All @@ -171,20 +181,22 @@ D8_TEST_LICENSE_TOKEN=<token> \

The test asserts:
1. `Execute()` returns `nil`
2. The bundle directory is **empty** — no `.tar` output
3. The tmp directory is **non-empty** — OCI layouts were written (installer pull), proving
`images_digests.json` extraction was attempted
2. The bundle directory has no `.tar` / `.chunk` output
3. The tmp directory is **non-empty** - the `installer`, `security` and `modules` services
stage OCI layout scaffolding there (no image blobs); the platform digest list itself is
streamed from the registry, not staged

Sample passing output:

```
=== RUN TestDryRunRealRegistry
INFO Deckhouse digests found: 319
INFO Found 320 images
INFO [dry-run] Platform images that would be pulled:
INFO registry.deckhouse.io/deckhouse/fe@sha256:e927fc9…
… 320 lines …
INFO Deckhouse components: 319 images
INFO registry.deckhouse.io/deckhouse/fe@sha256:e927fc9…
INFO Total: 322 platform images
… more dry-run plans for installer / security / modules …
pull_realregistry_test.go:94: tmpDir files written during dry-run: 51
pull_realregistry_test.go:95: bundleDir entries (must be 0): 0
--- PASS: TestDryRunRealRegistry (79.99s)
Expand All @@ -203,13 +215,12 @@ Puller.Execute()
│ ├─ findTagsToMirror() ← real network call
│ ├─ downloadList.FillDeckhouseImages() ← in-memory
│ └─ pullDeckhousePlatform()
│ ├─ pullDeckhouseReleaseChannels() ← writes to tmpDir
│ ├─ pullInstallers() ← writes to tmpDir ← KEY STEP
│ ├─ pullStandaloneInstallers() ← writes to tmpDir
│ │ (pullDeckhouseImages SKIPPED in dry-run)
│ ├─ ExtractImageDigestsFromInstaller ← reads images_digests.json
│ └─ [dry-run guard] print plan → return nil
│ (no platform.tar written)
│ └─ [DryRun] pullDeckhousePlatformDryRun()
│ ├─ extractImageDigestsFromRemote() ← streams images_tags.json /
│ │ images_digests.json from the remote image (no OCI layout)
│ └─ print plan → return nil
│ (pullDeckhouseReleaseChannels / pullInstallers /
│ pullStandaloneInstallers / pullDeckhouseImages all SKIPPED)
├─ installer.Service.PullInstaller() [DryRun=true]
│ ├─ validateInstallerAccess()
│ ├─ findTagsToMirror()
Expand All @@ -225,25 +236,26 @@ Puller.Execute()
└─ [dry-run guard] print plan → return nil

After Pull() returns:
if DryRun → print "[dry-run] Done." → return nil
if DryRun → print summary "No images were downloaded (dry-run)." → return nil
else → computeGOSTDigests, finalCleanup
```

The installer image **is** pulled to `tmpDir` in dry-run mode because
`images_digests.json` inside it is the only source for the ~300 component image
digest references that the platform bundle would contain. Without this step,
dry-run could only report the 5–10 top-level image tags, missing the vast majority
of what a real pull actually downloads.
The platform service reads `images_digests.json` (or `images_tags.json`) **without**
pulling the installer to `tmpDir`: `extractImageDigestsFromRemote` streams just the layer
containing that file straight from the registry. It is the only source for the ~300
component image digest references that the platform bundle would contain - without it,
dry-run could only report the 5-10 top-level image tags, missing the vast majority of
what a real pull actually downloads.

---

## Temporary files

| Location | Created in dry-run? | Description |
|----------|---------------------|-------------|
| `<tmpDir>/platform/install/` | **yes** | Installer OCI layout |
| `<tmpDir>/platform/install-standalone/` | **yes** | Standalone installer OCI layout |
| `<tmpDir>/platform/release/` | **yes** | Release-channel metadata OCI layout |
| `<tmpDir>/platform/...` | **no** | Platform digests are streamed; no platform OCI layout is written |
| `<tmpDir>/installer/` | **yes** | Installer OCI layout dir (scaffolding only, no blobs) |
| `<tmpDir>/security*/`, `<tmpDir>/modules*/` | **yes** | Security / module OCI layout dirs (scaffolding only, no blobs) |
| `<bundleDir>/platform.tar` | no | Not created |
| `<bundleDir>/security.tar` | no | Not created |
| `<bundleDir>/modules-*.tar` | no | Not created |
Expand Down
117 changes: 117 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# d8 Plugins (`d8 plugins`)

Plugins are versioned binaries distributed through the cluster registry.
`d8` installs, updates, and removes them for you.

**Contents:** [Source](#plugin-source) · [Commands](#commands) ·
[Versions & majors](#versions-majors-and-switching) ·
[Requirements](#requirements) ·
[Flags & env](#flags-and-environment-variables) ·
[Troubleshooting](#troubleshooting)

> [!NOTE]
> The `d8 plugins` command group is hidden from the root `--help` while the
> plugin ecosystem rolls out. The commands below are fully functional.

## 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`
command reaches the registry through the proxy, so a reachable cluster is
required. 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).

## Commands

| Command | What it does |
|---|---|
| `d8 plugins versions <name>` | lists all published versions of one plugin |
| `d8 plugins install <name>` | installs the newest version compatible with your cluster |
| `d8 plugins install <name> --version X` | installs an exact version |
| `d8 plugins install <name> --use-major N` | switches majors explicitly |
| `d8 plugins update <name>` / `update all` | updates within the current major |
| `d8 plugins list` | shows installed plugins (the proxy serves no catalog, so available plugins are not listed) |
| `d8 plugins contract <name>` | shows a plugin's contract: version, description, requirements |
| `d8 plugins remove <name>` / `remove all` | removes plugins |

```console
$ d8 plugins versions package
v0.1.2 newer
* v0.0.21 current
v0.0.20

$ d8 plugins install package
Installing plugin: package
Tag: v0.0.21
...
✓ Plugin 'package' successfully installed!
```

## Versions, majors and switching

Plugins are stored per major version, with a `current` symlink selecting the
active one:

```
/opt/deckhouse/lib/deckhouse-cli/plugins/<name>/v<major>/
```

Rules that follow from this layout:

- `d8 plugins update` stays **within the installed major**. Crossing majors is
always an explicit decision: `--use-major N` or `--version X`.
- Installing a version that is already on disk just repoints the symlink - no
download.
- Installing the active version says so and does nothing; `--force`
re-downloads.
- No root access to `/opt/deckhouse/lib`? Plugins go to `~/.deckhouse-cli`
automatically.

## Requirements

A plugin's contract may declare requirements:

- other plugins;
- Kubernetes / Deckhouse versions;
- enabled modules.

They are validated **before** anything is downloaded or switched:

```console
$ d8 plugins install package
...
Error: plugin requirements not satisfied # e.g. requires plugin delivery-kit
```

Plugins this one depends on are installed/upgraded automatically.

- `--skip-cluster-checks` (or `D8_PLUGINS_SKIP_CLUSTER_CHECKS=1`) - skip
cluster-side checks, e.g. in air-gapped scenarios.

## Flags and environment variables

| Flag | Env | Purpose |
|---|---|---|
| `--kubeconfig`, `-k` / `--context` | `KUBECONFIG` | cluster identity (the Bearer token source) |
| `--plugins-dir` | `DECKHOUSE_CLI_PATH` | plugins directory |
| `--skip-cluster-checks` | `D8_PLUGINS_SKIP_CLUSTER_CHECKS=1` | skip cluster-side requirement checks |
| `--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) |

## 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 <name>`; publishing is the plugin CI's job |
| `plugin requirements not satisfied` | the contract requires other plugins or cluster versions/modules | see `d8 plugins contract <name>`; 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 |
Loading
Loading