load plugins in order of dependencies.

This commit is contained in:
Aleksandar Sukovic 2022-05-03 11:17:04 +00:00
parent cd2d9b413d
commit 49ff490640
7 changed files with 804 additions and 33 deletions

2
build
View File

@ -44,6 +44,8 @@ fi
for plugPath in ./plugins/*; do for plugPath in ./plugins/*; do
pushd $plugPath pushd $plugPath
echo "Building plugin: $plugPath"
# build plugin # build plugin
go build -modfile=go.plug.mod -buildmode=plugin -o "../../bin/plugins/${plugPath##*/}.so" go build -modfile=go.plug.mod -buildmode=plugin -o "../../bin/plugins/${plugPath##*/}.so"

4
go.mod
View File

@ -16,9 +16,11 @@ require (
github.com/rs/zerolog v1.26.1 github.com/rs/zerolog v1.26.1
github.com/spf13/cobra v1.4.0 github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.10.1 github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.1
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.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/transport v0.13.0 // indirect
github.com/pion/turn/v2 v2.0.8 // indirect github.com/pion/turn/v2 v2.0.8 // indirect
github.com/pion/udp v0.1.1 // 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/afero v1.8.0 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
@ -51,4 +54,5 @@ require (
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )

1
go.sum
View File

@ -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.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-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 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-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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -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
}

View File

@ -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
}

View File

@ -16,18 +16,22 @@ import (
type ManagerCtx struct { type ManagerCtx struct {
logger zerolog.Logger logger zerolog.Logger
plugins map[string]types.Plugin plugins dependiencies
} }
func New(config *config.Plugins) *ManagerCtx { func New(config *config.Plugins) *ManagerCtx {
manager := &ManagerCtx{ manager := &ManagerCtx{
logger: log.With().Str("module", "plugins").Logger(), logger: log.With().Str("module", "plugins").Logger(),
plugins: map[string]types.Plugin{}, plugins: dependiencies{
deps: make(map[string]*dependency),
},
} }
manager.plugins.logger = manager.logger
if config.Enabled { if config.Enabled {
err := manager.loadDir(config.Dir) 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 return manager
@ -45,7 +49,6 @@ func (manager *ManagerCtx) loadDir(dir string) error {
err = manager.load(path) err = manager.load(path)
manager.logger.Err(err).Str("plugin", path).Msg("loading a plugin") manager.logger.Err(err).Str("plugin", path).Msg("loading a plugin")
return nil return nil
}) })
} }
@ -66,27 +69,28 @@ func (manager *ManagerCtx) load(path string) error {
return fmt.Errorf("not a valid plugin") return fmt.Errorf("not a valid plugin")
} }
_, ok = manager.plugins[p.Name()] if err = manager.plugins.addPlugin(p); err != nil {
if ok { return fmt.Errorf("failed to add plugin '%s': %w", p.Name(), err)
return fmt.Errorf("plugin '%s' already exists", p.Name())
} }
manager.plugins[p.Name()] = p manager.logger.Info().Msgf("loaded plugin '%s', total %d plugins", p.Name(), manager.plugins.len())
return nil return nil
} }
func (manager *ManagerCtx) InitConfigs(cmd *cobra.Command) { func (manager *ManagerCtx) InitConfigs(cmd *cobra.Command) {
for path, plug := range manager.plugins { _ = manager.plugins.forEach(func(plug *dependency) error {
if err := plug.Config().Init(cmd); err != nil { if err := plug.plugin.Config().Init(cmd); err != nil {
log.Err(err).Str("plugin", path).Msg("unable to initialize configuration") log.Err(err).Str("plugin", plug.plugin.Name()).Msg("unable to initialize configuration")
}
} }
return nil
})
} }
func (manager *ManagerCtx) SetConfigs() { func (manager *ManagerCtx) SetConfigs() {
for _, plug := range manager.plugins { _ = manager.plugins.forEach(func(plug *dependency) error {
plug.Config().Set() plug.plugin.Config().Set()
} return nil
})
} }
func (manager *ManagerCtx) Start( func (manager *ManagerCtx) Start(
@ -94,34 +98,30 @@ func (manager *ManagerCtx) Start(
webSocketManager types.WebSocketManager, webSocketManager types.WebSocketManager,
apiManager types.ApiManager, apiManager types.ApiManager,
) { ) {
for path, plug := range manager.plugins { _ = manager.plugins.start(types.PluginManagers{
err := plug.Start(types.PluginManagers{
SessionManager: sessionManager, SessionManager: sessionManager,
WebSocketManager: webSocketManager, WebSocketManager: webSocketManager,
ApiManager: apiManager, ApiManager: apiManager,
LoadServiceFromPlugin: manager.LookupService, LoadServiceFromPlugin: manager.LookupService,
}) })
manager.logger.Err(err).Str("plugin", path).Msg("plugin start")
}
} }
func (manager *ManagerCtx) Shutdown() error { func (manager *ManagerCtx) Shutdown() error {
for path, plug := range manager.plugins { _ = manager.plugins.forEach(func(plug *dependency) error {
err := plug.Shutdown() err := plug.plugin.Shutdown()
manager.logger.Err(err).Str("plugin", path).Msg("plugin shutdown") manager.logger.Err(err).Str("plugin", plug.plugin.Name()).Msg("plugin shutdown")
} return nil
})
return nil return nil
} }
func (manager *ManagerCtx) LookupService(pluginName string) (any, error) { func (manager *ManagerCtx) LookupService(pluginName string) (any, error) {
plug, ok := manager.plugins[pluginName] plug, ok := manager.plugins.findPlugin(pluginName)
if !ok { if !ok {
return nil, fmt.Errorf("plugin '%s' not found", pluginName) return nil, fmt.Errorf("plugin '%s' not found", pluginName)
} }
expPlug, ok := plug.(types.ExposablePlugin) expPlug, ok := plug.plugin.(types.ExposablePlugin)
if !ok { if !ok {
return nil, fmt.Errorf("plugin '%s' is not exposable", pluginName) return nil, fmt.Errorf("plugin '%s' is not exposable", pluginName)
} }

View File

@ -13,6 +13,11 @@ type Plugin interface {
Shutdown() error Shutdown() error
} }
type DependablePlugin interface {
Plugin
DependsOn() []string
}
type ExposablePlugin interface { type ExposablePlugin interface {
Plugin Plugin
ExposeService() any ExposeService() any