github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/connection/connection_watcher.go (about)

     1  package connection
     2  
     3  import (
     4  	"context"
     5  	"log"
     6  
     7  	"github.com/fsnotify/fsnotify"
     8  	filehelpers "github.com/turbot/go-kit/files"
     9  	"github.com/turbot/go-kit/filewatcher"
    10  	"github.com/turbot/go-kit/helpers"
    11  	"github.com/turbot/steampipe/pkg/cmdconfig"
    12  	"github.com/turbot/steampipe/pkg/constants"
    13  	"github.com/turbot/steampipe/pkg/filepaths"
    14  	"github.com/turbot/steampipe/pkg/steampipeconfig"
    15  )
    16  
    17  type ConnectionWatcher struct {
    18  	fileWatcherErrorHandler func(error)
    19  	watcher                 *filewatcher.FileWatcher
    20  	// interface exposing the plugin manager functions we need
    21  	pluginManager pluginManager
    22  }
    23  
    24  func NewConnectionWatcher(pluginManager pluginManager) (*ConnectionWatcher, error) {
    25  	w := &ConnectionWatcher{
    26  		pluginManager: pluginManager,
    27  	}
    28  
    29  	watcherOptions := &filewatcher.WatcherOptions{
    30  		Directories: []string{filepaths.EnsureConfigDir()},
    31  		Include:     filehelpers.InclusionsFromExtensions([]string{constants.ConfigExtension}),
    32  		ListFlag:    filehelpers.FilesRecursive,
    33  		EventMask:   fsnotify.Create | fsnotify.Remove | fsnotify.Rename | fsnotify.Write | fsnotify.Chmod,
    34  		OnChange: func(events []fsnotify.Event) {
    35  			w.handleFileWatcherEvent(events)
    36  		},
    37  	}
    38  	watcher, err := filewatcher.NewWatcher(watcherOptions)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	w.watcher = watcher
    43  
    44  	// set the file watcher error handler, which will get called when there are parsing errors
    45  	// after a file watcher event
    46  	w.fileWatcherErrorHandler = func(err error) {
    47  		log.Printf("[WARN] failed to reload connection config: %s", err.Error())
    48  	}
    49  
    50  	watcher.Start()
    51  
    52  	log.Printf("[INFO] created ConnectionWatcher")
    53  	return w, nil
    54  }
    55  
    56  func (w *ConnectionWatcher) handleFileWatcherEvent([]fsnotify.Event) {
    57  	defer func() {
    58  		if r := recover(); r != nil {
    59  			log.Printf("[WARN] ConnectionWatcher caught a panic: %s", helpers.ToError(r).Error())
    60  		}
    61  	}()
    62  	// this is a file system event handler and not bound to any context
    63  	ctx := context.Background()
    64  
    65  	log.Printf("[INFO] ConnectionWatcher handleFileWatcherEvent")
    66  	config, errorsAndWarnings := steampipeconfig.LoadConnectionConfig(context.Background())
    67  	// send notification if there were any errors or warnings
    68  	if !errorsAndWarnings.Empty() {
    69  		w.pluginManager.SendPostgresErrorsAndWarningsNotification(ctx, errorsAndWarnings)
    70  		// if there was an error return
    71  		if errorsAndWarnings.GetError() != nil {
    72  			log.Printf("[WARN] error loading updated connection config: %v", errorsAndWarnings.GetError())
    73  			return
    74  		}
    75  	}
    76  
    77  	log.Printf("[INFO] loaded updated config")
    78  
    79  	// We need to update the viper config and GlobalConfig
    80  	// as these are both used by RefreshConnectionAndSearchPathsWithLocalClient
    81  
    82  	// set the global steampipe config
    83  	steampipeconfig.GlobalConfig = config
    84  
    85  	// call on changed callback - we must call this BEFORE calling refresh connections
    86  	// convert config to format expected by plugin manager
    87  	// (plugin manager cannot reference steampipe config to avoid circular deps)
    88  	configMap := NewConnectionConfigMap(config.Connections)
    89  	w.pluginManager.OnConnectionConfigChanged(ctx, configMap, config.PluginsInstances)
    90  
    91  	// The only configurations from GlobalConfig which have
    92  	// impact during Refresh are Database options and the Connections
    93  	// themselves.
    94  	//
    95  	// It is safe to ignore the Workspace Profile here since this
    96  	// code runs in the plugin-manager and has been started with the
    97  	// install-dir properly set from the active Workspace Profile
    98  	//
    99  	// Workspace Profile does not have any setting which can alter
   100  	// behavior in service mode (namely search path). Therefore, it is safe
   101  	// to use the GlobalConfig here and ignore Workspace Profile in general
   102  	cmdconfig.SetDefaultsFromConfig(steampipeconfig.GlobalConfig.ConfigMap())
   103  
   104  	log.Printf("[INFO] calling RefreshConnections asyncronously")
   105  
   106  	// call RefreshConnections asyncronously
   107  	// the RefreshConnections implements its own locking to ensure only a single execution and a single queues execution
   108  	go RefreshConnections(ctx, w.pluginManager)
   109  
   110  	log.Printf("[TRACE] File watch event done")
   111  }
   112  
   113  func (w *ConnectionWatcher) Close() {
   114  	w.watcher.Close()
   115  }