2022-04-16 07:28:00 +12:00
|
|
|
package plugins
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"plugin"
|
|
|
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
2022-07-14 10:58:22 +12:00
|
|
|
"github.com/demodesk/neko/internal/config"
|
|
|
|
"github.com/demodesk/neko/pkg/types"
|
2022-04-16 07:28:00 +12:00
|
|
|
)
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
type ManagerCtx struct {
|
2022-04-16 07:28:00 +12:00
|
|
|
logger zerolog.Logger
|
2023-03-31 23:02:33 +13:00
|
|
|
config *config.Plugins
|
2022-05-03 23:17:04 +12:00
|
|
|
plugins dependiencies
|
2022-04-16 07:28:00 +12:00
|
|
|
}
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
func New(config *config.Plugins) *ManagerCtx {
|
|
|
|
manager := &ManagerCtx{
|
2022-05-03 23:17:04 +12:00
|
|
|
logger: log.With().Str("module", "plugins").Logger(),
|
2023-03-31 23:02:33 +13:00
|
|
|
config: config,
|
2022-05-03 23:17:04 +12:00
|
|
|
plugins: dependiencies{
|
|
|
|
deps: make(map[string]*dependency),
|
|
|
|
},
|
2022-04-16 07:28:00 +12:00
|
|
|
}
|
|
|
|
|
2022-05-03 23:17:04 +12:00
|
|
|
manager.plugins.logger = manager.logger
|
|
|
|
|
2022-04-16 07:28:00 +12:00
|
|
|
if config.Enabled {
|
|
|
|
err := manager.loadDir(config.Dir)
|
2023-03-31 23:02:33 +13:00
|
|
|
|
|
|
|
// 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())
|
2022-04-16 07:28:00 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
return manager
|
|
|
|
}
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
func (manager *ManagerCtx) loadDir(dir string) error {
|
2022-04-16 07:28:00 +12:00
|
|
|
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err = manager.load(path)
|
2023-03-31 23:02:33 +13:00
|
|
|
|
|
|
|
// return error if plugin is required
|
|
|
|
if err != nil && manager.config.Required {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise only log error if plugin is not required
|
2022-04-16 07:28:00 +12:00
|
|
|
manager.logger.Err(err).Str("plugin", path).Msg("loading a plugin")
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
func (manager *ManagerCtx) load(path string) error {
|
2022-04-16 07:28:00 +12:00
|
|
|
pl, err := plugin.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
sym, err := pl.Lookup("Plugin")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
p, ok := sym.(types.Plugin)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("not a valid plugin")
|
|
|
|
}
|
|
|
|
|
2022-05-03 23:17:04 +12:00
|
|
|
if err = manager.plugins.addPlugin(p); err != nil {
|
2023-03-31 23:02:33 +13:00
|
|
|
return fmt.Errorf("failed to add plugin: %w", err)
|
2022-04-19 22:14:59 +12:00
|
|
|
}
|
|
|
|
|
2022-04-16 07:28:00 +12:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
func (manager *ManagerCtx) InitConfigs(cmd *cobra.Command) {
|
2023-03-31 23:02:33 +13:00
|
|
|
_ = manager.plugins.forEach(func(d *dependency) error {
|
|
|
|
if err := d.plugin.Config().Init(cmd); err != nil {
|
|
|
|
log.Err(err).Str("plugin", d.plugin.Name()).Msg("unable to initialize configuration")
|
2022-04-16 07:28:00 +12:00
|
|
|
}
|
2022-05-03 23:17:04 +12:00
|
|
|
return nil
|
|
|
|
})
|
2022-04-16 07:28:00 +12:00
|
|
|
}
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
func (manager *ManagerCtx) SetConfigs() {
|
2023-03-31 23:02:33 +13:00
|
|
|
_ = manager.plugins.forEach(func(d *dependency) error {
|
|
|
|
d.plugin.Config().Set()
|
2022-05-03 23:17:04 +12:00
|
|
|
return nil
|
|
|
|
})
|
2022-04-16 07:28:00 +12:00
|
|
|
}
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
func (manager *ManagerCtx) Start(
|
2022-04-16 07:28:00 +12:00
|
|
|
sessionManager types.SessionManager,
|
|
|
|
webSocketManager types.WebSocketManager,
|
|
|
|
apiManager types.ApiManager,
|
|
|
|
) {
|
2023-03-31 23:02:33 +13:00
|
|
|
err := manager.plugins.start(types.PluginManagers{
|
2022-05-03 23:17:04 +12:00
|
|
|
SessionManager: sessionManager,
|
|
|
|
WebSocketManager: webSocketManager,
|
|
|
|
ApiManager: apiManager,
|
|
|
|
LoadServiceFromPlugin: manager.LookupService,
|
|
|
|
})
|
2023-03-31 23:02:33 +13:00
|
|
|
|
|
|
|
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...")
|
|
|
|
}
|
|
|
|
}
|
2022-04-16 07:28:00 +12:00
|
|
|
}
|
|
|
|
|
2022-04-19 22:14:59 +12:00
|
|
|
func (manager *ManagerCtx) Shutdown() error {
|
2023-03-31 23:02:33 +13:00
|
|
|
_ = manager.plugins.forEach(func(d *dependency) error {
|
|
|
|
err := d.plugin.Shutdown()
|
|
|
|
manager.logger.Err(err).Str("plugin", d.plugin.Name()).Msg("plugin shutdown")
|
2022-05-03 23:17:04 +12:00
|
|
|
return nil
|
|
|
|
})
|
2022-04-16 07:28:00 +12:00
|
|
|
return nil
|
|
|
|
}
|
2022-04-19 22:14:59 +12:00
|
|
|
|
|
|
|
func (manager *ManagerCtx) LookupService(pluginName string) (any, error) {
|
2022-05-03 23:17:04 +12:00
|
|
|
plug, ok := manager.plugins.findPlugin(pluginName)
|
2022-04-19 22:14:59 +12:00
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("plugin '%s' not found", pluginName)
|
|
|
|
}
|
|
|
|
|
2022-05-03 23:17:04 +12:00
|
|
|
expPlug, ok := plug.plugin.(types.ExposablePlugin)
|
2022-04-19 22:14:59 +12:00
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("plugin '%s' is not exposable", pluginName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return expPlug.ExposeService(), nil
|
|
|
|
}
|
2023-03-31 23:02:33 +13:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|