Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
8b85f60e63 |
@ -2,7 +2,7 @@ build: extension
|
|||||||
go build -o ./bin/app ./
|
go build -o ./bin/app ./
|
||||||
|
|
||||||
extension:
|
extension:
|
||||||
go build -o ./bin/myplugin.so -buildmode=plugin ./myplugin
|
go build -o ./bin/myext.so -buildmode=plugin ./myext
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
modd
|
modd
|
23
example/extendable/main.go
Normal file
23
example/extendable/main.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/extension"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
reg := extension.NewRegistry()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
extensions, err := reg.LoadAll(ctx, "./bin/*.so")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range extensions {
|
||||||
|
log.Printf("Loaded extension '%s', version '%s'", ext.ExtensionName(), ext.ExtensionVersion())
|
||||||
|
}
|
||||||
|
}
|
12
example/extendable/myext/extension.go
Normal file
12
example/extendable/myext/extension.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type MyExtension struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MyExtension) ExtensionName() string {
|
||||||
|
return "my.extension"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MyExtension) ExtensionVersion() string {
|
||||||
|
return "0.0.0"
|
||||||
|
}
|
11
example/extendable/myext/main.go
Normal file
11
example/extendable/myext/main.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/wpetit/goweb/extension"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterExtension(ctx context.Context) (extension.Extension, error) {
|
||||||
|
return &MyExtension{}, nil
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package hook
|
|
||||||
|
|
||||||
type Initializable interface {
|
|
||||||
HookInit() error
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/example/pluggable/hook"
|
|
||||||
"gitlab.com/wpetit/goweb/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
reg := plugin.NewRegistry()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
_, err := reg.LoadAll(ctx, "./bin/*.so")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ext := range reg.Plugins() {
|
|
||||||
log.Printf("Loaded plugin '%s', version '%s'", ext.PluginName(), ext.PluginVersion())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over plugins
|
|
||||||
err = reg.Each(func(p plugin.Plugin) error {
|
|
||||||
h, ok := p.(hook.Initializable)
|
|
||||||
if !ok {
|
|
||||||
// Skip non initializable plugins
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize plugin
|
|
||||||
if err := h.HookInit(); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(errors.WithStack(err))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"gitlab.com/wpetit/goweb/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterPlugin(ctx context.Context) (plugin.Plugin, error) {
|
|
||||||
return &MyPlugin{}, nil
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "log"
|
|
||||||
|
|
||||||
type MyPlugin struct{}
|
|
||||||
|
|
||||||
func (e *MyPlugin) PluginName() string {
|
|
||||||
return "my.plugin"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MyPlugin) PluginVersion() string {
|
|
||||||
return "0.0.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MyPlugin) HookInit() error {
|
|
||||||
log.Println("MyPlugin initialized !")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
8
extension/error.go
Normal file
8
extension/error.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package extension
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidRegisterFunc = errors.New("invalid register func")
|
||||||
|
ErrInvalidExtension = errors.New("invalid extension")
|
||||||
|
)
|
6
extension/extension.go
Normal file
6
extension/extension.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package extension
|
||||||
|
|
||||||
|
type Extension interface {
|
||||||
|
ExtensionName() string
|
||||||
|
ExtensionVersion() string
|
||||||
|
}
|
101
extension/registry.go
Normal file
101
extension/registry.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package extension
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
"plugin"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
extensions map[string]Extension
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Load(ctx context.Context, path string) (Extension, error) {
|
||||||
|
p, err := plugin.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
registerFuncSymbol, err := p.Lookup("RegisterExtension")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
register, ok := registerFuncSymbol.(func(context.Context) (Extension, error))
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.WithStack(ErrInvalidRegisterFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
ext, err := register(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ext == nil {
|
||||||
|
return nil, errors.WithStack(ErrInvalidExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mutex.Lock()
|
||||||
|
r.extensions[ext.ExtensionName()] = ext
|
||||||
|
r.mutex.Unlock()
|
||||||
|
|
||||||
|
return ext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) LoadAll(ctx context.Context, pattern string) ([]Extension, error) {
|
||||||
|
extensions := make([]Extension, 0)
|
||||||
|
|
||||||
|
matches, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range matches {
|
||||||
|
ext, err := r.Load(ctx, m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions = append(extensions, ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Extensions() []Extension {
|
||||||
|
r.mutex.RLock()
|
||||||
|
defer r.mutex.RUnlock()
|
||||||
|
|
||||||
|
extensions := make([]Extension, 0, len(r.extensions))
|
||||||
|
for _, e := range r.extensions {
|
||||||
|
extensions = append(extensions, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtensionFilterFunc func(ext Extension) bool
|
||||||
|
|
||||||
|
func (r *Registry) Filter(filter ExtensionFilterFunc) []Extension {
|
||||||
|
r.mutex.RLock()
|
||||||
|
defer r.mutex.RUnlock()
|
||||||
|
|
||||||
|
extensions := make([]Extension, 0, len(r.extensions))
|
||||||
|
for _, e := range r.extensions {
|
||||||
|
if filter(e) {
|
||||||
|
extensions = append(extensions, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
extensions: make(map[string]Extension),
|
||||||
|
}
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -13,7 +13,7 @@ require (
|
|||||||
github.com/gorilla/sessions v1.2.0
|
github.com/gorilla/sessions v1.2.0
|
||||||
github.com/leodido/go-urn v1.1.0 // indirect
|
github.com/leodido/go-urn v1.1.0 // indirect
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.8.1
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.1
|
gopkg.in/go-playground/validator.v9 v9.29.1
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -104,8 +104,6 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwU
|
|||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidRegisterFunc is returned when the plugin package
|
|
||||||
// could not find the expected RegisterPlugin func in the loaded
|
|
||||||
// plugin.
|
|
||||||
ErrInvalidRegisterFunc = errors.New("invalid register func")
|
|
||||||
// ErrInvalidPlugin is returned when a loaded plugin does
|
|
||||||
// not match the expected interface.
|
|
||||||
ErrInvalidPlugin = errors.New("invalid plugin")
|
|
||||||
// ErrPluginNotFound is returned when the given plugin could
|
|
||||||
// not be found in the registry.
|
|
||||||
ErrPluginNotFound = errors.New("plugin not found")
|
|
||||||
)
|
|
@ -1,6 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
type Plugin interface {
|
|
||||||
PluginName() string
|
|
||||||
PluginVersion() string
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
"plugin"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
plugins map[string]Plugin
|
|
||||||
mutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) Add(plg Plugin) {
|
|
||||||
r.mutex.Lock()
|
|
||||||
defer r.mutex.Unlock()
|
|
||||||
|
|
||||||
r.plugins[plg.PluginName()] = plg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) Get(name string) (Plugin, error) {
|
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
plg, exists := r.plugins[name]
|
|
||||||
if !exists {
|
|
||||||
return nil, errors.WithStack(ErrPluginNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return plg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) Load(ctx context.Context, path string) (Plugin, error) {
|
|
||||||
p, err := plugin.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
registerFuncSymbol, err := p.Lookup("RegisterPlugin")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
register, ok := registerFuncSymbol.(func(context.Context) (Plugin, error))
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.WithStack(ErrInvalidRegisterFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
plg, err := register(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if plg == nil {
|
|
||||||
return nil, errors.WithStack(ErrInvalidPlugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Add(plg)
|
|
||||||
|
|
||||||
return plg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) LoadAll(ctx context.Context, pattern string) ([]Plugin, error) {
|
|
||||||
extensions := make([]Plugin, 0)
|
|
||||||
|
|
||||||
matches, err := filepath.Glob(pattern)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range matches {
|
|
||||||
ext, err := r.Load(ctx, m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
extensions = append(extensions, ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
return extensions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) Plugins() []Plugin {
|
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
plugins := make([]Plugin, 0, len(r.plugins))
|
|
||||||
for _, p := range r.plugins {
|
|
||||||
plugins = append(plugins, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return plugins
|
|
||||||
}
|
|
||||||
|
|
||||||
type IteratorFunc func(plg Plugin) error
|
|
||||||
|
|
||||||
func (r *Registry) Each(iterator IteratorFunc) error {
|
|
||||||
r.mutex.RLock()
|
|
||||||
defer r.mutex.RUnlock()
|
|
||||||
|
|
||||||
for _, p := range r.plugins {
|
|
||||||
if err := iterator(p); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
plugins: make(map[string]Plugin),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
package plugin_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testPlugin struct {
|
|
||||||
name string
|
|
||||||
version string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPlugin) PluginName() string {
|
|
||||||
return p.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPlugin) PluginVersion() string {
|
|
||||||
return p.version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPlugin) Foo() string {
|
|
||||||
return "bar"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegistryEach(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
reg := plugin.NewRegistry()
|
|
||||||
|
|
||||||
plugins := []*testPlugin{
|
|
||||||
{"plugin.a", "0.0.0"},
|
|
||||||
{"plugin.b", "0.0.1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range plugins {
|
|
||||||
reg.Add(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
total := 0
|
|
||||||
|
|
||||||
err := reg.Each(func(p plugin.Plugin) error {
|
|
||||||
total++
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Error(errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := len(plugins), total; e != g {
|
|
||||||
t.Errorf("total: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegistryGet(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
reg := plugin.NewRegistry()
|
|
||||||
|
|
||||||
plugins := []*testPlugin{
|
|
||||||
{"plugin.a", "0.0.0"},
|
|
||||||
{"plugin.b", "0.0.1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range plugins {
|
|
||||||
reg.Add(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range plugins {
|
|
||||||
plugin, err := reg.Get(p.name)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := p.name, plugin.PluginName(); e != g {
|
|
||||||
t.Errorf("plugin.PluginName(): expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := p.version, plugin.PluginVersion(); e != g {
|
|
||||||
t.Errorf("plugin.PluginVersion(): expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := reg.Get("plugin.c")
|
|
||||||
if !errors.Is(err, plugin.ErrPluginNotFound) {
|
|
||||||
t.Errorf("err: expected '%v', got '%v'", plugin.ErrPluginNotFound, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p != nil {
|
|
||||||
t.Errorf("reg.Get(\"plugin.c\"): expected '%v', got '%v'", nil, p)
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user