diff --git a/cmd/run.go b/cmd/run.go index c8f230e9..790cbc07 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -12,7 +12,6 @@ import ( "github.com/gatewayd-io/gatewayd/logging" "github.com/gatewayd-io/gatewayd/network" "github.com/gatewayd-io/gatewayd/plugin" - "github.com/gatewayd-io/gatewayd/plugin/hook" "github.com/gatewayd-io/gatewayd/pool" "github.com/knadh/koanf" "github.com/knadh/koanf/parsers/yaml" @@ -24,14 +23,14 @@ import ( ) var ( - hooksConfig = hook.NewHookConfig() DefaultLogger = logging.NewLogger( logging.LoggerConfig{ Level: zerolog.InfoLevel, // Default log level NoColor: true, }, ) - pluginRegistry = plugin.NewRegistry(hooksConfig) + // The plugins are loaded and hooks registered before the configuration is loaded. + pluginRegistry = plugin.NewRegistry(config.Loose, config.PassDown, DefaultLogger) // Global koanf instance. Using "." as the key path delimiter. globalConfig = koanf.New(".") // Plugin koanf instance. Using "." as the key path delimiter. @@ -43,10 +42,6 @@ var runCmd = &cobra.Command{ Use: "run", Short: "Run a gatewayd instance", Run: func(cmd *cobra.Command, args []string) { - // The plugins are loaded and hooks registered - // before the configuration is loaded. - hooksConfig.Logger = DefaultLogger - // Load default plugin configuration. config.LoadPluginConfigDefaults(pluginConfig) @@ -90,7 +85,7 @@ var runCmd = &cobra.Command{ config.LoadEnvVars(globalConfig) // Get hooks signature verification policy. - hooksConfig.Verification = pConfig.GetVerificationPolicy() + pluginRegistry.Verification = pConfig.GetVerificationPolicy() // Unmarshal the global configuration for easier access. var gConfig config.GlobalConfig @@ -100,13 +95,13 @@ var runCmd = &cobra.Command{ os.Exit(gerr.FailedToLoadGlobalConfig) } - // The config will be passed to the plugins that register to the "OnConfigLoaded" hook. + // The config will be passed to the plugins that register to the "OnConfigLoaded" plugin. // The plugins can modify the config and return it. - updatedGlobalConfig, err := hooksConfig.Run( + updatedGlobalConfig, err := pluginRegistry.Run( context.Background(), globalConfig.All(), - hook.OnConfigLoaded, - hooksConfig.Verification) + plugin.OnConfigLoaded, + pluginRegistry.Verification) if err != nil { DefaultLogger.Error().Err(err).Msg("Failed to run OnConfigLoaded hooks") } @@ -139,7 +134,7 @@ var runCmd = &cobra.Command{ }) // Replace the default logger with the new one from the config. - hooksConfig.Logger = logger + pluginRegistry.Logger = logger // This is a notification hook, so we don't care about the result. data := map[string]interface{}{ @@ -150,8 +145,8 @@ var runCmd = &cobra.Command{ "fileName": loggerCfg.FileName, } // TODO: Use a context with a timeout - _, err = hooksConfig.Run( - context.Background(), data, hook.OnNewLogger, hooksConfig.Verification) + _, err = pluginRegistry.Run( + context.Background(), data, plugin.OnNewLogger, pluginRegistry.Verification) if err != nil { logger.Error().Err(err).Msg("Failed to run OnNewLogger hooks") } @@ -179,11 +174,11 @@ var runCmd = &cobra.Command{ "tcpKeepAlive": client.TCPKeepAlive, "tcpKeepAlivePeriod": client.TCPKeepAlivePeriod.String(), } - _, err := hooksConfig.Run( + _, err := pluginRegistry.Run( context.Background(), clientCfg, - hook.OnNewClient, - hooksConfig.Verification) + plugin.OnNewClient, + pluginRegistry.Verification) if err != nil { logger.Error().Err(err).Msg("Failed to run OnNewClient hooks") } @@ -204,14 +199,14 @@ var runCmd = &cobra.Command{ "the clients cannot connect due to no network connectivity " + "or the server is not running. exiting...") pluginRegistry.Shutdown() - os.Exit(1) + os.Exit(gerr.FailedToInitializePool) } - _, err = hooksConfig.Run( + _, err = pluginRegistry.Run( context.Background(), map[string]interface{}{"size": poolSize}, - hook.OnNewPool, - hooksConfig.Verification) + plugin.OnNewPool, + pluginRegistry.Verification) if err != nil { logger.Error().Err(err).Msg("Failed to run OnNewPool hooks") } @@ -222,7 +217,7 @@ var runCmd = &cobra.Command{ healthCheckPeriod := gConfig.Proxy[config.Default].HealthCheckPeriod proxy := network.NewProxy( pool, - hooksConfig, + pluginRegistry, elastic, reuseElasticClients, healthCheckPeriod, @@ -245,8 +240,8 @@ var runCmd = &cobra.Command{ "tcpKeepAlivePeriod": clientConfig.TCPKeepAlivePeriod.String(), }, } - _, err = hooksConfig.Run( - context.Background(), proxyCfg, hook.OnNewProxy, hooksConfig.Verification) + _, err = pluginRegistry.Run( + context.Background(), proxyCfg, plugin.OnNewProxy, pluginRegistry.Verification) if err != nil { logger.Error().Err(err).Msg("Failed to run OnNewProxy hooks") } @@ -285,7 +280,7 @@ var runCmd = &cobra.Command{ }, proxy, logger, - hooksConfig, + pluginRegistry, ) serverCfg := map[string]interface{}{ @@ -307,8 +302,8 @@ var runCmd = &cobra.Command{ "tcpKeepAlive": gConfig.Server.TCPKeepAlive.String(), "tcpNoDelay": gConfig.Server.TCPNoDelay, } - _, err = hooksConfig.Run( - context.Background(), serverCfg, hook.OnNewServer, hooksConfig.Verification) + _, err = pluginRegistry.Run( + context.Background(), serverCfg, plugin.OnNewServer, pluginRegistry.Verification) if err != nil { logger.Error().Err(err).Msg("Failed to run OnNewServer hooks") } @@ -326,16 +321,16 @@ var runCmd = &cobra.Command{ ) signalsCh := make(chan os.Signal, 1) signal.Notify(signalsCh, signals...) - go func(hooksConfig *hook.Config) { + go func(pluginRegistry *plugin.Registry) { for sig := range signalsCh { for _, s := range signals { if sig != s { // Notify the hooks that the server is shutting down. - _, err := hooksConfig.Run( + _, err := pluginRegistry.Run( context.Background(), map[string]interface{}{"signal": sig.String()}, - hook.OnSignal, - hooksConfig.Verification, + plugin.OnSignal, + pluginRegistry.Verification, ) if err != nil { logger.Error().Err(err).Msg("Failed to run OnSignal hooks") @@ -347,7 +342,7 @@ var runCmd = &cobra.Command{ } } } - }(hooksConfig) + }(pluginRegistry) // Run the server. if err := server.Run(); err != nil { diff --git a/errors/errors.go b/errors/errors.go index a84fdb67..2fdf3a8d 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -92,4 +92,5 @@ var ( const ( FailedToLoadPluginConfig = 1 FailedToLoadGlobalConfig = 2 + FailedToInitializePool = 3 ) diff --git a/network/proxy.go b/network/proxy.go index 896c1327..cb3ec7e1 100644 --- a/network/proxy.go +++ b/network/proxy.go @@ -6,7 +6,7 @@ import ( "github.com/gatewayd-io/gatewayd/config" gerr "github.com/gatewayd-io/gatewayd/errors" - "github.com/gatewayd-io/gatewayd/plugin/hook" + "github.com/gatewayd-io/gatewayd/plugin" "github.com/gatewayd-io/gatewayd/pool" "github.com/go-co-op/gocron" "github.com/panjf2000/gnet/v2" @@ -26,7 +26,7 @@ type Proxy struct { availableConnections pool.IPool busyConnections pool.IPool logger zerolog.Logger - hookConfig *hook.Config + pluginRegistry *plugin.Registry scheduler *gocron.Scheduler Elastic bool @@ -41,7 +41,7 @@ var _ IProxy = &Proxy{} // NewProxy creates a new proxy. func NewProxy( - connPool pool.IPool, hookConfig *hook.Config, + connPool pool.IPool, pluginRegistry *plugin.Registry, elastic, reuseElasticClients bool, healthCheckPeriod time.Duration, clientConfig *config.Client, logger zerolog.Logger, @@ -50,7 +50,7 @@ func NewProxy( availableConnections: connPool, busyConnections: pool.NewPool(config.EmptyPoolCapacity), logger: logger, - hookConfig: hookConfig, + pluginRegistry: pluginRegistry, scheduler: gocron.NewScheduler(time.UTC), Elastic: elastic, ReuseElasticClients: reuseElasticClients, @@ -363,7 +363,7 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { request, origErr := receiveTrafficFromClient() // Run the OnTrafficFromClient hooks. - result, err := pr.hookConfig.Run( + result, err := pr.pluginRegistry.Run( context.Background(), trafficData( gconn, @@ -375,8 +375,8 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { }, }, origErr), - hook.OnTrafficFromClient, - pr.hookConfig.Verification) + plugin.OnTrafficFromClient, + pr.pluginRegistry.Verification) if err != nil { pr.logger.Error().Err(err).Msg("Error running hook") } @@ -396,7 +396,7 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { _, err = sendTrafficToServer(request) // Run the OnTrafficToServer hooks. - _, err = pr.hookConfig.Run( + _, err = pr.pluginRegistry.Run( context.Background(), trafficData( gconn, @@ -408,8 +408,8 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { }, }, err), - hook.OnTrafficToServer, - pr.hookConfig.Verification) + plugin.OnTrafficToServer, + pr.pluginRegistry.Verification) if err != nil { pr.logger.Error().Err(err).Msg("Error running hook") } @@ -448,7 +448,7 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { } // Run the OnTrafficFromServer hooks. - result, err = pr.hookConfig.Run( + result, err = pr.pluginRegistry.Run( context.Background(), trafficData( gconn, @@ -464,8 +464,8 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { }, }, err), - hook.OnTrafficFromServer, - pr.hookConfig.Verification) + plugin.OnTrafficFromServer, + pr.pluginRegistry.Verification) if err != nil { pr.logger.Error().Err(err).Msg("Error running hook") } @@ -481,7 +481,7 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { errVerdict := sendTrafficToClient(response, received) // Run the OnTrafficToClient hooks. - _, err = pr.hookConfig.Run( + _, err = pr.pluginRegistry.Run( context.Background(), trafficData( gconn, @@ -498,8 +498,8 @@ func (pr *Proxy) PassThrough(gconn gnet.Conn) *gerr.GatewayDError { }, err, ), - hook.OnTrafficToClient, - pr.hookConfig.Verification) + plugin.OnTrafficToClient, + pr.pluginRegistry.Verification) if err != nil { pr.logger.Error().Err(err).Msg("Error running hook") } diff --git a/network/proxy_test.go b/network/proxy_test.go index a4c198de..4d5b4ccc 100644 --- a/network/proxy_test.go +++ b/network/proxy_test.go @@ -6,7 +6,7 @@ import ( embeddedpostgres "github.com/fergusstrange/embedded-postgres" "github.com/gatewayd-io/gatewayd/config" "github.com/gatewayd-io/gatewayd/logging" - "github.com/gatewayd-io/gatewayd/plugin/hook" + "github.com/gatewayd-io/gatewayd/plugin" "github.com/gatewayd-io/gatewayd/pool" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -53,8 +53,13 @@ func TestNewProxy(t *testing.T) { assert.Nil(t, err) // Create a proxy with a fixed buffer pool - proxy := NewProxy( - pool, hook.NewHookConfig(), false, false, config.DefaultHealthCheckPeriod, nil, logger) + proxy := NewProxy(pool, + plugin.NewRegistry(config.Loose, config.PassDown, logger), + false, + false, + config.DefaultHealthCheckPeriod, + nil, + logger) assert.NotNil(t, proxy) assert.Equal(t, 0, proxy.busyConnections.Size(), "Proxy should have no connected clients") @@ -83,7 +88,11 @@ func TestNewProxyElastic(t *testing.T) { pool := pool.NewPool(config.EmptyPoolCapacity) // Create a proxy with an elastic buffer pool - proxy := NewProxy(pool, hook.NewHookConfig(), true, false, config.DefaultHealthCheckPeriod, + proxy := NewProxy(pool, + plugin.NewRegistry(config.Loose, config.PassDown, logger), + true, + false, + config.DefaultHealthCheckPeriod, &config.Client{ Network: "tcp", Address: "localhost:5432", @@ -93,7 +102,8 @@ func TestNewProxyElastic(t *testing.T) { SendDeadline: config.DefaultSendDeadline, TCPKeepAlive: false, TCPKeepAlivePeriod: config.DefaultTCPKeepAlivePeriod, - }, logger) + }, + logger) assert.NotNil(t, proxy) assert.Equal(t, 0, proxy.busyConnections.Size()) diff --git a/network/server.go b/network/server.go index fdbb1d37..46215ef8 100644 --- a/network/server.go +++ b/network/server.go @@ -10,17 +10,17 @@ import ( "github.com/gatewayd-io/gatewayd/config" gerr "github.com/gatewayd-io/gatewayd/errors" - "github.com/gatewayd-io/gatewayd/plugin/hook" + "github.com/gatewayd-io/gatewayd/plugin" "github.com/panjf2000/gnet/v2" "github.com/rs/zerolog" ) type Server struct { gnet.BuiltinEventEngine - engine gnet.Engine - proxy IProxy - logger zerolog.Logger - hooksConfig *hook.Config + engine gnet.Engine + proxy IProxy + logger zerolog.Logger + pluginRegistry *plugin.Registry Network string // tcp/udp/unix Address string @@ -38,11 +38,11 @@ func (s *Server) OnBoot(engine gnet.Engine) gnet.Action { s.logger.Debug().Msg("GatewayD is booting...") // Run the OnBooting hooks. - _, err := s.hooksConfig.Run( + _, err := s.pluginRegistry.Run( context.Background(), map[string]interface{}{"status": fmt.Sprint(s.Status)}, - hook.OnBooting, - s.hooksConfig.Verification) + plugin.OnBooting, + s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run OnBooting hook") } @@ -53,11 +53,11 @@ func (s *Server) OnBoot(engine gnet.Engine) gnet.Action { s.Status = config.Running // Run the OnBooted hooks. - _, err = s.hooksConfig.Run( + _, err = s.pluginRegistry.Run( context.Background(), map[string]interface{}{"status": fmt.Sprint(s.Status)}, - hook.OnBooted, - s.hooksConfig.Verification) + plugin.OnBooted, + s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run OnBooted hook") } @@ -80,8 +80,8 @@ func (s *Server) OnOpen(gconn gnet.Conn) ([]byte, gnet.Action) { "remote": gconn.RemoteAddr().String(), }, } - _, err := s.hooksConfig.Run( - context.Background(), onOpeningData, hook.OnOpening, s.hooksConfig.Verification) + _, err := s.pluginRegistry.Run( + context.Background(), onOpeningData, plugin.OnOpening, s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run OnOpening hook") } @@ -121,8 +121,8 @@ func (s *Server) OnOpen(gconn gnet.Conn) ([]byte, gnet.Action) { "remote": gconn.RemoteAddr().String(), }, } - _, err = s.hooksConfig.Run( - context.Background(), onOpenedData, hook.OnOpened, s.hooksConfig.Verification) + _, err = s.pluginRegistry.Run( + context.Background(), onOpenedData, plugin.OnOpened, s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run OnOpened hook") } @@ -148,8 +148,8 @@ func (s *Server) OnClose(gconn gnet.Conn, err error) gnet.Action { if err != nil { data["error"] = err.Error() } - _, gatewaydErr := s.hooksConfig.Run( - context.Background(), data, hook.OnClosing, s.hooksConfig.Verification) + _, gatewaydErr := s.pluginRegistry.Run( + context.Background(), data, plugin.OnClosing, s.pluginRegistry.Verification) if gatewaydErr != nil { s.logger.Error().Err(gatewaydErr).Msg("Failed to run OnClosing hook") } @@ -179,8 +179,8 @@ func (s *Server) OnClose(gconn gnet.Conn, err error) gnet.Action { if err != nil { data["error"] = err.Error() } - _, gatewaydErr = s.hooksConfig.Run( - context.Background(), data, hook.OnClosed, s.hooksConfig.Verification) + _, gatewaydErr = s.pluginRegistry.Run( + context.Background(), data, plugin.OnClosed, s.pluginRegistry.Verification) if gatewaydErr != nil { s.logger.Error().Err(gatewaydErr).Msg("Failed to run OnClosed hook") } @@ -198,8 +198,8 @@ func (s *Server) OnTraffic(gconn gnet.Conn) gnet.Action { "remote": gconn.RemoteAddr().String(), }, } - _, err := s.hooksConfig.Run( - context.Background(), onTrafficData, hook.OnTraffic, s.hooksConfig.Verification) + _, err := s.pluginRegistry.Run( + context.Background(), onTrafficData, plugin.OnTraffic, s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run OnTraffic hook") } @@ -231,11 +231,11 @@ func (s *Server) OnShutdown(engine gnet.Engine) { s.logger.Debug().Msg("GatewayD is shutting down...") // Run the OnShutdown hooks. - _, err := s.hooksConfig.Run( + _, err := s.pluginRegistry.Run( context.Background(), map[string]interface{}{"connections": s.engine.CountConnections()}, - hook.OnShutdown, - s.hooksConfig.Verification) + plugin.OnShutdown, + s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run OnShutdown hook") } @@ -254,11 +254,11 @@ func (s *Server) OnTick() (time.Duration, gnet.Action) { "Active client connections") // Run the OnTick hooks. - _, err := s.hooksConfig.Run( + _, err := s.pluginRegistry.Run( context.Background(), map[string]interface{}{"connections": s.engine.CountConnections()}, - hook.OnTick, - s.hooksConfig.Verification) + plugin.OnTick, + s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run OnTick hook") } @@ -284,8 +284,8 @@ func (s *Server) Run() error { if err != nil && err.Unwrap() != nil { onRunData["error"] = err.OriginalError.Error() } - result, err := s.hooksConfig.Run( - context.Background(), onRunData, hook.OnRun, s.hooksConfig.Verification) + result, err := s.pluginRegistry.Run( + context.Background(), onRunData, plugin.OnRun, s.pluginRegistry.Verification) if err != nil { s.logger.Error().Err(err).Msg("Failed to run the hook") } @@ -334,7 +334,7 @@ func NewServer( options []gnet.Option, proxy IProxy, logger zerolog.Logger, - hooksConfig *hook.Config, + pluginRegistry *plugin.Registry, ) *Server { // Create the server. server := Server{ @@ -390,7 +390,7 @@ func NewServer( server.proxy = proxy server.logger = logger - server.hooksConfig = hooksConfig + server.pluginRegistry = pluginRegistry return &server } diff --git a/network/server_test.go b/network/server_test.go index 2ca0d568..4193f45f 100644 --- a/network/server_test.go +++ b/network/server_test.go @@ -9,7 +9,7 @@ import ( embeddedpostgres "github.com/fergusstrange/embedded-postgres" "github.com/gatewayd-io/gatewayd/config" "github.com/gatewayd-io/gatewayd/logging" - "github.com/gatewayd-io/gatewayd/plugin/hook" + "github.com/gatewayd-io/gatewayd/plugin" "github.com/gatewayd-io/gatewayd/pool" "github.com/panjf2000/gnet/v2" "github.com/rs/zerolog" @@ -39,7 +39,7 @@ func TestRunServer(t *testing.T) { logger := logging.NewLogger(cfg) - hooksConfig := hook.NewHookConfig() + pluginRegistry := plugin.NewRegistry(config.Loose, config.PassDown, logger) onTrafficFromClient := func( ctx context.Context, @@ -67,7 +67,7 @@ func TestRunServer(t *testing.T) { assert.Empty(t, paramsMap["error"]) return params, nil } - hooksConfig.Add(hook.OnTrafficFromClient, 1, onTrafficFromClient) + pluginRegistry.AddHook(plugin.OnTrafficFromClient, 1, onTrafficFromClient) onTrafficFromServer := func( ctx context.Context, @@ -92,7 +92,7 @@ func TestRunServer(t *testing.T) { assert.Empty(t, paramsMap["error"]) return params, nil } - hooksConfig.Add(hook.OnTrafficFromServer, 1, onTrafficFromServer) + pluginRegistry.AddHook(plugin.OnTrafficFromServer, 1, onTrafficFromServer) clientConfig := config.Client{ Network: "tcp", @@ -116,7 +116,7 @@ func TestRunServer(t *testing.T) { // Create a proxy with a fixed buffer pool. proxy := NewProxy( - pool, hooksConfig, false, false, config.DefaultHealthCheckPeriod, &clientConfig, logger) + pool, pluginRegistry, false, false, config.DefaultHealthCheckPeriod, &clientConfig, logger) // Create a server. server := NewServer( @@ -132,7 +132,7 @@ func TestRunServer(t *testing.T) { }, proxy, logger, - hooksConfig, + pluginRegistry, ) assert.NotNil(t, server) diff --git a/plugin/constants.go b/plugin/constants.go new file mode 100644 index 00000000..122ed96f --- /dev/null +++ b/plugin/constants.go @@ -0,0 +1,28 @@ +package plugin + +const ( + // Run command hooks (cmd/run.go). + OnConfigLoaded string = "onConfigLoaded" + OnNewLogger string = "onNewLogger" + OnNewPool string = "onNewPool" + OnNewClient string = "onNewClient" + OnNewProxy string = "onNewProxy" + OnNewServer string = "onNewServer" + OnSignal string = "onSignal" + // Server hooks (network/server.go). + OnRun string = "onRun" + OnBooting string = "onBooting" + OnBooted string = "onBooted" + OnOpening string = "onOpening" + OnOpened string = "onOpened" + OnClosing string = "onClosing" + OnClosed string = "onClosed" + OnTraffic string = "onTraffic" + OnShutdown string = "onShutdown" + OnTick string = "onTick" + // Proxy hooks (network/proxy.go). + OnTrafficFromClient string = "onTrafficFromClient" + OnTrafficToServer string = "onTrafficToServer" + OnTrafficFromServer string = "onTrafficFromServer" + OnTrafficToClient string = "onTrafficToClient" +) diff --git a/plugin/hook/constants.go b/plugin/hook/constants.go deleted file mode 100644 index b9c4e3e6..00000000 --- a/plugin/hook/constants.go +++ /dev/null @@ -1,28 +0,0 @@ -package hook - -const ( - // Run command hooks (cmd/run.go). - OnConfigLoaded Type = "onConfigLoaded" - OnNewLogger Type = "onNewLogger" - OnNewPool Type = "onNewPool" - OnNewProxy Type = "onNewProxy" - OnNewServer Type = "onNewServer" - OnSignal Type = "onSignal" - // Server hooks (network/server.go). - OnRun Type = "onRun" - OnBooting Type = "onBooting" - OnBooted Type = "onBooted" - OnOpening Type = "onOpening" - OnOpened Type = "onOpened" - OnClosing Type = "onClosing" - OnClosed Type = "onClosed" - OnTraffic Type = "onTraffic" - OnTrafficFromClient Type = "onTrafficFromClient" - OnTrafficToServer Type = "onTrafficToServer" - OnTrafficFromServer Type = "onTrafficFromServer" - OnTrafficToClient Type = "onTrafficToClient" - OnShutdown Type = "onShutdown" - OnTick Type = "onTick" - // Pool hooks (network/pool.go). - OnNewClient Type = "onNewClient" -) diff --git a/plugin/hook/hooks.go b/plugin/hook/hooks.go deleted file mode 100644 index 20784cf8..00000000 --- a/plugin/hook/hooks.go +++ /dev/null @@ -1,179 +0,0 @@ -package hook - -import ( - "context" - "sort" - - "github.com/gatewayd-io/gatewayd/config" - gerr "github.com/gatewayd-io/gatewayd/errors" - "github.com/gatewayd-io/gatewayd/plugin/utils" - "github.com/rs/zerolog" - "google.golang.org/grpc" - "google.golang.org/protobuf/types/known/structpb" -) - -type Config struct { - hooks map[Type]map[Priority]FunctionType - Logger zerolog.Logger - Verification config.Policy -} - -// NewHookConfig returns a new Config. -func NewHookConfig() *Config { - return &Config{ - hooks: map[Type]map[Priority]FunctionType{}, - } -} - -// Hooks returns the hooks. -func (h *Config) Hooks() map[Type]map[Priority]FunctionType { - return h.hooks -} - -// Add adds a hook with a priority to the hooks map. -func (h *Config) Add(hookType Type, prio Priority, hookFunc FunctionType) { - if len(h.hooks[hookType]) == 0 { - h.hooks[hookType] = map[Priority]FunctionType{prio: hookFunc} - } else { - if _, ok := h.hooks[hookType][prio]; ok { - h.Logger.Warn().Fields( - map[string]interface{}{ - "hookType": hookType, - "priority": prio, - }, - ).Msg("Hook is replaced") - } - h.hooks[hookType][prio] = hookFunc - } -} - -// Get returns the hooks of a specific type. -func (h *Config) Get(hookType Type) map[Priority]FunctionType { - return h.hooks[hookType] -} - -// Run runs the hooks of a specific type. The result of the previous hook is passed -// to the next hook as the argument, aka. chained. The context is passed to the -// hooks as well to allow them to cancel the execution. The args are passed to the -// first hook as the argument. The result of the first hook is passed to the second -// hook, and so on. The result of the last hook is eventually returned. The verification -// mode is used to determine how to handle errors. If the verification mode is set to -// Abort, the execution is aborted on the first error. If the verification mode is set -// to Remove, the hook is removed from the list of hooks on the first error. If the -// verification mode is set to Ignore, the error is ignored and the execution continues. -// If the verification mode is set to PassDown, the extra keys/values in the result -// are passed down to the next The verification mode is set to PassDown by default. -// The opts are passed to the hooks as well to allow them to use the grpc.CallOption. -// -//nolint:funlen -func (h *Config) Run( - ctx context.Context, - args map[string]interface{}, - hookType Type, - verification config.Policy, - opts ...grpc.CallOption, -) (map[string]interface{}, *gerr.GatewayDError) { - if ctx == nil { - return nil, gerr.ErrNilContext - } - - // Inherit context. - inheritedCtx, cancel := context.WithCancel(ctx) - defer cancel() - - // Cast custom fields to their primitive types, like time.Duration to float64. - args = utils.CastToPrimitiveTypes(args) - - // Create structpb.Struct from args. - var params *structpb.Struct - if len(args) == 0 { - params = &structpb.Struct{} - } else if casted, err := structpb.NewStruct(args); err == nil { - params = casted - } else { - return nil, gerr.ErrCastFailed.Wrap(err) - } - - // Sort hooks by priority. - priorities := make([]Priority, 0, len(h.hooks[hookType])) - for prio := range h.hooks[hookType] { - priorities = append(priorities, prio) - } - sort.SliceStable(priorities, func(i, j int) bool { - return priorities[i] < priorities[j] - }) - - // Run hooks, passing the result of the previous hook to the next one. - returnVal := &structpb.Struct{} - var removeList []Priority - // The signature of parameters and args MUST be the same for this to work. - for idx, prio := range priorities { - var result *structpb.Struct - var err error - if idx == 0 { - result, err = h.hooks[hookType][prio](inheritedCtx, params, opts...) - } else { - result, err = h.hooks[hookType][prio](inheritedCtx, returnVal, opts...) - } - - // This is done to ensure that the return value of the hook is always valid, - // and that the hook does not return any unexpected values. - // If the verification mode is non-strict (permissive), let the plugin pass - // extra keys/values to the next plugin in chain. - if utils.Verify(params, result) || verification == config.PassDown { - // Update the last return value with the current result - returnVal = result - continue - } - - // At this point, the hook returned an invalid value, so we need to handle it. - // The result of the current hook will be ignored, regardless of the policy. - switch verification { - // Ignore the result of this plugin, log an error and execute the next - case config.Ignore: - h.Logger.Error().Err(err).Fields( - map[string]interface{}{ - "hookType": hookType, - "priority": prio, - }, - ).Msg("Hook returned invalid value, ignoring") - if idx == 0 { - returnVal = params - } - // Abort execution of the plugins, log the error and return the result of the last - case config.Abort: - h.Logger.Error().Err(err).Fields( - map[string]interface{}{ - "hookType": hookType, - "priority": prio, - }, - ).Msg("Hook returned invalid value, aborting") - if idx == 0 { - return args, nil - } - return returnVal.AsMap(), nil - // Remove the hook from the registry, log the error and execute the next - case config.Remove: - h.Logger.Error().Err(err).Fields( - map[string]interface{}{ - "hookType": hookType, - "priority": prio, - }, - ).Msg("Hook returned invalid value, removing") - removeList = append(removeList, prio) - if idx == 0 { - returnVal = params - } - case config.PassDown: - default: - returnVal = result - } - } - - // Remove hooks that failed verification. - for _, prio := range removeList { - delete(h.hooks[hookType], prio) - } - - return returnVal.AsMap(), nil -} diff --git a/plugin/hook/hooks_test.go b/plugin/hook/hooks_test.go deleted file mode 100644 index 61ff4891..00000000 --- a/plugin/hook/hooks_test.go +++ /dev/null @@ -1,261 +0,0 @@ -package hook - -import ( - "context" - "testing" - - "github.com/gatewayd-io/gatewayd/config" - "github.com/stretchr/testify/assert" - "google.golang.org/grpc" - "google.golang.org/protobuf/types/known/structpb" -) - -// Test_NewHookConfig tests the NewHookConfig function. -func Test_NewHookConfig(t *testing.T) { - hc := NewHookConfig() - assert.NotNil(t, hc) -} - -// Test_HookConfig_Add tests the Add function. -func Test_HookConfig_Add(t *testing.T) { - hooks := NewHookConfig() - testFunc := func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return args, nil - } - hooks.Add(OnNewLogger, 0, testFunc) - assert.NotNil(t, hooks.Hooks()[OnNewLogger][0]) - assert.ObjectsAreEqual(testFunc, hooks.Hooks()[OnNewLogger][0]) -} - -// Test_HookConfig_Add_Multiple_Hooks tests the Add function with multiple hooks. -func Test_HookConfig_Add_Multiple_Hooks(t *testing.T) { - hooks := NewHookConfig() - hooks.Add(OnNewLogger, 0, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return args, nil - }) - hooks.Add(OnNewLogger, 1, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return args, nil - }) - assert.NotNil(t, hooks.Hooks()[OnNewLogger][0]) - assert.NotNil(t, hooks.Hooks()[OnNewLogger][1]) -} - -// Test_HookConfig_Get tests the Get function. -func Test_HookConfig_Get(t *testing.T) { - hooks := NewHookConfig() - testFunc := func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return args, nil - } - prio := Priority(0) - hooks.Add(OnNewLogger, prio, testFunc) - assert.NotNil(t, hooks.Get(OnNewLogger)) - assert.ObjectsAreEqual(testFunc, hooks.Get(OnNewLogger)[prio]) -} - -// Test_HookConfig_Run tests the Run function. -func Test_HookConfig_Run(t *testing.T) { - hooks := NewHookConfig() - hooks.Add(OnNewLogger, 0, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return args, nil - }) - result, err := hooks.Run( - context.Background(), map[string]interface{}{}, OnNewLogger, config.Ignore) - assert.NotNil(t, result) - assert.Nil(t, err) -} - -// Test_HookConfig_Run_PassDown tests the Run function with the PassDown option. -func Test_HookConfig_Run_PassDown(t *testing.T) { - hooks := NewHookConfig() - // The result of the hook will be nil and will be passed down to the next - hooks.Add(OnNewLogger, 0, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return nil, nil //nolint:nilnil - }) - // The consolidated result should be {"test": "test"}. - hooks.Add(OnNewLogger, 1, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - output, err := structpb.NewStruct(map[string]interface{}{ - "test": "test", - }) - assert.Nil(t, err) - return output, nil - }) - - // Although the first hook returns nil, and its signature doesn't match the params, - // so its result (nil) is passed down to the next hook in chain (prio 2). - // Then the second hook runs and returns a signature with a "test" key and value. - result, err := hooks.Run( - context.Background(), - map[string]interface{}{"test": "test"}, - OnNewLogger, - config.PassDown) - assert.Nil(t, err) - assert.NotNil(t, result) -} - -// Test_HookConfig_Run_PassDown_2 tests the Run function with the PassDown option. -func Test_HookConfig_Run_PassDown_2(t *testing.T) { - hooks := NewHookConfig() - // The result of the hook will be nil and will be passed down to the next - hooks.Add(OnNewLogger, 0, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - args.Fields["test1"] = &structpb.Value{ - Kind: &structpb.Value_StringValue{ //nolint:nosnakecase - StringValue: "test1", - }, - } - return args, nil - }) - // The consolidated result should be {"test1": "test1", "test2": "test2"}. - hooks.Add(OnNewLogger, 1, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - args.Fields["test2"] = &structpb.Value{ - Kind: &structpb.Value_StringValue{ //nolint:nosnakecase - StringValue: "test2", - }, - } - return args, nil - }) - // Although the first hook returns nil, and its signature doesn't match the params, - // so its result (nil) is passed down to the next hook in chain (prio 2). - // Then the second hook runs and returns a signature with a "test1" and "test2" key and value. - result, err := hooks.Run( - context.Background(), - map[string]interface{}{"test": "test"}, - OnNewLogger, - config.PassDown) - assert.Nil(t, err) - assert.NotNil(t, result) -} - -// Test_HookConfig_Run_Ignore tests the Run function with the Ignore option. -func Test_HookConfig_Run_Ignore(t *testing.T) { - hooks := NewHookConfig() - // This should not run, because the return value is not the same as the params - hooks.Add(OnNewLogger, 0, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return nil, nil //nolint:nilnil - }) - // This should run, because the return value is the same as the params - hooks.Add(OnNewLogger, 1, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - args.Fields["test"] = &structpb.Value{ - Kind: &structpb.Value_StringValue{ //nolint:nosnakecase - StringValue: "test", - }, - } - return args, nil - }) - // The first hook returns nil, and its signature doesn't match the params, - // so its result is ignored. - // Then the second hook runs and returns a signature with a "test" key and value. - result, err := hooks.Run( - context.Background(), - map[string]interface{}{"test": "test"}, - OnNewLogger, - config.Ignore) - assert.Nil(t, err) - assert.NotNil(t, result) -} - -// Test_HookConfig_Run_Abort tests the Run function with the Abort option. -func Test_HookConfig_Run_Abort(t *testing.T) { - hooks := NewHookConfig() - // This should not run, because the return value is not the same as the params - hooks.Add(OnNewLogger, 0, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return nil, nil //nolint:nilnil - }) - // This should not run, because the first hook returns nil, and its result is ignored. - hooks.Add(OnNewLogger, 1, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - output, err := structpb.NewStruct(map[string]interface{}{ - "test": "test", - }) - assert.Nil(t, err) - return output, nil - }) - // The first hook returns nil, and it aborts the execution of the rest of the - result, err := hooks.Run( - context.Background(), map[string]interface{}{}, OnNewLogger, config.Abort) - assert.Nil(t, err) - assert.Equal(t, map[string]interface{}{}, result) -} - -// Test_HookConfig_Run_Remove tests the Run function with the Remove option. -func Test_HookConfig_Run_Remove(t *testing.T) { - hooks := NewHookConfig() - // This should not run, because the return value is not the same as the params - hooks.Add(OnNewLogger, 0, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - return nil, nil //nolint:nilnil - }) - // This should not run, because the first hook returns nil, and its result is ignored. - hooks.Add(OnNewLogger, 1, func( - ctx context.Context, - args *structpb.Struct, - opts ...grpc.CallOption, - ) (*structpb.Struct, error) { - output, err := structpb.NewStruct(map[string]interface{}{ - "test": "test", - }) - assert.Nil(t, err) - return output, nil - }) - // The first hook returns nil, and its signature doesn't match the params, - // so its result is ignored. The failing hook is removed from the list and - // the execution continues with the next hook in the list. - result, err := hooks.Run( - context.Background(), map[string]interface{}{}, OnNewLogger, config.Remove) - assert.Nil(t, err) - assert.Equal(t, map[string]interface{}{}, result) - assert.Equal(t, 1, len(hooks.Hooks()[OnNewLogger])) -} diff --git a/plugin/plugin.go b/plugin/plugin.go index 2ae76915..27aadea3 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -4,7 +4,6 @@ import ( "net" gerr "github.com/gatewayd-io/gatewayd/errors" - "github.com/gatewayd-io/gatewayd/plugin/hook" pluginV1 "github.com/gatewayd-io/gatewayd/plugin/v1" goplugin "github.com/hashicorp/go-plugin" ) @@ -40,8 +39,8 @@ type Plugin struct { // internal and external config options Config map[string]string // hooks it attaches to - Hooks []hook.Type - Priority hook.Priority + Hooks []string + Priority Priority // required plugins to be loaded before this one // Built-in plugins are always loaded first Requires []Identifier diff --git a/plugin/plugin_registry.go b/plugin/plugin_registry.go index 56b7af2b..57c2b03d 100644 --- a/plugin/plugin_registry.go +++ b/plugin/plugin_registry.go @@ -2,21 +2,24 @@ package plugin import ( "context" + "sort" semver "github.com/Masterminds/semver/v3" "github.com/gatewayd-io/gatewayd/config" gerr "github.com/gatewayd-io/gatewayd/errors" "github.com/gatewayd-io/gatewayd/logging" - "github.com/gatewayd-io/gatewayd/plugin/hook" - "github.com/gatewayd-io/gatewayd/plugin/utils" pluginV1 "github.com/gatewayd-io/gatewayd/plugin/v1" "github.com/gatewayd-io/gatewayd/pool" goplugin "github.com/hashicorp/go-plugin" "github.com/mitchellh/mapstructure" + "github.com/rs/zerolog" + "google.golang.org/grpc" "google.golang.org/protobuf/types/known/structpb" ) -type IPluginRegistry interface { +//nolint:interfacebloat +type IRegistry interface { + // Plugin management Add(plugin *Plugin) bool Get(id Identifier) *Plugin List() []Identifier @@ -25,36 +28,57 @@ type IPluginRegistry interface { Shutdown() LoadPlugins(plugins []config.Plugin) RegisterHooks(id Identifier) + + // Hook management + AddHook(hookName string, priority Priority, hookMethod Method) + Hooks() map[string]map[Priority]Method + Run( + ctx context.Context, + args map[string]interface{}, + hookName string, + verification config.Policy, + opts ...grpc.CallOption, + ) (map[string]interface{}, *gerr.GatewayDError) } -type PluginRegistry struct { //nolint:golint,revive - plugins pool.IPool - hooksConfig *hook.Config +type Registry struct { + plugins pool.IPool + hooks map[string]map[Priority]Method + + Logger zerolog.Logger CompatPolicy config.CompatPolicy + Verification config.Policy } -var _ IPluginRegistry = &PluginRegistry{} +var _ IRegistry = &Registry{} // NewRegistry creates a new plugin registry. -func NewRegistry(hooksConfig *hook.Config) *PluginRegistry { - return &PluginRegistry{ - plugins: pool.NewPool(config.EmptyPoolCapacity), - hooksConfig: hooksConfig, +func NewRegistry( + compatPolicy config.CompatPolicy, + verification config.Policy, + logger zerolog.Logger, +) *Registry { + return &Registry{ + plugins: pool.NewPool(config.EmptyPoolCapacity), + hooks: map[string]map[Priority]Method{}, + Logger: logger, + CompatPolicy: compatPolicy, + Verification: verification, } } // Add adds a plugin to the registry. -func (reg *PluginRegistry) Add(plugin *Plugin) bool { +func (reg *Registry) Add(plugin *Plugin) bool { _, loaded, err := reg.plugins.GetOrPut(plugin.ID, plugin) if err != nil { - reg.hooksConfig.Logger.Error().Err(err).Msg("Failed to add plugin to registry") + reg.Logger.Error().Err(err).Msg("Failed to add plugin to registry") return false } return loaded } // Get returns a plugin from the registry. -func (reg *PluginRegistry) Get(id Identifier) *Plugin { +func (reg *Registry) Get(id Identifier) *Plugin { if plugin, ok := reg.plugins.Get(id).(*Plugin); ok { return plugin } @@ -63,7 +87,7 @@ func (reg *PluginRegistry) Get(id Identifier) *Plugin { } // List returns a list of all plugins in the registry. -func (reg *PluginRegistry) List() []Identifier { +func (reg *Registry) List() []Identifier { var plugins []Identifier reg.plugins.ForEach(func(key, _ interface{}) bool { if id, ok := key.(Identifier); ok { @@ -75,20 +99,20 @@ func (reg *PluginRegistry) List() []Identifier { } // Exists checks if a plugin exists in the registry. -func (reg *PluginRegistry) Exists(name, version, remoteURL string) bool { +func (reg *Registry) Exists(name, version, remoteURL string) bool { for _, plugin := range reg.List() { if plugin.Name == name && plugin.RemoteURL == remoteURL { // Parse the supplied version and the version in the registry. suppliedVer, err := semver.NewVersion(version) if err != nil { - reg.hooksConfig.Logger.Error().Err(err).Msg( + reg.Logger.Error().Err(err).Msg( "Failed to parse supplied plugin version") return false } registryVer, err := semver.NewVersion(plugin.Version) if err != nil { - reg.hooksConfig.Logger.Error().Err(err).Msg( + reg.Logger.Error().Err(err).Msg( "Failed to parse plugin version in registry") return false } @@ -99,7 +123,7 @@ func (reg *PluginRegistry) Exists(name, version, remoteURL string) bool { return true } - reg.hooksConfig.Logger.Debug().Str("name", name).Str("version", version).Msg( + reg.Logger.Debug().Str("name", name).Str("version", version).Msg( "Supplied plugin version is greater than the version in registry") return false } @@ -109,12 +133,12 @@ func (reg *PluginRegistry) Exists(name, version, remoteURL string) bool { } // Remove removes a plugin from the registry. -func (reg *PluginRegistry) Remove(id Identifier) { +func (reg *Registry) Remove(id Identifier) { reg.plugins.Remove(id) } // Shutdown shuts down all plugins in the registry. -func (reg *PluginRegistry) Shutdown() { +func (reg *Registry) Shutdown() { reg.plugins.ForEach(func(key, value interface{}) bool { if id, ok := key.(Identifier); ok { if plugin, ok := value.(*Plugin); ok { @@ -127,10 +151,158 @@ func (reg *PluginRegistry) Shutdown() { goplugin.CleanupClients() } +// Hooks returns the hooks map. +func (reg *Registry) Hooks() map[string]map[Priority]Method { + return reg.hooks +} + +// Add adds a hook with a priority to the hooks map. +func (reg *Registry) AddHook(hookName string, priority Priority, hookMethod Method) { + if len(reg.hooks[hookName]) == 0 { + reg.hooks[hookName] = map[Priority]Method{priority: hookMethod} + } else { + if _, ok := reg.hooks[hookName][priority]; ok { + reg.Logger.Warn().Fields( + map[string]interface{}{ + "hookName": hookName, + "priority": priority, + }, + ).Msg("Hook is replaced") + } + reg.hooks[hookName][priority] = hookMethod + } +} + +// Run runs the hooks of a specific type. The result of the previous hook is passed +// to the next hook as the argument, aka. chained. The context is passed to the +// hooks as well to allow them to cancel the execution. The args are passed to the +// first hook as the argument. The result of the first hook is passed to the second +// hook, and so on. The result of the last hook is eventually returned. The verification +// mode is used to determine how to handle errors. If the verification mode is set to +// Abort, the execution is aborted on the first error. If the verification mode is set +// to Remove, the hook is removed from the list of hooks on the first error. If the +// verification mode is set to Ignore, the error is ignored and the execution continues. +// If the verification mode is set to PassDown, the extra keys/values in the result +// are passed down to the next The verification mode is set to PassDown by default. +// The opts are passed to the hooks as well to allow them to use the grpc.CallOption. +// +//nolint:funlen +func (reg *Registry) Run( + ctx context.Context, + args map[string]interface{}, + hookName string, + verification config.Policy, + opts ...grpc.CallOption, +) (map[string]interface{}, *gerr.GatewayDError) { + if ctx == nil { + return nil, gerr.ErrNilContext + } + + // Inherit context. + inheritedCtx, cancel := context.WithCancel(ctx) + defer cancel() + + // Cast custom fields to their primitive types, like time.Duration to float64. + args = CastToPrimitiveTypes(args) + + // Create structpb.Struct from args. + var params *structpb.Struct + if len(args) == 0 { + params = &structpb.Struct{} + } else if casted, err := structpb.NewStruct(args); err == nil { + params = casted + } else { + return nil, gerr.ErrCastFailed.Wrap(err) + } + + // Sort hooks by priority. + priorities := make([]Priority, 0, len(reg.hooks[hookName])) + for priority := range reg.hooks[hookName] { + priorities = append(priorities, priority) + } + sort.SliceStable(priorities, func(i, j int) bool { + return priorities[i] < priorities[j] + }) + + // Run hooks, passing the result of the previous hook to the next one. + returnVal := &structpb.Struct{} + var removeList []Priority + // The signature of parameters and args MUST be the same for this to work. + for idx, priority := range priorities { + var result *structpb.Struct + var err error + if idx == 0 { + result, err = reg.hooks[hookName][priority](inheritedCtx, params, opts...) + } else { + result, err = reg.hooks[hookName][priority](inheritedCtx, returnVal, opts...) + } + + // This is done to ensure that the return value of the hook is always valid, + // and that the hook does not return any unexpected values. + // If the verification mode is non-strict (permissive), let the plugin pass + // extra keys/values to the next plugin in chain. + if Verify(params, result) || verification == config.PassDown { + // Update the last return value with the current result + returnVal = result + continue + } + + // At this point, the hook returned an invalid value, so we need to handle it. + // The result of the current hook will be ignored, regardless of the policy. + switch verification { + // Ignore the result of this plugin, log an error and execute the next + case config.Ignore: + reg.Logger.Error().Err(err).Fields( + map[string]interface{}{ + "hookName": hookName, + "priority": priority, + }, + ).Msg("Hook returned invalid value, ignoring") + if idx == 0 { + returnVal = params + } + // Abort execution of the plugins, log the error and return the result of the last + case config.Abort: + reg.Logger.Error().Err(err).Fields( + map[string]interface{}{ + "hookName": hookName, + "priority": priority, + }, + ).Msg("Hook returned invalid value, aborting") + if idx == 0 { + return args, nil + } + return returnVal.AsMap(), nil + // Remove the hook from the registry, log the error and execute the next + case config.Remove: + reg.Logger.Error().Err(err).Fields( + map[string]interface{}{ + "hookName": hookName, + "priority": priority, + }, + ).Msg("Hook returned invalid value, removing") + removeList = append(removeList, priority) + if idx == 0 { + returnVal = params + } + case config.PassDown: + default: + returnVal = result + } + } + + // Remove hooks that failed verification. + for _, priority := range removeList { + delete(reg.hooks[hookName], priority) + } + + return returnVal.AsMap(), nil +} + // LoadPlugins loads plugins from the config file. // //nolint:funlen -func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { +func (reg *Registry) LoadPlugins(plugins []config.Plugin) { // TODO: Append built-in plugins to the list of plugins // Built-in plugins are plugins that are compiled and shipped with the gatewayd binary. @@ -141,7 +313,7 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { continue } - reg.hooksConfig.Logger.Debug().Str("name", pCfg.Name).Msg("Loading plugin") + reg.Logger.Debug().Str("name", pCfg.Name).Msg("Loading plugin") plugin := &Plugin{ ID: Identifier{ Name: pCfg.Name, @@ -156,32 +328,32 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { // Is the plugin enabled? plugin.Enabled = pCfg.Enabled if !plugin.Enabled { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin is disabled") + reg.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin is disabled") continue } // File path of the plugin on disk. if plugin.LocalPath == "" { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg( + reg.Logger.Debug().Str("name", plugin.ID.Name).Msg( "Local file of the plugin doesn't exist or is not set") continue } // Checksum of the plugin. if plugin.ID.Checksum == "" { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg( + reg.Logger.Debug().Str("name", plugin.ID.Name).Msg( "Checksum of plugin doesn't exist or is not set") continue } // Verify the checksum. // TODO: Load the plugin from a remote location if the checksum didn't match? - if sum, err := utils.SHA256SUM(plugin.LocalPath); err != nil { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Err(err).Msg( + if sum, err := SHA256SUM(plugin.LocalPath); err != nil { + reg.Logger.Debug().Str("name", plugin.ID.Name).Err(err).Msg( "Failed to calculate checksum") continue } else if sum != plugin.ID.Checksum { - reg.hooksConfig.Logger.Debug().Fields( + reg.Logger.Debug().Fields( map[string]interface{}{ "calculated": sum, "expected": plugin.ID.Checksum, @@ -195,15 +367,15 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { // in the config file. Built-in plugins are loaded first, followed by user-defined // plugins. Built-in plugins have a priority of 0 to 999, and user-defined plugins // have a priority of 1000 or greater. - plugin.Priority = hook.Priority(config.PluginPriorityStart + uint(priority)) + plugin.Priority = Priority(config.PluginPriorityStart + uint(priority)) - logAdapter := logging.NewHcLogAdapter(®.hooksConfig.Logger, config.LoggerName) + logAdapter := logging.NewHcLogAdapter(®.Logger, config.LoggerName) plugin.client = goplugin.NewClient( &goplugin.ClientConfig{ HandshakeConfig: pluginV1.Handshake, Plugins: pluginV1.GetPluginMap(plugin.ID.Name), - Cmd: utils.NewCommand(plugin.LocalPath, plugin.Args, plugin.Env), + Cmd: NewCommand(plugin.LocalPath, plugin.Args, plugin.Env), AllowedProtocols: []goplugin.Protocol{ goplugin.ProtocolGRPC, }, @@ -220,22 +392,22 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { }, ) - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin loaded") + reg.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin loaded") if _, err := plugin.Start(); err != nil { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Err(err).Msg( + reg.Logger.Debug().Str("name", plugin.ID.Name).Err(err).Msg( "Failed to start plugin") } // Load metadata from the plugin. var metadata *structpb.Struct if pluginV1, err := plugin.Dispense(); err != nil { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Err(err).Msg( + reg.Logger.Debug().Str("name", plugin.ID.Name).Err(err).Msg( "Failed to dispense plugin") continue } else { if meta, origErr := pluginV1.GetPluginConfig( context.Background(), &structpb.Struct{}); err != nil { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Err(origErr).Msg( + reg.Logger.Debug().Str("name", plugin.ID.Name).Err(origErr).Msg( "Failed to get plugin metadata") continue } else { @@ -246,12 +418,12 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { // Retrieve plugin requirements. if err := mapstructure.Decode(metadata.Fields["requires"].GetListValue().AsSlice(), &plugin.Requires); err != nil { - reg.hooksConfig.Logger.Debug().Err(err).Msg("Failed to decode plugin requirements") + reg.Logger.Debug().Err(err).Msg("Failed to decode plugin requirements") } // Too many requirements or not enough plugins loaded. if len(plugin.Requires) > reg.plugins.Size() { - reg.hooksConfig.Logger.Debug().Msg( + reg.Logger.Debug().Msg( "The plugin has too many requirements, " + "and not enough of them exist in the registry, so it won't work properly") } @@ -259,19 +431,19 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { // Check if the plugin requirements are met. for _, req := range plugin.Requires { if !reg.Exists(req.Name, req.Version, req.RemoteURL) { - reg.hooksConfig.Logger.Debug().Fields( + reg.Logger.Debug().Fields( map[string]interface{}{ "name": plugin.ID.Name, "requirement": req.Name, }, ).Msg("The plugin requirement is not met, so it won't work properly") if reg.CompatPolicy == config.Strict { - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg( + reg.Logger.Debug().Str("name", plugin.ID.Name).Msg( "Registry is in strict compatibility mode, so the plugin won't be loaded") plugin.Stop() // Stop the plugin. continue } else { - reg.hooksConfig.Logger.Debug().Fields( + reg.Logger.Debug().Fields( map[string]interface{}{ "name": plugin.ID.Name, "requirement": req.Name, @@ -290,12 +462,12 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { // Retrieve authors. if err := mapstructure.Decode(metadata.Fields["authors"].GetListValue().AsSlice(), &plugin.Authors); err != nil { - reg.hooksConfig.Logger.Debug().Err(err).Msg("Failed to decode plugin authors") + reg.Logger.Debug().Err(err).Msg("Failed to decode plugin authors") } // Retrieve hooks. if err := mapstructure.Decode(metadata.Fields["hooks"].GetListValue().AsSlice(), &plugin.Hooks); err != nil { - reg.hooksConfig.Logger.Debug().Err(err).Msg("Failed to decode plugin hooks") + reg.Logger.Debug().Err(err).Msg("Failed to decode plugin hooks") } // Retrieve plugin config. @@ -304,95 +476,95 @@ func (reg *PluginRegistry) LoadPlugins(plugins []config.Plugin) { if val, ok := value.(string); ok { plugin.Config[key] = val } else { - reg.hooksConfig.Logger.Debug().Str("key", key).Msg( + reg.Logger.Debug().Str("key", key).Msg( "Failed to decode plugin config") } } - reg.hooksConfig.Logger.Trace().Msgf("Plugin metadata: %+v", plugin) + reg.Logger.Trace().Msgf("Plugin metadata: %+v", plugin) reg.Add(plugin) - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin metadata loaded") + reg.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin metadata loaded") reg.RegisterHooks(plugin.ID) - reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin hooks registered") + reg.Logger.Debug().Str("name", plugin.ID.Name).Msg("Plugin hooks registered") } } // RegisterHooks registers the hooks for the given plugin. // //nolint:funlen -func (reg *PluginRegistry) RegisterHooks(id Identifier) { +func (reg *Registry) RegisterHooks(id Identifier) { pluginImpl := reg.Get(id) - reg.hooksConfig.Logger.Debug().Str("name", pluginImpl.ID.Name).Msg( + reg.Logger.Debug().Str("name", pluginImpl.ID.Name).Msg( "Registering hooks for plugin") var pluginV1 pluginV1.GatewayDPluginServiceClient var err *gerr.GatewayDError if pluginV1, err = pluginImpl.Dispense(); err != nil { - reg.hooksConfig.Logger.Debug().Str("name", pluginImpl.ID.Name).Err(err).Msg( + reg.Logger.Debug().Str("name", pluginImpl.ID.Name).Err(err).Msg( "Failed to dispense plugin") return } - for _, hookType := range pluginImpl.Hooks { - var hookFunc hook.FunctionType - switch hookType { - case hook.OnConfigLoaded: - hookFunc = pluginV1.OnConfigLoaded - case hook.OnNewLogger: - hookFunc = pluginV1.OnNewLogger - case hook.OnNewPool: - hookFunc = pluginV1.OnNewPool - case hook.OnNewProxy: - hookFunc = pluginV1.OnNewProxy - case hook.OnNewServer: - hookFunc = pluginV1.OnNewServer - case hook.OnSignal: - hookFunc = pluginV1.OnSignal - case hook.OnRun: - hookFunc = pluginV1.OnRun - case hook.OnBooting: - hookFunc = pluginV1.OnBooting - case hook.OnBooted: - hookFunc = pluginV1.OnBooted - case hook.OnOpening: - hookFunc = pluginV1.OnOpening - case hook.OnOpened: - hookFunc = pluginV1.OnOpened - case hook.OnClosing: - hookFunc = pluginV1.OnClosing - case hook.OnClosed: - hookFunc = pluginV1.OnClosed - case hook.OnTraffic: - hookFunc = pluginV1.OnTraffic - case hook.OnTrafficFromClient: - hookFunc = pluginV1.OnTrafficFromClient - case hook.OnTrafficToServer: - hookFunc = pluginV1.OnTrafficToServer - case hook.OnTrafficFromServer: - hookFunc = pluginV1.OnTrafficFromServer - case hook.OnTrafficToClient: - hookFunc = pluginV1.OnTrafficToClient - case hook.OnShutdown: - hookFunc = pluginV1.OnShutdown - case hook.OnTick: - hookFunc = pluginV1.OnTick - case hook.OnNewClient: - hookFunc = pluginV1.OnNewClient + for _, hookName := range pluginImpl.Hooks { + var hookMethod Method + switch hookName { + case OnConfigLoaded: + hookMethod = pluginV1.OnConfigLoaded + case OnNewLogger: + hookMethod = pluginV1.OnNewLogger + case OnNewPool: + hookMethod = pluginV1.OnNewPool + case OnNewProxy: + hookMethod = pluginV1.OnNewProxy + case OnNewServer: + hookMethod = pluginV1.OnNewServer + case OnSignal: + hookMethod = pluginV1.OnSignal + case OnRun: + hookMethod = pluginV1.OnRun + case OnBooting: + hookMethod = pluginV1.OnBooting + case OnBooted: + hookMethod = pluginV1.OnBooted + case OnOpening: + hookMethod = pluginV1.OnOpening + case OnOpened: + hookMethod = pluginV1.OnOpened + case OnClosing: + hookMethod = pluginV1.OnClosing + case OnClosed: + hookMethod = pluginV1.OnClosed + case OnTraffic: + hookMethod = pluginV1.OnTraffic + case OnTrafficFromClient: + hookMethod = pluginV1.OnTrafficFromClient + case OnTrafficToServer: + hookMethod = pluginV1.OnTrafficToServer + case OnTrafficFromServer: + hookMethod = pluginV1.OnTrafficFromServer + case OnTrafficToClient: + hookMethod = pluginV1.OnTrafficToClient + case OnShutdown: + hookMethod = pluginV1.OnShutdown + case OnTick: + hookMethod = pluginV1.OnTick + case OnNewClient: + hookMethod = pluginV1.OnNewClient default: - reg.hooksConfig.Logger.Warn().Fields(map[string]interface{}{ - "hook": string(hookType), + reg.Logger.Warn().Fields(map[string]interface{}{ + "hook": hookName, "priority": pluginImpl.Priority, "name": pluginImpl.ID.Name, }).Msg( "Unknown hook, skipping") continue } - reg.hooksConfig.Logger.Debug().Fields(map[string]interface{}{ - "hook": string(hookType), + reg.Logger.Debug().Fields(map[string]interface{}{ + "hook": hookName, "priority": pluginImpl.Priority, "name": pluginImpl.ID.Name, }).Msg("Registering hook") - reg.hooksConfig.Add(hookType, pluginImpl.Priority, hookFunc) + reg.AddHook(hookName, pluginImpl.Priority, hookMethod) } } diff --git a/plugin/plugin_registry_test.go b/plugin/plugin_registry_test.go index c1aaa7b1..48f91ad9 100644 --- a/plugin/plugin_registry_test.go +++ b/plugin/plugin_registry_test.go @@ -1,20 +1,37 @@ package plugin import ( + "context" "testing" - "github.com/gatewayd-io/gatewayd/plugin/hook" + "github.com/gatewayd-io/gatewayd/config" + "github.com/gatewayd-io/gatewayd/logging" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/structpb" ) +func NewPluginRegistry(t *testing.T) *Registry { + t.Helper() + + cfg := logging.LoggerConfig{ + Output: config.Console, + TimeFormat: zerolog.TimeFormatUnix, + Level: zerolog.DebugLevel, + NoColor: true, + } + logger := logging.NewLogger(cfg) + reg := NewRegistry(config.Loose, config.PassDown, logger) + return reg +} + // TestPluginRegistry tests the PluginRegistry. func TestPluginRegistry(t *testing.T) { - hooksConfig := hook.NewHookConfig() - assert.NotNil(t, hooksConfig) - reg := NewRegistry(hooksConfig) + reg := NewPluginRegistry(t) assert.NotNil(t, reg) assert.NotNil(t, reg.plugins) - assert.NotNil(t, reg.hooksConfig) + assert.NotNil(t, reg.hooks) assert.Equal(t, 0, len(reg.List())) ident := Identifier{ @@ -36,3 +53,232 @@ func TestPluginRegistry(t *testing.T) { reg.Shutdown() } + +// Test_HookRegistry_Add tests the Add function. +func Test_PluginRegistry_AddHook(t *testing.T) { + testFunc := func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return args, nil + } + + reg := NewPluginRegistry(t) + reg.AddHook(OnNewLogger, 0, testFunc) + assert.NotNil(t, reg.Hooks()[OnNewLogger][0]) + assert.ObjectsAreEqual(testFunc, reg.Hooks()[OnNewLogger][0]) +} + +// Test_HookRegistry_Add_Multiple_Hooks tests the Add function with multiple hooks. +func Test_PluginRegistry_AddHook_Multiple(t *testing.T) { + reg := NewPluginRegistry(t) + reg.AddHook(OnNewLogger, 0, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return args, nil + }) + reg.AddHook(OnNewLogger, 1, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return args, nil + }) + assert.NotNil(t, reg.Hooks()[OnNewLogger][0]) + assert.NotNil(t, reg.Hooks()[OnNewLogger][1]) +} + +// Test_HookRegistry_Run tests the Run function. +func Test_PluginRegistry_Run(t *testing.T) { + reg := NewPluginRegistry(t) + reg.AddHook(OnNewLogger, 0, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return args, nil + }) + result, err := reg.Run( + context.Background(), map[string]interface{}{}, OnNewLogger, config.Ignore) + assert.NotNil(t, result) + assert.Nil(t, err) +} + +// Test_HookRegistry_Run_PassDown tests the Run function with the PassDown option. +func Test_PluginRegistry_Run_PassDown(t *testing.T) { + reg := NewPluginRegistry(t) + // The result of the hook will be nil and will be passed down to the next + reg.AddHook(OnNewLogger, 0, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return nil, nil //nolint:nilnil + }) + // The consolidated result should be {"test": "test"}. + reg.AddHook(OnNewLogger, 1, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + output, err := structpb.NewStruct(map[string]interface{}{ + "test": "test", + }) + assert.Nil(t, err) + return output, nil + }) + + // Although the first hook returns nil, and its signature doesn't match the params, + // so its result (nil) is passed down to the next hook in chain (priority 2). + // Then the second hook runs and returns a signature with a "test" key and value. + result, err := reg.Run( + context.Background(), + map[string]interface{}{"test": "test"}, + OnNewLogger, + config.PassDown) + assert.Nil(t, err) + assert.NotNil(t, result) +} + +// Test_HookRegistry_Run_PassDown_2 tests the Run function with the PassDown option. +func Test_HookRegistry_Run_PassDown_2(t *testing.T) { + reg := NewPluginRegistry(t) + // The result of the hook will be nil and will be passed down to the next + reg.AddHook(OnNewLogger, 0, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + args.Fields["test1"] = &structpb.Value{ + Kind: &structpb.Value_StringValue{ //nolint:nosnakecase + StringValue: "test1", + }, + } + return args, nil + }) + // The consolidated result should be {"test1": "test1", "test2": "test2"}. + reg.AddHook(OnNewLogger, 1, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + args.Fields["test2"] = &structpb.Value{ + Kind: &structpb.Value_StringValue{ //nolint:nosnakecase + StringValue: "test2", + }, + } + return args, nil + }) + // Although the first hook returns nil, and its signature doesn't match the params, + // so its result (nil) is passed down to the next hook in chain (priority 2). + // Then the second hook runs and returns a signature with a "test1" and "test2" key and value. + result, err := reg.Run( + context.Background(), + map[string]interface{}{"test": "test"}, + OnNewLogger, + config.PassDown) + assert.Nil(t, err) + assert.NotNil(t, result) +} + +// Test_HookRegistry_Run_Ignore tests the Run function with the Ignore option. +func Test_HookRegistry_Run_Ignore(t *testing.T) { + reg := NewPluginRegistry(t) + // This should not run, because the return value is not the same as the params + reg.AddHook(OnNewLogger, 0, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return nil, nil //nolint:nilnil + }) + // This should run, because the return value is the same as the params + reg.AddHook(OnNewLogger, 1, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + args.Fields["test"] = &structpb.Value{ + Kind: &structpb.Value_StringValue{ //nolint:nosnakecase + StringValue: "test", + }, + } + return args, nil + }) + // The first hook returns nil, and its signature doesn't match the params, + // so its result is ignored. + // Then the second hook runs and returns a signature with a "test" key and value. + result, err := reg.Run( + context.Background(), + map[string]interface{}{"test": "test"}, + OnNewLogger, + config.Ignore) + assert.Nil(t, err) + assert.NotNil(t, result) +} + +// Test_HookRegistry_Run_Abort tests the Run function with the Abort option. +func Test_HookRegistry_Run_Abort(t *testing.T) { + reg := NewPluginRegistry(t) + // This should not run, because the return value is not the same as the params + reg.AddHook(OnNewLogger, 0, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return nil, nil //nolint:nilnil + }) + // This should not run, because the first hook returns nil, and its result is ignored. + reg.AddHook(OnNewLogger, 1, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + output, err := structpb.NewStruct(map[string]interface{}{ + "test": "test", + }) + assert.Nil(t, err) + return output, nil + }) + // The first hook returns nil, and it aborts the execution of the rest of the + result, err := reg.Run( + context.Background(), map[string]interface{}{}, OnNewLogger, config.Abort) + assert.Nil(t, err) + assert.Equal(t, map[string]interface{}{}, result) +} + +// Test_HookRegistry_Run_Remove tests the Run function with the Remove option. +func Test_HookRegistry_Run_Remove(t *testing.T) { + reg := NewPluginRegistry(t) + // This should not run, because the return value is not the same as the params + reg.AddHook(OnNewLogger, 0, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + return nil, nil //nolint:nilnil + }) + // This should not run, because the first hook returns nil, and its result is ignored. + reg.AddHook(OnNewLogger, 1, func( + ctx context.Context, + args *structpb.Struct, + opts ...grpc.CallOption, + ) (*structpb.Struct, error) { + output, err := structpb.NewStruct(map[string]interface{}{ + "test": "test", + }) + assert.Nil(t, err) + return output, nil + }) + // The first hook returns nil, and its signature doesn't match the params, + // so its result is ignored. The failing hook is removed from the list and + // the execution continues with the next hook in the list. + result, err := reg.Run( + context.Background(), map[string]interface{}{}, OnNewLogger, config.Remove) + assert.Nil(t, err) + assert.Equal(t, map[string]interface{}{}, result) + assert.Equal(t, 1, len(reg.Hooks()[OnNewLogger])) +} diff --git a/plugin/hook/types.go b/plugin/types.go similarity index 79% rename from plugin/hook/types.go rename to plugin/types.go index c32e0467..f88c26ea 100644 --- a/plugin/hook/types.go +++ b/plugin/types.go @@ -1,4 +1,4 @@ -package hook +package plugin import ( "context" @@ -10,8 +10,7 @@ import ( type ( // Priority is the priority of a hook. // Smaller values are executed first (higher priority). - Priority uint - Type string - FunctionType func( + Priority uint + Method func( context.Context, *structpb.Struct, ...grpc.CallOption) (*structpb.Struct, error) ) diff --git a/plugin/utils/functions.go b/plugin/utils.go similarity index 99% rename from plugin/utils/functions.go rename to plugin/utils.go index c9ecf5f5..e8c0539c 100644 --- a/plugin/utils/functions.go +++ b/plugin/utils.go @@ -1,4 +1,4 @@ -package utils +package plugin import ( "bufio" diff --git a/plugin/utils/functions_test.go b/plugin/utils_test.go similarity index 96% rename from plugin/utils/functions_test.go rename to plugin/utils_test.go index 6d9578f1..b42f4f8c 100644 --- a/plugin/utils/functions_test.go +++ b/plugin/utils_test.go @@ -1,4 +1,4 @@ -package utils +package plugin import ( "testing" @@ -9,7 +9,7 @@ import ( // Test_sha256sum tests the sha256sum function. func Test_sha256sum(t *testing.T) { - checksum, err := SHA256SUM("../../LICENSE") + checksum, err := SHA256SUM("../LICENSE") assert.Nil(t, err) assert.Equal(t, "8486a10c4393cee1c25392769ddd3b2d6c242d6ec7928e1414efff7dfb2f07ef",