├── .gitignore ├── README.md ├── connections ├── .envrc.example ├── basic-plugin │ ├── client.js │ └── info.json ├── main.go └── twitter-plugin │ ├── client.js │ └── info.json ├── debugging └── main.go ├── events ├── creator │ ├── info.json │ └── script.js ├── main.go └── userUpdater │ ├── info.json │ └── script.js ├── extensions └── main.go ├── go.mod ├── go.sum ├── hashicorp ├── build.sh ├── interfaces │ └── hello_interface.go ├── main.go └── plugin │ └── hello_impl.go ├── plugins ├── build.sh ├── hello │ └── greeter.go └── main.go ├── simple └── main.go └── utils ├── client.go ├── generator.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | .DS_Store 3 | hashicorp/host 4 | hashicorp/plugin/hello 5 | plugins/host 6 | *.so 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plugins in GO Examples 2 | 3 | ## Prerequisites 4 | 5 | * Unix System (Linux/MacOS) 6 | * Go (Version > 1.10) 7 | * Twitter App + Access Data (for the connections example) 8 | * [direnv](https://direnv.net/) to inject the `env` Variables (for the connections example) 9 | -------------------------------------------------------------------------------- /connections/.envrc.example: -------------------------------------------------------------------------------- 1 | # Go to https://developer.twitter.com/en/apps 2 | # Create a Developer Account 3 | # Create a new App, request these parameter under "key and tokens" 4 | 5 | export TWITTER_API_KEY="" 6 | export TWITTER_API_SECRET_KEY="" 7 | export TWITTER_ACCESS_TOKEN="" 8 | export TWITTER_ACCESS_TOKEN_SECRET="" 9 | export API_USER="" 10 | export API_PASS="" -------------------------------------------------------------------------------- /connections/basic-plugin/client.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | request = { 4 | "host":"https://google.com" 5 | } 6 | response = GET(request); 7 | LOG("error response: " + JSON.stringify(response)); 8 | 9 | request = { 10 | "host":"http://example.com/api" 11 | } 12 | response = GET(request); 13 | var body = response["body"]; 14 | LOG("response: " + body); 15 | })(); 16 | -------------------------------------------------------------------------------- /connections/basic-plugin/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "whitelist": [ 3 | "http://example.com" 4 | ] 5 | } -------------------------------------------------------------------------------- /connections/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/url" 7 | "os" 8 | "path" 9 | 10 | "github.com/innoq/go-plugins-examples/utils" 11 | "github.com/robertkrimen/otto" 12 | ) 13 | 14 | func main() { 15 | if err := run(); err != nil { 16 | fmt.Fprintf(os.Stderr, "error: %v\n", err) 17 | os.Exit(1) 18 | } 19 | } 20 | 21 | func run() error { 22 | 23 | var err error 24 | 25 | err = runPlugin("basic-plugin") 26 | if err != nil { 27 | return err 28 | } 29 | 30 | err = runPlugin("twitter-plugin") 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return nil 36 | } 37 | 38 | func runPlugin(plugin string) error { 39 | 40 | fmt.Printf("\n\nrunning Plugin %s: \n\n", plugin) 41 | info, err := utils.ReadJSON(path.Join(plugin, "info.json")) 42 | if err != nil { 43 | return err 44 | } 45 | log.Printf("info: %s", info) 46 | fmt.Println("") 47 | vm, err := getVM(info) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | script, err := utils.ReadFile(path.Join(plugin, "client.js")) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | _, err = vm.Run(script) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return nil 63 | } 64 | 65 | func getVM(info map[string]interface{}) (*otto.Otto, error) { 66 | vm := otto.New() 67 | 68 | if envKeyValue, ok := info["env_variables"]; ok { 69 | envKeys := envKeyValue.([]interface{}) 70 | err := utils.InjectEnvironmentVariables(envKeys, vm) 71 | if err != nil { 72 | return nil, err 73 | } 74 | } 75 | 76 | err := vm.Set("LOG", func(message string) { 77 | log.Println(message) 78 | }) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | err = vm.Set("POST", loadPostRequest(info)) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | err = vm.Set("GET", loadGetRequest(info)) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return vm, nil 94 | } 95 | 96 | // Method - type of a http Method 97 | type Method func(request map[string]interface{}) map[string]interface{} 98 | 99 | func loadPostRequest(info map[string]interface{}) Method { 100 | return func(request map[string]interface{}) map[string]interface{} { 101 | response := make(map[string]interface{}) 102 | whitelist := info["whitelist"].([]interface{}) 103 | requestURL := request["host"].(string) 104 | err := checkWhitelist(requestURL, whitelist) 105 | if err != nil { 106 | response["error"] = err.Error() 107 | return response 108 | } 109 | 110 | client, err := utils.NewClient(request) 111 | if err != nil { 112 | response["error"] = err.Error() 113 | return response 114 | } 115 | content := "" 116 | if request["content"] != nil { 117 | content = request["content"].(string) 118 | } 119 | 120 | contentType := "application/json" 121 | if request["contentType"] != nil { 122 | contentType = request["contentType"].(string) 123 | } 124 | 125 | body, err := client.Post(content, contentType) 126 | if err != nil { 127 | response["error"] = err.Error() 128 | return response 129 | } 130 | 131 | response["body"] = body 132 | return response 133 | } 134 | } 135 | 136 | func loadGetRequest(info map[string]interface{}) Method { 137 | return func(request map[string]interface{}) map[string]interface{} { 138 | response := make(map[string]interface{}) 139 | whitelist := info["whitelist"].([]interface{}) 140 | requestURL := request["host"].(string) 141 | err := checkWhitelist(requestURL, whitelist) 142 | if err != nil { 143 | response["error"] = err.Error() 144 | return response 145 | } 146 | 147 | client, err := utils.NewClient(request) 148 | if err != nil { 149 | response["error"] = err.Error() 150 | return response 151 | } 152 | 153 | body, err := client.Get() 154 | if err != nil { 155 | response["error"] = err.Error() 156 | return response 157 | } 158 | 159 | response["body"] = body 160 | return response 161 | } 162 | } 163 | 164 | func checkWhitelist(requestURL string, whitelist []interface{}) error { 165 | url, err := url.Parse(requestURL) 166 | if err != nil { 167 | return err 168 | } 169 | host := fmt.Sprintf("%s://%s", url.Scheme, url.Host) 170 | for _, entry := range whitelist { 171 | if entry.(string) == host { 172 | return nil 173 | } 174 | } 175 | return fmt.Errorf("accessing %s is blocked", host) 176 | } 177 | -------------------------------------------------------------------------------- /connections/twitter-plugin/client.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | request = { 3 | "oauth1": { 4 | "consumerKey": env("TWITTER_API_KEY"), 5 | "consumerSecret": env("TWITTER_API_SECRET_KEY"), 6 | "accessToken": env("TWITTER_ACCESS_TOKEN"), 7 | "accessSecret": env("TWITTER_ACCESS_TOKEN_SECRET") 8 | }, 9 | "host":"https://api.twitter.com/1.1/search/tweets.json?q=from%3Atwitterdev&result_type=mixed&count=2" 10 | } 11 | response = GET(request); 12 | var body = response["body"]; 13 | for(i in body["statuses"]) { 14 | var status = body["statuses"][i]; 15 | LOG(status["created_at"] + " @"+status["user"]["screen_name"] + ": " + status["text"]); 16 | } 17 | })(); 18 | -------------------------------------------------------------------------------- /connections/twitter-plugin/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "whitelist": [ 3 | "https://api.twitter.com" 4 | ], 5 | "env_variables": [ 6 | "TWITTER_API_KEY", 7 | "TWITTER_API_SECRET_KEY", 8 | "TWITTER_ACCESS_TOKEN", 9 | "TWITTER_ACCESS_TOKEN_SECRET" 10 | ] 11 | } -------------------------------------------------------------------------------- /debugging/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/robertkrimen/otto" 8 | ) 9 | 10 | func main() { 11 | // Create a new otto interpreter instance. 12 | vm := otto.New() 13 | 14 | // This is where the magic happens! 15 | vm.SetDebuggerHandler(func(o *otto.Otto) { 16 | // The `Context` function is another hidden gem - I'll talk about that in 17 | // another post. 18 | c := o.Context() 19 | 20 | // Here, we go through all the symbols in scope, adding their names to a 21 | // list. 22 | var a []string 23 | for k := range c.Symbols { 24 | a = append(a, k) 25 | } 26 | 27 | sort.Strings(a) 28 | 29 | // Print out the symbols in scope. 30 | fmt.Printf("symbols in scope: %v\n", a) 31 | }) 32 | 33 | // Here's our script - very simple. 34 | s := ` 35 | var a = 1; 36 | var b = 2; 37 | debugger; 38 | ` 39 | 40 | // When we run this, we should see all the symbols printed out (including 41 | // `a` and `b`). 42 | if _, err := vm.Run(s); err != nil { 43 | panic(err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /events/creator/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "events" : [ 3 | ] 4 | } -------------------------------------------------------------------------------- /events/creator/script.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | console.log("data: " + JSON.stringify(data)); 3 | save(data); 4 | })(); 5 | -------------------------------------------------------------------------------- /events/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "path" 8 | 9 | "github.com/innoq/go-plugins-examples/utils" 10 | "github.com/robertkrimen/otto" 11 | ) 12 | 13 | var listeners map[string][]string 14 | var scripts map[string]string 15 | 16 | func init() { 17 | scripts = make(map[string]string) 18 | listeners = make(map[string][]string) 19 | for _, event := range []string{"create", "read", "update", "delete"} { 20 | listeners[event] = make([]string, 0) 21 | } 22 | err := loadPlugins() 23 | if err != nil { 24 | panic(err) 25 | } 26 | } 27 | 28 | func main() { 29 | vm := otto.New() 30 | vm.Set("save", saveMethod) 31 | generator := utils.NewDataGenerator() 32 | for i := 0; i < 42; i++ { 33 | fmt.Println("") 34 | data := generator.Next() 35 | vm.Set("data", data) 36 | _, err := vm.Run(scripts["creator"]) 37 | if err != nil { 38 | panic(err) 39 | } 40 | fmt.Println("") 41 | } 42 | } 43 | 44 | func read(vm *otto.Otto, script string) { 45 | vm.Run(script) 46 | } 47 | 48 | func saveMethod(data map[string]string) error { 49 | log.Printf("saving some data %s", data) 50 | return notifyListener(data) 51 | } 52 | 53 | func notifyListener(data map[string]string) error { 54 | event := data["event"] 55 | if listenerScripts, ok := listeners[event]; ok { 56 | vm := otto.New() 57 | for _, name := range listenerScripts { 58 | log.Printf("notify %s about %s event", name, event) 59 | err := vm.Set("data", data) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | _, err = vm.Run(scripts[name]) 65 | if err != nil { 66 | return err 67 | } 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func loadPlugins() error { 74 | files, err := ioutil.ReadDir(".") 75 | if err != nil { 76 | return err 77 | } 78 | 79 | for _, file := range files { 80 | if file.IsDir() { 81 | info, err := utils.ReadJSON(path.Join(file.Name(), "info.json")) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | script, err := utils.ReadFile(path.Join(file.Name(), "script.js")) 87 | if err != nil { 88 | return err 89 | } 90 | scripts[file.Name()] = script 91 | events := info["events"].([]interface{}) 92 | for _, eventEntry := range events { 93 | event := eventEntry.(string) 94 | listeners[event] = append(listeners[event], file.Name()) 95 | log.Printf("register %s for event %s", file.Name(), event) 96 | } 97 | } 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /events/userUpdater/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "events" : [ 3 | "create", 4 | "update" 5 | ] 6 | } -------------------------------------------------------------------------------- /events/userUpdater/script.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | console.log("event: " + JSON.stringify(data)); 3 | if(data["type"] === "user") { 4 | console.log("Ok, I will also update the User DB!"); 5 | } 6 | })(); 7 | -------------------------------------------------------------------------------- /extensions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/robertkrimen/otto" 7 | ) 8 | 9 | func main() { 10 | vm := otto.New() 11 | 12 | err := vm.Set("log", logJS) 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | vm.Run(` 18 | console.log("logging with JS!"); 19 | log("logging with Golang!"); 20 | `) 21 | 22 | jsDate, err := vm.Run(` 23 | (function(){ 24 | date = new Date(); 25 | return date; 26 | })(); 27 | `) 28 | if err != nil { 29 | panic(err) 30 | } 31 | log.Printf("jsDate: %s", jsDate) 32 | 33 | dataMap := make(map[string]interface{}) 34 | dataMap["foo"] = "bar" 35 | dataMap["one"] = "1" 36 | dataMap["two"] = "2" 37 | 38 | err = vm.Set("dataMap", dataMap) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | value, err := vm.Run(` 44 | (function(){ 45 | var keys = []; 46 | for(k in dataMap) { 47 | log(k + ": " + dataMap[k]); 48 | keys.push(k); 49 | } 50 | return keys; 51 | })(); 52 | `) 53 | 54 | keys, err := value.Export() 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | keyArray := keys.([]string) 60 | log.Printf("keys: %s", keyArray) 61 | 62 | } 63 | 64 | func logJS(content string) { 65 | log.Println(content) 66 | } 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/innoq/go-plugins-examples 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/dghubble/oauth1 v0.6.0 7 | github.com/google/uuid v1.1.1 // indirect 8 | github.com/lithammer/shortuuid v3.0.0+incompatible 9 | github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff 10 | gopkg.in/sourcemap.v1 v1.0.5 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/dghubble/oauth1 v0.6.0 h1:m1yC01Ohc/eF38jwZ8JUjL1a+XHHXtGQgK+MxQbmSx0= 3 | github.com/dghubble/oauth1 v0.6.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk= 4 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 5 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 6 | github.com/lithammer/shortuuid v1.0.0 h1:kdcbvjGVEgqeVeDIRtnANOi/F6ftbKrtbxY+cjQmK1Q= 7 | github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w= 8 | github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA= 11 | github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 14 | gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= 15 | gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= 16 | -------------------------------------------------------------------------------- /hashicorp/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go build -o ./plugin/hello ./plugin/hello_impl.go 4 | chmod +x ./plugin/hello 5 | go build -o host . 6 | 7 | echo "run me with ./host" -------------------------------------------------------------------------------- /hashicorp/interfaces/hello_interface.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "net/rpc" 5 | 6 | "github.com/hashicorp/go-plugin" 7 | ) 8 | 9 | // Hello is the interface that we're exposing as a plugin. 10 | type Hello interface { 11 | Greet() string 12 | } 13 | 14 | // Here is an implementation that talks over RPC 15 | type HelloRPC struct{ client *rpc.Client } 16 | 17 | func (h *HelloRPC) Greet() string { 18 | var resp string 19 | err := h.client.Call("Plugin.Greet", new(interface{}), &resp) 20 | if err != nil { 21 | // You usually want your interfaces to return errors. If they don't, 22 | // there isn't much other choice here. 23 | panic(err) 24 | } 25 | 26 | return resp 27 | } 28 | 29 | // Here is the RPC server that HelloRPC talks to, conforming to 30 | // the requirements of net/rpc 31 | type HelloRPCServer struct { 32 | // This is the real implementation 33 | Impl Hello 34 | } 35 | 36 | func (s *HelloRPCServer) Greet(args interface{}, resp *string) error { 37 | *resp = s.Impl.Greet() 38 | return nil 39 | } 40 | 41 | // This is the implementation of plugin.Plugin so we can serve/consume this 42 | // 43 | // This has two methods: Server must return an RPC server for this plugin 44 | // type. We construct a GreeterRPCServer for this. 45 | // 46 | // Client must return an implementation of our interface that communicates 47 | // over an RPC client. We return GreeterRPC for this. 48 | // 49 | // Ignore MuxBroker. That is used to create more multiplexed streams on our 50 | // plugin connection and is a more advanced use case. 51 | type HelloPlugin struct { 52 | // Impl Injection 53 | Impl Hello 54 | } 55 | 56 | func (p *HelloPlugin) Server(*plugin.MuxBroker) (interface{}, error) { 57 | return &HelloRPCServer{Impl: p.Impl}, nil 58 | } 59 | 60 | func (HelloPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { 61 | return &HelloRPC{client: c}, nil 62 | } 63 | -------------------------------------------------------------------------------- /hashicorp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/hashicorp/go-hclog" 10 | "github.com/hashicorp/go-plugin" 11 | interfaces "github.com/innoq/go-plugins-examples/hashicorp/interfaces" 12 | ) 13 | 14 | // handshakeConfigs are used to just do a basic handshake between 15 | // a plugin and host. If the handshake fails, a user friendly error is shown. 16 | // This prevents users from executing bad plugins or executing a plugin 17 | // directory. It is a UX feature, not a security feature. 18 | var handshakeConfig = plugin.HandshakeConfig{ 19 | ProtocolVersion: 1, 20 | MagicCookieKey: "BASIC_PLUGIN", 21 | MagicCookieValue: "hello", 22 | } 23 | 24 | // pluginMap is the map of plugins we can dispense. 25 | var pluginMap = map[string]plugin.Plugin{ 26 | "hello": &interfaces.HelloPlugin{}, 27 | } 28 | 29 | func main() { 30 | // Create an hclog.Logger 31 | logger := hclog.New(&hclog.LoggerOptions{ 32 | Name: "PluginHost", 33 | Output: os.Stdout, 34 | Level: hclog.Debug, 35 | }) 36 | 37 | // We're a host! Start by launching the plugin process. 38 | client := plugin.NewClient(&plugin.ClientConfig{ 39 | HandshakeConfig: handshakeConfig, 40 | Plugins: pluginMap, 41 | Cmd: exec.Command("./plugin/hello"), 42 | Logger: logger, 43 | }) 44 | defer client.Kill() 45 | 46 | // Connect via RPC 47 | rpcClient, err := client.Client() 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | // Request the plugin 53 | raw, err := rpcClient.Dispense("hello") 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | // We should have a HelloPlugin now! This feels like a normal interface 59 | // implementation but is in fact over an RPC connection. 60 | hello := raw.(interfaces.Hello) 61 | fmt.Println(hello.Greet()) 62 | } 63 | -------------------------------------------------------------------------------- /hashicorp/plugin/hello_impl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/hashicorp/go-hclog" 7 | "github.com/hashicorp/go-plugin" 8 | interfaces "github.com/innoq/go-plugins-examples/hashicorp/interfaces" 9 | ) 10 | 11 | // Here is a real implementation of Greeter 12 | type Hello struct { 13 | logger hclog.Logger 14 | } 15 | 16 | func (h *Hello) Greet() string { 17 | h.logger.Debug("message from HelloPlugin.Greet") 18 | return "Hello!" 19 | } 20 | 21 | func main() { 22 | logger := hclog.New(&hclog.LoggerOptions{ 23 | Name: "HelloPlugin", 24 | Level: hclog.Trace, 25 | Output: os.Stderr, 26 | JSONFormat: true, 27 | }) 28 | 29 | hello := &Hello{ 30 | logger: logger, 31 | } 32 | 33 | var handshakeConfig = plugin.HandshakeConfig{ 34 | ProtocolVersion: 1, 35 | MagicCookieKey: "BASIC_PLUGIN", 36 | MagicCookieValue: "hello", 37 | } 38 | 39 | // pluginMap is the map of plugins we can dispense. 40 | var pluginMap = map[string]plugin.Plugin{ 41 | "hello": &interfaces.HelloPlugin{Impl: hello}, 42 | } 43 | 44 | logger.Debug("message from plugin", "foo", "bar") 45 | 46 | plugin.Serve(&plugin.ServeConfig{ 47 | HandshakeConfig: handshakeConfig, 48 | Plugins: pluginMap, 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /plugins/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go build -buildmode=plugin -o hello/plugin.so hello/greeter.go 4 | go build -o host 5 | chmod +x ./host 6 | 7 | echo "run me with ./host" -------------------------------------------------------------------------------- /plugins/hello/greeter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type greeting string 6 | 7 | func (g greeting) Greet() { 8 | fmt.Println("Hello World!") 9 | } 10 | 11 | // Greeter - exported 12 | var Greeter greeting 13 | -------------------------------------------------------------------------------- /plugins/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "plugin" 7 | ) 8 | 9 | // Greeter - the interface of the greeter plugin 10 | type Greeter interface { 11 | Greet() 12 | } 13 | 14 | func main() { 15 | // determine module to load 16 | mod := "hello" 17 | if len(os.Args) == 2 { 18 | mod = os.Args[1] 19 | } 20 | 21 | // load module 22 | // 1. open the so file to load the symbols 23 | plug, err := plugin.Open(fmt.Sprintf("%s/plugin.so", mod)) 24 | if err != nil { 25 | fmt.Println(err) 26 | os.Exit(1) 27 | } 28 | 29 | // 2. look up a symbol (an exported function or variable) 30 | // in this case, variable Greeter 31 | symGreeter, err := plug.Lookup("Greeter") 32 | if err != nil { 33 | fmt.Println(err) 34 | os.Exit(1) 35 | } 36 | 37 | // 3. Assert that loaded symbol is of a desired type 38 | // in this case interface type Greeter (defined above) 39 | var greeter Greeter 40 | greeter, ok := symGreeter.(Greeter) 41 | if !ok { 42 | fmt.Println("unexpected type from module symbol") 43 | os.Exit(1) 44 | } 45 | 46 | // 4. use the module 47 | greeter.Greet() 48 | } 49 | -------------------------------------------------------------------------------- /simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/robertkrimen/otto" 4 | 5 | func main() { 6 | 7 | vm := otto.New() 8 | vm.Run(` 9 | console.log("Hello World!"); 10 | `) 11 | } 12 | -------------------------------------------------------------------------------- /utils/client.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | "time" 12 | 13 | "github.com/dghubble/oauth1" 14 | ) 15 | 16 | // Client - basic structure of a client request 17 | type Client struct { 18 | url string 19 | client *http.Client 20 | } 21 | 22 | // NewClient - this returns new client, based on the request 23 | func NewClient(request map[string]interface{}) (*Client, error) { 24 | var mainError error = nil 25 | var err error = nil 26 | httpClient := &http.Client{Timeout: 5 * time.Second} 27 | host := request["host"].(string) 28 | 29 | if oauth1Config, ok := request["oauth1"]; ok { 30 | config := oauth1Config.(map[string]interface{}) 31 | httpClient, err = returnOauth1Client(config) 32 | if err != nil { 33 | mainError = err 34 | } 35 | } 36 | 37 | return &Client{ 38 | url: host, 39 | client: httpClient, 40 | }, mainError 41 | } 42 | 43 | func returnOauth1Client(oauth1Config map[string]interface{}) (*http.Client, error) { 44 | consumerKey := oauth1Config["consumerKey"].(string) 45 | consumerSecret := oauth1Config["consumerSecret"].(string) 46 | accessToken := oauth1Config["accessToken"].(string) 47 | accessSecret := oauth1Config["accessSecret"].(string) 48 | if consumerKey == "" || consumerSecret == "" || accessToken == "" || accessSecret == "" { 49 | return nil, errors.New("Missing required environment variable") 50 | } 51 | config := oauth1.NewConfig(consumerKey, consumerSecret) 52 | token := oauth1.NewToken(accessToken, accessSecret) 53 | 54 | httpClient := config.Client(oauth1.NoContext, token) 55 | return httpClient, nil 56 | } 57 | 58 | // Get - finally performs the request 59 | func (c *Client) Get() (interface{}, error) { 60 | resp, err := c.client.Get(c.url) 61 | if err != nil { 62 | return "", err 63 | } 64 | defer resp.Body.Close() 65 | 66 | body, err := ioutil.ReadAll(resp.Body) 67 | if err != nil { 68 | return "", err 69 | } 70 | return mapContent(body, resp.Header.Get("Content-Type")) 71 | } 72 | 73 | // Post - finally performs the request 74 | func (c *Client) Post(content string, contentType string) (interface{}, error) { 75 | payload := []byte(content) 76 | resp, err := c.client.Post(c.url, contentType, bytes.NewBuffer(payload)) 77 | if err != nil { 78 | return []byte{}, err 79 | } 80 | defer resp.Body.Close() 81 | 82 | body, err := ioutil.ReadAll(resp.Body) 83 | if err != nil { 84 | return []byte{}, err 85 | } 86 | return mapContent(body, resp.Header.Get("Content-Type")) 87 | } 88 | 89 | // mapContent - this maps the reveiced bytes to a fitting type, matching the content-type 90 | func mapContent(body []byte, contentType string) (interface{}, error) { 91 | if strings.Contains(contentType, "/json") { 92 | var jsonBody interface{} 93 | err := json.Unmarshal(body, &jsonBody) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return jsonBody, nil 98 | } 99 | if strings.Contains(contentType, "text/") { 100 | return string(body), nil 101 | } 102 | return body, nil 103 | } 104 | 105 | // Debug - outputs some debug infos 106 | func (c *Client) Debug() { 107 | log.Printf("%v", c) 108 | } 109 | -------------------------------------------------------------------------------- /utils/generator.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/lithammer/shortuuid" 7 | ) 8 | 9 | // DataGenerator - Tool for creating random test data 10 | type DataGenerator struct{} 11 | 12 | // NewDataGenerator - create a new data generator 13 | func NewDataGenerator() *DataGenerator { 14 | return &DataGenerator{} 15 | } 16 | 17 | // Next - generate the next data entry 18 | func (d *DataGenerator) Next() map[string]string { 19 | data := make(map[string]string) 20 | i := rand.Intn(100) 21 | if i > 66 { 22 | data["type"] = "user" 23 | } else { 24 | data["type"] = "object" 25 | } 26 | if i > 75 { 27 | data["event"] = "create" 28 | } else if i > 50 { 29 | data["event"] = "update" 30 | } else if i > 25 { 31 | data["event"] = "delete" 32 | } else { 33 | data["event"] = "read" 34 | } 35 | data["id"] = shortuuid.New() 36 | return data 37 | } 38 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/robertkrimen/otto" 9 | ) 10 | 11 | // ReadJSON - reads the content of a JSON file, returns it as a map 12 | func ReadJSON(path string) (map[string]interface{}, error) { 13 | content, err := ReadFile(path) 14 | if err != nil { 15 | return make(map[string]interface{}), err 16 | } 17 | var jsonContent map[string]interface{} 18 | err = json.Unmarshal([]byte(content), &jsonContent) 19 | if err != nil { 20 | return make(map[string]interface{}), err 21 | } 22 | return jsonContent, nil 23 | } 24 | 25 | // ReadFile - reads the content of a file, returns it as a string 26 | func ReadFile(path string) (string, error) { 27 | data, err := ioutil.ReadFile(path) 28 | if err != nil { 29 | return "", err 30 | } 31 | return string(data), nil 32 | } 33 | 34 | // InjectEnvironmentVariables - inject all requeste Environment Variables 35 | func InjectEnvironmentVariables(envKeys []interface{}, vm *otto.Otto) error { 36 | envs := make(map[string]string) 37 | for _, element := range envKeys { 38 | k := element.(string) 39 | v := os.Getenv(k) 40 | envs[k] = v 41 | } 42 | err := vm.Set("env", func(k string) string { 43 | if v, ok := envs[k]; ok { 44 | return v 45 | } 46 | return "" 47 | }) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } 53 | --------------------------------------------------------------------------------