From b6c6b1327711c5acfa9ae2cf37fba59a8940ebe3 Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Mon, 9 Sep 2019 19:09:28 -0700 Subject: [PATCH 1/4] Support plugin loading --- plugin/default.go | 122 +++++++++++++++++++++++++++++++++++++++++++++ plugin/plugin.go | 29 +++++++++++ plugin/template.go | 20 ++++++++ 3 files changed, 171 insertions(+) create mode 100644 plugin/default.go create mode 100644 plugin/plugin.go create mode 100644 plugin/template.go diff --git a/plugin/default.go b/plugin/default.go new file mode 100644 index 00000000..d9d01ec0 --- /dev/null +++ b/plugin/default.go @@ -0,0 +1,122 @@ +// Package plugin provides the ability to load plugins +package plugin + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + pg "plugin" + "strings" + "text/template" + + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" + "github.com/micro/go-micro/config/cmd" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/transport" +) + +type plugin struct{} + +// Init sets up the plugin +func (p *plugin) Init(c *Config) error { + switch c.Type { + case "broker": + pg, ok := c.NewFunc.(func(...broker.Option) broker.Broker) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultBrokers[c.Name] = pg + case "client": + pg, ok := c.NewFunc.(func(...client.Option) client.Client) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultClients[c.Name] = pg + case "registry": + pg, ok := c.NewFunc.(func(...registry.Option) registry.Registry) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultRegistries[c.Name] = pg + + case "selector": + pg, ok := c.NewFunc.(func(...selector.Option) selector.Selector) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultSelectors[c.Name] = pg + case "server": + pg, ok := c.NewFunc.(func(...server.Option) server.Server) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultServers[c.Name] = pg + case "transport": + pg, ok := c.NewFunc.(func(...transport.Option) transport.Transport) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultTransports[c.Name] = pg + } + return fmt.Errorf("Unknown plugin type: %s for %s", c.Type, c.Name) +} + +// Load loads a plugin created with `go build -buildmode=plugin` +func (p *plugin) Load(path string) (*Config, error) { + plugin, err := pg.Open(path) + if err != nil { + return nil, err + } + s, err := plugin.Lookup("Plugin") + if err != nil { + return nil, err + } + pl, ok := s.(*Config) + if !ok { + return nil, errors.New("could not cast Plugin object") + } + return pl, nil +} + +// Generate creates a go file at the specified path. +// You must use `go build -buildmode=plugin`to build it. +func (p *plugin) Generate(path string, c *Config) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + t, err := template.New(c.Name).Parse(tmpl) + if err != nil { + return err + } + return t.Execute(f, c) +} + +// Build generates a dso plugin using the go command `go build -buildmode=plugin` +func (p *plugin) Build(path string, c *Config) error { + path = strings.TrimSuffix(path, ".so") + + // create go file in tmp path + temp := os.TempDir() + base := filepath.Base(path) + goFile := filepath.Join(temp, base+".go") + + // generate .go file + if err := p.Generate(goFile, c); err != nil { + return err + } + // remove .go file + defer os.Remove(goFile) + + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil && !os.IsExist(err) { + return fmt.Errorf("Failed to create dir %s: %v", filepath.Dir(path), err) + } + cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", path+".so", goFile) + return cmd.Run() +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 00000000..68291a36 --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,29 @@ +// Package plugin provides the ability to load plugins +package plugin + +// Plugin is a plugin loaded from a file +type Plugin interface { + // Initialise a plugin with the config + Init(c *Config) error + // Load loads a .so plugin at the given path + Load(path string) (*Config, error) + // Build a .so plugin with config at the path specified + Build(path string, c *Config) error +} + +// Config is the plugin config +type Config struct { + // Name of the plugin e.g rabbitmq + Name string + // Type of the plugin e.g broker + Type string + // Path specifies the import path + Path string + // NewFunc creates an instance of the plugin + NewFunc interface{} +} + +// NewPlugin creates a new plugin interface +func NewPlugin() Plugin { + return &plugin{} +} diff --git a/plugin/template.go b/plugin/template.go new file mode 100644 index 00000000..dd559bfa --- /dev/null +++ b/plugin/template.go @@ -0,0 +1,20 @@ +package plugin + +var ( + tmpl = ` +package main + +import ( + "github.com/micro/go-micro/plugin" + + "{{.Path}}" +) + +var Plugin = plugin.Config{ + Name: "{{.Name}}", + Type: "{{.Type}}", + Path: "{{.Path}}", + NewFunc: {{.Name}}.{{.NewFunc}}, +} +` +) From 3bfbcd5e6aee2676f3fb1f4896858f9aee881f2b Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Mon, 9 Sep 2019 19:43:13 -0700 Subject: [PATCH 2/4] Add default plugin loader --- plugin/plugin.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/plugin.go b/plugin/plugin.go index 68291a36..c648bfaf 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -23,6 +23,11 @@ type Config struct { NewFunc interface{} } +var ( + // Default plugin loader + DefaultPlugin = NewPlugin() +) + // NewPlugin creates a new plugin interface func NewPlugin() Plugin { return &plugin{} From a5ce3e32da95c09d981aff47d9669c0a0bd6cce0 Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Mon, 9 Sep 2019 20:17:36 -0700 Subject: [PATCH 3/4] Support plugin loading on service.Init --- config/cmd/cmd.go | 5 +++++ plugin/plugin.go | 12 ++++++++++++ service.go | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index beb4ba97..f80827d2 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -92,6 +92,11 @@ var ( EnvVar: "MICRO_CLIENT_POOL_TTL", Usage: "Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m", }, + cli.StringSliceFlag{ + Name: "plugin", + EnvVar: "MICRO_PLUGIN", + Usage: "Comma separated list of plugins e.g /path/to/plugin.so", + }, cli.IntFlag{ Name: "register_ttl", EnvVar: "MICRO_REGISTER_TTL", diff --git a/plugin/plugin.go b/plugin/plugin.go index c648bfaf..c1aecc6e 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -32,3 +32,15 @@ var ( func NewPlugin() Plugin { return &plugin{} } + +func Build(path string, c *Config) error { + return DefaultPlugin.Build(path, c) +} + +func Load(path string) (*Config, error) { + return DefaultPlugin.Load(path) +} + +func Init(c *Config) error { + return DefaultPlugin.Init(c) +} diff --git a/service.go b/service.go index e612a4b3..eed7f8ac 100644 --- a/service.go +++ b/service.go @@ -3,6 +3,7 @@ package micro import ( "os" "os/signal" + "strings" "sync" "syscall" @@ -10,7 +11,9 @@ import ( "github.com/micro/go-micro/config/cmd" "github.com/micro/go-micro/debug/handler" "github.com/micro/go-micro/metadata" + "github.com/micro/go-micro/plugin" "github.com/micro/go-micro/server" + "github.com/micro/go-micro/util/log" ) type service struct { @@ -44,6 +47,24 @@ func (s *service) Init(opts ...Option) { } s.once.Do(func() { + // setup the plugins + for _, p := range strings.Split(os.Getenv("MICRO_PLUGIN"), ",") { + if len(p) == 0 { + continue + } + + // load the plugin + c, err := plugin.Load(p) + if err != nil { + log.Fatal(err) + } + + // initialise the plugin + if err := plugin.Init(c); err != nil { + log.Fatal(err) + } + } + // Initialise the command flags, overriding new service _ = s.opts.Cmd.Init( cmd.Broker(&s.opts.Broker), From 065c7d5616b3d5b95ef425fe96e1074ce3f75d4b Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Tue, 10 Sep 2019 05:32:49 -0700 Subject: [PATCH 4/4] fix plugin init --- config/cmd/cmd.go | 5 ----- plugin/default.go | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index f80827d2..beb4ba97 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -92,11 +92,6 @@ var ( EnvVar: "MICRO_CLIENT_POOL_TTL", Usage: "Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m", }, - cli.StringSliceFlag{ - Name: "plugin", - EnvVar: "MICRO_PLUGIN", - Usage: "Comma separated list of plugins e.g /path/to/plugin.so", - }, cli.IntFlag{ Name: "register_ttl", EnvVar: "MICRO_REGISTER_TTL", diff --git a/plugin/default.go b/plugin/default.go index d9d01ec0..bcafbc3b 100644 --- a/plugin/default.go +++ b/plugin/default.go @@ -62,8 +62,11 @@ func (p *plugin) Init(c *Config) error { return fmt.Errorf("Invalid plugin %s", c.Name) } cmd.DefaultTransports[c.Name] = pg + default: + return fmt.Errorf("Unknown plugin type: %s for %s", c.Type, c.Name) } - return fmt.Errorf("Unknown plugin type: %s for %s", c.Type, c.Name) + + return nil } // Load loads a plugin created with `go build -buildmode=plugin`