Add dynamic reading of config file support

This commit is contained in:
spf13 2015-11-09 23:22:04 -05:00
parent c374c6d0a9
commit e37b56e207
2 changed files with 75 additions and 4 deletions

View file

@ -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)
}

View file

@ -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) }