github.com/lastbackend/toolkit@v0.0.0-20241020043710-cafa37b95aad/pkg/runtime/controller/plugin.go (about)

     1  package controller
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/lastbackend/toolkit"
     8  	"github.com/lastbackend/toolkit/pkg/runtime"
     9  	"github.com/lastbackend/toolkit/pkg/runtime/logger"
    10  	"github.com/lastbackend/toolkit/pkg/util/types"
    11  	"golang.org/x/sync/errgroup"
    12  	"reflect"
    13  )
    14  
    15  const PluginHookMethodPreStart = "PreStart"
    16  const PluginHookMethodOnStart = "OnStart"
    17  const PluginHookMethodOnStartSync = "OnStartSync"
    18  const PluginHookMethodOnStop = "OnStop"
    19  const PluginHookMethodOnStopSync = "OnStopSync"
    20  
    21  type pluginManager struct {
    22  	runtime.Plugin
    23  
    24  	log          logger.Logger
    25  	constructors []any
    26  	plugins      []toolkit.Plugin
    27  }
    28  
    29  func (c *pluginManager) Provide(constructor ...any) {
    30  	c.constructors = append(c.constructors, constructor...)
    31  }
    32  
    33  func (c *pluginManager) Constructors() []any {
    34  	return c.constructors
    35  }
    36  
    37  func (c *pluginManager) Register(plugins []toolkit.Plugin) {
    38  	c.log.V(5).Info("pluginManager.Register.start")
    39  	c.plugins = append(c.plugins, plugins...)
    40  	c.log.V(5).Infof("pluginManager.Register.plugins %v", c.plugins)
    41  	c.log.V(5).Info("pluginManager.Register.end")
    42  	return
    43  }
    44  
    45  func (c *pluginManager) PreStart(ctx context.Context) error {
    46  	c.log.V(5).Info("pluginManager.PreStart.start")
    47  	err := c.hook(ctx, PluginHookMethodPreStart, true)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	c.log.V(5).Info("pluginManager.PreStart.end")
    52  	return nil
    53  }
    54  
    55  func (c *pluginManager) OnStart(ctx context.Context) error {
    56  	c.log.V(5).Info("pluginManager.OnStart.start")
    57  
    58  	err := c.hook(ctx, PluginHookMethodOnStartSync, true)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	err = c.hook(ctx, PluginHookMethodOnStart, false)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	c.log.V(5).Info("pluginManager.OnStart.end")
    69  	return nil
    70  }
    71  
    72  func (c *pluginManager) OnStop(ctx context.Context) error {
    73  	c.log.V(5).Info("pluginManager.OnStop.start")
    74  
    75  	err := c.hook(ctx, PluginHookMethodOnStopSync, true)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	err = c.hook(ctx, PluginHookMethodOnStop, false)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	c.log.V(5).Info("pluginManager.OnStop.end")
    86  	return nil
    87  }
    88  
    89  func (c *pluginManager) hook(ctx context.Context, kind string, sync bool) error {
    90  
    91  	if sync {
    92  		c.log.V(5).Infof("pluginManager.%s.start:sync", kind)
    93  		defer func() {
    94  			c.log.V(5).Infof("pluginManager.%s.end:sync", kind)
    95  		}()
    96  	} else {
    97  		c.log.V(5).Infof("pluginManager.%s.start:async", kind)
    98  		defer func() {
    99  			c.log.V(5).Infof("pluginManager.%s.end:async", kind)
   100  		}()
   101  	}
   102  
   103  	// start all non-sync methods
   104  	g, ctx := errgroup.WithContext(ctx)
   105  	for i := 0; i < len(c.plugins); i++ {
   106  
   107  		if c.plugins[i] == nil {
   108  			continue
   109  		}
   110  
   111  		plugin := c.plugins[i]
   112  
   113  		if sync {
   114  			if err := c.call(ctx, plugin, kind); err != nil {
   115  				return err
   116  			}
   117  		} else {
   118  			g.Go(func() error {
   119  				return c.call(ctx, plugin, kind)
   120  			})
   121  		}
   122  	}
   123  
   124  	if err := g.Wait(); err != nil {
   125  		c.log.V(5).Errorf("can not start toolkit:", err.Error())
   126  		return err
   127  	}
   128  
   129  	return nil
   130  
   131  }
   132  
   133  func (c *pluginManager) call(ctx context.Context, pkg toolkit.Plugin, kind string) error {
   134  
   135  	args := []reflect.Value{reflect.ValueOf(ctx)}
   136  	meth := reflect.ValueOf(pkg).MethodByName(kind)
   137  	name := types.Type(pkg)
   138  
   139  	if !reflect.ValueOf(meth).IsZero() {
   140  		c.log.V(5).Infof("pluginManager.%s.call: %s", kind, name)
   141  		res := meth.Call(args)
   142  
   143  		if len(res) < 1 {
   144  			return nil
   145  		}
   146  
   147  		if len(res) > 1 {
   148  			return errors.New(fmt.Sprintf("pluginManager.%s.call:%s:err: method results are not supported. Only error is supported", kind, name))
   149  		}
   150  
   151  		var err error
   152  		if v := res[0].Interface(); v != nil {
   153  			err = v.(error)
   154  		}
   155  		return err
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func newPluginController(_ context.Context, runtime runtime.Runtime) runtime.Plugin {
   162  	pl := new(pluginManager)
   163  	pl.log = runtime.Log()
   164  	pl.constructors = make([]any, 0)
   165  	pl.plugins = make([]toolkit.Plugin, 0)
   166  	return pl
   167  }