quorum/plugin/settings.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)
}