github.com/safing/portbase@v0.19.5/modules/subsystems/module.go (about) 1 package subsystems 2 3 import ( 4 "context" 5 "flag" 6 "fmt" 7 8 "github.com/safing/portbase/config" 9 "github.com/safing/portbase/modules" 10 "github.com/safing/portbase/runtime" 11 ) 12 13 const configChangeEvent = "config change" 14 15 var ( 16 // DefaultManager is the default subsystem registry. 17 DefaultManager *Manager 18 19 module *modules.Module 20 printGraphFlag bool 21 ) 22 23 // Register registers a new subsystem. It's like Manager.Register 24 // but uses DefaultManager and panics on error. 25 func Register(id, name, description string, module *modules.Module, configKeySpace string, option *config.Option) { 26 err := DefaultManager.Register(id, name, description, module, configKeySpace, option) 27 if err != nil { 28 panic(err) 29 } 30 } 31 32 func init() { 33 // The subsystem layer takes over module management. Note that 34 // no one must have called EnableModuleManagement. Otherwise 35 // the subsystem layer will silently fail managing module 36 // dependencies! 37 // TODO(ppacher): we SHOULD panic here! 38 // TASK(#1431) 39 40 modules.EnableModuleManagement(func(m *modules.Module) { 41 if DefaultManager == nil { 42 return 43 } 44 DefaultManager.handleModuleUpdate(m) 45 }) 46 47 module = modules.Register("subsystems", prep, start, nil, "config", "database", "runtime", "base") 48 module.Enable() 49 50 // TODO(ppacher): can we create the default registry during prep phase? 51 var err error 52 DefaultManager, err = NewManager(runtime.DefaultRegistry) 53 if err != nil { 54 panic("Failed to create default registry: " + err.Error()) 55 } 56 57 flag.BoolVar(&printGraphFlag, "print-subsystem-graph", false, "print the subsystem module dependency graph") 58 } 59 60 func prep() error { 61 if printGraphFlag { 62 DefaultManager.PrintGraph() 63 return modules.ErrCleanExit 64 } 65 66 // We need to listen for configuration changes so we can 67 // start/stop depended modules in case a subsystem is 68 // (de-)activated. 69 if err := module.RegisterEventHook( 70 "config", 71 configChangeEvent, 72 "control subsystems", 73 func(ctx context.Context, _ interface{}) error { 74 err := DefaultManager.CheckConfig(ctx) 75 if err != nil { 76 module.Error( 77 "modulemgmt-failed", 78 "A Module failed to start", 79 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), 80 ) 81 return nil 82 } 83 module.Resolve("modulemgmt-failed") 84 return nil 85 }, 86 ); err != nil { 87 return fmt.Errorf("register event hook: %w", err) 88 } 89 90 return nil 91 } 92 93 func start() error { 94 // Registration of subsystems is only allowed during 95 // preparation. Make sure any further call to Register() 96 // panics. 97 if err := DefaultManager.Start(); err != nil { 98 return err 99 } 100 101 module.StartWorker("initial subsystem configuration", DefaultManager.CheckConfig) 102 103 return nil 104 } 105 106 // PrintGraph prints the subsystem and module graph. 107 func (mng *Manager) PrintGraph() { 108 mng.l.RLock() 109 defer mng.l.RUnlock() 110 111 fmt.Println("subsystems dependency graph:") 112 113 // unmark subsystems module 114 module.Disable() 115 116 // mark roots 117 for _, sub := range mng.subsys { 118 sub.module.Enable() // mark as tree root 119 } 120 121 for _, sub := range mng.subsys { 122 printModuleGraph("", sub.module, true) 123 } 124 125 fmt.Println("\nsubsystem module groups:") 126 _ = start() // no errors for what we need here 127 for _, sub := range mng.subsys { 128 fmt.Printf("├── %s\n", sub.Name) 129 for _, mod := range sub.Modules[1:] { 130 fmt.Printf("│ ├── %s\n", mod.Name) 131 } 132 } 133 } 134 135 func printModuleGraph(prefix string, module *modules.Module, root bool) { 136 fmt.Printf("%s├── %s\n", prefix, module.Name) 137 if root || !module.Enabled() { 138 for _, dep := range module.Dependencies() { 139 printModuleGraph(fmt.Sprintf("│ %s", prefix), dep, false) 140 } 141 } 142 }