package subsystems import ( "context" "flag" "fmt" "github.com/safing/portbase/config" "github.com/safing/portbase/modules" "github.com/safing/portbase/runtime" ) const configChangeEvent = "config change" var ( // DefaultManager is the default subsystem registry. DefaultManager *Manager module *modules.Module printGraphFlag bool ) // Register registers a new subsystem. It's like Manager.Register // but uses DefaultManager and panics on error. func Register(id, name, description string, module *modules.Module, configKeySpace string, option *config.Option) { err := DefaultManager.Register(id, name, description, module, configKeySpace, option) if err != nil { panic(err) } } func init() { // The subsystem layer takes over module management. Note that // no one must have called EnableModuleManagement. Otherwise // the subsystem layer will silently fail managing module // dependencies! // TODO(ppacher): we SHOULD panic here! // TASK(#1431) modules.EnableModuleManagement(func(m *modules.Module) { if DefaultManager == nil { return } DefaultManager.handleModuleUpdate(m) }) module = modules.Register("subsystems", prep, start, nil, "config", "database", "runtime", "base") module.Enable() // TODO(ppacher): can we create the default registry during prep phase? var err error DefaultManager, err = NewManager(runtime.DefaultRegistry) if err != nil { panic("Failed to create default registry: " + err.Error()) } flag.BoolVar(&printGraphFlag, "print-subsystem-graph", false, "print the subsystem module dependency graph") } func prep() error { if printGraphFlag { DefaultManager.PrintGraph() return modules.ErrCleanExit } // We need to listen for configuration changes so we can // start/stop depended modules in case a subsystem is // (de-)activated. if err := module.RegisterEventHook( "config", configChangeEvent, "control subsystems", func(ctx context.Context, _ interface{}) error { err := DefaultManager.CheckConfig(ctx) if err != nil { module.Error( "modulemgmt-failed", "A Module failed to start", fmt.Sprintf("The subsystem framework failed to start or stop one or more modules.\nError: %s\nCheck logs for more information or try to restart.", err), ) return nil } module.Resolve("modulemgmt-failed") return nil }, ); err != nil { return fmt.Errorf("register event hook: %w", err) } return nil } func start() error { // Registration of subsystems is only allowed during // preparation. Make sure any further call to Register() // panics. if err := DefaultManager.Start(); err != nil { return err } module.StartWorker("initial subsystem configuration", DefaultManager.CheckConfig) return nil } // PrintGraph prints the subsystem and module graph. func (mng *Manager) PrintGraph() { mng.l.RLock() defer mng.l.RUnlock() fmt.Println("subsystems dependency graph:") // unmark subsystems module module.Disable() // mark roots for _, sub := range mng.subsys { sub.module.Enable() // mark as tree root } for _, sub := range mng.subsys { printModuleGraph("", sub.module, true) } fmt.Println("\nsubsystem module groups:") _ = start() // no errors for what we need here for _, sub := range mng.subsys { fmt.Printf("├── %s\n", sub.Name) for _, mod := range sub.Modules[1:] { fmt.Printf("│ ├── %s\n", mod.Name) } } } func printModuleGraph(prefix string, module *modules.Module, root bool) { fmt.Printf("%s├── %s\n", prefix, module.Name) if root || !module.Enabled() { for _, dep := range module.Dependencies() { printModuleGraph(fmt.Sprintf("│ %s", prefix), dep, false) } } }