diff --git a/app/electron/main.ts b/app/electron/main.ts index bab5cd2024..fd9347a7d4 100644 --- a/app/electron/main.ts +++ b/app/electron/main.ts @@ -69,6 +69,10 @@ const args = yargs(hideBin(process.argv)) describe: 'Disable use of GPU. For people who may have buggy graphics drivers', type: 'boolean', }, + 'list-plugins': { + describe: 'List all static and user-added plugins.', + type: 'boolean', + }, }) .positional('kubeconfig', { describe: @@ -78,6 +82,28 @@ const args = yargs(hideBin(process.argv)) .help() .parseSync(); +const listPlugins = args['list-plugins'] === true; + +// If the user wants to list plugins, we don't need to start the app. +if (listPlugins) { + const staticDir = path.join(process.resourcesPath, '.plugins'); + const pluginsDir = path.join(app.getPath('userData'), 'plugins'); + + const staticPlugins = fs.existsSync(staticDir) ? fs.readdirSync(staticDir) : []; + const userPlugins = fs.existsSync(pluginsDir) ? fs.readdirSync(pluginsDir) : []; + + console.log(' Static Plugins:'); + staticPlugins.forEach(plugin => { + console.log(` - ${plugin}`); + }); + console.log(' User Plugins:'); + userPlugins.forEach(plugin => { + console.log(` - ${plugin}`); + }); + + process.exit(0); +} + const isHeadlessMode = args.headless === true; let disableGPU = args['disable-gpu'] === true; const defaultPort = 4466; diff --git a/backend/cmd/server.go b/backend/cmd/server.go index 2959990c40..142443f9a6 100644 --- a/backend/cmd/server.go +++ b/backend/cmd/server.go @@ -8,6 +8,7 @@ import ( "github.com/headlamp-k8s/headlamp/backend/pkg/config" "github.com/headlamp-k8s/headlamp/backend/pkg/kubeconfig" "github.com/headlamp-k8s/headlamp/backend/pkg/logger" + "github.com/headlamp-k8s/headlamp/backend/pkg/plugins" ) func main() { @@ -17,6 +18,15 @@ func main() { os.Exit(1) } + if conf.ListPlugins { + if err := plugins.ListPlugins(conf.StaticDir, conf.PluginsDir); err != nil { + logger.Log(logger.LevelError, nil, err, "error listing plugins") + os.Exit(1) + } + + os.Exit(0) + } + cache := cache.New[interface{}]() kubeConfigStore := kubeconfig.NewContextStore() diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go index bb110b897e..bebd87f423 100644 --- a/backend/pkg/config/config.go +++ b/backend/pkg/config/config.go @@ -25,6 +25,7 @@ type Config struct { InsecureSsl bool `koanf:"insecure-ssl"` EnableHelm bool `koanf:"enable-helm"` EnableDynamicClusters bool `koanf:"enable-dynamic-clusters"` + ListPlugins bool `koanf:"list-plugins"` Port uint `koanf:"port"` KubeConfigPath string `koanf:"kubeconfig"` StaticDir string `koanf:"html-static-dir"` @@ -153,6 +154,7 @@ func flagset() *flag.FlagSet { f.Bool("dev", false, "Allow connections from other origins") f.Bool("insecure-ssl", false, "Accept/Ignore all server SSL certificates") f.Bool("enable-dynamic-clusters", false, "Enable dynamic clusters, which stores stateless clusters in the frontend.") + f.Bool("list-plugins", false, "List all static and user-added plugins") f.String("kubeconfig", "", "Absolute path to the kubeconfig file") f.String("html-static-dir", "", "Static HTML directory to serve") diff --git a/backend/pkg/plugins/plugins.go b/backend/pkg/plugins/plugins.go index 431052af21..f989f2e03f 100644 --- a/backend/pkg/plugins/plugins.go +++ b/backend/pkg/plugins/plugins.go @@ -77,8 +77,8 @@ func periodicallyWatchSubfolders(watcher *fsnotify.Watcher, path string, interva } } -// GeneratePluginPaths takes the staticPluginDir and pluginDir and returns a list of plugin paths. -func GeneratePluginPaths(staticPluginDir string, pluginDir string) ([]string, error) { +// GenerateSeparatePluginPaths takes the staticPluginDir and pluginDir and returns separate lists of plugin paths. +func GenerateSeparatePluginPaths(staticPluginDir string, pluginDir string) ([]string, []string, error) { var pluginListURLStatic []string if staticPluginDir != "" { @@ -86,11 +86,21 @@ func GeneratePluginPaths(staticPluginDir string, pluginDir string) ([]string, er pluginListURLStatic, err = pluginBasePathListForDir(staticPluginDir, "static-plugins") if err != nil { - return nil, err + return nil, nil, err } } pluginListURL, err := pluginBasePathListForDir(pluginDir, "plugins") + if err != nil { + return nil, nil, err + } + + return pluginListURLStatic, pluginListURL, nil +} + +// GeneratePluginPaths generates a concatenated list of plugin paths from the staticPluginDir and pluginDir. +func GeneratePluginPaths(staticPluginDir string, pluginDir string) ([]string, error) { + pluginListURLStatic, pluginListURL, err := GenerateSeparatePluginPaths(staticPluginDir, pluginDir) if err != nil { return nil, err } @@ -103,6 +113,28 @@ func GeneratePluginPaths(staticPluginDir string, pluginDir string) ([]string, er return pluginListURL, nil } +func ListPlugins(staticPluginDir, pluginDir string) error { + staticPlugins, userPlugins, err := GenerateSeparatePluginPaths(staticPluginDir, pluginDir) + if err != nil { + logger.Log(logger.LevelError, nil, err, "listing plugins") + return fmt.Errorf("error listing plugins: %w", err) + } + + fmt.Println(" Static Plugins:") + + for _, plugin := range staticPlugins { + fmt.Println(" -", plugin) + } + + fmt.Println(" User-added Plugins:") + + for _, plugin := range userPlugins { + fmt.Println(" -", plugin) + } + + return nil +} + // pluginBasePathListForDir returns a list of valid plugin paths for the given directory. func pluginBasePathListForDir(pluginDir string, baseURL string) ([]string, error) { files, err := os.ReadDir(pluginDir)