diff --git a/internal/config/plugin.go b/internal/config/plugin.go index 2e1fbe39..029bda89 100644 --- a/internal/config/plugin.go +++ b/internal/config/plugin.go @@ -6,11 +6,14 @@ import ( "path/filepath" "strings" + "github.com/adrg/xdg" + "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" ) -// K9sPlugins manages K9s plugins. -var K9sPlugins = filepath.Join(K9sHome(), "plugin.yml") +// K9sPluginsFilePath manages K9s plugins. +var K9sPluginsFilePath = filepath.Join(K9sHome(), "plugin.yml") +var K9sPluginDirectory = filepath.Join("k9s", "plugins") // Plugins represents a collection of plugins. type Plugins struct { @@ -42,23 +45,54 @@ func NewPlugins() Plugins { // Load K9s plugins. func (p Plugins) Load() error { - return p.LoadPlugins(K9sPlugins) + var pluginDirs []string + for _, dataDir := range xdg.DataDirs { + pluginDirs = append(pluginDirs, filepath.Join(dataDir, K9sPluginDirectory)) + } + return p.LoadPlugins(K9sPluginsFilePath, pluginDirs) } -// LoadPlugins loads plugins from a given file. -func (p Plugins) LoadPlugins(path string) error { +// LoadPlugins loads plugins from a given file and a set of plugin directories. +func (p Plugins) LoadPlugins(path string, pluginDirs []string) error { f, err := os.ReadFile(path) if err != nil { return err } - var pp Plugins if err := yaml.Unmarshal(f, &pp); err != nil { return err } + + for _, pluginDir := range pluginDirs { + pluginFiles, err := os.ReadDir(pluginDir) + if err != nil { + log.Warn().Msgf("Failed reading plugin path %s; %s", pluginDir, err) + continue + } + for _, file := range pluginFiles { + if file.IsDir() || !isYamlFile(file) { + continue + } + pluginFile, err := os.ReadFile(filepath.Join(pluginDir, file.Name())) + if err != nil { + return err + } + var plugin Plugin + if err = yaml.Unmarshal(pluginFile, &plugin); err != nil { + return err + } + p.Plugin[strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))] = plugin + } + } + for k, v := range pp.Plugin { p.Plugin[k] = v } return nil } + +func isYamlFile(file os.DirEntry) bool { + ext := filepath.Ext(file.Name()) + return ext == ".yml" || ext == ".yaml" +} diff --git a/internal/config/plugin_test.go b/internal/config/plugin_test.go index 961118fc..8350625e 100644 --- a/internal/config/plugin_test.go +++ b/internal/config/plugin_test.go @@ -7,18 +7,61 @@ import ( "github.com/stretchr/testify/assert" ) -func TestPluginLoad(t *testing.T) { +var pluginYmlTestData = config.Plugin{ + Scopes: []string{"po", "dp"}, + Args: []string{"-n", "$NAMESPACE", "-boolean"}, + ShortCut: "shift-s", + Description: "blee", + Command: "duh", + Confirm: true, + Background: false, +} + +var test1YmlTestData = config.Plugin{ + Scopes: []string{"po", "dp"}, + Args: []string{"-n", "$NAMESPACE", "-boolean"}, + ShortCut: "shift-s", + Description: "blee", + Command: "duh", + Confirm: true, + Background: false, +} + +var test2YmlTestData = config.Plugin{ + Scopes: []string{"svc", "ing"}, + Args: []string{"-n", "$NAMESPACE", "-oyaml"}, + ShortCut: "shift-r", + Description: "bla", + Command: "duha", + Confirm: false, + Background: true, +} + +func TestSinglePluginFileLoad(t *testing.T) { p := config.NewPlugins() - assert.Nil(t, p.LoadPlugins("testdata/plugin.yml")) + assert.Nil(t, p.LoadPlugins("testdata/plugin.yml", []string{"/random/dir/not/exist"})) assert.Equal(t, 1, len(p.Plugin)) k, ok := p.Plugin["blah"] assert.True(t, ok) - assert.Equal(t, "shift-s", k.ShortCut) - assert.True(t, k.Confirm) - assert.Equal(t, "blee", k.Description) - assert.Equal(t, []string{"po", "dp"}, k.Scopes) - assert.Equal(t, "duh", k.Command) - assert.False(t, k.Background) - assert.Equal(t, []string{"-n", "$NAMESPACE", "-boolean"}, k.Args) + + assert.ObjectsAreEqual(pluginYmlTestData, k) +} + +func TestMultiplePluginFilesLoad(t *testing.T) { + p := config.NewPlugins() + assert.Nil(t, p.LoadPlugins("testdata/plugin.yml", []string{"testdata/plugins"})) + + testPlugins := map[string]config.Plugin{ + "blah": pluginYmlTestData, + "test1": test1YmlTestData, + "test2": test2YmlTestData, + } + + assert.Equal(t, len(testPlugins), len(p.Plugin)) + for name, expectedPlugin := range testPlugins { + k, ok := p.Plugin[name] + assert.True(t, ok) + assert.ObjectsAreEqual(expectedPlugin, k) + } } diff --git a/internal/config/testdata/plugins/test1.yml b/internal/config/testdata/plugins/test1.yml new file mode 100644 index 00000000..6d8eec13 --- /dev/null +++ b/internal/config/testdata/plugins/test1.yml @@ -0,0 +1,12 @@ +shortCut: shift-s +confirm: true +description: blee +scopes: + - po + - dp +command: duh +background: false +args: + - -n + - $NAMESPACE + - -boolean diff --git a/internal/config/testdata/plugins/test2.yml b/internal/config/testdata/plugins/test2.yml new file mode 100644 index 00000000..379dee91 --- /dev/null +++ b/internal/config/testdata/plugins/test2.yml @@ -0,0 +1,12 @@ +shortCut: shift-r +confirm: false +description: bla +scopes: + - svc + - ing +command: duha +background: true +args: + - -n + - $NAMESPACE + - -oyaml