diff --git a/go.mod b/go.mod index 658841d5a..3bf6d9dd9 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect diff --git a/go.sum b/go.sum index dae0d27c0..145180568 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/garyburd/redigo v1.6.4 h1:LFu2R3+ZOPgSMWMOL+saa/zXRjw0ID2G8FepO53BGlg= github.com/garyburd/redigo v1.6.4/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw= github.com/getsentry/sentry-go v0.20.0 h1:bwXW98iMRIWxn+4FgPW7vMrjmbym6HblXALmhjHmQaQ= @@ -486,6 +488,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/config/config.go b/internal/config/config.go index 4a1013e4a..073d92db1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,8 +8,10 @@ import ( "path/filepath" "reflect" "strings" + "time" env "github.com/Netflix/go-env" + "github.com/fsnotify/fsnotify" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" ) @@ -53,6 +55,71 @@ func getConfigPaths() []string { return computedConfigPaths } +func WatchChanges(callback func()) (*fsnotify.Watcher, error) { + configPaths := getConfigPaths() + + w, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + // Start listening for events + go func() { + reload := false + for { + select { + // Read from errors + case err, ok := <-w.Errors: + if !ok { + // Channel was closed (i.e. Watcher.Close() was called) + return + } + log.WithError(err).Warning("failed to watch for file changes") + // Read from events + case ev, ok := <-w.Events: + if !ok { + // Channel was closed (i.e. Watcher.Close() was called) + return + } + + // Ignores files we're not interested in + // We get the whole list again in case a file in a directory we're watching was created + // and that file is of interest + for _, f := range getConfigPaths() { + if f == ev.Name { + reload = true + log.Debugf("%s file change, setting configuration to be reloaded", ev.Name) + } + } + default: + if reload { + log.Infof("configuration files changed, reloading configuration") + Reload() + callback() + // Cooldown after configuration reloading + time.Sleep(14 * time.Second) + } + // Otherwise `select` fires all of the time + time.Sleep(1 * time.Second) + reload = false + } + } + }() + + // Register the files to be watch + for _, p := range configPaths { + // Actually, watch the directory, not the files, as that's how fsnotify works + // We assume we're only getting valid files as getConfigPaths already handles all the necessary checks + err = w.Add(filepath.Dir(p)) + log.Warningf("watching for changes for file %s in dir %s", p, filepath.Dir(p)) + if err != nil { + return w, err + } + } + + return w, nil +} + func Get() *Config { if cfg == nil { c := &Config{} @@ -62,6 +129,12 @@ func Get() *Config { return cfg } +func Reload() { + c := &Config{} + c.Setup(getConfigPaths()...) + cfg = c +} + func (c *Config) Setup(paths ...string) { for _, path := range paths { err := c.LoadConfig(path)