Compare commits

..
35 Commits
Author SHA1 Message Date
Vivek VandMárk Sági-Kazár 72b022eb35 Added capacity to slice creation 2019-11-01 11:15:09 +01:00
Navid ShaikhandMárk Sági-Kazár 40e41dd224 Updates YAML to v2.2.4
Make sure we're not vulnerable to CVE-2019-11253.
See https://github.com/kubernetes/kubernetes/issues/83253
2019-10-16 10:29:20 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 71509d2887 Revert the effective changes of #673 2019-09-28 12:02:57 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 398adc5a7d Add breaking test that requires reverting #673 2019-09-28 12:02:57 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 371f39c3ab Add Go 1.13 to travis 2019-09-27 23:35:02 +02:00
Mark Sagi-KazarandMárk Sági-Kazár c171232d3a Add go version to the mod file 2019-09-27 23:35:02 +02:00
Lars LehtonenandMárk Sági-Kazár 583f79b3ea remote: fix two dropped errors 2019-09-27 21:42:20 +02:00
inkychrisandMárk Sági-Kazár 99520c81d8 Implemented ability to unmarshal keys containing dots to structs.
Changed formatting of test objects for better git diffing and readibility.
Fixed failing tests on Windows.
2019-09-11 16:03:08 +02:00
Mark Sagi-KazarandMárk Sági-Kazár bd1db6bb8c Run go mod tidy 2019-08-29 15:42:25 +02:00
TwiNandMárk Sági-Kazár e697d557b7 Fix small typo 2019-08-17 00:18:41 +02:00
Christian MuehlhaeuserandMárk Sági-Kazár 33bf76add3 Simplify code
Removed unnecessary conversions.
2019-07-29 18:37:00 +02:00
Christian MuehlhaeuserandMárk Sági-Kazár 1b33e8258e Add error handling
Added error handling around ineffectual err assignments. Please review
thoroughly.
2019-07-28 14:50:13 +02:00
Christian MuehlhaeuserandMárk Sági-Kazár d65fa7608b Fixed typo
Just a nitpicky typo fix.
2019-07-28 14:47:04 +02:00
Rodrigo ChiossiandBjørn Erik Pedersen cdccc8152c Fix SafeWriteConfig
If the config file does not exist and the force flag is not set,
OpenFile would not use O_CREATE flag, causing viper to fail with
error "File not exist" and to not create the config file.
This patch changes the behavior of writeConfig() so that if force is set
to false, OpenFile will use O_EXCL flag, thus failing if the file
already exists or creating a new file otherwise.

