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 }