From 474efa09cf2681c6a7fec5a763ecb3f596e0d96c Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 20 Apr 2026 21:42:42 +0300 Subject: [PATCH] Split plugin commands into individual files Signed-off-by: Stefan Prodan --- cmd/flux/plugin.go | 228 ----------------------------------- cmd/flux/plugin_install.go | 80 ++++++++++++ cmd/flux/plugin_list.go | 57 +++++++++ cmd/flux/plugin_search.go | 81 +++++++++++++ cmd/flux/plugin_uninstall.go | 48 ++++++++ cmd/flux/plugin_update.go | 102 ++++++++++++++++ 6 files changed, 368 insertions(+), 228 deletions(-) create mode 100644 cmd/flux/plugin_install.go create mode 100644 cmd/flux/plugin_list.go create mode 100644 cmd/flux/plugin_search.go create mode 100644 cmd/flux/plugin_uninstall.go create mode 100644 cmd/flux/plugin_update.go diff --git a/cmd/flux/plugin.go b/cmd/flux/plugin.go index 44df6385..efeb3df3 100644 --- a/cmd/flux/plugin.go +++ b/cmd/flux/plugin.go @@ -18,7 +18,6 @@ package main import ( "fmt" - "runtime" "strings" "time" @@ -26,7 +25,6 @@ import ( "github.com/spf13/cobra" "github.com/fluxcd/flux2/v2/internal/plugin" - "github.com/fluxcd/flux2/v2/pkg/printers" ) var pluginHandler = plugin.NewHandler() @@ -41,66 +39,7 @@ var pluginCmd = &cobra.Command{ }, } -var pluginListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List installed plugins", - Long: `The plugin list command shows all installed plugins with their versions and paths.`, - RunE: pluginListCmdRun, -} - -var pluginInstallCmd = &cobra.Command{ - Use: "install [@]", - Short: "Install a plugin from the catalog", - Long: `The plugin install command downloads and installs a plugin from the Flux plugin catalog. - -Examples: - # Install the latest version - flux plugin install operator - - # Install a specific version - flux plugin install operator@0.45.0`, - Args: cobra.ExactArgs(1), - RunE: pluginInstallCmdRun, -} - -var pluginUninstallCmd = &cobra.Command{ - Use: "uninstall ", - Short: "Uninstall a plugin", - Long: `The plugin uninstall command removes a plugin binary and its receipt from the plugin directory.`, - Args: cobra.ExactArgs(1), - RunE: pluginUninstallCmdRun, -} - -var pluginUpdateCmd = &cobra.Command{ - Use: "update [name]", - Short: "Update installed plugins", - Long: `The plugin update command updates installed plugins to their latest versions. - -Examples: - # Update a single plugin - flux plugin update operator - - # Update all installed plugins - flux plugin update`, - Args: cobra.MaximumNArgs(1), - RunE: pluginUpdateCmdRun, -} - -var pluginSearchCmd = &cobra.Command{ - Use: "search [query]", - Short: "Search the plugin catalog", - Long: `The plugin search command lists available plugins from the Flux plugin catalog.`, - Args: cobra.MaximumNArgs(1), - RunE: pluginSearchCmdRun, -} - func init() { - pluginCmd.AddCommand(pluginListCmd) - pluginCmd.AddCommand(pluginInstallCmd) - pluginCmd.AddCommand(pluginUninstallCmd) - pluginCmd.AddCommand(pluginUpdateCmd) - pluginCmd.AddCommand(pluginSearchCmd) rootCmd.AddCommand(pluginCmd) } @@ -149,173 +88,6 @@ func registerPlugins() { } } -func pluginListCmdRun(cmd *cobra.Command, args []string) error { - pluginDir := pluginHandler.PluginDir() - plugins := pluginHandler.Discover(builtinCommandNames()) - if len(plugins) == 0 { - cmd.Println("No plugins found") - return nil - } - - header := []string{"NAME", "VERSION", "PATH"} - var rows [][]string - for _, p := range plugins { - version := "manual" - if receipt := plugin.ReadReceipt(pluginDir, p.Name); receipt != nil { - version = receipt.Version - } - rows = append(rows, []string{p.Name, version, p.Path}) - } - - return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) -} - -func pluginInstallCmdRun(cmd *cobra.Command, args []string) error { - nameVersion := args[0] - name, version := parseNameVersion(nameVersion) - - catalogClient := newCatalogClient() - manifest, err := catalogClient.FetchManifest(name) - if err != nil { - return err - } - - pv, err := plugin.ResolveVersion(manifest, version) - if err != nil { - return err - } - - plat, err := plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH) - if err != nil { - return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH) - } - - pluginDir := pluginHandler.EnsurePluginDir() - - installer := plugin.NewInstaller() - sp := newPluginSpinner(fmt.Sprintf("installing %s v%s", name, pv.Version)) - sp.Start() - if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { - sp.Stop() - return err - } - sp.Stop() - - logger.Successf("installed %s v%s", name, pv.Version) - return nil -} - -func pluginUninstallCmdRun(cmd *cobra.Command, args []string) error { - name := args[0] - pluginDir := pluginHandler.PluginDir() - - if err := plugin.Uninstall(pluginDir, name); err != nil { - return err - } - - logger.Successf("uninstalled %s", name) - return nil -} - -func pluginUpdateCmdRun(cmd *cobra.Command, args []string) error { - catalogClient := newCatalogClient() - - plugins := pluginHandler.Discover(builtinCommandNames()) - if len(plugins) == 0 { - cmd.Println("No plugins found") - return nil - } - - // If a specific plugin is requested, filter to just that one. - if len(args) == 1 { - name := args[0] - var found bool - for _, p := range plugins { - if p.Name == name { - plugins = []plugin.Plugin{p} - found = true - break - } - } - if !found { - return fmt.Errorf("plugin %q is not installed", name) - } - } - - pluginDir := pluginHandler.EnsurePluginDir() - installer := plugin.NewInstaller() - for _, p := range plugins { - result := plugin.CheckUpdate(pluginDir, p.Name, catalogClient, runtime.GOOS, runtime.GOARCH) - if result.Err != nil { - logger.Failuref("error checking %s: %v", p.Name, result.Err) - continue - } - if result.Skipped { - if result.SkipReason == plugin.SkipReasonManual { - logger.Warningf("skipping %s (%s)", p.Name, result.SkipReason) - } else { - logger.Successf("%s already up to date (v%s)", p.Name, result.FromVersion) - } - continue - } - - sp := newPluginSpinner(fmt.Sprintf("updating %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion)) - sp.Start() - if err := installer.Install(pluginDir, result.Manifest, result.Version, result.Platform); err != nil { - sp.Stop() - logger.Failuref("error updating %s: %v", p.Name, err) - continue - } - sp.Stop() - logger.Successf("updated %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion) - } - - return nil -} - -func pluginSearchCmdRun(cmd *cobra.Command, args []string) error { - catalogClient := newCatalogClient() - catalog, err := catalogClient.FetchCatalog() - if err != nil { - return err - } - - var query string - if len(args) == 1 { - query = strings.ToLower(args[0]) - } - - pluginDir := pluginHandler.PluginDir() - header := []string{"NAME", "DESCRIPTION", "INSTALLED"} - var rows [][]string - for _, entry := range catalog.Plugins { - if query != "" { - if !strings.Contains(strings.ToLower(entry.Name), query) && - !strings.Contains(strings.ToLower(entry.Description), query) { - continue - } - } - - installed := "" - if receipt := plugin.ReadReceipt(pluginDir, entry.Name); receipt != nil { - installed = receipt.Version - } - - rows = append(rows, []string{entry.Name, entry.Description, installed}) - } - - if len(rows) == 0 { - if query != "" { - cmd.Printf("No plugins matching %q found in catalog\n", query) - } else { - cmd.Println("No plugins found in catalog") - } - return nil - } - - return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) -} - // parseNameVersion splits "operator@0.45.0" into ("operator", "0.45.0"). // If no @ is present, version is empty (latest). func parseNameVersion(s string) (string, string) { diff --git a/cmd/flux/plugin_install.go b/cmd/flux/plugin_install.go new file mode 100644 index 00000000..71b9b643 --- /dev/null +++ b/cmd/flux/plugin_install.go @@ -0,0 +1,80 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "runtime" + + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" +) + +var pluginInstallCmd = &cobra.Command{ + Use: "install [@]", + Short: "Install a plugin from the catalog", + Long: `The plugin install command downloads and installs a plugin from the Flux plugin catalog. + +Examples: + # Install the latest version + flux plugin install operator + + # Install a specific version + flux plugin install operator@0.45.0`, + Args: cobra.ExactArgs(1), + RunE: pluginInstallCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginInstallCmd) +} + +func pluginInstallCmdRun(cmd *cobra.Command, args []string) error { + nameVersion := args[0] + name, version := parseNameVersion(nameVersion) + + catalogClient := newCatalogClient() + manifest, err := catalogClient.FetchManifest(name) + if err != nil { + return err + } + + pv, err := plugin.ResolveVersion(manifest, version) + if err != nil { + return err + } + + plat, err := plugin.ResolvePlatform(pv, runtime.GOOS, runtime.GOARCH) + if err != nil { + return fmt.Errorf("plugin %q v%s has no binary for %s/%s", name, pv.Version, runtime.GOOS, runtime.GOARCH) + } + + pluginDir := pluginHandler.EnsurePluginDir() + + installer := plugin.NewInstaller() + sp := newPluginSpinner(fmt.Sprintf("installing %s v%s", name, pv.Version)) + sp.Start() + if err := installer.Install(pluginDir, manifest, pv, plat); err != nil { + sp.Stop() + return err + } + sp.Stop() + + logger.Successf("installed %s v%s", name, pv.Version) + return nil +} diff --git a/cmd/flux/plugin_list.go b/cmd/flux/plugin_list.go new file mode 100644 index 00000000..cdc58ddc --- /dev/null +++ b/cmd/flux/plugin_list.go @@ -0,0 +1,57 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" + "github.com/fluxcd/flux2/v2/pkg/printers" +) + +var pluginListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List installed plugins", + Long: `The plugin list command shows all installed plugins with their versions and paths.`, + RunE: pluginListCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginListCmd) +} + +func pluginListCmdRun(cmd *cobra.Command, args []string) error { + pluginDir := pluginHandler.PluginDir() + plugins := pluginHandler.Discover(builtinCommandNames()) + if len(plugins) == 0 { + cmd.Println("No plugins found") + return nil + } + + header := []string{"NAME", "VERSION", "PATH"} + var rows [][]string + for _, p := range plugins { + version := "manual" + if receipt := plugin.ReadReceipt(pluginDir, p.Name); receipt != nil { + version = receipt.Version + } + rows = append(rows, []string{p.Name, version, p.Path}) + } + + return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) +} diff --git a/cmd/flux/plugin_search.go b/cmd/flux/plugin_search.go new file mode 100644 index 00000000..0c9add19 --- /dev/null +++ b/cmd/flux/plugin_search.go @@ -0,0 +1,81 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" + "github.com/fluxcd/flux2/v2/pkg/printers" +) + +var pluginSearchCmd = &cobra.Command{ + Use: "search [query]", + Short: "Search the plugin catalog", + Long: `The plugin search command lists available plugins from the Flux plugin catalog.`, + Args: cobra.MaximumNArgs(1), + RunE: pluginSearchCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginSearchCmd) +} + +func pluginSearchCmdRun(cmd *cobra.Command, args []string) error { + catalogClient := newCatalogClient() + catalog, err := catalogClient.FetchCatalog() + if err != nil { + return err + } + + var query string + if len(args) == 1 { + query = strings.ToLower(args[0]) + } + + pluginDir := pluginHandler.PluginDir() + header := []string{"NAME", "DESCRIPTION", "INSTALLED"} + var rows [][]string + for _, entry := range catalog.Plugins { + if query != "" { + if !strings.Contains(strings.ToLower(entry.Name), query) && + !strings.Contains(strings.ToLower(entry.Description), query) { + continue + } + } + + installed := "" + if receipt := plugin.ReadReceipt(pluginDir, entry.Name); receipt != nil { + installed = receipt.Version + } + + rows = append(rows, []string{entry.Name, entry.Description, installed}) + } + + if len(rows) == 0 { + if query != "" { + cmd.Printf("No plugins matching %q found in catalog\n", query) + } else { + cmd.Println("No plugins found in catalog") + } + return nil + } + + return printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) +} diff --git a/cmd/flux/plugin_uninstall.go b/cmd/flux/plugin_uninstall.go new file mode 100644 index 00000000..844f6d5a --- /dev/null +++ b/cmd/flux/plugin_uninstall.go @@ -0,0 +1,48 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" +) + +var pluginUninstallCmd = &cobra.Command{ + Use: "uninstall ", + Aliases: []string{"delete"}, + Short: "Uninstall a plugin", + Long: `The plugin uninstall command removes a plugin binary and its receipt from the plugin directory.`, + Args: cobra.ExactArgs(1), + RunE: pluginUninstallCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginUninstallCmd) +} + +func pluginUninstallCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + pluginDir := pluginHandler.PluginDir() + + if err := plugin.Uninstall(pluginDir, name); err != nil { + return err + } + + logger.Successf("uninstalled %s", name) + return nil +} diff --git a/cmd/flux/plugin_update.go b/cmd/flux/plugin_update.go new file mode 100644 index 00000000..db3a5e0e --- /dev/null +++ b/cmd/flux/plugin_update.go @@ -0,0 +1,102 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "runtime" + + "github.com/spf13/cobra" + + "github.com/fluxcd/flux2/v2/internal/plugin" +) + +var pluginUpdateCmd = &cobra.Command{ + Use: "update [name]", + Aliases: []string{"upgrade"}, + Short: "Update installed plugins", + Long: `The plugin update command updates installed plugins to their latest versions. + +Examples: + # Update a single plugin + flux plugin update operator + + # Update all installed plugins + flux plugin update`, + Args: cobra.MaximumNArgs(1), + RunE: pluginUpdateCmdRun, +} + +func init() { + pluginCmd.AddCommand(pluginUpdateCmd) +} + +func pluginUpdateCmdRun(cmd *cobra.Command, args []string) error { + catalogClient := newCatalogClient() + + plugins := pluginHandler.Discover(builtinCommandNames()) + if len(plugins) == 0 { + cmd.Println("No plugins found") + return nil + } + + // If a specific plugin is requested, filter to just that one. + if len(args) == 1 { + name := args[0] + var found bool + for _, p := range plugins { + if p.Name == name { + plugins = []plugin.Plugin{p} + found = true + break + } + } + if !found { + return fmt.Errorf("plugin %q is not installed", name) + } + } + + pluginDir := pluginHandler.EnsurePluginDir() + installer := plugin.NewInstaller() + for _, p := range plugins { + result := plugin.CheckUpdate(pluginDir, p.Name, catalogClient, runtime.GOOS, runtime.GOARCH) + if result.Err != nil { + logger.Failuref("error checking %s: %v", p.Name, result.Err) + continue + } + if result.Skipped { + if result.SkipReason == plugin.SkipReasonManual { + logger.Warningf("skipping %s (%s)", p.Name, result.SkipReason) + } else { + logger.Successf("%s already up to date (v%s)", p.Name, result.FromVersion) + } + continue + } + + sp := newPluginSpinner(fmt.Sprintf("updating %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion)) + sp.Start() + if err := installer.Install(pluginDir, result.Manifest, result.Version, result.Platform); err != nil { + sp.Stop() + logger.Failuref("error updating %s: %v", p.Name, err) + continue + } + sp.Stop() + logger.Successf("updated %s v%s → v%s", p.Name, result.FromVersion, result.ToVersion) + } + + return nil +}