From b6c6b1327711c5acfa9ae2cf37fba59a8940ebe3 Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Mon, 9 Sep 2019 19:09:28 -0700 Subject: [PATCH] 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}}, +} +` +)