zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/server/config_reloader.go (about)

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