diff --git a/README.md b/README.md index a8915e6..681bf76 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ and formats. It supports: * setting defaults * reading from JSON, TOML, and YAML config files +* live watching and re-reading of config files (optional) * reading from environment variables * reading from remote config systems (Etcd or Consul), and watching changes * reading from command line flags @@ -78,7 +79,7 @@ to an application. Here is an example of how to use Viper to search for and read a configuration file. None of the specific paths are required, but at least one path should be provided -where a configuration file is expected. +where a configuration file is expected. ```go viper.SetConfigName("config") // name of config file (without extension) @@ -91,6 +92,26 @@ if err != nil { // Handle errors reading the config file } ``` +### Watching and re-reading config files + +Viper supports the ability to have your application live read a config file while running. + +Gone are the days of needing to restart a server to have a config take effect, +viper powered applications can read an update to a config file while running and +not miss a beat. + +Simply tell the viper instance to watchConfig. +Optionally you can provide a function for Viper to run each time a change occurs. + +**Make sure you add all of the configPaths prior to calling `WatchConfig()`** + +```go + viper.WatchConfig() + viper.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) + }) +``` + ### Reading Config from io.Reader Viper predefines many configuration sources such as files, environment @@ -286,15 +307,15 @@ runtime_viper.Unmarshal(&runtime_conf) go func(){ for { time.Sleep(time.Second * 5) // delay after each request - + // currently, only tested with etcd support err := runtime_viper.WatchRemoteConfig() if err != nil { log.Errorf("unable to read remote config: %v", err) continue } - - // unmarshal new config into our runtime config struct. you can also use channel + + // unmarshal new config into our runtime config struct. you can also use channel // to implement a signal to notify the system of the changes runtime_viper.Unmarshal(&runtime_conf) } diff --git a/viper.go b/viper.go index af6e5ed..20c12ec 100644 --- a/viper.go +++ b/viper.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "os" "path/filepath" "reflect" @@ -35,6 +36,7 @@ import ( "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" + "gopkg.in/fsnotify.v1" ) var v *Viper @@ -151,6 +153,8 @@ type Viper struct { env map[string]string aliases map[string]string typeByDefValue bool + + onConfigChange func(fsnotify.Event) } // Returns an initialized Viper instance. @@ -219,6 +223,52 @@ var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties // Universally supported remote providers. var SupportedRemoteProviders []string = []string{"etcd", "consul"} +func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) } +func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) { + v.onConfigChange = run +} + +func WatchConfig() { v.WatchConfig() } +func (v *Viper) WatchConfig() { + go func() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event := <-watcher.Events: + if event.Op&fsnotify.Write == fsnotify.Write { + err := v.ReadInConfig() + if err != nil { + log.Println("error:", err) + } + v.onConfigChange(event) + } + case err := <-watcher.Errors: + log.Println("error:", err) + } + } + }() + + if v.configFile != "" { + watcher.Add(v.configFile) + } else { + for _, x := range v.configPaths { + err = watcher.Add(x) + if err != nil { + log.Fatal(err) + } + } + } + <-done + }() +} + // Explicitly define the path, name and extension of the config file // Viper will use this and not check any of the config paths func SetConfigFile(in string) { v.SetConfigFile(in) }