github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/pkg/plugin/plugin.go (about)

     1  package plugin
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  
     9  	"github.com/hellofresh/janus/pkg/proxy"
    10  	log "github.com/sirupsen/logrus"
    11  )
    12  
    13  var (
    14  	lock sync.RWMutex
    15  
    16  	// plugins is a map of plugin name to Plugin.
    17  	plugins = make(map[string]Plugin)
    18  
    19  	// eventHooks is a map of hook name to Hook. All hooks plugins
    20  	// must have a name.
    21  	eventHooks = make(map[string][]EventHook)
    22  )
    23  
    24  // SetupFunc is used to set up a plugin, or in other words,
    25  // execute a directive. It will be called once per key for
    26  // each server block it appears in.
    27  type SetupFunc func(def *proxy.RouterDefinition, rawConfig Config) error
    28  
    29  // ValidateFunc validates configuration data against the plugin struct
    30  type ValidateFunc func(rawConfig Config) (bool, error)
    31  
    32  // Config initialization options.
    33  type Config map[string]interface{}
    34  
    35  // Plugin defines basic methods for plugins
    36  type Plugin struct {
    37  	Action   SetupFunc
    38  	Validate ValidateFunc
    39  }
    40  
    41  // RegisterPlugin plugs in plugin. All plugins should register
    42  // themselves, even if they do not perform an action associated
    43  // with a directive. It is important for the process to know
    44  // which plugins are available.
    45  //
    46  // The plugin MUST have a name: lower case and one word.
    47  // If this plugin has an action, it must be the name of
    48  // the directive that invokes it. A name is always required
    49  // and must be unique for the server type.
    50  func RegisterPlugin(name string, plugin Plugin) error {
    51  	lock.Lock()
    52  	defer lock.Unlock()
    53  
    54  	if name == "" {
    55  		return errors.New("plugin must have a name")
    56  	}
    57  	if _, dup := plugins[name]; dup {
    58  		return fmt.Errorf("plugin named %q already registered", name)
    59  	}
    60  	plugins[name] = plugin
    61  	return nil
    62  }
    63  
    64  // EventHook is a type which holds information about a startup hook plugin.
    65  type EventHook func(event interface{}) error
    66  
    67  // RegisterEventHook plugs in hook. All the hooks should register themselves
    68  // and they must have a name.
    69  func RegisterEventHook(name string, hook EventHook) error {
    70  	log.WithField("event_name", name).Debug("Event registered")
    71  	lock.Lock()
    72  	defer lock.Unlock()
    73  
    74  	if name == "" {
    75  		return errors.New("event hook must have a name")
    76  	}
    77  
    78  	if hooks, dup := eventHooks[name]; dup {
    79  		eventHooks[name] = append(hooks, hook)
    80  	} else {
    81  		eventHooks[name] = append([]EventHook{}, hook)
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  // EmitEvent executes the different hooks passing the EventType as an
    88  // argument. This is a blocking function. Hook developers should
    89  // use 'go' keyword if they don't want to block Janus.
    90  func EmitEvent(name string, event interface{}) error {
    91  	log.WithField("event_name", name).Debug("Event triggered")
    92  
    93  	hooks, found := eventHooks[name]
    94  	if !found {
    95  		return fmt.Errorf("plugin for event %q not found", name)
    96  	}
    97  
    98  	for _, hook := range hooks {
    99  		err := hook(event)
   100  		if err != nil {
   101  			log.WithError(err).WithField("event_name", name).Warn("an error occurred when an event was triggered")
   102  		}
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // ValidateConfig validates the plugin configuration data
   109  func ValidateConfig(name string, rawConfig Config) (bool, error) {
   110  	logger := log.WithField("plugin_name", name)
   111  
   112  	if plugin, ok := plugins[name]; ok {
   113  		if plugin.Validate == nil {
   114  			logger.Debug("Validation function undefined; assuming valid configuration")
   115  			return true, nil
   116  		}
   117  
   118  		result, err := plugin.Validate(rawConfig)
   119  		if !result || err != nil {
   120  			logger.WithField("config", rawConfig).Info("Invalid plugin configuration")
   121  		}
   122  
   123  		return result, err
   124  	}
   125  
   126  	return false, fmt.Errorf("plugin %q not found", name)
   127  }
   128  
   129  // DirectiveAction gets the action for a plugin
   130  func DirectiveAction(name string) (SetupFunc, error) {
   131  	if plugin, ok := plugins[name]; ok {
   132  		if plugin.Action == nil {
   133  			return nil, fmt.Errorf("action function undefined for plugin %q", name)
   134  		}
   135  
   136  		return plugin.Action, nil
   137  	}
   138  
   139  	return nil, fmt.Errorf("plugin %q not found", name)
   140  }
   141  
   142  // Decode decodes a map string interface into a struct
   143  // for some reasons mapstructure.Decode() gives empty arrays for all resulting config fields
   144  // this is quick workaround hack t make it work
   145  // FIXME: investigate and fix mapstructure.Decode() behaviour and remove this dirty hack
   146  func Decode(rawConfig map[string]interface{}, obj interface{}) error {
   147  	valJSON, err := json.Marshal(rawConfig)
   148  	if nil != err {
   149  		return err
   150  	}
   151  
   152  	err = json.Unmarshal(valJSON, obj)
   153  	if nil != err {
   154  		return err
   155  	}
   156  
   157  	return nil
   158  }