From 8b85f60e63ff33e637a63d0c6ce67fa30b92df97 Mon Sep 17 00:00:00 2001 From: William Petit Date: Tue, 17 Nov 2020 09:19:15 +0100 Subject: [PATCH] WIP: extension feature --- example/extendable/.gitignore | 1 + example/extendable/Makefile | 11 +++ example/extendable/main.go | 23 ++++++ example/extendable/modd.conf | 6 ++ example/extendable/myext/extension.go | 12 +++ example/extendable/myext/main.go | 11 +++ extension/error.go | 8 ++ extension/extension.go | 6 ++ extension/registry.go | 101 ++++++++++++++++++++++++++ 9 files changed, 179 insertions(+) create mode 100644 example/extendable/.gitignore create mode 100644 example/extendable/Makefile create mode 100644 example/extendable/main.go create mode 100644 example/extendable/modd.conf create mode 100644 example/extendable/myext/extension.go create mode 100644 example/extendable/myext/main.go create mode 100644 extension/error.go create mode 100644 extension/extension.go create mode 100644 extension/registry.go diff --git a/example/extendable/.gitignore b/example/extendable/.gitignore new file mode 100644 index 0000000..7447f89 --- /dev/null +++ b/example/extendable/.gitignore @@ -0,0 +1 @@ +/bin \ No newline at end of file diff --git a/example/extendable/Makefile b/example/extendable/Makefile new file mode 100644 index 0000000..71756d4 --- /dev/null +++ b/example/extendable/Makefile @@ -0,0 +1,11 @@ +build: extension + go build -o ./bin/app ./ + +extension: + go build -o ./bin/myext.so -buildmode=plugin ./myext + +watch: + modd + +run: + ./bin/app \ No newline at end of file diff --git a/example/extendable/main.go b/example/extendable/main.go new file mode 100644 index 0000000..d639e6c --- /dev/null +++ b/example/extendable/main.go @@ -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()) + } +} diff --git a/example/extendable/modd.conf b/example/extendable/modd.conf new file mode 100644 index 0000000..f2ed2c2 --- /dev/null +++ b/example/extendable/modd.conf @@ -0,0 +1,6 @@ +**/*.go +modd.conf +Makefile { + prep: make build + prep: make run +} \ No newline at end of file diff --git a/example/extendable/myext/extension.go b/example/extendable/myext/extension.go new file mode 100644 index 0000000..d741a58 --- /dev/null +++ b/example/extendable/myext/extension.go @@ -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" +} diff --git a/example/extendable/myext/main.go b/example/extendable/myext/main.go new file mode 100644 index 0000000..c41f850 --- /dev/null +++ b/example/extendable/myext/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "context" + + "gitlab.com/wpetit/goweb/extension" +) + +func RegisterExtension(ctx context.Context) (extension.Extension, error) { + return &MyExtension{}, nil +} diff --git a/extension/error.go b/extension/error.go new file mode 100644 index 0000000..a7aeb89 --- /dev/null +++ b/extension/error.go @@ -0,0 +1,8 @@ +package extension + +import "errors" + +var ( + ErrInvalidRegisterFunc = errors.New("invalid register func") + ErrInvalidExtension = errors.New("invalid extension") +) diff --git a/extension/extension.go b/extension/extension.go new file mode 100644 index 0000000..1c5e6f8 --- /dev/null +++ b/extension/extension.go @@ -0,0 +1,6 @@ +package extension + +type Extension interface { + ExtensionName() string + ExtensionVersion() string +} diff --git a/extension/registry.go b/extension/registry.go new file mode 100644 index 0000000..acb3eb0 --- /dev/null +++ b/extension/registry.go @@ -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), + } +}