Skip to content

Commit

Permalink
some move to hashicorp/go-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
qwerty287 committed Nov 22, 2023
1 parent f5bbbbe commit 91afda4
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 46 deletions.
4 changes: 2 additions & 2 deletions cmd/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ import (
"go.woodpecker-ci.org/woodpecker/pipeline/backend"
"go.woodpecker-ci.org/woodpecker/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/shared/utils"
"go.woodpecker-ci.org/woodpecker/shared/addon"
addonTypes "go.woodpecker-ci.org/woodpecker/shared/addon/types"
"go.woodpecker-ci.org/woodpecker/shared/utils"
"go.woodpecker-ci.org/woodpecker/version"
)

Expand Down Expand Up @@ -247,7 +247,7 @@ func run(c *cli.Context) error {
}

func getEngine(c *cli.Context, backendCtx context.Context) (types.Engine, error) {
addonEngine, err := addon.Load[types.Engine](c.StringSlice("addons"), addonTypes.TypeForge)
addonEngine, err := addon.Load[types.Engine](c.StringSlice("addons"), addonTypes.TypeEngine)
if err != nil {
log.Error().Err(err).Msg("cannot load addon")
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/google/tink/go v1.7.0
github.com/google/uuid v1.4.0
github.com/gorilla/securecookie v1.1.2
github.com/hashicorp/go-plugin v1.4.3
github.com/jellydator/ttlcache/v3 v3.1.0
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9
Expand Down Expand Up @@ -104,6 +105,7 @@ require (
github.com/hashicorp/go-hclog v1.2.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/go-version v1.5.0 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand All @@ -118,10 +120,12 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
Expand Down
48 changes: 48 additions & 0 deletions go.sum

Large diffs are not rendered by default.

85 changes: 44 additions & 41 deletions shared/addon/addon.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,66 @@ package addon

import (
"errors"
"fmt"
"os"
"plugin"
"reflect"
"os/exec"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/hashicorp/go-plugin"

"go.woodpecker-ci.org/woodpecker/shared/addon/rpc"
"go.woodpecker-ci.org/woodpecker/shared/addon/types"
)

var pluginCache = map[string]*plugin.Plugin{}

type Addon[T any] struct {
Type types.Type
Value T
}

func Load[T any](files []string, t types.Type) (*Addon[T], error) {
for _, file := range files {
if _, has := pluginCache[file]; !has {
p, err := plugin.Open(file)
if err != nil {
return nil, err
}
pluginCache[file] = p
}
//for _, file := range files {
c := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: rpc.HandshakeConfig,
Plugins: plugin.PluginSet{
string(t): &rpc.AddonPlugin[T]{},
},
Cmd: exec.Command(files[0]),
Logger: nil, // TODO zerolog wrapper
})

typeLookup, err := pluginCache[file].Lookup("Type")
if err != nil {
return nil, err
}
if addonType, is := typeLookup.(*types.Type); !is {
return nil, errors.New("addon type has incorrect type")
} else if *addonType != t {
continue
}
rpcClient, err := c.Client()
if err != nil {
return nil, err
}

mainLookup, err := pluginCache[file].Lookup("Addon")
if err != nil {
return nil, err
}
fmt.Println(reflect.TypeOf(mainLookup))
main, is := mainLookup.(func(zerolog.Logger, []string) (T, error))
if !is {
return nil, errors.New("addon main has incorrect type")
}
raw, err := rpcClient.Dispense(string(t))
if err != nil {
return nil, err
}

addon, ok := raw.(types.Addon[T])
if !ok {
return nil, errors.New("addon has bad type")
}

mainOut, err := main(log.Logger, os.Environ())
if err != nil {
return nil, err
}
return &Addon[T]{
Type: t,
Value: mainOut,
}, nil
if addon.Type() != t {
//continue
return nil, nil
}

mainOut, err := addon.Addon(os.Environ())
if err != nil {
return nil, err
}

//mainOutTyped, is := mainOut.(T)
//if !is {
//return nil, errors.New("main output has bad type")
//}

return &Addon[T]{
Type: t,
Value: mainOut,
}, nil
//}

return nil, nil
}
17 changes: 17 additions & 0 deletions shared/addon/execute/execute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package execute

import (
"github.com/hashicorp/go-plugin"

"go.woodpecker-ci.org/woodpecker/shared/addon/rpc"
addonTypes "go.woodpecker-ci.org/woodpecker/shared/addon/types"
)

func Execute[T any](addon addonTypes.Addon[T]) {
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: rpc.HandshakeConfig,
Plugins: plugin.PluginSet{
string(addon.Type()): &rpc.AddonPlugin[T]{Impl: addon},
},
})
}
34 changes: 34 additions & 0 deletions shared/addon/rpc/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package rpc

