zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/cli/server/config_reloader.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"os/signal"
     7  	"syscall"
     8  
     9  	"github.com/fsnotify/fsnotify"
    10  	"github.com/rs/zerolog/log"
    11  
    12  	"zotregistry.io/zot/pkg/api"
    13  	"zotregistry.io/zot/pkg/api/config"
    14  )
    15  
    16  type HotReloader struct {
    17  	watcher  *fsnotify.Watcher
    18  	filePath string
    19  	ctlr     *api.Controller
    20  }
    21  
    22  func NewHotReloader(ctlr *api.Controller, filePath string) (*HotReloader, error) {
    23  	// creates a new file watcher
    24  	watcher, err := fsnotify.NewWatcher()
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	hotReloader := &HotReloader{
    30  		watcher:  watcher,
    31  		filePath: filePath,
    32  		ctlr:     ctlr,
    33  	}
    34  
    35  	return hotReloader, nil
    36  }
    37  
    38  func signalHandler(ctlr *api.Controller, sigCh chan os.Signal, ctx context.Context, cancel context.CancelFunc) {
    39  	select {
    40  	// if signal then shutdown
    41  	case sig := <-sigCh:
    42  		defer cancel()
    43  
    44  		ctlr.Log.Info().Interface("signal", sig).Msg("received signal")
    45  
    46  		// gracefully shutdown http server
    47  		ctlr.Shutdown() //nolint: contextcheck
    48  
    49  		close(sigCh)
    50  	// if reload then return
    51  	case <-ctx.Done():
    52  		return
    53  	}
    54  }
    55  
    56  func initShutDownRoutine(ctlr *api.Controller, ctx context.Context, cancel context.CancelFunc) {
    57  	sigCh := make(chan os.Signal, 1)
    58  
    59  	go signalHandler(ctlr, sigCh, ctx, cancel)
    60  
    61  	// block all async signals to this server
    62  	signal.Ignore()
    63  
    64  	// handle SIGINT and SIGHUP.
    65  	signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
    66  }
    67  
    68  func (hr *HotReloader) Start() context.Context {
    69  	reloadCtx, cancelFunc := context.WithCancel(context.Background())
    70  
    71  	done := make(chan bool)
    72  
    73  	initShutDownRoutine(hr.ctlr, reloadCtx, cancelFunc)
    74  
    75  	// run watcher
    76  	go func() {
    77  		defer hr.watcher.Close()
    78  
    79  		go func() {
    80  			for {
    81  				select {
    82  				// watch for events
    83  				case event := <-hr.watcher.Events:
    84  					if event.Op == fsnotify.Write {
    85  						log.Info().Msg("config file changed, trying to reload config")
    86  
    87  						newConfig := config.New()
    88  
    89  						err := LoadConfiguration(newConfig, hr.filePath)
    90  						if err != nil {
    91  							log.Error().Err(err).Msg("couldn't reload config, retry writing it.")
    92  
    93  							continue
    94  						}
    95  						// if valid config then reload
    96  						cancelFunc()
    97  
    98  						// create new context
    99  						reloadCtx, cancelFunc = context.WithCancel(context.Background())
   100  
   101  						// init shutdown routine
   102  						initShutDownRoutine(hr.ctlr, reloadCtx, cancelFunc)
   103  
   104  						hr.ctlr.LoadNewConfig(reloadCtx, newConfig)
   105  					}
   106  				// watch for errors
   107  				case err := <-hr.watcher.Errors:
   108  					log.Panic().Err(err).Str("config", hr.filePath).Msg("fsnotfy error while watching config")
   109  				}
   110  			}
   111  		}()
   112  
   113  		if err := hr.watcher.Add(hr.filePath); err != nil {
   114  			log.Panic().Err(err).Str("config", hr.filePath).Msg("error adding config file to FsNotify watcher")
   115  		}
   116  
   117  		<-done
   118  	}()
   119  
   120  	return reloadCtx
   121  }