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  }