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  }