feat: allow for multiple plugin files in $XDG_DATA_DIRS/k9s/plugins (#2029)
parent
32b9493a0d
commit
1bfd824ab4
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
shortCut: shift-s
|
||||
confirm: true
|
||||
description: blee
|
||||
scopes:
|
||||
- po
|
||||
- dp
|
||||
command: duh
|
||||
background: false
|
||||
args:
|
||||
- -n
|
||||
- $NAMESPACE
|
||||
- -boolean
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
shortCut: shift-r
|
||||
confirm: false
|
||||
description: bla
|
||||
scopes:
|
||||
- svc
|
||||
- ing
|
||||
command: duha
|
||||
background: true
|
||||
args:
|
||||
- -n
|
||||
- $NAMESPACE
|
||||
- -oyaml
|
||||
Loading…
Reference in New Issue