Skip to content

Commit

Permalink
Add option to list plugins
Browse files Browse the repository at this point in the history
This change creates a CLI option in the backend + desktop to list all
plugins, with a distinction between static/shipped plugins and
user-added ones.

Fixes: #2161

Signed-off-by: Evangelos Skopelitis <eskopelitis@microsoft.com>
  • Loading branch information
skoeva committed Sep 19, 2024
1 parent 488fdf4 commit 0834cd7
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 3 deletions.
35 changes: 35 additions & 0 deletions app/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -78,6 +82,37 @@ 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) : [];

if (staticPlugins.length > 0) {
console.log(` Static Plugins (${staticDir}):`);
staticPlugins.forEach(plugin => {
console.log(` - ${plugin}`);
});
} else {
console.log(` No static plugins found.`);
}

if (userPlugins.length > 0) {
console.log(` User-added Plugins (${pluginsDir}):`);
userPlugins.forEach(plugin => {
console.log(` - ${plugin}`);
});
} else {
console.log(` No user-added plugins found.`);
}

process.exit(0);
}

const isHeadlessMode = args.headless === true;
let disableGPU = args['disable-gpu'] === true;
const defaultPort = 4466;
Expand Down
10 changes: 10 additions & 0 deletions backend/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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, "listing plugins")
os.Exit(1)
}

os.Exit(0)
}

cache := cache.New[interface{}]()
kubeConfigStore := kubeconfig.NewContextStore()

Expand Down
2 changes: 2 additions & 0 deletions backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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")
Expand Down
47 changes: 44 additions & 3 deletions backend/pkg/plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,30 @@ 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, pluginDir string) ([]string, []string, error) {
var pluginListURLStatic []string

if staticPluginDir != "" {
var err error

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, pluginDir string) ([]string, error) {
pluginListURLStatic, pluginListURL, err := generateSeparatePluginPaths(staticPluginDir, pluginDir)
if err != nil {
return nil, err
}
Expand All @@ -103,6 +113,37 @@ func GeneratePluginPaths(staticPluginDir string, pluginDir string) ([]string, er
return pluginListURL, nil
}

var GenerateSeparatePluginPathsFunc = generateSeparatePluginPaths

// ListPlugins lists the plugins in the static and user-added plugin directories.
func ListPlugins(staticPluginDir, pluginDir string) error {
staticPlugins, userPlugins, err := GenerateSeparatePluginPathsFunc(staticPluginDir, pluginDir)
if err != nil {
logger.Log(logger.LevelError, nil, err, "listing plugins")
return fmt.Errorf("listing plugins: %w", err)
}

if len(staticPlugins) > 0 {
fmt.Printf(" Static Plugins (%s):\n", staticPluginDir)
for _, plugin := range staticPlugins {

Check failure on line 128 in backend/pkg/plugins/plugins.go

View workflow job for this annotation

GitHub Actions / build

ranges should only be cuddled with assignments used in the iteration (wsl)
fmt.Println(" -", plugin)
}
} else {
fmt.Printf(" No static plugins found.\n")
}

if len(userPlugins) > 0 {
fmt.Printf(" User-added Plugins (%s):\n", pluginDir)
for _, plugin := range userPlugins {

Check failure on line 137 in backend/pkg/plugins/plugins.go

View workflow job for this annotation

GitHub Actions / build

ranges should only be cuddled with assignments used in the iteration (wsl)
fmt.Println(" -", plugin)
}
} else {
fmt.Printf(" No user-added plugins found.\n")
}

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)
Expand Down
16 changes: 16 additions & 0 deletions backend/pkg/plugins/plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,22 @@ func TestGeneratePluginPaths(t *testing.T) { //nolint:funlen
require.NoError(t, err)
}

func TestListPlugins(t *testing.T) {
originalFunc := plugins.GenerateSeparatePluginPathsFunc
defer func() { plugins.GenerateSeparatePluginPathsFunc = originalFunc }()

// mock the GenerateSeparatePluginPaths function to create lists of plugins
plugins.GenerateSeparatePluginPathsFunc = func(staticPluginDir string, pluginDir string) ([]string, []string, error) {
staticPlugins := []string{"static-plugin-1", "static-plugin-2"}
userPlugins := []string{"user-plugin-1", "user-plugin-2"}

return staticPlugins, userPlugins, nil
}

err := plugins.ListPlugins("", "")
require.NoError(t, err)
}

func TestHandlePluginEvents(t *testing.T) { //nolint:funlen
// Create a temporary directory if it doesn't exist
_, err := os.Stat("/tmp/")
Expand Down

0 comments on commit 0834cd7

Please sign in to comment.