import (
"net/rpc"

"github.com/rs/zerolog/log"

"go.woodpecker-ci.org/woodpecker/shared/addon/types"
)

type AddonRPCClient[T any] struct {
client *rpc.Client
}

func (a *AddonRPCClient[T]) Type() types.Type {
var resp types.Type
err := a.client.Call("Plugin.Type", new(any), &resp)
if err != nil {
log.Error().Err(err).Msg("could not get addon type")
return ""
}

return resp
}

func (a *AddonRPCClient[T]) Addon(env []string) (T, error) {
var resp T
err := a.client.Call("Plugin.Addon", map[string]any{
//"logger": logger,
"env": env,
}, &resp)

return resp, err
}
27 changes: 27 additions & 0 deletions shared/addon/rpc/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package rpc

import (
"net/rpc"

"github.com/hashicorp/go-plugin"

"go.woodpecker-ci.org/woodpecker/shared/addon/types"
)

var HandshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "woodpecker_plugin_key",
MagicCookieValue: "woodpecker_plugin_value",
}

type AddonPlugin[T any] struct {
Impl types.Addon[T]
}

func (a *AddonPlugin[T]) Server(_ *plugin.MuxBroker) (interface{}, error) {
return &AddonRPCServer[T]{Impl: a.Impl}, nil
}

func (*AddonPlugin[T]) Client(_ *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &AddonRPCClient[T]{client: c}, nil
}
20 changes: 20 additions & 0 deletions shared/addon/rpc/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package rpc

import (
"go.woodpecker-ci.org/woodpecker/shared/addon/types"
)

type AddonRPCServer[T any] struct {
Impl types.Addon[T]
}

func (a *AddonRPCServer[T]) Type(_ interface{}, resp *types.Type) error {
*resp = a.Impl.Type()
return nil
}

func (a *AddonRPCServer[T]) Addon(args map[string]interface{}, resp *T) error {
addon, err := a.Impl.Addon( /*args["logger"].(zerolog.Logger), */ args["env"].([]string))
*resp = addon
return err
}
17 changes: 15 additions & 2 deletions shared/addon/test-addon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,28 @@ import (
"net/http"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"

"go.woodpecker-ci.org/woodpecker/server/forge"
forge_types "go.woodpecker-ci.org/woodpecker/server/forge/types"
"go.woodpecker-ci.org/woodpecker/server/model"
"go.woodpecker-ci.org/woodpecker/shared/addon/execute"
addon_types "go.woodpecker-ci.org/woodpecker/shared/addon/types"
)

var Type = addon_types.TypeForge
func main() {
execute.Execute[forge.Forge](&TestAddon{})
}

type TestAddon struct {
}

func (a *TestAddon) Type() addon_types.Type {
return addon_types.TypeForge
}

func Addon(logger zerolog.Logger, env []string) (forge.Forge, error) {
func (a *TestAddon) Addon(env []string) (forge.Forge, error) {
logger := log.Logger // TODO send via rpc
logger.Error().Msg("hello world from addon")
return &config{l: logger}, nil
}
Expand Down
6 changes: 6 additions & 0 deletions shared/addon/types/addon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package types

type Addon[T any] interface {
Type() Type
Addon([]string) (T, error)
}
2 changes: 1 addition & 1 deletion shared/addon/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package types
type Type string

const (
TypeForge Type = "forge"
TypeForge Type = "forge"
TypeEngine Type = "engine"
)

0 comments on commit 91afda4

Please sign in to comment.