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
|
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
4
go.mod
@ -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
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.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=
|
||||||
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user