mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
load plugins in order of dependencies.
This commit is contained in:
parent
cd2d9b413d
commit
49ff490640
2
build
2
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"
|
||||
|
||||
|
4
go.mod
4
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
|
||||
)
|
||||
|
1
go.sum
1
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=
|
||||
|
130
internal/plugins/dependency.go
Normal file
130
internal/plugins/dependency.go
Normal 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
|
||||
}
|
629
internal/plugins/dependency_test.go
Normal file
629
internal/plugins/dependency_test.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -13,6 +13,11 @@ type Plugin interface {
|
||||
Shutdown() error
|
||||
}
|
||||
|
||||
type DependablePlugin interface {
|
||||
Plugin
|
||||
DependsOn() []string
|
||||
}
|
||||
|
||||
type ExposablePlugin interface {
|
||||
Plugin
|
||||
ExposeService() any
|
||||
|
Loading…
Reference in New Issue
Block a user