diff --git a/plugin/default.go b/plugin/default.go new file mode 100644 index 00000000..bcafbc3b --- /dev/null +++ b/plugin/default.go @@ -0,0 +1,125 @@ +// 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 + default: + 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` +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..c1aecc6e --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,46 @@ +// 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{} +} + +var ( + // Default plugin loader + DefaultPlugin = NewPlugin() +) + +// NewPlugin creates a new plugin interface +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/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}}, +} +` +) 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),