From 49ff4906405386c4c765de4ea6bf50204f313e59 Mon Sep 17 00:00:00 2001 From: Aleksandar Sukovic Date: Tue, 3 May 2022 11:17:04 +0000 Subject: [PATCH] load plugins in order of dependencies. --- build | 2 + go.mod | 4 + go.sum | 1 + internal/plugins/dependency.go | 130 ++++++ internal/plugins/dependency_test.go | 629 ++++++++++++++++++++++++++++ internal/plugins/manager.go | 66 +-- pkg/types/plugins.go | 5 + 7 files changed, 804 insertions(+), 33 deletions(-) create mode 100644 internal/plugins/dependency.go create mode 100644 internal/plugins/dependency_test.go diff --git a/build b/build index 0ec7aad3..d69b8dd4 100755 --- a/build +++ b/build @@ -44,6 +44,8 @@ fi for plugPath in ./plugins/*; do pushd $plugPath + echo "Building plugin: $plugPath" + # build plugin go build -modfile=go.plug.mod -buildmode=plugin -o "../../bin/plugins/${plugPath##*/}.so" diff --git a/go.mod b/go.mod index 989ec78b..56de7c04 100644 --- a/go.mod +++ b/go.mod @@ -16,9 +16,11 @@ require ( github.com/rs/zerolog v1.26.1 github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.10.1 + github.com/stretchr/testify v1.7.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -39,6 +41,7 @@ require ( github.com/pion/transport v0.13.0 // indirect github.com/pion/turn/v2 v2.0.8 // indirect github.com/pion/udp v0.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.8.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -51,4 +54,5 @@ require ( golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 4b1ddb29..6a606236 100644 --- a/go.sum +++ b/go.sum @@ -580,6 +580,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/plugins/dependency.go b/internal/plugins/dependency.go new file mode 100644 index 00000000..31f2dddc --- /dev/null +++ b/internal/plugins/dependency.go @@ -0,0 +1,130 @@ +package plugins + +import ( + "fmt" + + "github.com/rs/zerolog" + + "gitlab.com/demodesk/neko/server/pkg/types" +) + +type dependency struct { + plugin types.Plugin + dependsOn []*dependency + invoked bool + logger zerolog.Logger +} + +type dependiencies struct { + deps map[string]*dependency + logger zerolog.Logger +} + +func (d *dependiencies) addPlugin(plugin types.Plugin) error { + plug, ok := d.deps[plugin.Name()] + if !ok { + plug = &dependency{} + } else if plug.plugin != nil { + return fmt.Errorf("plugin '%s' already added", plugin.Name()) + } + + plug.plugin = plugin + plug.logger = d.logger + d.deps[plugin.Name()] = plug + + dplug, ok := plugin.(types.DependablePlugin) + if !ok { + return nil + } + + for _, dep := range dplug.DependsOn() { + var dependsOn *dependency + dependsOn, ok = d.deps[dep] + if !ok { + dependsOn = &dependency{} + } else if dependsOn.plugin != nil { + // if there is a cyclical dependency, break it and return error + if tdep := dependsOn.findPlugin(plugin.Name()); tdep != nil { + dependsOn.dependsOn = nil + delete(d.deps, plugin.Name()) + return fmt.Errorf("cyclical dependency detected: '%s' <-> '%s'", plugin.Name(), tdep.plugin.Name()) + } + } + + plug.dependsOn = append(plug.dependsOn, dependsOn) + d.deps[dep] = dependsOn + } + + return nil +} + +func (d *dependiencies) start(pm types.PluginManagers) error { + for _, p := range d.deps { + if err := p.start(pm); err != nil { + return err + } + } + return nil +} + +func (d *dependiencies) findPlugin(name string) (*dependency, bool) { + for _, p := range d.deps { + if found := p.findPlugin(name); found != nil { + return found, true + } + } + return nil, false +} + +func (a *dependency) findPlugin(name string) *dependency { + if a == nil { + return nil + } + + if a.plugin.Name() == name { + return a + } + + for _, p := range a.dependsOn { + if found := p.findPlugin(name); found != nil { + return found + } + } + + return nil +} + +func (d *dependiencies) forEach(f func(*dependency) error) error { + for _, dp := range d.deps { + if err := f(dp); err != nil { + return err + } + } + return nil +} + +func (d *dependiencies) len() int { + return len(d.deps) +} + +func (a *dependency) start(pm types.PluginManagers) error { + if a.invoked { + return nil + } + + a.invoked = true + + for _, do := range a.dependsOn { + if err := do.start(pm); err != nil { + return err + } + } + + err := a.plugin.Start(pm) + a.logger.Err(err).Str("plugin", a.plugin.Name()).Msg("plugin start") + if err != nil { + return fmt.Errorf("plugin %s failed to start: %s", a.plugin.Name(), err) + } + + return nil +} diff --git a/internal/plugins/dependency_test.go b/internal/plugins/dependency_test.go new file mode 100644 index 00000000..259eba3c --- /dev/null +++ b/internal/plugins/dependency_test.go @@ -0,0 +1,629 @@ +package plugins + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "gitlab.com/demodesk/neko/server/pkg/types" +) + +func Test_deps_addPlugin(t *testing.T) { + type args struct { + p []types.Plugin + } + tests := []struct { + name string + args args + want map[string]*dependency + skipRun bool + wantErr1 bool + wantErr2 bool + }{ + { + name: "three plugins - no dependencies", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "first"}, + &dummyPlugin{name: "second"}, + &dummyPlugin{name: "third"}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "second": { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "third": { + plugin: &dummyPlugin{name: "third", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, { + name: "three plugins - one dependency", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "third", dep: []string{"second"}}, + &dummyPlugin{name: "first"}, + &dummyPlugin{name: "second"}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "second": { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "third": { + plugin: &dummyPlugin{name: "third", dep: []string{"second"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, { + name: "three plugins - one double dependency", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "third", dep: []string{"first", "second"}}, + &dummyPlugin{name: "first"}, + &dummyPlugin{name: "second"}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "second": { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "third": { + plugin: &dummyPlugin{name: "third", dep: []string{"first", "second"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, { + name: "three plugins - two dependencies", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "third", dep: []string{"first"}}, + &dummyPlugin{name: "first"}, + &dummyPlugin{name: "second", dep: []string{"first"}}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first"}, + invoked: false, + dependsOn: nil, + }, + "third": { + plugin: &dummyPlugin{name: "third", dep: []string{"first"}}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first"}, + invoked: false, + dependsOn: nil, + }, + }, + }, + "second": { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first"}, + invoked: false, + dependsOn: nil, + }, + }, + }, + }, + skipRun: true, + }, { + name: "three plugins - three dependencies", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "third", dep: []string{"second"}}, + &dummyPlugin{name: "first"}, + &dummyPlugin{name: "second", dep: []string{"first"}}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "second": { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + "third": { + plugin: &dummyPlugin{name: "third", dep: []string{"second"}, idx: 2}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, + }, + }, { + name: "four plugins - added in reverse order, with dependencies", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "forth", dep: []string{"third"}}, + &dummyPlugin{name: "third", dep: []string{"second"}}, + &dummyPlugin{name: "second", dep: []string{"first"}}, + &dummyPlugin{name: "first"}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: false, + dependsOn: nil, + }, + "second": { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: false, + dependsOn: nil, + }, + }, + }, + "third": { + plugin: &dummyPlugin{name: "third", dep: []string{"second"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: false, + dependsOn: nil, + }, + }, + }, + }, + }, + "forth": { + plugin: &dummyPlugin{name: "forth", dep: []string{"third"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "third", dep: []string{"second"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: false, + dependsOn: nil, + }, + }, + }, + }, + }, + }, + }, + }, + skipRun: true, + }, { + name: "four plugins - two double dependencies", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "forth", dep: []string{"first", "third"}}, + &dummyPlugin{name: "third", dep: []string{"first", "second"}}, + &dummyPlugin{name: "second"}, + &dummyPlugin{name: "first"}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "second": { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "third": { + plugin: &dummyPlugin{name: "third", dep: []string{"first", "second"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + "forth": { + plugin: &dummyPlugin{name: "forth", dep: []string{"first", "third"}, idx: 2}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + { + plugin: &dummyPlugin{name: "third", dep: []string{"first", "second"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", idx: 0}, + invoked: true, + dependsOn: nil, + }, + { + plugin: &dummyPlugin{name: "second", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, + }, + }, { + // So, when we have plugin A in the list and want to add plugin C we can't determine the proper order without + // resolving their direct dependiencies first: + // + // Can be C->D->A->B if D depends on A + // + // So to do it properly I would imagine tht we need to resolve all direct dependiencies first and build multiple lists: + // + // i.e. A->B->C D F->G + // + // and then join these lists in any order. + name: "add indirect dependency CDAB", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "A", dep: []string{"B"}}, + &dummyPlugin{name: "C", dep: []string{"D"}}, + &dummyPlugin{name: "B"}, + &dummyPlugin{name: "D", dep: []string{"A"}}, + }, + }, + want: map[string]*dependency{ + "B": { + plugin: &dummyPlugin{name: "B", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "A": { + plugin: &dummyPlugin{name: "A", dep: []string{"B"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "B", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + "D": { + plugin: &dummyPlugin{name: "D", dep: []string{"A"}, idx: 2}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "A", dep: []string{"B"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "B", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, + "C": { + plugin: &dummyPlugin{name: "C", dep: []string{"D"}, idx: 3}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "D", dep: []string{"A"}, idx: 2}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "A", dep: []string{"B"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "B", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + // So, when we have plugin A in the list and want to add plugin C we can't determine the proper order without + // resolving their direct dependiencies first: + // + // Can be A->B->C->D (in this test) if B depends on C + // + // So to do it properly I would imagine tht we need to resolve all direct dependiencies first and build multiple lists: + // + // i.e. A->B->C D F->G + // + // and then join these lists in any order. + name: "add indirect dependency ABCD", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "C", dep: []string{"D"}}, + &dummyPlugin{name: "D"}, + &dummyPlugin{name: "B", dep: []string{"C"}}, + &dummyPlugin{name: "A", dep: []string{"B"}}, + }, + }, + want: map[string]*dependency{ + "D": { + plugin: &dummyPlugin{name: "D", idx: 0}, + invoked: true, + dependsOn: nil, + }, + "C": { + plugin: &dummyPlugin{name: "C", dep: []string{"D"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "D", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + "B": { + plugin: &dummyPlugin{name: "B", dep: []string{"C"}, idx: 2}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "C", dep: []string{"D"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "D", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, + "A": { + plugin: &dummyPlugin{name: "A", dep: []string{"B"}, idx: 3}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "B", dep: []string{"C"}, idx: 2}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "C", dep: []string{"D"}, idx: 1}, + invoked: true, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "D", idx: 0}, + invoked: true, + dependsOn: nil, + }, + }, + }, + }, + }, + }, + }, + }, + }, { + name: "add duplicate plugin", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "first"}, + &dummyPlugin{name: "first"}, + }, + }, + want: map[string]*dependency{ + "first": {plugin: &dummyPlugin{name: "first", idx: 0}, invoked: true}, + }, + wantErr1: true, + }, { + name: "cyclical dependency", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "first", dep: []string{"second"}}, + &dummyPlugin{name: "second", dep: []string{"first"}}, + }, + }, + want: map[string]*dependency{ + "first": { + plugin: &dummyPlugin{name: "first", dep: []string{"second"}, idx: 1}, + invoked: true, + }, + }, + wantErr1: true, + }, { + name: "four plugins - cyclical transitive dependencies in reverse order", + args: args{ + p: []types.Plugin{ + &dummyPlugin{name: "forth", dep: []string{"third"}}, + &dummyPlugin{name: "third", dep: []string{"second"}}, + &dummyPlugin{name: "second", dep: []string{"first"}}, + &dummyPlugin{name: "first", dep: []string{"forth"}}, + }, + }, + want: map[string]*dependency{ + "second": { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", dep: []string{"forth"}, idx: 0}, + invoked: false, + dependsOn: nil, + }, + }, + }, + "third": { + plugin: &dummyPlugin{name: "third", dep: []string{"second"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "second", dep: []string{"first"}, idx: 0}, + invoked: false, + dependsOn: []*dependency{ + { + plugin: &dummyPlugin{name: "first", dep: []string{"forth"}, idx: 0}, + invoked: false, + dependsOn: nil, + }, + }, + }, + }, + }, + "forth": { + plugin: &dummyPlugin{name: "forth", dep: []string{"third"}, idx: 0}, + invoked: false, + dependsOn: nil, + }, + }, + wantErr1: true, + skipRun: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &dependiencies{deps: make(map[string]*dependency)} + + var ( + err error + counter int + ) + for _, p := range tt.args.p { + if !tt.skipRun { + p.(*dummyPlugin).counter = &counter + } + if err = d.addPlugin(p); err != nil { + break + } + } + if err != nil != tt.wantErr1 { + t.Errorf("dependiencies.addPlugin() error = %v, wantErr1 %v", err, tt.wantErr1) + return + } + + if !tt.skipRun { + if err := d.start(types.PluginManagers{}); (err != nil) != tt.wantErr2 { + t.Errorf("dependiencies.start() error = %v, wantErr1 %v", err, tt.wantErr2) + } + } + + assert.Equal(t, tt.want, d.deps) + }) + } +} + +type dummyPlugin struct { + name string + dep []string + idx int + counter *int +} + +func (d dummyPlugin) Name() string { + return d.name +} + +func (d dummyPlugin) DependsOn() []string { + return d.dep +} + +func (d dummyPlugin) Config() types.PluginConfig { + return nil +} + +func (d *dummyPlugin) Start(types.PluginManagers) error { + if len(d.dep) > 0 { + *d.counter++ + d.idx = *d.counter + } + d.counter = nil + return nil +} + +func (d dummyPlugin) Shutdown() error { + return nil +} diff --git a/internal/plugins/manager.go b/internal/plugins/manager.go index aa902722..ea88ca61 100644 --- a/internal/plugins/manager.go +++ b/internal/plugins/manager.go @@ -16,18 +16,22 @@ import ( type ManagerCtx struct { logger zerolog.Logger - plugins map[string]types.Plugin + plugins dependiencies } func New(config *config.Plugins) *ManagerCtx { manager := &ManagerCtx{ - logger: log.With().Str("module", "plugins").Logger(), - plugins: map[string]types.Plugin{}, + logger: log.With().Str("module", "plugins").Logger(), + plugins: dependiencies{ + deps: make(map[string]*dependency), + }, } + manager.plugins.logger = manager.logger + if config.Enabled { err := manager.loadDir(config.Dir) - manager.logger.Err(err).Msgf("loading finished, total %d plugins", len(manager.plugins)) + manager.logger.Err(err).Msgf("loading finished, total %d plugins", manager.plugins.len()) } return manager @@ -45,7 +49,6 @@ func (manager *ManagerCtx) loadDir(dir string) error { err = manager.load(path) manager.logger.Err(err).Str("plugin", path).Msg("loading a plugin") - return nil }) } @@ -66,27 +69,28 @@ func (manager *ManagerCtx) load(path string) error { return fmt.Errorf("not a valid plugin") } - _, ok = manager.plugins[p.Name()] - if ok { - return fmt.Errorf("plugin '%s' already exists", p.Name()) + if err = manager.plugins.addPlugin(p); err != nil { + return fmt.Errorf("failed to add plugin '%s': %w", p.Name(), err) } - manager.plugins[p.Name()] = p + manager.logger.Info().Msgf("loaded plugin '%s', total %d plugins", p.Name(), manager.plugins.len()) return nil } func (manager *ManagerCtx) InitConfigs(cmd *cobra.Command) { - for path, plug := range manager.plugins { - if err := plug.Config().Init(cmd); err != nil { - log.Err(err).Str("plugin", path).Msg("unable to initialize configuration") + _ = manager.plugins.forEach(func(plug *dependency) error { + if err := plug.plugin.Config().Init(cmd); err != nil { + log.Err(err).Str("plugin", plug.plugin.Name()).Msg("unable to initialize configuration") } - } + return nil + }) } func (manager *ManagerCtx) SetConfigs() { - for _, plug := range manager.plugins { - plug.Config().Set() - } + _ = manager.plugins.forEach(func(plug *dependency) error { + plug.plugin.Config().Set() + return nil + }) } func (manager *ManagerCtx) Start( @@ -94,34 +98,30 @@ func (manager *ManagerCtx) Start( webSocketManager types.WebSocketManager, apiManager types.ApiManager, ) { - for path, plug := range manager.plugins { - err := plug.Start(types.PluginManagers{ - SessionManager: sessionManager, - WebSocketManager: webSocketManager, - ApiManager: apiManager, - LoadServiceFromPlugin: manager.LookupService, - }) - - manager.logger.Err(err).Str("plugin", path).Msg("plugin start") - } + _ = manager.plugins.start(types.PluginManagers{ + SessionManager: sessionManager, + WebSocketManager: webSocketManager, + ApiManager: apiManager, + LoadServiceFromPlugin: manager.LookupService, + }) } func (manager *ManagerCtx) Shutdown() error { - for path, plug := range manager.plugins { - err := plug.Shutdown() - manager.logger.Err(err).Str("plugin", path).Msg("plugin shutdown") - } - + _ = manager.plugins.forEach(func(plug *dependency) error { + err := plug.plugin.Shutdown() + manager.logger.Err(err).Str("plugin", plug.plugin.Name()).Msg("plugin shutdown") + return nil + }) return nil } func (manager *ManagerCtx) LookupService(pluginName string) (any, error) { - plug, ok := manager.plugins[pluginName] + plug, ok := manager.plugins.findPlugin(pluginName) if !ok { return nil, fmt.Errorf("plugin '%s' not found", pluginName) } - expPlug, ok := plug.(types.ExposablePlugin) + expPlug, ok := plug.plugin.(types.ExposablePlugin) if !ok { return nil, fmt.Errorf("plugin '%s' is not exposable", pluginName) } diff --git a/pkg/types/plugins.go b/pkg/types/plugins.go index 27887aa6..7176e382 100644 --- a/pkg/types/plugins.go +++ b/pkg/types/plugins.go @@ -13,6 +13,11 @@ type Plugin interface { Shutdown() error } +type DependablePlugin interface { + Plugin + DependsOn() []string +} + type ExposablePlugin interface { Plugin ExposeService() any