mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Add plugins required flag (#40)
* add required plugins flag. * update plugins logs. * add plugins subcommand. * move preflight to prerun.
This commit is contained in:
parent
43a649d2c4
commit
1a752e43d2
49
cmd/plugins.go
Normal file
49
cmd/plugins.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/demodesk/neko/internal/config"
|
||||||
|
"github.com/demodesk/neko/internal/plugins"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
command := &cobra.Command{
|
||||||
|
Use: "plugins [directory]",
|
||||||
|
Short: "load, verify and list plugins",
|
||||||
|
Long: `load, verify and list plugins`,
|
||||||
|
Run: pluginsCmd,
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
}
|
||||||
|
root.AddCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluginsCmd(cmd *cobra.Command, args []string) {
|
||||||
|
pluginDir := "/etc/neko/plugins"
|
||||||
|
if len(args) > 0 {
|
||||||
|
pluginDir = args[0]
|
||||||
|
}
|
||||||
|
log.Info().Str("dir", pluginDir).Msg("plugins directory")
|
||||||
|
|
||||||
|
plugs := plugins.New(&config.Plugins{
|
||||||
|
Enabled: true,
|
||||||
|
Required: true,
|
||||||
|
Dir: pluginDir,
|
||||||
|
})
|
||||||
|
|
||||||
|
meta := plugs.Metadata()
|
||||||
|
if len(meta) == 0 {
|
||||||
|
log.Fatal().Msg("no plugins found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal indent to stdout
|
||||||
|
dec := json.NewEncoder(os.Stdout)
|
||||||
|
dec.SetIndent("", " ")
|
||||||
|
err := dec.Encode(meta)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("unable to marshal metadata")
|
||||||
|
}
|
||||||
|
}
|
@ -27,11 +27,10 @@ func init() {
|
|||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: "serve neko streaming server",
|
Short: "serve neko streaming server",
|
||||||
Long: `serve neko streaming server`,
|
Long: `serve neko streaming server`,
|
||||||
Run: service.Command,
|
PreRun: service.PreRun,
|
||||||
|
Run: service.Run,
|
||||||
}
|
}
|
||||||
|
|
||||||
cobra.OnInitialize(service.Preflight)
|
|
||||||
|
|
||||||
if err := service.Init(command); err != nil {
|
if err := service.Init(command); err != nil {
|
||||||
log.Panic().Err(err).Msg("unable to initialize configuration")
|
log.Panic().Err(err).Msg("unable to initialize configuration")
|
||||||
}
|
}
|
||||||
@ -91,7 +90,7 @@ func (c *serve) Init(cmd *cobra.Command) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *serve) Preflight() {
|
func (c *serve) PreRun(cmd *cobra.Command, args []string) {
|
||||||
c.logger = log.With().Str("service", "neko").Logger()
|
c.logger = log.With().Str("service", "neko").Logger()
|
||||||
|
|
||||||
c.configs.Desktop.Set()
|
c.configs.Desktop.Set()
|
||||||
@ -198,7 +197,7 @@ func (c *serve) Shutdown() {
|
|||||||
c.logger.Err(err).Msg("member manager disconnect")
|
c.logger.Err(err).Msg("member manager disconnect")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *serve) Command(cmd *cobra.Command, args []string) {
|
func (c *serve) Run(cmd *cobra.Command, args []string) {
|
||||||
c.logger.Info().Msg("starting neko server")
|
c.logger.Info().Msg("starting neko server")
|
||||||
c.Start(cmd)
|
c.Start(cmd)
|
||||||
c.logger.Info().Msg("neko ready")
|
c.logger.Info().Msg("neko ready")
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
type Plugins struct {
|
type Plugins struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Dir string
|
Dir string
|
||||||
|
Required bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Plugins) Init(cmd *cobra.Command) error {
|
func (Plugins) Init(cmd *cobra.Command) error {
|
||||||
@ -21,10 +22,16 @@ func (Plugins) Init(cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Bool("plugins.required", false, "if true, neko will exit if there is an error when loading a plugin")
|
||||||
|
if err := viper.BindPFlag("plugins.required", cmd.PersistentFlags().Lookup("plugins.required")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Plugins) Set() {
|
func (s *Plugins) Set() {
|
||||||
s.Enabled = viper.GetBool("plugins.enabled")
|
s.Enabled = viper.GetBool("plugins.enabled")
|
||||||
s.Dir = viper.GetString("plugins.dir")
|
s.Dir = viper.GetString("plugins.dir")
|
||||||
|
s.Required = viper.GetBool("plugins.required")
|
||||||
}
|
}
|
||||||
|
@ -15,88 +15,113 @@ type dependency struct {
|
|||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *dependency) findPlugin(name string) (*dependency, bool) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.plugin.Name() == name {
|
||||||
|
return a, true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dep := range a.dependsOn {
|
||||||
|
plug, ok := dep.findPlugin(name)
|
||||||
|
if ok {
|
||||||
|
return plug, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *dependency) startPlugin(pm types.PluginManagers) error {
|
||||||
|
if a.invoked {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
a.invoked = true
|
||||||
|
|
||||||
|
for _, do := range a.dependsOn {
|
||||||
|
if err := do.startPlugin(pm); err != nil {
|
||||||
|
return fmt.Errorf("plugin's '%s' dependency: %w", a.plugin.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.plugin.Start(pm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("plugin '%s' failed to start: %w", a.plugin.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.logger.Info().Str("plugin", a.plugin.Name()).Msg("plugin started")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type dependiencies struct {
|
type dependiencies struct {
|
||||||
deps map[string]*dependency
|
deps map[string]*dependency
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dependiencies) addPlugin(plugin types.Plugin) error {
|
func (d *dependiencies) addPlugin(plugin types.Plugin) error {
|
||||||
plug, ok := d.deps[plugin.Name()]
|
pluginName := plugin.Name()
|
||||||
|
|
||||||
|
plug, ok := d.deps[pluginName]
|
||||||
if !ok {
|
if !ok {
|
||||||
plug = &dependency{}
|
plug = &dependency{}
|
||||||
} else if plug.plugin != nil {
|
} else if plug.plugin != nil {
|
||||||
return fmt.Errorf("plugin '%s' already added", plugin.Name())
|
return fmt.Errorf("plugin '%s' already added", pluginName)
|
||||||
}
|
}
|
||||||
|
|
||||||
plug.plugin = plugin
|
plug.plugin = plugin
|
||||||
plug.logger = d.logger
|
plug.logger = d.logger
|
||||||
d.deps[plugin.Name()] = plug
|
d.deps[pluginName] = plug
|
||||||
|
|
||||||
dplug, ok := plugin.(types.DependablePlugin)
|
dplug, ok := plugin.(types.DependablePlugin)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dep := range dplug.DependsOn() {
|
for _, depName := range dplug.DependsOn() {
|
||||||
var dependsOn *dependency
|
dependsOn, ok := d.deps[depName]
|
||||||
dependsOn, ok = d.deps[dep]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
dependsOn = &dependency{}
|
dependsOn = &dependency{}
|
||||||
} else if dependsOn.plugin != nil {
|
} else if dependsOn.plugin != nil {
|
||||||
// if there is a cyclical dependency, break it and return error
|
// if there is a cyclical dependency, break it and return error
|
||||||
if tdep := dependsOn.findPlugin(plugin.Name()); tdep != nil {
|
if tdep, ok := dependsOn.findPlugin(pluginName); ok {
|
||||||
dependsOn.dependsOn = nil
|
dependsOn.dependsOn = nil
|
||||||
delete(d.deps, plugin.Name())
|
delete(d.deps, pluginName)
|
||||||
return fmt.Errorf("cyclical dependency detected: '%s' <-> '%s'", plugin.Name(), tdep.plugin.Name())
|
return fmt.Errorf("cyclical dependency detected: '%s' <-> '%s'", pluginName, tdep.plugin.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plug.dependsOn = append(plug.dependsOn, dependsOn)
|
plug.dependsOn = append(plug.dependsOn, dependsOn)
|
||||||
d.deps[dep] = dependsOn
|
d.deps[depName] = dependsOn
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dependiencies) findPlugin(name string) (*dependency, bool) {
|
||||||
|
for _, dep := range d.deps {
|
||||||
|
plug, ok := dep.findPlugin(name)
|
||||||
|
if ok {
|
||||||
|
return plug, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
func (d *dependiencies) start(pm types.PluginManagers) error {
|
func (d *dependiencies) start(pm types.PluginManagers) error {
|
||||||
for _, p := range d.deps {
|
for _, dep := range d.deps {
|
||||||
if err := p.start(pm); err != nil {
|
if err := dep.startPlugin(pm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dependiencies) findPlugin(name string) (*dependency, bool) {
|
|
||||||
for _, p := range d.deps {
|
|
||||||
if found := p.findPlugin(name); found != nil {
|
|
||||||
return found, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *dependency) findPlugin(name string) *dependency {
|
|
||||||
if a == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.plugin.Name() == name {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range a.dependsOn {
|
|
||||||
if found := p.findPlugin(name); found != nil {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dependiencies) forEach(f func(*dependency) error) error {
|
func (d *dependiencies) forEach(f func(*dependency) error) error {
|
||||||
for _, dp := range d.deps {
|
for _, dep := range d.deps {
|
||||||
if err := f(dp); err != nil {
|
if err := f(dep); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,25 +131,3 @@ func (d *dependiencies) forEach(f func(*dependency) error) error {
|
|||||||
func (d *dependiencies) len() int {
|
func (d *dependiencies) len() int {
|
||||||
return len(d.deps)
|
return len(d.deps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *dependency) start(pm types.PluginManagers) error {
|
|
||||||
if a.invoked {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
a.invoked = true
|
|
||||||
|
|
||||||
for _, do := range a.dependsOn {
|
|
||||||
if err := do.start(pm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := a.plugin.Start(pm)
|
|
||||||
a.logger.Err(err).Str("plugin", a.plugin.Name()).Msg("plugin start")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("plugin %s failed to start: %s", a.plugin.Name(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -16,12 +16,14 @@ import (
|
|||||||
|
|
||||||
type ManagerCtx struct {
|
type ManagerCtx struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
|
config *config.Plugins
|
||||||
plugins dependiencies
|
plugins dependiencies
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config *config.Plugins) *ManagerCtx {
|
func New(config *config.Plugins) *ManagerCtx {
|
||||||
manager := &ManagerCtx{
|
manager := &ManagerCtx{
|
||||||
logger: log.With().Str("module", "plugins").Logger(),
|
logger: log.With().Str("module", "plugins").Logger(),
|
||||||
|
config: config,
|
||||||
plugins: dependiencies{
|
plugins: dependiencies{
|
||||||
deps: make(map[string]*dependency),
|
deps: make(map[string]*dependency),
|
||||||
},
|
},
|
||||||
@ -31,7 +33,13 @@ func New(config *config.Plugins) *ManagerCtx {
|
|||||||
|
|
||||||
if config.Enabled {
|
if config.Enabled {
|
||||||
err := manager.loadDir(config.Dir)
|
err := manager.loadDir(config.Dir)
|
||||||
manager.logger.Err(err).Msgf("loading finished, total %d plugins", manager.plugins.len())
|
|
||||||
|
// only log error if plugin is not required
|
||||||
|
if err != nil && config.Required {
|
||||||
|
manager.logger.Fatal().Err(err).Msg("error loading plugins")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.logger.Info().Msgf("loading finished, total %d plugins", manager.plugins.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager
|
return manager
|
||||||
@ -48,6 +56,13 @@ func (manager *ManagerCtx) loadDir(dir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = manager.load(path)
|
err = manager.load(path)
|
||||||
|
|
||||||
|
// return error if plugin is required
|
||||||
|
if err != nil && manager.config.Required {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise only log error if plugin is not required
|
||||||
manager.logger.Err(err).Str("plugin", path).Msg("loading a plugin")
|
manager.logger.Err(err).Str("plugin", path).Msg("loading a plugin")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -70,25 +85,24 @@ func (manager *ManagerCtx) load(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = manager.plugins.addPlugin(p); err != nil {
|
if err = manager.plugins.addPlugin(p); err != nil {
|
||||||
return fmt.Errorf("failed to add plugin '%s': %w", p.Name(), err)
|
return fmt.Errorf("failed to add plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.logger.Info().Msgf("loaded plugin '%s', total %d plugins", p.Name(), manager.plugins.len())
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *ManagerCtx) InitConfigs(cmd *cobra.Command) {
|
func (manager *ManagerCtx) InitConfigs(cmd *cobra.Command) {
|
||||||
_ = manager.plugins.forEach(func(plug *dependency) error {
|
_ = manager.plugins.forEach(func(d *dependency) error {
|
||||||
if err := plug.plugin.Config().Init(cmd); err != nil {
|
if err := d.plugin.Config().Init(cmd); err != nil {
|
||||||
log.Err(err).Str("plugin", plug.plugin.Name()).Msg("unable to initialize configuration")
|
log.Err(err).Str("plugin", d.plugin.Name()).Msg("unable to initialize configuration")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *ManagerCtx) SetConfigs() {
|
func (manager *ManagerCtx) SetConfigs() {
|
||||||
_ = manager.plugins.forEach(func(plug *dependency) error {
|
_ = manager.plugins.forEach(func(d *dependency) error {
|
||||||
plug.plugin.Config().Set()
|
d.plugin.Config().Set()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -98,18 +112,26 @@ func (manager *ManagerCtx) Start(
|
|||||||
webSocketManager types.WebSocketManager,
|
webSocketManager types.WebSocketManager,
|
||||||
apiManager types.ApiManager,
|
apiManager types.ApiManager,
|
||||||
) {
|
) {
|
||||||
_ = manager.plugins.start(types.PluginManagers{
|
err := manager.plugins.start(types.PluginManagers{
|
||||||
SessionManager: sessionManager,
|
SessionManager: sessionManager,
|
||||||
WebSocketManager: webSocketManager,
|
WebSocketManager: webSocketManager,
|
||||||
ApiManager: apiManager,
|
ApiManager: apiManager,
|
||||||
LoadServiceFromPlugin: manager.LookupService,
|
LoadServiceFromPlugin: manager.LookupService,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if manager.config.Required {
|
||||||
|
manager.logger.Fatal().Err(err).Msg("failed to start plugins, exiting...")
|
||||||
|
} else {
|
||||||
|
manager.logger.Err(err).Msg("failed to start plugins, skipping...")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *ManagerCtx) Shutdown() error {
|
func (manager *ManagerCtx) Shutdown() error {
|
||||||
_ = manager.plugins.forEach(func(plug *dependency) error {
|
_ = manager.plugins.forEach(func(d *dependency) error {
|
||||||
err := plug.plugin.Shutdown()
|
err := d.plugin.Shutdown()
|
||||||
manager.logger.Err(err).Str("plugin", plug.plugin.Name()).Msg("plugin shutdown")
|
manager.logger.Err(err).Str("plugin", d.plugin.Name()).Msg("plugin shutdown")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@ -128,3 +150,28 @@ func (manager *ManagerCtx) LookupService(pluginName string) (any, error) {
|
|||||||
|
|
||||||
return expPlug.ExposeService(), nil
|
return expPlug.ExposeService(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (manager *ManagerCtx) Metadata() []types.PluginMetadata {
|
||||||
|
var plugins []types.PluginMetadata
|
||||||
|
|
||||||
|
_ = manager.plugins.forEach(func(d *dependency) error {
|
||||||
|
dependsOn := make([]string, 0)
|
||||||
|
deps, isDependalbe := d.plugin.(types.DependablePlugin)
|
||||||
|
if isDependalbe {
|
||||||
|
dependsOn = deps.DependsOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isExposable := d.plugin.(types.ExposablePlugin)
|
||||||
|
|
||||||
|
plugins = append(plugins, types.PluginMetadata{
|
||||||
|
Name: d.plugin.Name(),
|
||||||
|
IsDependable: isDependalbe,
|
||||||
|
IsExposable: isExposable,
|
||||||
|
DependsOn: dependsOn,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
}
|
||||||
|
@ -28,6 +28,13 @@ type PluginConfig interface {
|
|||||||
Set()
|
Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PluginMetadata struct {
|
||||||
|
Name string
|
||||||
|
IsDependable bool
|
||||||
|
IsExposable bool
|
||||||
|
DependsOn []string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type PluginManagers struct {
|
type PluginManagers struct {
|
||||||
SessionManager SessionManager
|
SessionManager SessionManager
|
||||||
WebSocketManager WebSocketManager
|
WebSocketManager WebSocketManager
|
||||||
|
Loading…
Reference in New Issue
Block a user