github.com/Unheilbar/quorum@v1.0.0/plugin/base.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	slog "log"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"reflect"
    13  	"time"
    14  
    15  	"github.com/ethereum/go-ethereum/common"
    16  	iplugin "github.com/ethereum/go-ethereum/internal/plugin"
    17  	"github.com/ethereum/go-ethereum/log"
    18  	"github.com/ethereum/go-ethereum/plugin/initializer"
    19  	"github.com/hashicorp/go-hclog"
    20  	"github.com/hashicorp/go-plugin"
    21  )
    22  
    23  type managedPlugin interface {
    24  	Start() error
    25  	Stop() error
    26  
    27  	Info() (PluginInterfaceName, interface{})
    28  }
    29  
    30  // Plugin-meta.json
    31  type MetaData struct {
    32  	Version    string   `json:"version"`
    33  	Os         string   `json:"os"`
    34  	Arch       string   `json:"arch"`
    35  	EntryPoint string   `json:"entrypoint"`
    36  	Parameters []string `json:"parameters,omitempty"`
    37  }
    38  
    39  type basePlugin struct {
    40  	pm               *PluginManager
    41  	pluginInterface  PluginInterfaceName // plugin provider name
    42  	pluginDefinition *PluginDefinition
    43  	client           *plugin.Client
    44  	gateways         plugin.PluginSet // gateways to invoke RPC API implementation of interfaces supported by this plugin
    45  	pluginWorkspace  string           // plugin workspace
    46  	commands         []string         // plugin executable commands
    47  	logger           log.Logger
    48  }
    49  
    50  var basePluginPointerType = reflect.TypeOf(&basePlugin{})
    51  
    52  func newBasePlugin(pm *PluginManager, pluginInterface PluginInterfaceName, pluginDefinition PluginDefinition, gateways plugin.PluginSet) (*basePlugin, error) {
    53  	gateways[initializer.ConnectorName] = &initializer.PluginConnector{}
    54  
    55  	// build basePlugin
    56  	return &basePlugin{
    57  		pm:               pm,
    58  		pluginInterface:  pluginInterface,
    59  		logger:           log.New("provider", pluginInterface, "plugin", pluginDefinition.Name, "version", pluginDefinition.Version),
    60  		pluginDefinition: &pluginDefinition,
    61  		gateways:         gateways,
    62  	}, nil
    63  }
    64  
    65  // metadata.Command must be populated correctly here
    66  func (bp *basePlugin) load() error {
    67  	// Get plugin distribution path
    68  	pluginDistFilePath, err := bp.pm.downloader.Download(bp.pluginDefinition)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	// get file checksum
    73  	pluginChecksum, err := bp.checksum(pluginDistFilePath)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	bp.logger.Info("verifying plugin integrity", "checksum", pluginChecksum)
    78  	if err := bp.pm.verifier.VerifySignature(bp.pluginDefinition, pluginChecksum); err != nil {
    79  		return fmt.Errorf("unable to verify plugin signature: %v", err)
    80  	}
    81  	bp.logger.Info("unpacking plugin", "checksum", pluginChecksum)
    82  	// Unpack plugin
    83  	unPackDir, pluginMeta, err := unpackPlugin(pluginDistFilePath)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	// Create Execution Command
    88  	var command *exec.Cmd
    89  	executable := path.Join(unPackDir, pluginMeta.EntryPoint)
    90  	if !common.FileExist(executable) {
    91  		return fmt.Errorf("entry point does not exist")
    92  	}
    93  	bp.logger.Debug("Plugin executable", "path", executable)
    94  	if len(pluginMeta.Parameters) == 0 {
    95  		command = exec.Command(executable)
    96  		bp.commands = []string{executable}
    97  	} else {
    98  		command = exec.Command(executable, pluginMeta.Parameters...)
    99  		bp.commands = append([]string{executable}, pluginMeta.Parameters...)
   100  	}
   101  	command.Dir = unPackDir
   102  	bp.client = plugin.NewClient(&plugin.ClientConfig{
   103  		HandshakeConfig:  iplugin.DefaultHandshakeConfig,
   104  		Plugins:          bp.gateways,
   105  		AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
   106  		Cmd:              command,
   107  		AutoMTLS:         true,
   108  		Logger:           &logDelegate{bp.logger.New("from", "plugin")},
   109  	})
   110  
   111  	bp.pluginWorkspace = unPackDir
   112  	return nil
   113  }
   114  
   115  func (bp *basePlugin) Start() (err error) {
   116  	startTime := time.Now()
   117  	defer func(startTime time.Time) {
   118  		if err == nil {
   119  			bp.logger.Info("Plugin started", "took", time.Since(startTime))
   120  		} else {
   121  			bp.logger.Error("Plugin failed to start", "error", err, "took", time.Since(startTime))
   122  			_ = bp.Stop()
   123  		}
   124  	}(startTime)
   125  	bp.logger.Info("Starting plugin")
   126  	bp.logger.Debug("Starting plugin: Loading")
   127  	err = bp.load()
   128  	if err != nil {
   129  		return
   130  	}
   131  	bp.logger.Debug("Starting plugin: Creating client")
   132  	_, err = bp.client.Client()
   133  	if err != nil {
   134  		return
   135  	}
   136  	bp.logger.Debug("Starting plugin: Initializing")
   137  	err = bp.init()
   138  	return
   139  }
   140  
   141  func (bp *basePlugin) Stop() error {
   142  	if bp.client != nil {
   143  		bp.client.Kill()
   144  	}
   145  	if bp.pluginWorkspace == "" {
   146  		return nil
   147  	}
   148  	return bp.cleanPluginWorkspace()
   149  }
   150  
   151  func (bp *basePlugin) cleanPluginWorkspace() error {
   152  	workspace, err := os.Open(bp.pluginWorkspace)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	defer workspace.Close()
   157  	names, err := workspace.Readdirnames(-1)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	for _, name := range names {
   162  		err = os.RemoveAll(filepath.Join(bp.pluginWorkspace, name))
   163  		if err != nil {
   164  			return err
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func (bp *basePlugin) init() error {
   171  	bp.logger.Info("Initializing plugin")
   172  	raw, err := bp.dispense(initializer.ConnectorName)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	c, ok := raw.(initializer.PluginInitializer)
   177  	if !ok {
   178  		return fmt.Errorf("missing plugin initializer. Make sure it is in the plugin set")
   179  	}
   180  	rawConfig, err := ReadMultiFormatConfig(bp.pluginDefinition.Config)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	return c.Init(context.Background(), bp.pm.nodeName, rawConfig)
   185  }
   186  
   187  func (bp *basePlugin) dispense(name string) (interface{}, error) {
   188  	rpcClient, err := bp.client.Client()
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	return rpcClient.Dispense(name)
   193  }
   194  
   195  func (bp *basePlugin) Config() *PluginDefinition {
   196  	return bp.pluginDefinition
   197  }
   198  
   199  func (bp *basePlugin) checksum(pluginFile string) (string, error) {
   200  	return getSha256Checksum(pluginFile)
   201  }
   202  
   203  func (bp *basePlugin) Info() (PluginInterfaceName, interface{}) {
   204  	info := make(map[string]interface{})
   205  	info["name"] = bp.pluginDefinition.Name
   206  	info["version"] = bp.pluginDefinition.Version
   207  	info["config"] = bp.pluginDefinition.Config
   208  	info["executable"] = bp.commands
   209  	return bp.pluginInterface, info
   210  }
   211  
   212  type logDelegate struct {
   213  	eLogger log.Logger
   214  }
   215  
   216  func (ld *logDelegate) Trace(msg string, args ...interface{}) {
   217  	ld.eLogger.Trace(msg, args...)
   218  }
   219  
   220  func (ld *logDelegate) Log(level hclog.Level, msg string, args ...interface{}) {
   221  	//TODO : implement the method
   222  }
   223  
   224  func (ld *logDelegate) Name() string {
   225  	return ""
   226  }
   227  
   228  func (ld *logDelegate) Debug(msg string, args ...interface{}) {
   229  	ld.eLogger.Debug(msg, args...)
   230  }
   231  
   232  func (ld *logDelegate) Info(msg string, args ...interface{}) {
   233  	ld.eLogger.Info(msg, args...)
   234  }
   235  
   236  func (ld *logDelegate) Warn(msg string, args ...interface{}) {
   237  	ld.eLogger.Warn(msg, args...)
   238  }
   239  
   240  func (ld *logDelegate) Error(msg string, args ...interface{}) {
   241  	ld.eLogger.Error(msg, args...)
   242  }
   243  
   244  func (ld *logDelegate) IsTrace() bool {
   245  	return true
   246  }
   247  
   248  func (*logDelegate) IsDebug() bool {
   249  	return true
   250  }
   251  
   252  func (*logDelegate) IsInfo() bool {
   253  	return true
   254  }
   255  
   256  func (*logDelegate) IsWarn() bool {
   257  	return true
   258  }
   259  
   260  func (*logDelegate) IsError() bool {
   261  	return true
   262  }
   263  
   264  func (ld *logDelegate) With(args ...interface{}) hclog.Logger {
   265  	return &logDelegate{ld.eLogger.New(args...)}
   266  }
   267  
   268  func (ld *logDelegate) Named(name string) hclog.Logger {
   269  	return ld
   270  }
   271  
   272  func (ld *logDelegate) ResetNamed(name string) hclog.Logger {
   273  	return ld
   274  }
   275  
   276  func (ld *logDelegate) SetLevel(level hclog.Level) {
   277  }
   278  
   279  func (*logDelegate) StandardLogger(opts *hclog.StandardLoggerOptions) *slog.Logger {
   280  	return nil
   281  }
   282  
   283  func (*logDelegate) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
   284  	return nil
   285  }
   286  
   287  // ImpliedArgs returns With key/value pairs
   288  func (*logDelegate) ImpliedArgs() []interface{} {
   289  	return nil
   290  }