Signed-off-by: Rodrigo Chiossi <rodrigo.chiossi@intel.com>
2019-07-28 12:33:29 +02:00
bpizziandMárk Sági-Kazár e02bc9eca5 Fixed missing f.Close() in writeConfig()
Defering can cause trouble because we're writing to the file outside the function
where the defering is registered, calling f.Sync() ensures that bytes are flushed
to disk even is Close() is called too soon.
2019-07-13 12:28:02 +02:00
Nicolas MartinandMárk Sági-Kazár e325492b82 Add missing call to initWF.Done() 2019-07-13 12:13:30 +02:00
Márk Sági-Kazár e6d1c6bc9a Improve godoc 2019-07-13 12:11:16 +02:00
CodeLingo BotandMárk Sági-Kazár 275a36d0a0 Fix function comments based on best practices from Effective Go
Signed-off-by: CodeLingo Bot <bot@codelingo.io>
2019-07-13 12:11:16 +02:00
Márk Sági-Kazár 7fdad0204e Fix typo 2019-07-13 12:04:36 +02:00
AGirardandMárk Sági-Kazár b8221cf4ee Add GetIntSlice helper method 2019-07-13 12:04:36 +02:00
Márk Sági-Kazár 72cbe340cb Improve travis 2019-07-13 11:53:30 +02:00
lucperkinsandMárk Sági-Kazár 5ae3a072d1 Add documentation for no config file found
Signed-off-by: lucperkins <lucperkins@gmail.com>
2019-07-13 11:37:02 +02:00
Márk Sági-Kazár d34be8d9ee Improve BindEnv details 2019-07-13 11:35:03 +02:00
Bruce WangandMárk Sági-Kazár 96441b4b74 BindEnv details specified. Now less confusion. 2019-07-13 11:35:03 +02:00
victor23dandMárk Sági-Kazár 2b4f7d3cde Add dotenv documentation in README.md 2019-07-13 11:28:35 +02:00
Márk Sági-KazárandSteve Francia 3349bd9cc2 Improve Unmarshal docs (#713)
Closes #589
Closes  #588
2019-06-14 11:17:12 -04:00
okazu-dmandMárk Sági-Kazár 3b4aca7571 Fix example code
Unmarshal -> viper.Unmarshal
2019-06-13 10:51:37 +02:00
liubog2008andMárk Sági-Kazár b235f72abb fix(leak): fix leak in dependency magiconair/properities 2019-06-11 23:33:57 +02:00
AlphaandSteve Francia fde59dd3e4 ADD: installation method (#604)
* ADD: installation method

* Update README.md
2019-06-11 16:58:53 -04:00
Pawan RawalandSteve Francia 0c777cfac1 Fix indentation for yamlStringSettings (#651)
The indentation seemed messed up and didn't read well. This change fixes it.
2019-06-11 16:53:59 -04:00
Márk Sági-KazárandSteve Francia ad5ed02fa4 Add support for int slice flags (#637)
* Add support for int slice flags

* Add int slice test to unmarshal
2019-06-11 16:51:57 -04:00
EmilLutaandSteve Francia 0da4d41b2d Add WriteConfig documentation (#649) 2019-06-11 16:51:04 -04:00
0xflotusandSteve Francia a52795991d fixed marshal (#647) 2019-06-11 16:50:29 -04:00
Leonardo FedaltoandSteve Francia 33688bf23c Fix typo on README.md (#710) 2019-06-11 16:48:19 -04:00
mexismeandSteve Francia 3620d3d9e1 Support .env format files (#528)
* Support `.env` format files

* Missing "dotenv" from SupportedExtns
2019-06-11 16:47:43 -04:00
8 changed files with 409 additions and 67 deletions
+4 -3
View File
@@ -4,10 +4,13 @@ language: go
env:
global:
- GO111MODULE="on"
- GO111MODULE="on"
- GOFLAGS="-mod=readonly"
go:
- 1.11.x
- 1.12.x
- 1.13.x
- tip
os:
@@ -27,5 +30,3 @@ script:
after_success:
- go get -u -d github.com/spf13/hugo
- cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd -
sudo: false
+60 -17
View File
@@ -15,6 +15,10 @@ Many Go projects are built using Viper including:
[![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) [![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/spf13/viper?status.svg)](https://godoc.org/github.com/spf13/viper)
## Install
```console
go get -u github.com/spf13/viper
```
## What is Viper?
@@ -23,7 +27,7 @@ to work within an application, and can handle all types of configuration needs
and formats. It supports:
* setting defaults
* reading from JSON, TOML, YAML, HCL, and Java properties config files
* reading from JSON, TOML, YAML, HCL, envfile and Java properties 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
@@ -42,7 +46,7 @@ Viper is here to help with that.
Viper does the following for you:
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, or Java properties formats.
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, envfile or Java properties formats.
2. Provide a mechanism to set default values for your different
configuration options.
3. Provide a mechanism to set override values for options specified through
@@ -83,7 +87,7 @@ viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "cat
### Reading Config Files
Viper requires minimal configuration so it knows where to look for config files.
Viper supports JSON, TOML, YAML, HCL, and Java Properties files. Viper can search multiple paths, but
Viper supports JSON, TOML, YAML, HCL, envfile and Java Properties files. Viper can search multiple paths, but
currently a single Viper instance only supports a single configuration file.
Viper does not default to any configuration search paths leaving defaults decision
to an application.
@@ -103,6 +107,42 @@ if err != nil { // Handle errors reading the config file
}
```
You can handle the specific case where no config file is found like this:
```go
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
} else {
// Config file was found but another error was produced
}
}
// Config file found and successfully parsed
```
### Writing Config Files
Reading from config files is useful, but at times you want to store all modifications made at run time.
For that, a bunch of commands are available, each with its own purpose:
* WriteConfig - writes the current viper configuration to the predefined path, if exists. Errors if no predefined path. Will overwrite the current config file, if it exists.
* SafeWriteConfig - writes the current viper configuration to the predefined path. Errors if no predefined path. Will not overwrite the current config file, if it exists.
* WriteConfigAs - writes the current viper configuration to the given filepath. Will overwrite the given file, if it exists.
* SafeWriteConfigAs - writes the current viper configuration to the given filepath. Will not overwrite the given file, if it exists.
As a rule of the thumb, everything marked with safe won't overwrite any file, but just create if not existent, whilst the default behavior is to create or truncate.
A small examples section:
```go
viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName'
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written
viper.SafeWriteConfigAs("/path/to/my/.other_config")
```
### Watching and re-reading config files
Viper supports the ability to have your application live read a config file while running.
@@ -186,7 +226,7 @@ with ENV:
* `BindEnv(string...) : error`
* `SetEnvPrefix(string)`
* `SetEnvKeyReplacer(string...) *strings.Replacer`
* `AllowEmptyEnvVar(bool)`
* `AllowEmptyEnv(bool)`
_When working with ENV variables, its important to recognize that Viper
treats ENV variables as case sensitive._
@@ -199,9 +239,9 @@ prefix.
`BindEnv` takes one or two parameters. The first parameter is the key name, the
second is the name of the environment variable. The name of the environment
variable is case sensitive. If the ENV variable name is not provided, then
Viper will automatically assume that the key name matches the ENV variable name,
but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV
variable name, it **does not** automatically add the prefix.
Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter),
it **does not** automatically add the prefix. For example if the second parameter is "id",
Viper will look for the ENV variable "ID".
One important thing to recognize when working with ENV variables is that the
value will be read each time it is accessed. Viper does not fix the value when
@@ -346,7 +386,7 @@ package:
`import _ "github.com/spf13/viper/remote"`
Viper will read a config string (as JSON, TOML, YAML or HCL) retrieved from a path
Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path
in a Key/Value store such as etcd or Consul. These values take precedence over
default values, but are overridden by configuration values retrieved from disk,
flags, or environment variables.
@@ -381,7 +421,7 @@ how to use Consul.
#### etcd
```go
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()
```
@@ -409,7 +449,7 @@ fmt.Println(viper.Get("hostname")) // myhostname.com
```go
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()
```
@@ -420,7 +460,7 @@ err := viper.ReadRemoteConfig()
var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
// read from remote config the first time.
err := runtime_viper.ReadRemoteConfig()
@@ -456,6 +496,7 @@ The following functions and methods exist:
* `GetBool(key string) : bool`
* `GetFloat64(key string) : float64`
* `GetInt(key string) : int`
* `GetIntSlice(key string) : []int`
* `GetString(key string) : string`
* `GetStringMap(key string) : map[string]interface{}`
* `GetStringMapString(key string) : map[string]string`
@@ -611,15 +652,17 @@ type config struct {
var C config
err := Unmarshal(&C)
err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
```
Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default.
### Marshalling to string
You may need to marhsal all the settings held in viper into a string rather than write them to a file.
You may need to marshal all the settings held in viper into a string rather than write them to a file.
You can use your favorite format's marshaller with the config returned by `AllSettings()`.
```go
@@ -630,11 +673,11 @@ import (
func yamlStringSettings() string {
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
t.Fatalf("unable to marshal config to YAML: %v", err)
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
return string(bs)
}
```
+1 -1
View File
@@ -36,7 +36,7 @@ type pflagValue struct {
flag *pflag.Flag
}
// HasChanges returns whether the flag has changes or not.
// HasChanged returns whether the flag has changes or not.
func (p pflagValue) HasChanged() bool {
return p.flag.Changed
}
+5 -2
View File
@@ -1,5 +1,7 @@
module github.com/spf13/viper
go 1.12
require (
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
github.com/coreos/bbolt v1.3.2 // indirect
@@ -18,7 +20,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect
github.com/hashicorp/hcl v1.0.0
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/magiconair/properties v1.8.0
github.com/magiconair/properties v1.8.1
github.com/mitchellh/mapstructure v1.1.2
github.com/pelletier/go-toml v1.2.0
github.com/prometheus/client_golang v0.9.3 // indirect
@@ -28,6 +30,7 @@ require (
github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.2.2
github.com/subosito/gotenv v1.2.0
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
github.com/ugorji/go v1.1.4 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
@@ -39,5 +42,5 @@ require (
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
google.golang.org/grpc v1.21.0 // indirect
gopkg.in/yaml.v2 v2.2.2
gopkg.in/yaml.v2 v2.2.4
)
+6 -4
View File
@@ -71,8 +71,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
@@ -115,6 +115,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
@@ -173,6 +175,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+3 -2
View File
@@ -77,11 +77,12 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
var err error
if rp.SecretKeyring() != "" {
kr, err := os.Open(rp.SecretKeyring())
defer kr.Close()
var kr *os.File
kr, err = os.Open(rp.SecretKeyring())
if err != nil {
return nil, err
}
defer kr.Close()
if rp.Provider() == "etcd" {
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
} else {
+64 -20
View File
@@ -3,7 +3,7 @@
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// Viper is a application configuration system.
// Viper is an application configuration system.
// It believes that applications can be configured a variety of ways
// via flags, ENVIRONMENT variables, configuration files retrieved
// from the file system, or a remote key/value store.
@@ -45,6 +45,7 @@ import (
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
"github.com/subosito/gotenv"
)
// ConfigMarshalError happens when failing to marshal the configuration.
@@ -225,12 +226,12 @@ func New() *Viper {
return v
}
// Intended for testing, will reset all to default settings.
// Reset is intended for testing, will reset all to default settings.
// In the public interface for the viper package so applications
// can use it in their testing as well.
func Reset() {
v = New()
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env"}
SupportedRemoteProviders = []string{"etcd", "consul"}
}
@@ -269,7 +270,7 @@ type RemoteProvider interface {
}
// SupportedExts are universally supported extensions.
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"}
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env"}
// SupportedRemoteProviders are universally supported remote providers.
var SupportedRemoteProviders = []string{"etcd", "consul"}
@@ -294,6 +295,7 @@ func (v *Viper) WatchConfig() {
filename, err := v.getConfigFile()
if err != nil {
log.Printf("error: %v\n", err)
initWG.Done()
return
}
@@ -343,7 +345,7 @@ func (v *Viper) WatchConfig() {
}
}()
watcher.Add(configDir)
initWG.Done() // done initalizing the watch in this go routine, so the parent routine can move on...
initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on...
eventsWG.Wait() // now, wait for event loop to end in this go-routine...
}()
initWG.Wait() // make sure that the go routine above fully ended before returning
@@ -705,6 +707,8 @@ func (v *Viper) Get(key string) interface{} {
return cast.ToDuration(val)
case []string:
return cast.ToStringSlice(val)
case []int:
return cast.ToIntSlice(val)
}
}
@@ -794,6 +798,12 @@ func (v *Viper) GetDuration(key string) time.Duration {
return cast.ToDuration(v.Get(key))
}
// GetIntSlice returns the value associated with the key as a slice of int values.
func GetIntSlice(key string) []int { return v.GetIntSlice(key) }
func (v *Viper) GetIntSlice(key string) []int {
return cast.ToIntSlice(v.Get(key))
}
// GetStringSlice returns the value associated with the key as a slice of strings.
func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
func (v *Viper) GetStringSlice(key string) []string {
@@ -1012,6 +1022,11 @@ func (v *Viper) find(lcaseKey string) interface{} {
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return res
case "intSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToIntSlice(res)
default:
return flag.ValueString()
}
@@ -1081,6 +1096,11 @@ func (v *Viper) find(lcaseKey string) interface{} {
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return res
case "intSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToIntSlice(res)
default:
return flag.ValueString()
}
@@ -1123,8 +1143,8 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
v.envKeyReplacer = r
}
// Aliases provide another accessor for the same key.
// This enables one to change a name without breaking the application
// RegisterAlias creates an alias that provides another accessor for the same key.
// This enables one to change a name without breaking the application.
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
func (v *Viper) RegisterAlias(alias string, key string) {
v.registerAlias(alias, strings.ToLower(key))
@@ -1344,21 +1364,21 @@ func (v *Viper) writeConfig(filename string, force bool) error {
if v.config == nil {
v.config = make(map[string]interface{})
}
var flags int
if force == true {
flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY
} else {
if _, err := os.Stat(filename); os.IsNotExist(err) {
flags = os.O_WRONLY
} else {
return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename)
}
flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
if !force {
flags |= os.O_EXCL
}
f, err := v.fs.OpenFile(filename, flags, v.configPermissions)
if err != nil {
return err
}
return v.marshalWriter(f, configType)
defer f.Close()
if err := v.marshalWriter(f, configType); err != nil {
return err
}
return f.Sync()
}
// Unmarshal a Reader into a map.
@@ -1382,7 +1402,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
}
case "hcl":
obj, err := hcl.Parse(string(buf.Bytes()))
obj, err := hcl.Parse(buf.String())
if err != nil {
return ConfigParseError{err}
}
@@ -1400,6 +1420,15 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
c[k] = v
}
case "dotenv", "env":
env, err := gotenv.StrictParse(buf)
if err != nil {
return ConfigParseError{err}
}
for k, v := range env {
c[k] = v
}
case "properties", "props", "prop":
v.properties = properties.NewProperties()
var err error
@@ -1440,6 +1469,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
case "hcl":
b, err := json.Marshal(c)
if err != nil {
return ConfigMarshalError{err}
}
ast, err := hcl.Parse(string(b))
if err != nil {
return ConfigMarshalError{err}
@@ -1465,6 +1497,18 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
return ConfigMarshalError{err}
}
case "dotenv", "env":
lines := []string{}
for _, key := range v.AllKeys() {
envName := strings.ToUpper(strings.Replace(key, ".", "_", -1))
val := v.Get(key)
lines = append(lines, fmt.Sprintf("%v=%v", envName, val))
}
s := strings.Join(lines, "\n")
if _, err := f.WriteString(s); err != nil {
return ConfigMarshalError{err}
}
case "toml":
t, err := toml.TreeFromMap(c)
if err != nil {
@@ -1679,7 +1723,7 @@ func (v *Viper) AllKeys() []string {
m = v.flattenAndMergeMap(m, v.defaults, "")
// convert set of paths to list
a := []string{}
a := make([]string, 0, len(m))
for x := range m {
a = append(a, x)
}
@@ -1728,7 +1772,7 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac
func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool {
// scan keys
outer:
for k, _ := range m {
for k := range m {
path := strings.Split(k, v.keyDelim)
// scan intermediate paths
var parentKey string
+266 -18
View File
@@ -8,12 +8,12 @@ package viper
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
"runtime"
"sort"
@@ -64,6 +64,11 @@ organization = "MongoDB"
Bio = "MongoDB Chief Developer Advocate & Hacker at Large"
dob = 1979-05-27T07:32:00Z # First class dates? Why not?`)
var dotenvExample = []byte(`
TITLE_DOTENV="DotEnv Example"
TYPE_DOTENV=donut
NAME_DOTENV=Cake`)
var jsonExample = []byte(`{
"id": "0001",
"type": "donut",
@@ -136,6 +141,10 @@ func initConfigs() {
r = bytes.NewReader(tomlExample)
unmarshalReader(r, v.config)
SetConfigType("env")
r = bytes.NewReader(dotenvExample)
unmarshalReader(r, v.config)
SetConfigType("json")
remote := bytes.NewReader(remoteExample)
unmarshalReader(remote, v.kvstore)
@@ -179,6 +188,14 @@ func initTOML() {
unmarshalReader(r, v.config)
}
func initDotEnv() {
Reset()
SetConfigType("env")
r := bytes.NewReader(dotenvExample)
unmarshalReader(r, v.config)
}
func initHcl() {
Reset()
SetConfigType("hcl")
@@ -191,11 +208,16 @@ func initHcl() {
func initDirs(t *testing.T) (string, string, func()) {
var (
testDirs = []string{`a a`, `b`, `c\c`, `D_`}
testDirs = []string{`a a`, `b`, `C_`}
config = `improbable`
)
if runtime.GOOS != "windows" {
testDirs = append(testDirs, `d\d`)
}
root, err := ioutil.TempDir("", "")
require.NoError(t, err, "Failed to create temporary directory")
cleanup := true
defer func() {
@@ -208,7 +230,7 @@ func initDirs(t *testing.T) (string, string, func()) {
assert.Nil(t, err)
err = os.Chdir(root)
assert.Nil(t, err)
require.Nil(t, err)
for _, dir := range testDirs {
err = os.Mkdir(dir, 0750)
@@ -228,7 +250,7 @@ func initDirs(t *testing.T) (string, string, func()) {
}
}
//stubs for PFlag Values
// stubs for PFlag Values
type stringValue string
func newStringValue(val string, p *string) *stringValue {
@@ -246,7 +268,7 @@ func (s *stringValue) Type() string {
}
func (s *stringValue) String() string {
return fmt.Sprintf("%s", *s)
return string(*s)
}
func TestBasics(t *testing.T) {
@@ -342,6 +364,11 @@ func TestTOML(t *testing.T) {
assert.Equal(t, "TOML Example", Get("title"))
}
func TestDotEnv(t *testing.T) {
initDotEnv()
assert.Equal(t, "DotEnv Example", Get("title_dotenv"))
}
func TestHCL(t *testing.T) {
initHcl()
assert.Equal(t, "0001", Get("id"))
@@ -394,7 +421,10 @@ func TestEmptyEnv(t *testing.T) {
BindEnv("type") // Empty environment variable
BindEnv("name") // Bound, but not set environment variable
os.Clearenv()
os.Unsetenv("type")
os.Unsetenv("TYPE")
os.Unsetenv("name")
os.Unsetenv("NAME")
os.Setenv("TYPE", "")
@@ -410,7 +440,10 @@ func TestEmptyEnv_Allowed(t *testing.T) {
BindEnv("type") // Empty environment variable
BindEnv("name") // Bound, but not set environment variable
os.Clearenv()
os.Unsetenv("type")
os.Unsetenv("TYPE")
os.Unsetenv("name")
os.Unsetenv("NAME")
os.Setenv("TYPE", "")
@@ -470,12 +503,92 @@ func TestSetEnvKeyReplacer(t *testing.T) {
func TestAllKeys(t *testing.T) {
initConfigs()
ks := sort.StringSlice{"title", "newkey", "owner.organization", "owner.dob", "owner.bio", "name", "beard", "ppu", "batters.batter", "hobbies", "clothing.jacket", "clothing.trousers", "clothing.pants.size", "age", "hacker", "id", "type", "eyes", "p_id", "p_ppu", "p_batters.batter.type", "p_type", "p_name", "foos"}
ks := sort.StringSlice{
"title",
"newkey",
"owner.organization",
"owner.dob",
"owner.bio",
"name",
"beard",
"ppu",
"batters.batter",
"hobbies",
"clothing.jacket",
"clothing.trousers",
"clothing.pants.size",
"age",
"hacker",
"id",
"type",
"eyes",
"p_id",
"p_ppu",
"p_batters.batter.type",
"p_type",
"p_name",
"foos",
"title_dotenv",
"type_dotenv",
"name_dotenv",
}
dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
all := map[string]interface{}{"owner": map[string]interface{}{"organization": "MongoDB", "bio": "MongoDB Chief Developer Advocate & Hacker at Large", "dob": dob}, "title": "TOML Example", "ppu": 0.55, "eyes": "brown", "clothing": map[string]interface{}{"trousers": "denim", "jacket": "leather", "pants": map[string]interface{}{"size": "large"}}, "id": "0001", "batters": map[string]interface{}{"batter": []interface{}{map[string]interface{}{"type": "Regular"}, map[string]interface{}{"type": "Chocolate"}, map[string]interface{}{"type": "Blueberry"}, map[string]interface{}{"type": "Devil's Food"}}}, "hacker": true, "beard": true, "hobbies": []interface{}{"skateboarding", "snowboarding", "go"}, "age": 35, "type": "donut", "newkey": "remote", "name": "Cake", "p_id": "0001", "p_ppu": "0.55", "p_name": "Cake", "p_batters": map[string]interface{}{"batter": map[string]interface{}{"type": "Regular"}}, "p_type": "donut", "foos": []map[string]interface{}{map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"key": 1}, map[string]interface{}{"key": 2}, map[string]interface{}{"key": 3}, map[string]interface{}{"key": 4}}}}}
all := map[string]interface{}{
"owner": map[string]interface{}{
"organization": "MongoDB",
"bio": "MongoDB Chief Developer Advocate & Hacker at Large",
"dob": dob,
},
"title": "TOML Example",
"ppu": 0.55,
"eyes": "brown",
"clothing": map[string]interface{}{
"trousers": "denim",
"jacket": "leather",
"pants": map[string]interface{}{"size": "large"},
},
"id": "0001",
"batters": map[string]interface{}{
"batter": []interface{}{
map[string]interface{}{"type": "Regular"},
map[string]interface{}{"type": "Chocolate"},
map[string]interface{}{"type": "Blueberry"},
map[string]interface{}{"type": "Devil's Food"},
},
},
"hacker": true,
"beard": true,
"hobbies": []interface{}{
"skateboarding",
"snowboarding",
"go",
},
"age": 35,
"type": "donut",
"newkey": "remote",
"name": "Cake",
"p_id": "0001",
"p_ppu": "0.55",
"p_name": "Cake",
"p_batters": map[string]interface{}{
"batter": map[string]interface{}{"type": "Regular"},
},
"p_type": "donut",
"foos": []map[string]interface{}{
{
"foo": []map[string]interface{}{
{"key": 1},
{"key": 2},
{"key": 3},
{"key": 4}},
},
},
"title_dotenv": "DotEnv Example",
"type_dotenv": "donut",
"name_dotenv": "Cake",
}
var allkeys sort.StringSlice
allkeys = AllKeys()
allkeys := sort.StringSlice(AllKeys())
allkeys.Sort()
ks.Sort()
@@ -516,11 +629,13 @@ func TestUnmarshal(t *testing.T) {
SetDefault("port", 1313)
Set("name", "Steve")
Set("duration", "1s1ms")
Set("modes", []int{1, 2, 3})
type config struct {
Port int
Name string
Duration time.Duration
Modes []int
}
var C config
@@ -530,14 +645,33 @@ func TestUnmarshal(t *testing.T) {
t.Fatalf("unable to decode into struct, %v", err)
}
assert.Equal(t, &config{Name: "Steve", Port: 1313, Duration: time.Second + time.Millisecond}, &C)
assert.Equal(
t,
&config{
Name: "Steve",
Port: 1313,
Duration: time.Second + time.Millisecond,
Modes: []int{1, 2, 3},
},
&C,
)
Set("port", 1234)
err = Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
assert.Equal(t, &config{Name: "Steve", Port: 1234, Duration: time.Second + time.Millisecond}, &C)
assert.Equal(
t,
&config{
Name: "Steve",
Port: 1234,
Duration: time.Second + time.Millisecond,
Modes: []int{1, 2, 3},
},
&C,
)
}
func TestUnmarshalWithDecoderOptions(t *testing.T) {
@@ -658,6 +792,51 @@ func TestBindPFlagsStringSlice(t *testing.T) {
}
}
func TestBindPFlagsIntSlice(t *testing.T) {
tests := []struct {
Expected []int
Value string
}{
{nil, ""},
{[]int{1}, "1"},
{[]int{2, 3}, "2,3"},
}
v := New() // create independent Viper object
defaultVal := []int{0}
v.SetDefault("intslice", defaultVal)
for _, testValue := range tests {
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
flagSet.IntSlice("intslice", testValue.Expected, "test")
for _, changed := range []bool{true, false} {
flagSet.VisitAll(func(f *pflag.Flag) {
f.Value.Set(testValue.Value)
f.Changed = changed
})
err := v.BindPFlags(flagSet)
if err != nil {
t.Fatalf("error binding flag set, %v", err)
}
type TestInt struct {
IntSlice []int
}
val := &TestInt{}
if err := v.Unmarshal(val); err != nil {
t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
}
if changed {
assert.Equal(t, testValue.Expected, val.IntSlice)
} else {
assert.Equal(t, defaultVal, val.IntSlice)
}
}
}
}
func TestBindPFlag(t *testing.T) {
var testString = "testing"
var testValue = newStringValue(testString, &testString)
@@ -673,7 +852,7 @@ func TestBindPFlag(t *testing.T) {
assert.Equal(t, testString, Get("testvalue"))
flag.Value.Set("testing_mutate")
flag.Changed = true //hack for pflag usage
flag.Changed = true // hack for pflag usage
assert.Equal(t, "testing_mutate", Get("testvalue"))
@@ -756,8 +935,11 @@ func TestFindsNestedKeys(t *testing.T) {
"hobbies": []interface{}{
"skateboarding", "snowboarding", "go",
},
"title": "TOML Example",
"newkey": "remote",
"TITLE_DOTENV": "DotEnv Example",
"TYPE_DOTENV": "donut",
"NAME_DOTENV": "Cake",
"title": "TOML Example",
"newkey": "remote",
"batters": map[string]interface{}{
"batter": []interface{}{
map[string]interface{}{
@@ -859,6 +1041,7 @@ func TestDirsSearch(t *testing.T) {
v.SetDefault(`key`, `default`)
entries, err := ioutil.ReadDir(root)
assert.Nil(t, err)
for _, e := range entries {
if e.IsDir() {
v.AddConfigPath(e.Name())
@@ -868,7 +1051,7 @@ func TestDirsSearch(t *testing.T) {
err = v.ReadInConfig()
assert.Nil(t, err)
assert.Equal(t, `value is `+path.Base(v.configPaths[0]), v.GetString(`key`))
assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`))
}
func TestWrongDirsSearchNotFound(t *testing.T) {
@@ -1077,6 +1260,43 @@ func TestWriteConfigTOML(t *testing.T) {
assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
}
var dotenvWriteExpected = []byte(`
TITLE="DotEnv Write Example"
NAME=Oreo
KIND=Biscuit
`)
func TestWriteConfigDotEnv(t *testing.T) {
fs := afero.NewMemMapFs()
v := New()
v.SetFs(fs)
v.SetConfigName("c")
v.SetConfigType("env")
err := v.ReadConfig(bytes.NewBuffer(dotenvWriteExpected))
if err != nil {
t.Fatal(err)
}
if err := v.WriteConfigAs("c.env"); err != nil {
t.Fatal(err)
}
// The TOML String method does not order the contents.
// Therefore, we must read the generated file and compare the data.
v2 := New()
v2.SetFs(fs)
v2.SetConfigName("c")
v2.SetConfigType("env")
v2.SetConfigFile("c.env")
err = v2.ReadInConfig()
if err != nil {
t.Fatal(err)
}
assert.Equal(t, v.GetString("title"), v2.GetString("title"))
assert.Equal(t, v.GetString("type"), v2.GetString("type"))
assert.Equal(t, v.GetString("kind"), v2.GetString("kind"))
}
var yamlWriteExpected = []byte(`age: 35
beard: true
clothing:
@@ -1132,6 +1352,9 @@ hello:
universe:
- mw
- ad
ints:
- 1
- 2
fu: bar
`)
@@ -1198,6 +1421,10 @@ func TestMergeConfig(t *testing.T) {
t.Fatalf("len(universe) != 2, = %d", len(universe))
}
if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
t.Fatalf("len(ints) != 2, = %d", len(ints))
}
if fu := v.GetString("fu"); fu != "bar" {
t.Fatalf("fu != \"bar\", = %s", fu)
}
@@ -1238,6 +1465,10 @@ func TestMergeConfigNoMerge(t *testing.T) {
t.Fatalf("len(universe) != 2, = %d", len(universe))
}
if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
t.Fatalf("len(ints) != 2, = %d", len(ints))
}
if fu := v.GetString("fu"); fu != "bar" {
t.Fatalf("fu != \"bar\", = %s", fu)
}
@@ -1609,6 +1840,23 @@ func TestWatchFile(t *testing.T) {
}
func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
flags.String("foo.bar", "cobra_flag", "")
v := New()
assert.NoError(t, v.BindPFlags(flags))
config := &struct {
Foo struct {
Bar string
}
}{}
assert.NoError(t, v.Unmarshal(config))
assert.Equal(t, "cobra_flag", config.Foo.Bar)
}
func BenchmarkGetBool(b *testing.B) {
key := "BenchmarkGetBool"
v = New()
@@ -1633,7 +1881,7 @@ func BenchmarkGet(b *testing.B) {
}
}
// This is the "perfect result" for the above.
// BenchmarkGetBoolFromMap is the "perfect result" for the above.
func BenchmarkGetBoolFromMap(b *testing.B) {
m := make(map[string]bool)
key := "BenchmarkGetBool"