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 * setting defaults
* reading from JSON, TOML, and YAML config files * reading from JSON, TOML, and YAML config files
* live watching and re-reading of config files (optional)
* reading from environment variables * reading from environment variables
* reading from remote config systems (Etcd or Consul), and watching changes * reading from remote config systems (Etcd or Consul), and watching changes
* reading from command line flags * 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. 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 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 ```go
viper.SetConfigName("config") // name of config file (without extension) 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 ### Reading Config from io.Reader
Viper predefines many configuration sources such as files, environment Viper predefines many configuration sources such as files, environment
@ -286,15 +307,15 @@ runtime_viper.Unmarshal(&runtime_conf)
go func(){ go func(){
for { for {
time.Sleep(time.Second * 5) // delay after each request time.Sleep(time.Second * 5) // delay after each request
// currently, only tested with etcd support // currently, only tested with etcd support
err := runtime_viper.WatchRemoteConfig() err := runtime_viper.WatchRemoteConfig()
if err != nil { if err != nil {
log.Errorf("unable to read remote config: %v", err) log.Errorf("unable to read remote config: %v", err)
continue 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 // to implement a signal to notify the system of the changes
runtime_viper.Unmarshal(&runtime_conf) runtime_viper.Unmarshal(&runtime_conf)
} }

View file

@ -24,6 +24,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -35,6 +36,7 @@ import (
"github.com/spf13/cast" "github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"gopkg.in/fsnotify.v1"
) )
var v *Viper var v *Viper
@ -151,6 +153,8 @@ type Viper struct {
env map[string]string env map[string]string
aliases map[string]string aliases map[string]string
typeByDefValue bool typeByDefValue bool
onConfigChange func(fsnotify.Event)
} }
// Returns an initialized Viper instance. // Returns an initialized Viper instance.
@ -219,6 +223,52 @@ var SupportedExts []string = []string{"json", "toml", "yaml", "yml", "properties
// Universally supported remote providers. // Universally supported remote providers.
var SupportedRemoteProviders []string = []string{"etcd", "consul"} 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 // Explicitly define the path, name and extension of the config file
// Viper will use this and not check any of the config paths // Viper will use this and not check any of the config paths
func SetConfigFile(in string) { v.SetConfigFile(in) } func SetConfigFile(in string) { v.SetConfigFile(in) }