github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/plugin/settings.go (about)

     1  package plugin
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/url"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"strings"
    13  
    14  	"github.com/kisexp/xdchain/plugin/account"
    15  	"github.com/kisexp/xdchain/plugin/helloworld"
    16  	"github.com/kisexp/xdchain/plugin/security"
    17  	"github.com/kisexp/xdchain/rpc"
    18  	"github.com/hashicorp/go-plugin"
    19  	"github.com/naoina/toml"
    20  )
    21  
    22  const (
    23  	HelloWorldPluginInterfaceName = PluginInterfaceName("helloworld") // lower-case always
    24  	SecurityPluginInterfaceName   = PluginInterfaceName("security")
    25  	AccountPluginInterfaceName    = PluginInterfaceName("account")
    26  )
    27  
    28  var (
    29  	// define additional plugins being supported here
    30  	pluginProviders = map[PluginInterfaceName]pluginProvider{
    31  		HelloWorldPluginInterfaceName: {
    32  			apiProviderFunc: func(ns string, pm *PluginManager) ([]rpc.API, error) {
    33  				template := new(HelloWorldPluginTemplate)
    34  				if err := pm.GetPluginTemplate(HelloWorldPluginInterfaceName, template); err != nil {
    35  					return nil, err
    36  				}
    37  				service, err := template.Get()
    38  				if err != nil {
    39  					return nil, err
    40  				}
    41  				return []rpc.API{{
    42  					Namespace: ns,
    43  					Version:   "1.0.0",
    44  					Service:   service,
    45  					Public:    true,
    46  				}}, nil
    47  			},
    48  			pluginSet: plugin.PluginSet{
    49  				helloworld.ConnectorName: &helloworld.PluginConnector{},
    50  			},
    51  		},
    52  		SecurityPluginInterfaceName: {
    53  			pluginSet: plugin.PluginSet{
    54  				security.TLSConfigurationConnectorName: &security.TLSConfigurationSourcePluginConnector{},
    55  				security.AuthenticationConnectorName:   &security.AuthenticationManagerPluginConnector{},
    56  			},
    57  		},
    58  		AccountPluginInterfaceName: {
    59  			apiProviderFunc: func(ns string, pm *PluginManager) ([]rpc.API, error) {
    60  				f := new(ReloadableAccountServiceFactory)
    61  				if err := pm.GetPluginTemplate(AccountPluginInterfaceName, f); err != nil {
    62  					return nil, err
    63  				}
    64  				service, err := f.Create()
    65  				if err != nil {
    66  					return nil, err
    67  				}
    68  				return []rpc.API{{
    69  					Namespace: ns,
    70  					Version:   "1.0.0",
    71  					Service:   account.NewCreator(service),
    72  					Public:    true,
    73  				}}, nil
    74  			},
    75  			pluginSet: plugin.PluginSet{
    76  				account.ConnectorName: &account.PluginConnector{},
    77  			},
    78  		},
    79  	}
    80  
    81  	// this is the place holder for future solution of the plugin central
    82  	quorumPluginCentralConfiguration = &PluginCentralConfiguration{
    83  		CertFingerprint:        "",
    84  		BaseURL:                "https://artifacts.consensys.net/public/quorum-go-plugins/",
    85  		PublicKeyURI:           DefaultPublicKeyFile,
    86  		InsecureSkipTLSVerify:  false,
    87  		PluginDistPathTemplate: "maven/bin/{{.Name}}/{{.Version}}/{{.Name}}-{{.Version}}-{{.OS}}-{{.Arch}}.zip",
    88  		PluginSigPathTemplate:  "maven/bin/{{.Name}}/{{.Version}}/{{.Name}}-{{.Version}}-{{.OS}}-{{.Arch}}-sha256.checksum.asc",
    89  	}
    90  )
    91  
    92  type pluginProvider struct {
    93  	// this allows exposing plugin interfaces to geth RPC API automatically.
    94  	// nil value implies that plugin won't expose its methods to geth RPC API
    95  	apiProviderFunc rpcAPIProviderFunc
    96  	// contains connectors being registered to the plugin library
    97  	pluginSet plugin.PluginSet
    98  }
    99  
   100  type rpcAPIProviderFunc func(ns string, pm *PluginManager) ([]rpc.API, error)
   101  type Version string
   102  
   103  // This is to describe a plugin
   104  //
   105  // Information is used to discover the plugin binary and verify its integrity
   106  // before forking a process running the plugin
   107  type PluginDefinition struct {
   108  	Name string `json:"name" toml:""`
   109  	// the semver version of the plugin
   110  	Version Version `json:"version" toml:""`
   111  	// plugin configuration in a form of map/slice/string
   112  	Config interface{} `json:"config,omitempty" toml:",omitempty"`
   113  }
   114  
   115  func ReadMultiFormatConfig(config interface{}) ([]byte, error) {
   116  	if config == nil {
   117  		return []byte{}, nil
   118  	}
   119  	switch k := reflect.TypeOf(config).Kind(); k {
   120  	case reflect.Map, reflect.Slice:
   121  		return json.Marshal(config)
   122  	case reflect.String:
   123  		configStr := config.(string)
   124  		u, err := url.Parse(configStr)
   125  		if err != nil { // just return as is
   126  			return []byte(configStr), nil
   127  		}
   128  		switch s := u.Scheme; s {
   129  		case "file":
   130  			return ioutil.ReadFile(filepath.Join(u.Host, u.Path))
   131  		case "env": // config string in an env variable
   132  			varName := u.Host
   133  			isFile := u.Query().Get("type") == "file"
   134  			if v, ok := os.LookupEnv(varName); ok {
   135  				if isFile {
   136  					return ioutil.ReadFile(v)
   137  				} else {
   138  					return []byte(v), nil
   139  				}
   140  			} else {
   141  				return nil, fmt.Errorf("env variable %s not found", varName)
   142  			}
   143  		default:
   144  			return []byte(configStr), nil
   145  		}
   146  	default:
   147  		return nil, fmt.Errorf("unsupported type of config [%s]", k)
   148  	}
   149  }
   150  
   151  // return plugin distribution name. i.e.: <Name>-<Version>-<OS>-<Arch>
   152  func (m *PluginDefinition) FullName() string {
   153  	return fmt.Sprintf("%s-%s-%s-%s", m.Name, m.Version, runtime.GOOS, runtime.GOARCH)
   154  }
   155  
   156  // return plugin distribution file name stored locally
   157  func (m *PluginDefinition) DistFileName() string {
   158  	return fmt.Sprintf("%s.zip", m.FullName())
   159  }
   160  
   161  // return plugin distribution signature file name stored locally
   162  func (m *PluginDefinition) SignatureFileName() string {
   163  	return fmt.Sprintf("%s.sha256sum.asc", m.DistFileName())
   164  }
   165  
   166  // must be always be lowercase when define constants
   167  // as when unmarshaling from config, value will be case-lowered
   168  type PluginInterfaceName string
   169  
   170  // When this is used as a key in map. This function is not invoked.
   171  func (p *PluginInterfaceName) UnmarshalJSON(data []byte) error {
   172  	var v string
   173  	if err := json.Unmarshal(data, &v); err != nil {
   174  		return err
   175  	}
   176  	*p = PluginInterfaceName(strings.ToLower(v))
   177  	return nil
   178  }
   179  
   180  func (p *PluginInterfaceName) UnmarshalTOML(data []byte) error {
   181  	var v string
   182  	if err := toml.Unmarshal(data, &v); err != nil {
   183  		return err
   184  	}
   185  	*p = PluginInterfaceName(strings.ToLower(v))
   186  	return nil
   187  }
   188  
   189  func (p *PluginInterfaceName) UnmarshalText(data []byte) error {
   190  	*p = PluginInterfaceName(strings.ToLower(string(data)))
   191  	return nil
   192  }
   193  
   194  func (p PluginInterfaceName) String() string {
   195  	return string(p)
   196  }
   197  
   198  // this defines plugins used in the geth node
   199  type Settings struct {
   200  	BaseDir       EnvironmentAwaredValue                   `json:"baseDir" toml:""`
   201  	CentralConfig *PluginCentralConfiguration              `json:"central" toml:"Central"`
   202  	Providers     map[PluginInterfaceName]PluginDefinition `json:"providers" toml:""`
   203  }
   204  
   205  func (s *Settings) GetPluginDefinition(name PluginInterfaceName) (*PluginDefinition, bool) {
   206  	m, ok := s.Providers[name]
   207  	return &m, ok
   208  }
   209  
   210  func (s *Settings) SetDefaults() {
   211  	if s.CentralConfig == nil {
   212  		s.CentralConfig = quorumPluginCentralConfiguration
   213  	} else {
   214  		s.CentralConfig.SetDefaults()
   215  	}
   216  }
   217  
   218  // CheckSettingsAreSupported validates Settings by ensuring that only supportedPlugins are defined.
   219  // It is not required for all supportedPlugins to be defined.
   220  // An error containing plugin details is returned if one or more unsupported plugins are defined.
   221  func (s *Settings) CheckSettingsAreSupported(supportedPlugins []PluginInterfaceName) error {
   222  	errList := []PluginInterfaceName{}
   223  	for name := range s.Providers {
   224  		isValid := false
   225  		for _, supportedPlugin := range supportedPlugins {
   226  			if supportedPlugin == name {
   227  				isValid = true
   228  				break
   229  			}
   230  		}
   231  		if !isValid {
   232  			errList = append(errList, name)
   233  		}
   234  	}
   235  	if len(errList) != 0 {
   236  		return fmt.Errorf("unsupported plugins configured: %v", errList)
   237  	}
   238  	return nil
   239  }
   240  
   241  type PluginCentralConfiguration struct {
   242  	// To implement certificate pinning while communicating with PluginCentral
   243  	// if it's empty, we skip cert pinning logic
   244  	CertFingerprint       string `json:"certFingerprint" toml:""`
   245  	BaseURL               string `json:"baseURL" toml:""`
   246  	PublicKeyURI          string `json:"publicKeyURI" toml:""`
   247  	InsecureSkipTLSVerify bool   `json:"insecureSkipTLSVerify" toml:""`
   248  
   249  	// URL path template to the plugin distribution file.
   250  	// It uses Golang text template.
   251  	PluginDistPathTemplate string `json:"pluginDistPathTemplate" toml:""`
   252  	// URL path template to the plugin sha256 checksum signature file.
   253  	// It uses Golang text template.
   254  	PluginSigPathTemplate string `json:"pluginSigPathTemplate" toml:""`
   255  }
   256  
   257  // populate default values from quorumPluginCentralConfiguration
   258  func (c *PluginCentralConfiguration) SetDefaults() {
   259  	if len(c.BaseURL) == 0 {
   260  		c.BaseURL = quorumPluginCentralConfiguration.BaseURL
   261  	}
   262  	if len(c.PublicKeyURI) == 0 {
   263  		c.PublicKeyURI = quorumPluginCentralConfiguration.PublicKeyURI
   264  	}
   265  	if len(c.PluginDistPathTemplate) == 0 {
   266  		c.PluginDistPathTemplate = quorumPluginCentralConfiguration.PluginDistPathTemplate
   267  	}
   268  	if len(c.PluginSigPathTemplate) == 0 {
   269  		c.PluginSigPathTemplate = quorumPluginCentralConfiguration.PluginSigPathTemplate
   270  	}
   271  }
   272  
   273  // support URI format with 'env' scheme during JSON/TOML/TEXT unmarshalling
   274  // e.g.: env://FOO_VAR means read a string value from FOO_VAR environment variable
   275  type EnvironmentAwaredValue string
   276  
   277  func (d *EnvironmentAwaredValue) UnmarshalJSON(data []byte) error {
   278  	return d.unmarshal(data)
   279  }
   280  
   281  func (d *EnvironmentAwaredValue) UnmarshalTOML(data []byte) error {
   282  	return d.unmarshal(data)
   283  }
   284  
   285  func (d *EnvironmentAwaredValue) UnmarshalText(data []byte) error {
   286  	return d.unmarshal(data)
   287  }
   288  
   289  func (d *EnvironmentAwaredValue) unmarshal(data []byte) error {
   290  	v := string(data)
   291  	isString := strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"")
   292  	if !isString {
   293  		return fmt.Errorf("not a string")
   294  	}
   295  	v = strings.TrimFunc(v, func(r rune) bool {
   296  		return r == '"'
   297  	})
   298  	if u, err := url.Parse(v); err == nil {
   299  		switch u.Scheme {
   300  		case "env":
   301  			v = os.Getenv(u.Host)
   302  		}
   303  	}
   304  	*d = EnvironmentAwaredValue(v)
   305  	return nil
   306  }
   307  
   308  func (d EnvironmentAwaredValue) String() string {
   309  	return string(d)
   310  }