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 }