Add dynamic reading of config file support
diff --git a/README.md b/README.md
index a8915e6..681bf76 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@
* 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 @@
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 @@
}
```
+### 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 @@
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 @@
"fmt"
"io"
"io/ioutil"
+ "log"
"os"
"path/filepath"
"reflect"
@@ -35,6 +36,7 @@
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
+ "gopkg.in/fsnotify.v1"
)
var v *Viper
@@ -151,6 +153,8 @@
env map[string]string
aliases map[string]string
typeByDefValue bool
+
+ onConfigChange func(fsnotify.Event)
}
// Returns an initialized Viper instance.
@@ -219,6 +223,52 @@
// 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) }