github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/tstore/plugin.go (about) 1 // Copyright (c) 2020-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package tstore 6 7 import ( 8 "errors" 9 "fmt" 10 "path/filepath" 11 "sort" 12 13 backend "github.com/decred/politeia/politeiad/backendv2" 14 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins" 15 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins/comments" 16 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins/dcrdata" 17 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins/pi" 18 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins/ticketvote" 19 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins/usermd" 20 cmplugin "github.com/decred/politeia/politeiad/plugins/comments" 21 ddplugin "github.com/decred/politeia/politeiad/plugins/dcrdata" 22 piplugin "github.com/decred/politeia/politeiad/plugins/pi" 23 tkplugin "github.com/decred/politeia/politeiad/plugins/ticketvote" 24 umplugin "github.com/decred/politeia/politeiad/plugins/usermd" 25 ) 26 27 const ( 28 // pluginDataDirname is the plugin data directory name. It is 29 // located in the tstore backend data directory and is provided 30 // to the plugins for storing plugin data. 31 pluginDataDirname = "plugins" 32 ) 33 34 // plugin represents a tstore plugin. 35 type plugin struct { 36 id string 37 client plugins.PluginClient 38 } 39 40 // plugin returns the specified plugin. Only plugins that have been registered 41 // will be returned. 42 func (t *Tstore) plugin(pluginID string) (plugin, bool) { 43 t.Lock() 44 defer t.Unlock() 45 46 plugin, ok := t.plugins[pluginID] 47 return plugin, ok 48 } 49 50 // pluginIDs returns the plugin ID of all registered plugins. 51 func (t *Tstore) pluginIDs() []string { 52 t.Lock() 53 defer t.Unlock() 54 55 ids := make([]string, 0, len(t.plugins)) 56 for k := range t.plugins { 57 ids = append(ids, k) 58 } 59 60 // Sort IDs so the returned order is deterministic 61 sort.SliceStable(ids, func(i, j int) bool { 62 return ids[i] < ids[j] 63 }) 64 65 return ids 66 } 67 68 // PluginRegister registers a plugin. Plugin commands and hooks can be executed 69 // on the plugin once registered. 70 func (t *Tstore) PluginRegister(b backend.Backend, p backend.Plugin) error { 71 log.Tracef("PluginRegister: %v", p.ID) 72 73 var ( 74 pluginClient plugins.PluginClient 75 err error 76 77 dataDir = filepath.Join(t.dataDir, pluginDataDirname) 78 ) 79 switch p.ID { 80 case cmplugin.PluginID: 81 tstoreClient := NewTstoreClient(t, cmplugin.PluginID) 82 pluginClient, err = comments.New(tstoreClient, 83 p.Settings, dataDir, p.Identity) 84 if err != nil { 85 return err 86 } 87 case ddplugin.PluginID: 88 pluginClient, err = dcrdata.New(p.Settings, t.activeNetParams) 89 if err != nil { 90 return err 91 } 92 case piplugin.PluginID: 93 tstoreClient := NewTstoreClient(t, piplugin.PluginID) 94 pluginClient, err = pi.New(b, tstoreClient, 95 p.Settings, dataDir, p.Identity) 96 if err != nil { 97 return err 98 } 99 case tkplugin.PluginID: 100 tstoreClient := NewTstoreClient(t, tkplugin.PluginID) 101 pluginClient, err = ticketvote.New(b, tstoreClient, 102 p.Settings, dataDir, p.Identity, t.activeNetParams) 103 if err != nil { 104 return err 105 } 106 case umplugin.PluginID: 107 tstoreClient := NewTstoreClient(t, umplugin.PluginID) 108 pluginClient, err = usermd.New(tstoreClient, p.Settings, dataDir) 109 if err != nil { 110 return err 111 } 112 default: 113 return backend.ErrPluginIDInvalid 114 } 115 116 t.Lock() 117 defer t.Unlock() 118 119 t.plugins[p.ID] = plugin{ 120 id: p.ID, 121 client: pluginClient, 122 } 123 124 return nil 125 } 126 127 // PluginSetup performs any required setup for the specified plugin. 128 func (t *Tstore) PluginSetup(pluginID string) error { 129 log.Tracef("PluginSetup: %v", pluginID) 130 131 p, ok := t.plugin(pluginID) 132 if !ok { 133 return backend.ErrPluginIDInvalid 134 } 135 136 return p.client.Setup() 137 } 138 139 // PluginHookPre executes a tstore backend pre hook. Pre hooks are hooks that 140 // are executed prior to the tstore backend writing data to disk. These hooks 141 // give plugins the opportunity to add plugin specific validation to record 142 // methods or plugin commands that write data. 143 func (t *Tstore) PluginHookPre(h plugins.HookT, payload string) error { 144 log.Tracef("PluginHookPre: %v", plugins.Hooks[h]) 145 146 // Pass hook event and payload to each plugin 147 for _, v := range t.pluginIDs() { 148 p, _ := t.plugin(v) 149 err := p.client.Hook(h, payload) 150 if err != nil { 151 var e backend.PluginError 152 if errors.As(err, &e) { 153 return err 154 } 155 return fmt.Errorf("hook %v: %v", v, err) 156 } 157 } 158 159 return nil 160 } 161 162 // PluginHookPost executes a tstore backend post hook. Post hooks are hooks that 163 // are executed after the tstore backend successfully writes data to disk. 164 // These hooks give plugins the opportunity to cache data from the write. 165 func (t *Tstore) PluginHookPost(h plugins.HookT, payload string) { 166 log.Tracef("PluginHookPost: %v", plugins.Hooks[h]) 167 168 // Pass hook event and payload to each plugin 169 for _, v := range t.pluginIDs() { 170 p, ok := t.plugin(v) 171 if !ok { 172 log.Errorf("%v PluginHookPost: plugin not found %v", v) 173 continue 174 } 175 err := p.client.Hook(h, payload) 176 if err != nil { 177 // This is the post plugin hook so the data has already been 178 // saved to tstore. We do not have the ability to unwind. Log 179 // the error and continue. 180 log.Criticalf("%v PluginHookPost %v %v: %v: %v", 181 v, h, err, payload) 182 continue 183 } 184 } 185 } 186 187 // PluginRead executes a read-only plugin command. 188 func (t *Tstore) PluginRead(token []byte, pluginID, cmd, payload string) (string, error) { 189 log.Tracef("PluginRead: %x %v %v", token, pluginID, cmd) 190 191 // The token is optional 192 if len(token) > 0 { 193 // Read methods are allowed to use short tokens. Lookup the full 194 // length token. 195 var err error 196 token, err = t.fullLengthToken(token) 197 if err != nil { 198 return "", err 199 } 200 } 201 202 // Get plugin 203 p, ok := t.plugin(pluginID) 204 if !ok { 205 return "", backend.ErrPluginIDInvalid 206 } 207 208 // Execute plugin command 209 return p.client.Cmd(token, cmd, payload) 210 } 211 212 // PluginWrite executes a plugin command that writes data. 213 func (t *Tstore) PluginWrite(token []byte, pluginID, cmd, payload string) (string, error) { 214 log.Tracef("PluginWrite: %x %v %v", token, pluginID, cmd) 215 216 // Get plugin 217 p, ok := t.plugin(pluginID) 218 if !ok { 219 return "", backend.ErrPluginIDInvalid 220 } 221 222 // Execute plugin command 223 return p.client.Cmd(token, cmd, payload) 224 } 225 226 // Plugins returns all registered plugins for the tstore instance. 227 func (t *Tstore) Plugins() []backend.Plugin { 228 log.Tracef("Plugins") 229 230 t.Lock() 231 defer t.Unlock() 232 233 plugins := make([]backend.Plugin, 0, len(t.plugins)) 234 for _, v := range t.plugins { 235 plugins = append(plugins, backend.Plugin{ 236 ID: v.id, 237 Settings: v.client.Settings(), 238 }) 239 } 240 241 return plugins 242 }