From 8ac91c49e41326a91b7e292c951cf7174934e820 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 30 Mar 2026 12:13:21 +0300 Subject: [PATCH] [RFC] Flux CLI Plugin System Signed-off-by: Stefan Prodan --- rfcs/xxxx-cli-plugin-system/README.md | 312 ++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 rfcs/xxxx-cli-plugin-system/README.md diff --git a/rfcs/xxxx-cli-plugin-system/README.md b/rfcs/xxxx-cli-plugin-system/README.md new file mode 100644 index 00000000..1cb8983f --- /dev/null +++ b/rfcs/xxxx-cli-plugin-system/README.md @@ -0,0 +1,312 @@ +# RFC-XXXX Flux CLI Plugin System + +**Status:** provisional + +**Creation date:** 2026-03-30 + +**Last update:** 2026-03-30 + +## Summary + +This RFC proposes a plugin system for the Flux CLI that allows external CLI tools to be +discoverable and invocable as `flux ` subcommands. Plugins are installed from a +centralized catalog hosted on GitHub, with SHA-256 checksum verification and automatic +version updates. The design follows the established kubectl plugin pattern used across +the Kubernetes ecosystem. + +## Motivation + +The Flux CLI currently has no mechanism for extending its functionality with external tools. +Projects like [flux-operator](https://github.com/controlplaneio-fluxcd/flux-operator) and +[flux-local](https://github.com/allenporter/flux-local) provide complementary CLI tools +that users install and invoke separately. This creates a fragmented user experience where +Flux-related workflows require switching between multiple binaries with different flag +conventions and discovery mechanisms. + +The Kubernetes ecosystem has a proven model for CLI extensibility: kubectl plugins are +executables prefixed with `kubectl-` that can be discovered, installed via +[krew](https://krew.sigs.k8s.io/), and invoked as `kubectl `. This model has +been widely adopted and is well understood by Kubernetes users. + +### Goals + +- Allow external CLI tools to be invoked as `flux ` subcommands without modifying + the external binary. +- Provide a `flux plugin install` command to download plugins from a centralized catalog + with checksum verification. +- Support shell completion for plugin subcommands by delegating to the plugin's own + Cobra `__complete` command. +- Support plugins written as scripts (Python, Bash, etc.) via symlinks into the + plugin directory. +- Ensure built-in commands always take priority over plugins. +- Keep the plugin system lightweight with zero impact on non-plugin Flux commands. + +### Non-Goals + +- Plugin dependency management (plugins are standalone binaries). +- Cosign/SLSA signature verification (SHA-256 only in v1beta1; signatures can be added later). +- Automatic update checks on startup (users run `flux plugin update` explicitly). +- Private catalog authentication (users can use `$FLUXCD_PLUGIN_CATALOG` with TLS). +- Flag sharing between Flux and plugins (`--namespace`, `--context`, etc. are not + forwarded; plugins manage their own flags). + +## Proposal + +### Plugin Discovery + +Plugins are executables prefixed with `flux-` placed in a single plugin directory. +The `flux-` binary maps to the `flux ` command. For example, +`flux-operator` becomes `flux operator`. + +The default plugin directory is `~/.fluxcd/plugins/`. Users can override it with the +`$FLUXCD_PLUGINS` environment variable. Only this single directory is scanned. + +When a plugin is discovered, it appears under a "Plugin Commands:" group in `flux --help`: + +``` +Plugin Commands: + operator Runs the operator plugin + +Additional Commands: + bootstrap Deploy Flux on a cluster the GitOps way. + ... +``` + +### Plugin Execution + +On macOS and Linux, `flux operator export report` replaces the current process with +`flux-operator export report` via `syscall.Exec`, matching kubectl's behavior. +On Windows, the plugin runs as a child process with full I/O passthrough. +All arguments after the plugin name are passed through verbatim with +`DisableFlagParsing: true`. + +### Shell Completion + +Shell completion is delegated to the plugin binary via Cobra's `__complete` protocol. +When the user types `flux operator get `, Flux runs +`flux-operator __complete get ""` and returns the results. This works automatically +for all Cobra-based plugins (like flux-operator). Non-Cobra plugins gracefully degrade +to no completions. + +### Plugin Catalog + +A dedicated GitHub repository ([fluxcd/plugins](https://github.com/fluxcd/plugins)) +serves as the plugin catalog. Each plugin has a YAML manifest: + +```yaml +apiVersion: cli.fluxcd.io/v1beta1 +kind: Plugin +name: operator +description: Flux Operator CLI +homepage: https://fluxoperator.dev/ +source: https://github.com/controlplaneio-fluxcd/flux-operator +bin: flux-operator +versions: + - version: 0.45.0 + platforms: + - os: darwin + arch: arm64 + url: https://github.com/.../flux-operator_0.45.0_darwin_arm64.tar.gz + checksum: sha256:cd85d5d84d264... + - os: linux + arch: amd64 + url: https://github.com/.../flux-operator_0.45.0_linux_amd64.tar.gz + checksum: sha256:96198da969096... +``` + +A generated `catalog.yaml` (`PluginCatalog` kind) contains static metadata for all +plugins, enabling `flux plugin search` with a single HTTP fetch. + +### CLI Commands + +| Command | Description | +|---------|-------------| +| `flux plugin list` (alias: `ls`) | List installed plugins with versions and paths | +| `flux plugin install [@]` | Install a plugin from the catalog | +| `flux plugin uninstall ` | Remove a plugin binary and receipt | +| `flux plugin update [name]` | Update one or all installed plugins | +| `flux plugin search [query]` | Search the plugin catalog | + +### Install Flow + +1. Fetch `plugins/.yaml` from the catalog URL +2. Validate `apiVersion: cli.fluxcd.io/v1beta1` and `kind: Plugin` +3. Resolve version (latest if unspecified, or match `@version`) +4. Find platform entry matching `runtime.GOOS` / `runtime.GOARCH` +5. Download archive to temp file with SHA-256 checksum verification +6. Extract only the declared binary from the archive (tar.gz or zip), streaming + directly to disk without buffering in memory +7. Write binary to plugin directory as `flux-` (mode `0755`) +8. Write install receipt (`flux-.yaml`) recording version, platform, download URL, checksum and timestamp + +Install is idempotent -- reinstalling overwrites the binary and receipt. + +### Install Receipts + +When a plugin is installed via `flux plugin install`, a receipt file is written +next to the binary: + +```yaml +name: operator +version: "0.45.0" +installedAt: "2026-03-30T10:00:00Z" +platform: + os: darwin + arch: arm64 + url: https://github.com/.../flux-operator_0.45.0_darwin_arm64.tar.gz + checksum: sha256:cd85d5d84d264... +``` + +Receipts enable `flux plugin list` to show versions, `flux plugin update` to compare +installed vs. latest, and provenance tracking. Manually installed plugins (no receipt) +show `manual` in listings and are skipped by `flux plugin update`. + +### User Stories + +#### Flux User Installs a Plugin + +As a Flux user, I want to install the Flux Operator CLI as a plugin so that I can +manage Flux instances using `flux operator` instead of a separate `flux-operator` binary. + +```bash +flux plugin install operator +flux operator get instance -n flux-system +``` + +#### Flux User Updates Plugins + +As a Flux user, I want to update all my installed plugins to the latest versions +with a single command. + +```bash +flux plugin update +``` + +#### Flux User Symlinks a Python Plugin + +As a Flux user, I want to use [flux-local](https://github.com/allenporter/flux-local) +(a Python tool) as a Flux CLI plugin by symlinking it into the plugin directory. +Since flux-local is not a Go binary distributed via the catalog, I install it with +pip and register it manually. + +```bash +uv venv +source .venv/bin/activate +uv pip install flux-local +ln -s "$(pwd)/.venv/bin/flux-local" ~/.fluxcd/plugins/flux-local +flux local test +``` + +Manually symlinked plugins show `manual` in `flux plugin list` and are skipped by +`flux plugin update`. + +#### Flux User Discovers Available Plugins + +As a Flux user, I want to search for available plugins so that I can extend my +Flux CLI with community tools. + +```bash +flux plugin search +``` + +#### Plugin Author Publishes a Plugin + +As a plugin author, I want to submit my tool to the Flux plugin catalog so that +Flux users can install it with `flux plugin install `. + +1. Release binary with GoReleaser (produces tarballs/zips + checksums) +2. Submit a PR to `fluxcd/plugins` with `plugins/.yaml` +3. Subsequent releases are picked up by automated polling workflows + +### Alternatives + +#### PATH-based Discovery (kubectl model) + +kubectl discovers plugins by scanning `$PATH` for `kubectl-*` executables. This is +simple but has drawbacks: + +- Scanning the entire PATH is slow on some systems +- No control over what's discoverable (any `flux-*` binary on PATH becomes a plugin) +- No install/update mechanism built in (requires a separate tool like krew) + +The single-directory approach is faster, more predictable, and integrates install/update +directly into the CLI. + +## Design Details + +### Package Structure + +``` +internal/plugin/ + discovery.go # Plugin dir scanning, DI-based Handler + completion.go # Shell completion via Cobra __complete protocol + exec_unix.go # syscall.Exec (//go:build !windows) + exec_windows.go # os/exec fallback (//go:build windows) + catalog.go # Catalog fetching, manifest parsing, version/platform resolution + install.go # Download, verify, extract, receipts + update.go # Compare receipts vs catalog, update check + +cmd/flux/ + plugin.go # Cobra command registration, all plugin subcommands +``` + +The `internal/plugin` package uses dependency injection (injectable `ReadDir`, `Stat`, +`GetEnv`, `HomeDir` on a `Handler` struct) for testability. Tests mock these functions +directly without filesystem fixtures. + +### Plugin Directory + +- **Default**: `~/.fluxcd/plugins/` -- auto-created by install/update commands + (best-effort, no error if filesystem is read-only). +- **Override**: `$FLUXCD_PLUGINS` env var replaces the default directory path. + When set, the CLI does not auto-create the directory. + +### Startup Behavior + +`registerPlugins()` is called in `main()` before `rootCmd.Execute()`. It scans the +plugin directory and registers discovered plugins as Cobra subcommands. The scan is +lightweight (a single `ReadDir` call) and only occurs if the plugin directory exists. +Built-in commands always take priority. + +### Manifest Validation + +Both plugin manifests and the catalog are validated after fetching: + +- `apiVersion` must be `cli.fluxcd.io/v1beta1` +- `kind` must be `Plugin` or `PluginCatalog` respectively +- Checksum format is `:` (currently `sha256:...`), allowing future + algorithm migration without schema changes + +### Security Considerations + +- **Checksum verification**: All downloaded archives are verified against SHA-256 + checksums declared in the catalog manifest before extraction. +- **Path traversal protection**: Archive extraction guards against tar traversal. +- **Response size limits**: HTTP responses from the catalog are capped at 10 MiB to + prevent unbounded memory allocation from malicious servers. +- **No code execution during discovery**: Plugin directory scanning only reads directory + entries and file metadata. No plugin binary is executed during startup. +- **Retryable fetching**: All HTTP/S operations use automatic retries for transient network failures. + +### Catalog Repository CI + +The `fluxcd/plugins` repository includes CI workflows that: + +1. Validate plugin manifests on every PR (schema, name consistency, URL reachability, + checksum verification, binary presence in archives, no builtin collisions) +2. Regenerate `catalog.yaml` when plugins are added or removed +3. Automatically poll upstream repositories for new releases and create update PRs + +### Known Limitations (v1beta1) + +1. **No cosign/SLSA verification** -- SHA-256 only. Signature verification can be added later. +2. **No plugin dependencies** -- plugins are standalone binaries. +3. **No automatic update checks** -- users run `flux plugin update` explicitly. +4. **No private catalog auth** -- `$FLUXCD_PLUGIN_CATALOG` works for private URLs but no token injection. +5. **No version constraints** -- no `>=0.44.0` ranges. Exact version or latest only. +6. **Flag names differ between Flux and plugins** -- e.g., `--context` (flux) vs + `--kube-context` (flux-operator). This is a plugin concern, not a system concern. + +## Implementation History + +- **2026-03-30** PoC plugin catalog repository with example manifests and CI validation workflows available at [fluxcd/plugins](https://github.com/fluxcd/plugins).