Skip to content

[deckhouse-cli] Let installed plugins take over built-in commands, and ship the platform's plugins with the bundle - #473

Merged
ldmonster merged 6 commits into
mainfrom
feat/use-plugins-or-fallback
Sep 3, 2026
Merged

[deckhouse-cli] Let installed plugins take over built-in commands, and ship the platform's plugins with the bundle#473
ldmonster merged 6 commits into
mainfrom
feat/use-plugins-or-fallback

Conversation

@ldmonster

Copy link
Copy Markdown
Collaborator

What this does

A plugin installed under a built-in command's name now serves that command, and d8 mirror puts the plugins the platform needs into the bundle. Around those two changes the plugin surface is tidied up: one command instead of install/update, an honest list, a readable versions, and an explicit split between the two transports.

Plugins override built-in commands

Nine top-level commands are now overridable: delivery-kit, data, snapshot, iam, network, v, stronghold, package, system. Install a plugin named exactly like one of them and it serves the command; with no such plugin installed the built-in does. The table lives in overridableCommands (cmd/d8/root.go).

  • Already-installed only. The decision never reaches the registry, so d8 starts with no network access. A plugin published upstream but not installed locally does not displace a built-in.
  • Canonical names only. A plugin must match the command's own name, never an alias — there is no dk plugin, only delivery-kit. The built-in's aliases carry over to the wrapper, so d8 dk and d8 s keep working after an override.
  • Lazily built. builtin is a thunk, so the werf and virtualization command trees are not assembled for a command a plugin serves.
  • Resolved before flag parsing, so only DECKHOUSE_CLI_PATH can retarget the plugins root; --plugins-dir is parsed too late. Both the configured root and the ~/.deckhouse-cli fallback are searched (layout.ResolveInstalled).
  • Dependency bookkeeping. delivery-kit and package satisfy a plugin's dependency on that name while they ship as built-ins; a name an installed plugin took over drops off that list so the real plugin satisfies it, version constraints included.

NewDeliveryCommand used to return the process-wide graceful-termination context that Execute runs on. It is now commands.NewRootContext(), established before any command is built — otherwise a plugin serving delivery-kit would take the whole termination path down with it.

d8 mirror: the platform brings its plugins

Mirroring the platform now also mirrors package and system — unconditionally, with no module pairing and regardless of --include-plugin. A bundle without them can install the platform but not operate it. This is a new ReasonPlatform provenance alongside the existing module/dependency/explicit ones, and the pull summary gets a platform group that leads the module groups.

The selected version still has to be one the mirrored platform can run: the contract's deckhouse constraint is checked like anywhere else. A registry that does not publish these plugins, or denies access to them, produces a warning rather than a failed pull — older registries predate them, and the platform itself mirrored fine.

Dependencies named after a built-in d8 command are now mirrored too when the registry publishes them. package depends on delivery-kit, and previously that dependency was short-circuited ("there is a built-in command, nothing to pull") and the plugin never entered the bundle. An air-gapped cluster cannot fetch it later, and once installed the plugin takes the command over, so the pull is attempted first and the built-in is only the fallback: when the plugin is not published, the dependency is still satisfied and only a note is recorded, so a dependent is never dropped over it.

install absorbs update

d8 dist plugins update is gone, with no deprecation shim. Installing a plugin that is already present updates it, which is what update <name> already did — it called the same InstallPlugin. The one capability worth keeping was bulk update:

d8 dist plugins install <name>          # install or update
d8 dist plugins install --all           # update every installed plugin
d8 dist plugins install --all --force   # the same, re-pulling

--all is a flag rather than a subcommand, so a plugin literally named all stays reachable. It rejects a plugin name, --version and --use-major — those pin a single plugin and cannot mean anything across a set — and accepts --force, which applies uniformly. The old update all silently ignored those flags.

Two transports, with different capabilities

The plugin subsystem reaches the registry over TransportRPP (the in-cluster proxy, default) or TransportRegistry (direct, via the hidden legacy --source). Both implement pluginSource and report which they are, so messages can name the transport.

They differ in exactly one capability, now expressed as a separate pluginCatalog interface: enumerating the published plugins. Only the registry transport has it. The proxy cannot serve it — its allowlist (isAllowedCLIImagePath in the Deckhouse repo) admits deckhouse-cli and deckhouse-cli/plugins/<name> and explicitly refuses the bare deckhouse-cli/plugins path, which is where the plugin-name tags live. rppPluginSource therefore does not implement pluginCatalog and the request is never made; AvailablePlugins returns ErrCatalogUnsupported naming the transport.

list shows both halves

d8 dist plugins list now prints the plugins installed on disk and, when the transport can enumerate them, those published in the registry and ready to install. Everything that cannot be resolved is reported in place rather than dropped:

  • registry unreachable → the reason, with the installed half intact;
  • names returned but empty → No plugins found in the registry;
  • name found, versions unreadable → the row with versions unavailable;
  • published but nothing stable → no versions found;
  • a name from the registry that cannot address a repository → reported, never turned into a route.

list also got its own PersistentPreRunE: the parent hook made InitPluginServices fatal for every subcommand, so an unreachable cluster used to suppress the installed list too, even though it is read straight from disk.

versions collapses per-platform tags

A release is published as a multi-platform index plus one tag per platform, so the listing repeated every version four or five times. They are now collapsed into one line per release with the platforms alongside:

  v0.0.35  newer    linux/amd64
* v0.0.34  current  linux/amd64, linux/arm64, darwin/arm64
  v0.0.33           linux/amd64, darwin/arm64

The platform is read as a suffix of the semver prerelease, not as the whole of it, so a release that is itself a pre-release keeps its identity: v0.0.1-test-linux-amd64 collapses onto v0.0.1-test, not onto v0.0.1. Detection requires both halves to be a real GOOS/GOARCH pair, so v2.0.0-rc.1 is never mistaken for a platform.

Also

  • d8 dist plugins is no longer hidden from d8 dist --help.
  • layout.InstalledNames / ResolveInstallRoot replace three copies of the "walk the plugins root for current symlinks" loop, and are the reason the override finds installs in the ~/.deckhouse-cli fallback.
  • NewPluginCommand takes options (WithInstallRoot), closing an existing TODO.

Breaking changes

  • d8 dist plugins update is removed. Scripts using it must switch to d8 dist plugins install <name> or install --all. No shim, no deprecation period.
  • DECKHOUSE_PLUGINS_ENABLED is removed. It gated only whether d8 system was the built-in or a plugin shim; that is now decided by whether a system plugin is installed.

Known limitation

The published-plugins half of d8 dist plugins list does not work over the proxy today: the allowlist refuses the plugins-repository path (a 404, not a 403). The CLI side is complete and degrades honestly; enabling it needs a one-line change to isAllowedCLIImagePath in the Deckhouse repo, plus a proxy release. Direct registry access (--source) enumerates today.

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
@ldmonster ldmonster self-assigned this Sep 3, 2026
@ldmonster ldmonster added the enhancement New feature or request label Sep 3, 2026
@ldmonster
ldmonster merged commit 93a076a into main Sep 3, 2026
9 checks passed
@ldmonster
ldmonster deleted the feat/use-plugins-or-fallback branch September 3, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant