github.com/DataDog/viper@v1.13.3/watch_config.go (about)

     1  // +build !aix
     2  
     3  package viper
     4  
     5  import (
     6  	"log"
     7  	"path/filepath"
     8  	"sync"
     9  
    10  	"github.com/fsnotify/fsnotify"
    11  )
    12  
    13  func WatchConfig() { v.WatchConfig() }
    14  
    15  func (v *Viper) WatchConfig() {
    16  	initWG := sync.WaitGroup{}
    17  	initWG.Add(1)
    18  	go func() {
    19  		watcher, err := fsnotify.NewWatcher()
    20  		if err != nil {
    21  			log.Fatal(err)
    22  		}
    23  		defer watcher.Close()
    24  		// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
    25  		filename, err := v.getConfigFile()
    26  		if err != nil {
    27  			log.Printf("error: %v\n", err)
    28  			return
    29  		}
    30  
    31  		configFile := filepath.Clean(filename)
    32  		configDir, _ := filepath.Split(configFile)
    33  		realConfigFile, _ := filepath.EvalSymlinks(filename)
    34  
    35  		eventsWG := sync.WaitGroup{}
    36  		eventsWG.Add(1)
    37  		go func() {
    38  			for {
    39  				select {
    40  				case event, ok := <-watcher.Events:
    41  					if !ok { // 'Events' channel is closed
    42  						eventsWG.Done()
    43  						return
    44  					}
    45  					currentConfigFile, _ := filepath.EvalSymlinks(filename)
    46  					// we only care about the config file with the following cases:
    47  					// 1 - if the config file was modified or created
    48  					// 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)
    49  					const writeOrCreateMask = fsnotify.Write | fsnotify.Create
    50  					if (filepath.Clean(event.Name) == configFile &&
    51  						event.Op&writeOrCreateMask != 0) ||
    52  						(currentConfigFile != "" && currentConfigFile != realConfigFile) {
    53  						realConfigFile = currentConfigFile
    54  						err := v.ReadInConfig()
    55  						if err != nil {
    56  							log.Printf("error reading config file: %v\n", err)
    57  						}
    58  						if v.onConfigChange != nil {
    59  							v.onConfigChange(event)
    60  						}
    61  					} else if filepath.Clean(event.Name) == configFile &&
    62  						event.Op&fsnotify.Remove&fsnotify.Remove != 0 {
    63  						eventsWG.Done()
    64  						return
    65  					}
    66  
    67  				case err, ok := <-watcher.Errors:
    68  					if ok { // 'Errors' channel is not closed
    69  						log.Printf("watcher error: %v\n", err)
    70  					}
    71  					eventsWG.Done()
    72  					return
    73  				}
    74  			}
    75  		}()
    76  		watcher.Add(configDir)
    77  		initWG.Done()   // done initalizing the watch in this go routine, so the parent routine can move on...
    78  		eventsWG.Wait() // now, wait for event loop to end in this go-routine...
    79  	}()
    80  	initWG.Wait() // make sure that the go routine above fully ended before returning
    81  }