From f5dc53e0f2b61813605b3848bc871325efba3eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Vall=C3=A9e?= Date: Tue, 12 Jan 2021 17:01:26 +0100 Subject: [PATCH] improve config file watcher to support k8s configmap reloads The config file watcher assumed a simple configuration where the existing configuration file is overwritten by a text editor. In practice this does not detect some more complex configuration change scenarios such as : * The configuration file is behind a symlink and the symlink is updated * The configuration file is behind a double symlink (k8s configmap update) * The configuration file is not updated but removed the re-created In order to fix these cases : * Watch the configuration file parent directory instead of the file itself, this lets us grab events event if the file was removed and a new file is used. * In addition to read/write event matching, watch for any change in the configuration file path. This handles the symlink case where the file itself hasn't changed but its location did v2: - apply gofmt --- internal/confwatcher/confwatcher.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/internal/confwatcher/confwatcher.go b/internal/confwatcher/confwatcher.go index 23a7d884..23b4c1fb 100644 --- a/internal/confwatcher/confwatcher.go +++ b/internal/confwatcher/confwatcher.go @@ -14,7 +14,8 @@ const ( // ConfWatcher is a configuration file watcher. type ConfWatcher struct { - inner *fsnotify.Watcher + inner *fsnotify.Watcher + watchedPath string // out signal chan struct{} @@ -30,17 +31,19 @@ func New(confPath string) (*ConfWatcher, error) { // use absolute path to support Darwin absolutePath, _ := filepath.Abs(confPath) + parentPath := filepath.Dir(absolutePath) - err = inner.Add(absolutePath) + err = inner.Add(parentPath) if err != nil { inner.Close() return nil, err } w := &ConfWatcher{ - inner: inner, - signal: make(chan struct{}), - done: make(chan struct{}), + inner: inner, + watchedPath: absolutePath, + signal: make(chan struct{}), + done: make(chan struct{}), } go w.run() @@ -61,6 +64,7 @@ func (w *ConfWatcher) run() { defer close(w.done) var lastCalled time.Time + previousWatchedPath, _ := filepath.EvalSymlinks(w.watchedPath) outer: for { @@ -70,10 +74,19 @@ outer: continue } - if (event.Op&fsnotify.Write) == fsnotify.Write || - (event.Op&fsnotify.Create) == fsnotify.Create { + currentWatchedPath, _ := filepath.EvalSymlinks(w.watchedPath) + eventPath, _ := filepath.Abs(event.Name) + + if currentWatchedPath == "" { + // Watched file was removed wait for write event to trigger reload + previousWatchedPath = "" + } else if currentWatchedPath != previousWatchedPath || + (eventPath == currentWatchedPath && + ((event.Op&fsnotify.Write) == fsnotify.Write || + (event.Op&fsnotify.Create) == fsnotify.Create)) { // wait some additional time to allow the writer to complete its job time.Sleep(additionalWait) + previousWatchedPath = currentWatchedPath lastCalled = time.Now() w.signal <- struct{}{}