mirror of https://github.com/poanetwork/quorum.git
238 lines
6.6 KiB
Go
238 lines
6.6 KiB
Go
package plugin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/plugin/helloworld"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
"github.com/hashicorp/go-plugin"
|
|
"github.com/naoina/toml"
|
|
)
|
|
|
|
const (
|
|
HelloWorldPluginInterfaceName = PluginInterfaceName("helloworld") // lower-case always
|
|
)
|
|
|
|
var (
|
|
// define additional plugins being supported here
|
|
pluginProviders = map[PluginInterfaceName]pluginProvider{
|
|
HelloWorldPluginInterfaceName: {
|
|
apiProviderFunc: func(ns string, pm *PluginManager) ([]rpc.API, error) {
|
|
template := new(HelloWorldPluginTemplate)
|
|
if err := pm.GetPluginTemplate(HelloWorldPluginInterfaceName, template); err != nil {
|
|
return nil, err
|
|
}
|
|
service, err := template.Get()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []rpc.API{{
|
|
Namespace: ns,
|
|
Version: "1.0.0",
|
|
Service: service,
|
|
Public: true,
|
|
}}, nil
|
|
},
|
|
pluginSet: plugin.PluginSet{
|
|
helloworld.ConnectorName: &helloworld.PluginConnector{},
|
|
},
|
|
},
|
|
}
|
|
|
|
// this is the place holder for future solution of the plugin central
|
|
quorumPluginCentralConfiguration = &PluginCentralConfiguration{
|
|
CertFingerprint: "",
|
|
BaseURL: "https://dl.bintray.com/quorumengineering/quorum-plugins",
|
|
PublicKeyURI: "/.pgp/" + DefaultPublicKeyFile,
|
|
InsecureSkipTLSVerify: false,
|
|
}
|
|
)
|
|
|
|
type pluginProvider struct {
|
|
// this allows exposing plugin interfaces to geth RPC API automatically.
|
|
// nil value implies that plugin won't expose its methods to geth RPC API
|
|
apiProviderFunc rpcAPIProviderFunc
|
|
// contains connectors being registered to the plugin library
|
|
pluginSet plugin.PluginSet
|
|
}
|
|
|
|
type rpcAPIProviderFunc func(ns string, pm *PluginManager) ([]rpc.API, error)
|
|
type Version string
|
|
|
|
// This is to describe a plugin
|
|
//
|
|
// Information is used to discover the plugin binary and verify its integrity
|
|
// before forking a process running the plugin
|
|
type PluginDefinition struct {
|
|
Name string `json:"name" toml:""`
|
|
// the semver version of the plugin
|
|
Version Version `json:"version" toml:""`
|
|
// plugin configuration in a form of map/slice/string
|
|
Config interface{} `json:"config,omitempty" toml:",omitempty"`
|
|
}
|
|
|
|
func (m *PluginDefinition) ReadConfig() ([]byte, error) {
|
|
if m.Config == nil {
|
|
return []byte{}, nil
|
|
}
|
|
switch k := reflect.TypeOf(m.Config).Kind(); k {
|
|
case reflect.Map, reflect.Slice:
|
|
return json.Marshal(m.Config)
|
|
case reflect.String:
|
|
configStr := m.Config.(string)
|
|
u, err := url.Parse(configStr)
|
|
if err != nil { // just return as is
|
|
return []byte(configStr), nil
|
|
}
|
|
switch s := u.Scheme; s {
|
|
case "file":
|
|
return ioutil.ReadFile(filepath.Join(u.Host, u.Path))
|
|
case "env": // config string in an env variable
|
|
varName := u.Host
|
|
isFile := u.Query().Get("type") == "file"
|
|
if v, ok := os.LookupEnv(varName); ok {
|
|
if isFile {
|
|
m.Config = v
|
|
return ioutil.ReadFile(v)
|
|
} else {
|
|
return []byte(v), nil
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("env variable %s not found", varName)
|
|
}
|
|
default:
|
|
return []byte(configStr), nil
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported type of config [%s]", k)
|
|
}
|
|
}
|
|
|
|
// return remote folder storing the plugin distribution file and signature file
|
|
//
|
|
// e.g.: my-plugin/v1.0.0/darwin-amd64
|
|
func (m *PluginDefinition) RemotePath() string {
|
|
return fmt.Sprintf("%s/v%s/%s-%s", m.Name, m.Version, runtime.GOOS, runtime.GOARCH)
|
|
}
|
|
|
|
// return plugin name and version
|
|
func (m *PluginDefinition) FullName() string {
|
|
return fmt.Sprintf("%s-%s", m.Name, m.Version)
|
|
}
|
|
|
|
// return plugin distribution file name
|
|
func (m *PluginDefinition) DistFileName() string {
|
|
return fmt.Sprintf("%s.zip", m.FullName())
|
|
}
|
|
|
|
// return plugin distribution signature file name
|
|
func (m *PluginDefinition) SignatureFileName() string {
|
|
return fmt.Sprintf("%s.sha256sum.asc", m.DistFileName())
|
|
}
|
|
|
|
// must be always be lowercase when define constants
|
|
// as when unmarshaling from config, value will be case-lowered
|
|
type PluginInterfaceName string
|
|
|
|
// When this is used as a key in map. This function is not invoked.
|
|
func (p *PluginInterfaceName) UnmarshalJSON(data []byte) error {
|
|
var v string
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
return err
|
|
}
|
|
*p = PluginInterfaceName(strings.ToLower(v))
|
|
return nil
|
|
}
|
|
|
|
func (p *PluginInterfaceName) UnmarshalTOML(data []byte) error {
|
|
var v string
|
|
if err := toml.Unmarshal(data, &v); err != nil {
|
|
return err
|
|
}
|
|
*p = PluginInterfaceName(strings.ToLower(v))
|
|
return nil
|
|
}
|
|
|
|
func (p *PluginInterfaceName) UnmarshalText(data []byte) error {
|
|
*p = PluginInterfaceName(strings.ToLower(string(data)))
|
|
return nil
|
|
}
|
|
|
|
func (p PluginInterfaceName) String() string {
|
|
return string(p)
|
|
}
|
|
|
|
// this defines plugins used in the geth node
|
|
type Settings struct {
|
|
BaseDir EnvironmentAwaredValue `json:"baseDir" toml:""`
|
|
CentralConfig *PluginCentralConfiguration `json:"central" toml:"Central"`
|
|
Providers map[PluginInterfaceName]PluginDefinition `json:"providers" toml:""`
|
|
}
|
|
|
|
func (s *Settings) GetPluginDefinition(name PluginInterfaceName) (*PluginDefinition, bool) {
|
|
m, ok := s.Providers[name]
|
|
return &m, ok
|
|
}
|
|
|
|
func (s *Settings) SetDefaults() {
|
|
if s.CentralConfig == nil {
|
|
s.CentralConfig = quorumPluginCentralConfiguration
|
|
}
|
|
}
|
|
|
|
type PluginCentralConfiguration struct {
|
|
// To implement certificate pinning while communicating with PluginCentral
|
|
// if it's empty, we skip cert pinning logic
|
|
CertFingerprint string `json:"certFingerprint" toml:""`
|
|
BaseURL string `json:"baseURL" toml:""`
|
|
PublicKeyURI string `json:"publicKeyURI" toml:""`
|
|
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify" toml:""`
|
|
}
|
|
|
|
// support URI format with 'env' scheme during JSON/TOML/TEXT unmarshalling
|
|
// e.g.: env://FOO_VAR means read a string value from FOO_VAR environment variable
|
|
type EnvironmentAwaredValue string
|
|
|
|
func (d *EnvironmentAwaredValue) UnmarshalJSON(data []byte) error {
|
|
return d.unmarshal(data)
|
|
}
|
|
|
|
func (d *EnvironmentAwaredValue) UnmarshalTOML(data []byte) error {
|
|
return d.unmarshal(data)
|
|
}
|
|
|
|
func (d *EnvironmentAwaredValue) UnmarshalText(data []byte) error {
|
|
return d.unmarshal(data)
|
|
}
|
|
|
|
func (d *EnvironmentAwaredValue) unmarshal(data []byte) error {
|
|
v := string(data)
|
|
isString := strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"")
|
|
if !isString {
|
|
return fmt.Errorf("not a string")
|
|
}
|
|
v = strings.TrimFunc(v, func(r rune) bool {
|
|
return r == '"'
|
|
})
|
|
if u, err := url.Parse(v); err == nil {
|
|
switch u.Scheme {
|
|
case "env":
|
|
v = os.Getenv(u.Host)
|
|
}
|
|
}
|
|
*d = EnvironmentAwaredValue(v)
|
|
return nil
|
|
}
|
|
|
|
func (d EnvironmentAwaredValue) String() string {
|
|
return string(d)
|
|
}
|