mirror of
https://github.com/spf13/viper.git
synced 2024-04-21 12:31:38 +00:00
Compare commits
@@ -0,0 +1,2 @@
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,35 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 10
|
||||
matrix:
|
||||
go: ['1.11', '1.12', '1.13']
|
||||
env:
|
||||
VERBOSE: 1
|
||||
GOFLAGS: -mod=readonly
|
||||
GOPROXY: https://proxy.golang.org
|
||||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
- name: Run linter
|
||||
run: make lint
|
||||
+19
-28
@@ -1,29 +1,20 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
/bin/
|
||||
/build/
|
||||
/var/
|
||||
/vendor/
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.bench
|
||||
|
||||
.vscode
|
||||
|
||||
# exclude dependencies in the `/vendor` folder
|
||||
vendor
|
||||
# IDE integration
|
||||
/.vscode/*
|
||||
!/.vscode/launch.json
|
||||
!/.vscode/tasks.json
|
||||
/.idea/*
|
||||
!/.idea/codeStyles/
|
||||
!/.idea/copyright/
|
||||
!/.idea/dataSources.xml
|
||||
!/.idea/*.iml
|
||||
!/.idea/externalDependencies.xml
|
||||
!/.idea/go.imports.xml
|
||||
!/.idea/modules.xml
|
||||
!/.idea/runConfigurations/
|
||||
!/.idea/scopes/
|
||||
!/.idea/sqldialects.xml
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
linters-settings:
|
||||
golint:
|
||||
min-confidence: 0.1
|
||||
goimports:
|
||||
local-prefixes: github.com/spf13/viper
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- funlen
|
||||
- maligned
|
||||
|
||||
# TODO: fix me
|
||||
- wsl
|
||||
- gochecknoinits
|
||||
- gosimple
|
||||
- gochecknoglobals
|
||||
- errcheck
|
||||
- lll
|
||||
- godox
|
||||
- scopelint
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- gocritic
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalDependencies">
|
||||
<plugin id="name.kropp.intellij.makefile" />
|
||||
<plugin id="org.jetbrains.plugins.go" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GoImports">
|
||||
<option name="groupStdlibImports" value="true" />
|
||||
<option name="importSorting" value="GOFMT" />
|
||||
<option name="moveAllImportsInOneDeclaration" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/viper.iml" filepath="$PROJECT_DIR$/.idea/viper.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Check" type="CompoundRunConfigurationType">
|
||||
<toRun name="Tests" type="GoTestRunConfiguration" />
|
||||
<toRun name="Lint" type="MAKEFILE_TARGET_RUN_CONFIGURATION" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Lint" type="MAKEFILE_TARGET_RUN_CONFIGURATION" factoryName="Makefile">
|
||||
<makefile filename="$PROJECT_DIR$/Makefile" target="lint" workingDirectory="" arguments="">
|
||||
<envs />
|
||||
</makefile>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
+23
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Tests" type="GoTestRunConfiguration" factoryName="Go Test">
|
||||
<module name="viper" />
|
||||
<working_directory value="$PROJECT_DIR$/" />
|
||||
<go_parameters value="-i" />
|
||||
<EXTENSION ID="net.ashald.envfile">
|
||||
<option name="IS_ENABLED" value="false" />
|
||||
<option name="IS_SUBST" value="false" />
|
||||
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||
<ENTRIES>
|
||||
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
|
||||
</ENTRIES>
|
||||
</EXTENSION>
|
||||
<framework value="gotest" />
|
||||
<kind value="DIRECTORY" />
|
||||
<package value="github.com/spf13/viper" />
|
||||
<directory value="$PROJECT_DIR$/" />
|
||||
<filePath value="$PROJECT_DIR$/" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
+4
-3
@@ -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
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
|
||||
OS = $(shell uname | tr A-Z a-z)
|
||||
|
||||
# Build variables
|
||||
BUILD_DIR ?= build
|
||||
ifeq (${VERBOSE}, 1)
|
||||
ifeq ($(filter -v,${GOARGS}),)
|
||||
GOARGS += -v
|
||||
endif
|
||||
TEST_FORMAT = short-verbose
|
||||
endif
|
||||
|
||||
# Dependency versions
|
||||
GOTESTSUM_VERSION = 0.3.5
|
||||
GOLANGCI_VERSION = 1.21.0
|
||||
|
||||
# Add the ability to override some variables
|
||||
# Use with care
|
||||
-include override.mk
|
||||
|
||||
.PHONY: clear
|
||||
clear: ## Clear the working area and the project
|
||||
rm -rf bin/
|
||||
|
||||
.PHONY: check
|
||||
check: test lint ## Run tests and linters
|
||||
|
||||
bin/gotestsum: bin/gotestsum-${GOTESTSUM_VERSION}
|
||||
@ln -sf gotestsum-${GOTESTSUM_VERSION} bin/gotestsum
|
||||
bin/gotestsum-${GOTESTSUM_VERSION}:
|
||||
@mkdir -p bin
|
||||
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum-${GOTESTSUM_VERSION} && chmod +x ./bin/gotestsum-${GOTESTSUM_VERSION}
|
||||
|
||||
TEST_PKGS ?= ./...
|
||||
TEST_REPORT_NAME ?= results.xml
|
||||
.PHONY: test
|
||||
test: TEST_REPORT ?= main
|
||||
test: TEST_FORMAT ?= short
|
||||
test: SHELL = /bin/bash
|
||||
test: bin/gotestsum ## Run tests
|
||||
@mkdir -p ${BUILD_DIR}/test_results/${TEST_REPORT}
|
||||
bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/test_results/${TEST_REPORT}/${TEST_REPORT_NAME} --format ${TEST_FORMAT} -- $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...)
|
||||
|
||||
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
|
||||
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
|
||||
bin/golangci-lint-${GOLANGCI_VERSION}:
|
||||
@mkdir -p bin
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}
|
||||
@mv bin/golangci-lint $@
|
||||
|
||||
.PHONY: lint
|
||||
lint: bin/golangci-lint ## Run linter
|
||||
bin/golangci-lint run
|
||||
|
||||
.PHONY: fix
|
||||
fix: bin/golangci-lint ## Fix lint violations
|
||||
bin/golangci-lint run --fix
|
||||
|
||||
.PHONY: list
|
||||
list: ## List all make targets
|
||||
@${MAKE} -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort
|
||||
|
||||
.PHONY: help
|
||||
.DEFAULT_GOAL := help
|
||||
help:
|
||||
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
# Variable outputting/exporting rules
|
||||
var-%: ; @echo $($*)
|
||||
varexport-%: ; @echo $*=$($*)
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Go configuration with fangs!
|
||||
|
||||
[](https://github.com/spf13/viper)
|
||||
[](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://godoc.org/github.com/spf13/viper)
|
||||
|
||||
Many Go projects are built using Viper including:
|
||||
|
||||
* [Hugo](http://gohugo.io)
|
||||
@@ -12,8 +16,14 @@ Many Go projects are built using Viper including:
|
||||
* [BloomApi](https://www.bloomapi.com/)
|
||||
* [doctl](https://github.com/digitalocean/doctl)
|
||||
* [Clairctl](https://github.com/jgsqware/clairctl)
|
||||
* [Mercure](https://mercure.rocks)
|
||||
|
||||
[](https://travis-ci.org/spf13/viper) [](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://godoc.org/github.com/spf13/viper)
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
go get github.com/spf13/viper
|
||||
```
|
||||
|
||||
|
||||
## What is Viper?
|
||||
@@ -23,7 +33,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
|
||||
@@ -31,8 +41,8 @@ and formats. It supports:
|
||||
* reading from buffer
|
||||
* setting explicit values
|
||||
|
||||
Viper can be thought of as a registry for all of your applications
|
||||
configuration needs.
|
||||
Viper can be thought of as a registry for all of your applications configuration needs.
|
||||
|
||||
|
||||
## Why Viper?
|
||||
|
||||
@@ -42,34 +52,31 @@ 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.
|
||||
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
|
||||
command line flags.
|
||||
4. Provide an alias system to easily rename parameters without breaking existing
|
||||
code.
|
||||
5. Make it easy to tell the difference between when a user has provided a
|
||||
command line or config file which is the same as the default.
|
||||
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, 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 command line flags.
|
||||
4. Provide an alias system to easily rename parameters without breaking existing code.
|
||||
5. Make it easy to tell the difference between when a user has provided a command line or config file which is the same as the default.
|
||||
|
||||
Viper uses the following precedence order. Each item takes precedence over the
|
||||
item below it:
|
||||
Viper uses the following precedence order. Each item takes precedence over the item below it:
|
||||
|
||||
* explicit call to Set
|
||||
* explicit call to `Set`
|
||||
* flag
|
||||
* env
|
||||
* config
|
||||
* key/value store
|
||||
* default
|
||||
|
||||
Viper configuration keys are case insensitive.
|
||||
**Important:** Viper configuration keys are case insensitive.
|
||||
There are ongoing discussions about making that optional.
|
||||
|
||||
|
||||
## Putting Values into Viper
|
||||
|
||||
### Establishing Defaults
|
||||
|
||||
A good configuration system will support default values. A default value is not
|
||||
required for a key, but it’s useful in the event that a key hasn’t been set via
|
||||
required for a key, but it’s useful in the event that a key hasn't been set via
|
||||
config file, environment variable, remote configuration or flag.
|
||||
|
||||
Examples:
|
||||
@@ -83,7 +90,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, INI, 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 +110,44 @@ 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
|
||||
```
|
||||
|
||||
*NOTE:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
|
||||
|
||||
### 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 +231,7 @@ with ENV:
|
||||
* `BindEnv(string...) : error`
|
||||
* `SetEnvPrefix(string)`
|
||||
* `SetEnvKeyReplacer(string...) *strings.Replacer`
|
||||
* `AllowEmptyEnvVar(bool)`
|
||||
* `AllowEmptyEnv(bool)`
|
||||
|
||||
_When working with ENV variables, it’s important to recognize that Viper
|
||||
treats ENV variables as case sensitive._
|
||||
@@ -199,9 +244,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
|
||||
@@ -218,6 +263,9 @@ keys to an extent. This is useful if you want to use `-` or something in your
|
||||
`Get()` calls, but want your environmental variables to use `_` delimiters. An
|
||||
example of using it can be found in `viper_test.go`.
|
||||
|
||||
Alternatively, you can use `EnvKeyReplacer` with `NewWithOptions` factory function.
|
||||
Unlike `SetEnvKeyReplacer`, it accepts a `StringReplacer` interface allowing you to write custom string replacing logic.
|
||||
|
||||
By default empty environment variables are considered unset and will fall back to
|
||||
the next configuration source. To treat empty environment variables as set, use
|
||||
the `AllowEmptyEnv` method.
|
||||
@@ -346,7 +394,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 +429,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 +457,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 +468,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 +504,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 +660,43 @@ 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)
|
||||
}
|
||||
```
|
||||
|
||||
If you want to unmarshal configuration where the keys themselves contain dot (the default key delimiter),
|
||||
you have to change the delimiter:
|
||||
|
||||
```go
|
||||
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
|
||||
|
||||
v.SetDefault("chart::values", map[string]interface{}{
|
||||
"ingress": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{
|
||||
"traefik.frontend.rule.type": "PathPrefix",
|
||||
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
type config struct {
|
||||
Chart struct{
|
||||
Values map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
var C config
|
||||
|
||||
v.Unmarshal(&C)
|
||||
```
|
||||
|
||||
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 +707,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)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -672,13 +749,6 @@ different vipers.
|
||||
|
||||
## Q & A
|
||||
|
||||
Q: Why not INI files?
|
||||
|
||||
A: Ini files are pretty awful. There’s no standard format, and they are hard to
|
||||
validate. Viper is designed to work with JSON, TOML or YAML files. If someone
|
||||
really wants to add this feature, I’d be happy to merge it. It’s easy to specify
|
||||
which formats your application will permit.
|
||||
|
||||
Q: Why is it called “Viper”?
|
||||
|
||||
A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,24 +1,48 @@
|
||||
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
|
||||
github.com/coreos/etcd v3.3.10+incompatible // indirect
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible // indirect
|
||||
github.com/coreos/go-semver v0.2.0 // indirect
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gogo/protobuf v1.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0
|
||||
github.com/magiconair/properties v1.8.0
|
||||
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||
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
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/soheilhy/cmux v0.1.4 // indirect
|
||||
github.com/spf13/afero v1.1.2
|
||||
github.com/spf13/cast v1.3.0
|
||||
github.com/spf13/jwalterweatherman v1.0.0
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect
|
||||
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
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a // indirect
|
||||
golang.org/x/text v0.3.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
go.etcd.io/bbolt v1.3.2 // indirect
|
||||
go.uber.org/atomic v1.4.0 // indirect
|
||||
go.uber.org/multierr v1.1.0 // indirect
|
||||
go.uber.org/zap v1.10.0 // indirect
|
||||
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/ini.v1 v1.51.0
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
)
|
||||
|
||||
@@ -1,25 +1,117 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:KjVWqrZ5U0wa3CxY2AxlH6/UcB+PK2td1DcsYhA+HRs=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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.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=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
@@ -28,24 +120,73 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
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/stretchr/testify v1.2.3-0.20181014000028-04af85275a5c h1:03OmljzZYsezlgAfa+f/cY8E8XXPiFh5bgANMhUlDI4=
|
||||
github.com/stretchr/testify v1.2.3-0.20181014000028-04af85275a5c/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.3-0.20181115233458-8019298d9fa5 h1:ixuBiBNIIQ3RKRSZy9B0DgaqreXG6NDHrbwAFGg8Mwk=
|
||||
github.com/stretchr/testify v1.2.3-0.20181115233458-8019298d9fa5/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xordataexchange/crypt v0.0.2 h1:VBfFXTpEwLq2hzs42qCHOyKw5AqEm9DYGqBuINmzUZY=
|
||||
github.com/xordataexchange/crypt v0.0.2/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
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=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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.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=
|
||||
|
||||
+4
-4
@@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
crypt "github.com/xordataexchange/crypt/config"
|
||||
)
|
||||
|
||||
@@ -63,9 +64,7 @@ func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *vi
|
||||
Error: resp.Error,
|
||||
Value: resp.Value,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}(cryptoResponseCh, viperResponsCh, quitwc, quit)
|
||||
|
||||
@@ -77,11 +76,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 {
|
||||
|
||||
@@ -114,11 +114,11 @@ func absPathify(inPath string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check if File / Directory Exists
|
||||
// Check if file Exists
|
||||
func exists(fs afero.Fs, path string) (bool, error) {
|
||||
_, err := fs.Stat(path)
|
||||
stat, err := fs.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
return !stat.IsDir(), nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
|
||||
@@ -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.
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -33,18 +34,19 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/printer"
|
||||
"github.com/magiconair/properties"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"github.com/pelletier/go-toml"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/ini.v1"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// ConfigMarshalError happens when failing to marshal the configuration.
|
||||
@@ -114,6 +116,14 @@ func (fnfe ConfigFileNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations)
|
||||
}
|
||||
|
||||
// ConfigFileAlreadyExistsError denotes failure to write new configuration file.
|
||||
type ConfigFileAlreadyExistsError string
|
||||
|
||||
// Error returns the formatted error when configuration already exists.
|
||||
func (faee ConfigFileAlreadyExistsError) Error() string {
|
||||
return fmt.Sprintf("Config File %q Already Exists", string(faee))
|
||||
}
|
||||
|
||||
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
|
||||
// mapstructure.DecoderConfig options
|
||||
type DecoderConfigOption func(*mapstructure.DecoderConfig)
|
||||
@@ -180,13 +190,14 @@ type Viper struct {
|
||||
remoteProviders []*defaultRemoteProvider
|
||||
|
||||
// Name of file to look for inside the path
|
||||
configName string
|
||||
configFile string
|
||||
configType string
|
||||
envPrefix string
|
||||
configName string
|
||||
configFile string
|
||||
configType string
|
||||
configPermissions os.FileMode
|
||||
envPrefix string
|
||||
|
||||
automaticEnvApplied bool
|
||||
envKeyReplacer *strings.Replacer
|
||||
envKeyReplacer StringReplacer
|
||||
allowEmptyEnv bool
|
||||
|
||||
config map[string]interface{}
|
||||
@@ -210,6 +221,7 @@ func New() *Viper {
|
||||
v := new(Viper)
|
||||
v.keyDelim = "."
|
||||
v.configName = "config"
|
||||
v.configPermissions = os.FileMode(0644)
|
||||
v.fs = afero.NewOsFs()
|
||||
v.config = make(map[string]interface{})
|
||||
v.override = make(map[string]interface{})
|
||||
@@ -223,12 +235,58 @@ func New() *Viper {
|
||||
return v
|
||||
}
|
||||
|
||||
// Intended for testing, will reset all to default settings.
|
||||
// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney.
|
||||
// If you're unfamiliar with this style,
|
||||
// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and
|
||||
// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis.
|
||||
type Option interface {
|
||||
apply(v *Viper)
|
||||
}
|
||||
|
||||
type optionFunc func(v *Viper)
|
||||
|
||||
func (fn optionFunc) apply(v *Viper) {
|
||||
fn(v)
|
||||
}
|
||||
|
||||
// KeyDelimiter sets the delimiter used for determining key parts.
|
||||
// By default it's value is ".".
|
||||
func KeyDelimiter(d string) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
v.keyDelim = d
|
||||
})
|
||||
}
|
||||
|
||||
// StringReplacer applies a set of replacements to a string.
|
||||
type StringReplacer interface {
|
||||
// Replace returns a copy of s with all replacements performed.
|
||||
Replace(s string) string
|
||||
}
|
||||
|
||||
// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys.
|
||||
func EnvKeyReplacer(r StringReplacer) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
v.envKeyReplacer = r
|
||||
})
|
||||
}
|
||||
|
||||
// NewWithOptions creates a new Viper instance.
|
||||
func NewWithOptions(opts ...Option) *Viper {
|
||||
v := New()
|
||||
|
||||
for _, opt := range opts {
|
||||
opt.apply(v)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// 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", "ini"}
|
||||
SupportedRemoteProviders = []string{"etcd", "consul"}
|
||||
}
|
||||
|
||||
@@ -267,7 +325,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", "ini"}
|
||||
|
||||
// SupportedRemoteProviders are universally supported remote providers.
|
||||
var SupportedRemoteProviders = []string{"etcd", "consul"}
|
||||
@@ -292,6 +350,7 @@ func (v *Viper) WatchConfig() {
|
||||
filename, err := v.getConfigFile()
|
||||
if err != nil {
|
||||
log.Printf("error: %v\n", err)
|
||||
initWG.Done()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -341,7 +400,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
|
||||
@@ -666,7 +725,7 @@ func GetViper() *Viper {
|
||||
func Get(key string) interface{} { return v.Get(key) }
|
||||
func (v *Viper) Get(key string) interface{} {
|
||||
lcaseKey := strings.ToLower(key)
|
||||
val := v.find(lcaseKey)
|
||||
val := v.find(lcaseKey, true)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -687,6 +746,12 @@ func (v *Viper) Get(key string) interface{} {
|
||||
return cast.ToString(val)
|
||||
case int32, int16, int8, int:
|
||||
return cast.ToInt(val)
|
||||
case uint:
|
||||
return cast.ToUint(val)
|
||||
case uint32:
|
||||
return cast.ToUint32(val)
|
||||
case uint64:
|
||||
return cast.ToUint64(val)
|
||||
case int64:
|
||||
return cast.ToInt64(val)
|
||||
case float64, float32:
|
||||
@@ -697,6 +762,8 @@ func (v *Viper) Get(key string) interface{} {
|
||||
return cast.ToDuration(val)
|
||||
case []string:
|
||||
return cast.ToStringSlice(val)
|
||||
case []int:
|
||||
return cast.ToIntSlice(val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,6 +817,24 @@ func (v *Viper) GetInt64(key string) int64 {
|
||||
return cast.ToInt64(v.Get(key))
|
||||
}
|
||||
|
||||
// GetUint returns the value associated with the key as an unsigned integer.
|
||||
func GetUint(key string) uint { return v.GetUint(key) }
|
||||
func (v *Viper) GetUint(key string) uint {
|
||||
return cast.ToUint(v.Get(key))
|
||||
}
|
||||
|
||||
// GetUint32 returns the value associated with the key as an unsigned integer.
|
||||
func GetUint32(key string) uint32 { return v.GetUint32(key) }
|
||||
func (v *Viper) GetUint32(key string) uint32 {
|
||||
return cast.ToUint32(v.Get(key))
|
||||
}
|
||||
|
||||
// GetUint64 returns the value associated with the key as an unsigned integer.
|
||||
func GetUint64(key string) uint64 { return v.GetUint64(key) }
|
||||
func (v *Viper) GetUint64(key string) uint64 {
|
||||
return cast.ToUint64(v.Get(key))
|
||||
}
|
||||
|
||||
// GetFloat64 returns the value associated with the key as a float64.
|
||||
func GetFloat64(key string) float64 { return v.GetFloat64(key) }
|
||||
func (v *Viper) GetFloat64(key string) float64 {
|
||||
@@ -768,6 +853,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 {
|
||||
@@ -811,8 +902,6 @@ func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConf
|
||||
return err
|
||||
}
|
||||
|
||||
v.insensitiviseMaps()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -828,8 +917,6 @@ func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error
|
||||
return err
|
||||
}
|
||||
|
||||
v.insensitiviseMaps()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -862,8 +949,11 @@ func decode(input interface{}, config *mapstructure.DecoderConfig) error {
|
||||
|
||||
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
|
||||
// in the destination struct.
|
||||
func (v *Viper) UnmarshalExact(rawVal interface{}) error {
|
||||
config := defaultDecoderConfig(rawVal)
|
||||
func UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||
return v.UnmarshalExact(rawVal, opts...)
|
||||
}
|
||||
func (v *Viper) UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
|
||||
config := defaultDecoderConfig(rawVal, opts...)
|
||||
config.ErrorUnused = true
|
||||
|
||||
err := decode(v.AllSettings(), config)
|
||||
@@ -872,8 +962,6 @@ func (v *Viper) UnmarshalExact(rawVal interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
v.insensitiviseMaps()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -930,7 +1018,7 @@ func BindEnv(input ...string) error { return v.BindEnv(input...) }
|
||||
func (v *Viper) BindEnv(input ...string) error {
|
||||
var key, envkey string
|
||||
if len(input) == 0 {
|
||||
return fmt.Errorf("BindEnv missing key to bind to")
|
||||
return fmt.Errorf("missing key to bind to")
|
||||
}
|
||||
|
||||
key = strings.ToLower(input[0])
|
||||
@@ -947,12 +1035,15 @@ func (v *Viper) BindEnv(input ...string) error {
|
||||
}
|
||||
|
||||
// Given a key, find the value.
|
||||
// Viper will check in the following order:
|
||||
// flag, env, config file, key/value store, default.
|
||||
//
|
||||
// Viper will check to see if an alias exists first.
|
||||
// Viper will then check in the following order:
|
||||
// flag, env, config file, key/value store.
|
||||
// Lastly, if no value was found and flagDefault is true, and if the key
|
||||
// corresponds to a flag, the flag's default value is returned.
|
||||
//
|
||||
// Note: this assumes a lower-cased key given.
|
||||
func (v *Viper) find(lcaseKey string) interface{} {
|
||||
|
||||
func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
|
||||
var (
|
||||
val interface{}
|
||||
exists bool
|
||||
@@ -992,6 +1083,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()
|
||||
}
|
||||
@@ -1048,24 +1144,31 @@ func (v *Viper) find(lcaseKey string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// last chance: if no other value is returned and a flag does exist for the value,
|
||||
// get the flag's value even if the flag's value has not changed
|
||||
if flag, exists := v.pflags[lcaseKey]; exists {
|
||||
switch flag.ValueType() {
|
||||
case "int", "int8", "int16", "int32", "int64":
|
||||
return cast.ToInt(flag.ValueString())
|
||||
case "bool":
|
||||
return cast.ToBool(flag.ValueString())
|
||||
case "stringSlice":
|
||||
s := strings.TrimPrefix(flag.ValueString(), "[")
|
||||
s = strings.TrimSuffix(s, "]")
|
||||
res, _ := readAsCSV(s)
|
||||
return res
|
||||
default:
|
||||
return flag.ValueString()
|
||||
if flagDefault {
|
||||
// last chance: if no value is found and a flag does exist for the key,
|
||||
// get the flag's default value even if the flag's value has not been set.
|
||||
if flag, exists := v.pflags[lcaseKey]; exists {
|
||||
switch flag.ValueType() {
|
||||
case "int", "int8", "int16", "int32", "int64":
|
||||
return cast.ToInt(flag.ValueString())
|
||||
case "bool":
|
||||
return cast.ToBool(flag.ValueString())
|
||||
case "stringSlice":
|
||||
s := strings.TrimPrefix(flag.ValueString(), "[")
|
||||
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()
|
||||
}
|
||||
}
|
||||
// last item, no need to check shadowing
|
||||
}
|
||||
// last item, no need to check shadowing
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1084,7 +1187,7 @@ func readAsCSV(val string) ([]string, error) {
|
||||
func IsSet(key string) bool { return v.IsSet(key) }
|
||||
func (v *Viper) IsSet(key string) bool {
|
||||
lcaseKey := strings.ToLower(key)
|
||||
val := v.find(lcaseKey)
|
||||
val := v.find(lcaseKey, false)
|
||||
return val != nil
|
||||
}
|
||||
|
||||
@@ -1103,8 +1206,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))
|
||||
@@ -1267,11 +1370,13 @@ func (v *Viper) MergeConfig(in io.Reader) error {
|
||||
}
|
||||
|
||||
// MergeConfigMap merges the configuration from the map given with an existing config.
|
||||
// Note that the map given may be modified.
|
||||
func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) }
|
||||
func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
|
||||
if v.config == nil {
|
||||
v.config = make(map[string]interface{})
|
||||
}
|
||||
insensitiviseMap(cfg)
|
||||
mergeMaps(cfg, v.config, nil)
|
||||
return nil
|
||||
}
|
||||
@@ -1289,11 +1394,10 @@ func (v *Viper) WriteConfig() error {
|
||||
// SafeWriteConfig writes current configuration to file only if the file does not exist.
|
||||
func SafeWriteConfig() error { return v.SafeWriteConfig() }
|
||||
func (v *Viper) SafeWriteConfig() error {
|
||||
filename, err := v.getConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
if len(v.configPaths) < 1 {
|
||||
return errors.New("missing configuration for 'configPath'")
|
||||
}
|
||||
return v.writeConfig(filename, false)
|
||||
return v.SafeWriteConfigAs(filepath.Join(v.configPaths[0], v.configName+"."+v.configType))
|
||||
}
|
||||
|
||||
// WriteConfigAs writes current configuration to a given filename.
|
||||
@@ -1305,15 +1409,18 @@ func (v *Viper) WriteConfigAs(filename string) error {
|
||||
// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.
|
||||
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }
|
||||
func (v *Viper) SafeWriteConfigAs(filename string) error {
|
||||
alreadyExists, err := afero.Exists(v.fs, filename)
|
||||
if alreadyExists && err == nil {
|
||||
return ConfigFileAlreadyExistsError(filename)
|
||||
}
|
||||
return v.writeConfig(filename, false)
|
||||
}
|
||||
|
||||
func writeConfig(filename string, force bool) error { return v.writeConfig(filename, force) }
|
||||
func (v *Viper) writeConfig(filename string, force bool) error {
|
||||
jww.INFO.Println("Attempting to write configuration to file.")
|
||||
ext := filepath.Ext(filename)
|
||||
if len(ext) <= 1 {
|
||||
return fmt.Errorf("Filename: %s requires valid extension.", filename)
|
||||
return fmt.Errorf("filename: %s requires valid extension", filename)
|
||||
}
|
||||
configType := ext[1:]
|
||||
if !stringInSlice(configType, SupportedExts) {
|
||||
@@ -1322,21 +1429,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, os.FileMode(0644))
|
||||
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.
|
||||
@@ -1360,7 +1467,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}
|
||||
}
|
||||
@@ -1378,6 +1485,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
|
||||
@@ -1393,6 +1509,23 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||
// set innermost value
|
||||
deepestMap[lastKey] = value
|
||||
}
|
||||
|
||||
case "ini":
|
||||
cfg := ini.Empty()
|
||||
err := cfg.Append(buf.Bytes())
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
sections := cfg.Sections()
|
||||
for i := 0; i < len(sections); i++ {
|
||||
section := sections[i]
|
||||
keys := section.Keys()
|
||||
for j := 0; j < len(keys); j++ {
|
||||
key := keys[j]
|
||||
value := cfg.Section(section.Name()).Key(key.Name()).String()
|
||||
c[section.Name()+"."+key.Name()] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insensitiviseMap(c)
|
||||
@@ -1400,9 +1533,6 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||
}
|
||||
|
||||
// Marshal a map into Writer.
|
||||
func marshalWriter(f afero.File, configType string) error {
|
||||
return v.marshalWriter(f, configType)
|
||||
}
|
||||
func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||
c := v.AllSettings()
|
||||
switch configType {
|
||||
@@ -1418,6 +1548,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}
|
||||
@@ -1443,6 +1576,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 {
|
||||
@@ -1461,6 +1606,22 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||
if _, err = f.WriteString(string(b)); err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
case "ini":
|
||||
keys := v.AllKeys()
|
||||
cfg := ini.Empty()
|
||||
ini.PrettyFormat = false
|
||||
for i := 0; i < len(keys); i++ {
|
||||
key := keys[i]
|
||||
lastSep := strings.LastIndex(key, ".")
|
||||
sectionName := key[:(lastSep)]
|
||||
keyName := key[(lastSep + 1):]
|
||||
if sectionName == "default" {
|
||||
sectionName = ""
|
||||
}
|
||||
cfg.Section(sectionName).Key(keyName).SetValue(Get(key).(string))
|
||||
}
|
||||
cfg.WriteTo(f)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1577,13 +1738,6 @@ func (v *Viper) WatchRemoteConfigOnChannel() error {
|
||||
return v.watchKeyValueConfigOnChannel()
|
||||
}
|
||||
|
||||
func (v *Viper) insensitiviseMaps() {
|
||||
insensitiviseMap(v.config)
|
||||
insensitiviseMap(v.defaults)
|
||||
insensitiviseMap(v.override)
|
||||
insensitiviseMap(v.kvstore)
|
||||
}
|
||||
|
||||
// Retrieve the first found remote configuration.
|
||||
func (v *Viper) getKeyValueConfig() error {
|
||||
if RemoteConfig == nil {
|
||||
@@ -1614,7 +1768,7 @@ func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}
|
||||
func (v *Viper) watchKeyValueConfigOnChannel() error {
|
||||
for _, rp := range v.remoteProviders {
|
||||
respc, _ := RemoteConfig.WatchChannel(rp)
|
||||
//Todo: Add quit channel
|
||||
// Todo: Add quit channel
|
||||
go func(rc <-chan *RemoteResponse) {
|
||||
for {
|
||||
b := <-rc
|
||||
@@ -1650,7 +1804,7 @@ func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface
|
||||
}
|
||||
|
||||
// AllKeys returns all keys holding a value, regardless of where they are set.
|
||||
// Nested keys are returned with a v.keyDelim (= ".") separator
|
||||
// Nested keys are returned with a v.keyDelim separator
|
||||
func AllKeys() []string { return v.AllKeys() }
|
||||
func (v *Viper) AllKeys() []string {
|
||||
m := map[string]bool{}
|
||||
@@ -1664,7 +1818,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)
|
||||
}
|
||||
@@ -1673,7 +1827,7 @@ func (v *Viper) AllKeys() []string {
|
||||
|
||||
// flattenAndMergeMap recursively flattens the given map into a map[string]bool
|
||||
// of key paths (used as a set, easier to manipulate than a []string):
|
||||
// - each path is merged into a single key string, delimited with v.keyDelim (= ".")
|
||||
// - each path is merged into a single key string, delimited with v.keyDelim
|
||||
// - if a path is shadowed by an earlier value in the initial shadow map,
|
||||
// it is skipped.
|
||||
// The resulting set of paths is merged to the given shadow set at the same time.
|
||||
@@ -1713,7 +1867,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
|
||||
@@ -1776,6 +1930,12 @@ func (v *Viper) SetConfigType(in string) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfigPermissions sets the permissions for the config file.
|
||||
func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }
|
||||
func (v *Viper) SetConfigPermissions(perm os.FileMode) {
|
||||
v.configPermissions = perm.Perm()
|
||||
}
|
||||
|
||||
func (v *Viper) getConfigType() string {
|
||||
if v.configType != "" {
|
||||
return v.configType
|
||||
@@ -1816,6 +1976,10 @@ func (v *Viper) searchInPath(in string) (filename string) {
|
||||
}
|
||||
}
|
||||
|
||||
if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
|
||||
return filepath.Join(in, v.configName)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
+561
-45
@@ -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",
|
||||
@@ -113,6 +118,24 @@ var remoteExample = []byte(`{
|
||||
"newkey":"remote"
|
||||
}`)
|
||||
|
||||
var iniExample = []byte(`; Package name
|
||||
NAME = ini
|
||||
; Package version
|
||||
VERSION = v1
|
||||
; Package import path
|
||||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
||||
|
||||
# Information about package author
|
||||
# Bio can be written in multiple lines.
|
||||
[author]
|
||||
NAME = Unknown ; Succeeding comment
|
||||
E-MAIL = fake@localhost
|
||||
GITHUB = https://github.com/%(NAME)s
|
||||
BIO = """Gopher.
|
||||
Coding addict.
|
||||
Good man.
|
||||
""" # Succeeding comment`)
|
||||
|
||||
func initConfigs() {
|
||||
Reset()
|
||||
var r io.Reader
|
||||
@@ -136,9 +159,17 @@ 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)
|
||||
|
||||
SetConfigType("ini")
|
||||
r = bytes.NewReader(iniExample)
|
||||
unmarshalReader(r, v.config)
|
||||
}
|
||||
|
||||
func initConfig(typ, config string) {
|
||||
@@ -179,6 +210,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")
|
||||
@@ -187,15 +226,27 @@ func initHcl() {
|
||||
unmarshalReader(r, v.config)
|
||||
}
|
||||
|
||||
func initIni() {
|
||||
Reset()
|
||||
SetConfigType("ini")
|
||||
r := bytes.NewReader(iniExample)
|
||||
|
||||
unmarshalReader(r, v.config)
|
||||
}
|
||||
|
||||
// make directories for testing
|
||||
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 +259,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 +279,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 +297,7 @@ func (s *stringValue) Type() string {
|
||||
}
|
||||
|
||||
func (s *stringValue) String() string {
|
||||
return fmt.Sprintf("%s", *s)
|
||||
return string(*s)
|
||||
}
|
||||
|
||||
func TestBasics(t *testing.T) {
|
||||
@@ -256,6 +307,42 @@ func TestBasics(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSearchInPath(t *testing.T) {
|
||||
filename := ".dotfilenoext"
|
||||
path := "/tmp"
|
||||
file := filepath.Join(path, filename)
|
||||
SetConfigName(filename)
|
||||
AddConfigPath(path)
|
||||
_, createErr := v.fs.Create(file)
|
||||
defer func() {
|
||||
_ = v.fs.Remove(file)
|
||||
}()
|
||||
assert.NoError(t, createErr)
|
||||
filename, err := v.getConfigFile()
|
||||
assert.Equal(t, file, filename)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSearchInPath_FilesOnly(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
err := fs.Mkdir("/tmp/config", 0777)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = fs.Create("/tmp/config/config.yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
v := New()
|
||||
|
||||
v.SetFs(fs)
|
||||
v.AddConfigPath("/tmp")
|
||||
v.AddConfigPath("/tmp/config")
|
||||
|
||||
filename, err := v.getConfigFile()
|
||||
assert.Equal(t, "/tmp/config/config.yaml", filename)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
SetDefault("age", 45)
|
||||
assert.Equal(t, 45, Get("age"))
|
||||
@@ -342,6 +429,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"))
|
||||
@@ -353,6 +445,11 @@ func TestHCL(t *testing.T) {
|
||||
assert.NotEqual(t, "cronut", Get("type"))
|
||||
}
|
||||
|
||||
func TestIni(t *testing.T) {
|
||||
initIni()
|
||||
assert.Equal(t, "ini", Get("default.name"))
|
||||
}
|
||||
|
||||
func TestRemotePrecedence(t *testing.T) {
|
||||
initJSON()
|
||||
|
||||
@@ -385,7 +482,6 @@ func TestEnv(t *testing.T) {
|
||||
AutomaticEnv()
|
||||
|
||||
assert.Equal(t, "crunk", Get("name"))
|
||||
|
||||
}
|
||||
|
||||
func TestEmptyEnv(t *testing.T) {
|
||||
@@ -394,7 +490,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 +509,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", "")
|
||||
|
||||
@@ -467,15 +569,122 @@ func TestSetEnvKeyReplacer(t *testing.T) {
|
||||
assert.Equal(t, "30s", Get("refresh-interval"))
|
||||
}
|
||||
|
||||
func TestEnvKeyReplacer(t *testing.T) {
|
||||
v := NewWithOptions(EnvKeyReplacer(strings.NewReplacer("-", "_")))
|
||||
|
||||
v.AutomaticEnv()
|
||||
_ = os.Setenv("REFRESH_INTERVAL", "30s")
|
||||
|
||||
assert.Equal(t, "30s", v.Get("refresh-interval"))
|
||||
}
|
||||
|
||||
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",
|
||||
"author.bio",
|
||||
"author.e-mail",
|
||||
"author.github",
|
||||
"author.name",
|
||||
"newkey",
|
||||
"owner.organization",
|
||||
"owner.dob",
|
||||
"owner.bio",
|
||||
"name",
|
||||
"beard",
|
||||
"ppu",
|
||||
"batters.batter",
|
||||
"hobbies",
|
||||
"clothing.jacket",
|
||||
"clothing.trousers",
|
||||
"default.import_path",
|
||||
"default.name",
|
||||
"default.version",
|
||||
"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",
|
||||
"author": map[string]interface{}{
|
||||
"e-mail": "fake@localhost",
|
||||
"github": "https://github.com/Unknown",
|
||||
"name": "Unknown",
|
||||
"bio": "Gopher.\nCoding addict.\nGood man.\n",
|
||||
},
|
||||
"ppu": 0.55,
|
||||
"eyes": "brown",
|
||||
"clothing": map[string]interface{}{
|
||||
"trousers": "denim",
|
||||
"jacket": "leather",
|
||||
"pants": map[string]interface{}{"size": "large"},
|
||||
},
|
||||
"default": map[string]interface{}{
|
||||
"import_path": "gopkg.in/ini.v1",
|
||||
"name": "ini",
|
||||
"version": "v1",
|
||||
},
|
||||
"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 +725,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 +741,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) {
|
||||
@@ -609,7 +839,6 @@ func TestBindPFlags(t *testing.T) {
|
||||
for name, expected := range mutatedTestValues {
|
||||
assert.Equal(t, expected, v.Get(name))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestBindPFlagsStringSlice(t *testing.T) {
|
||||
@@ -658,6 +887,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,10 +947,9 @@ 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"))
|
||||
|
||||
}
|
||||
|
||||
func TestBoundCaseSensitivity(t *testing.T) {
|
||||
@@ -698,7 +971,6 @@ func TestBoundCaseSensitivity(t *testing.T) {
|
||||
|
||||
BindPFlag("eYEs", flag)
|
||||
assert.Equal(t, "green", Get("eyes"))
|
||||
|
||||
}
|
||||
|
||||
func TestSizeInBytes(t *testing.T) {
|
||||
@@ -756,8 +1028,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{}{
|
||||
@@ -798,18 +1073,18 @@ func TestFindsNestedKeys(t *testing.T) {
|
||||
"owner.dob": dob,
|
||||
"beard": true,
|
||||
"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,
|
||||
},
|
||||
},
|
||||
@@ -818,10 +1093,8 @@ func TestFindsNestedKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
for key, expectedValue := range expected {
|
||||
|
||||
assert.Equal(t, expectedValue, v.Get(key))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestReadBufConfig(t *testing.T) {
|
||||
@@ -841,16 +1114,50 @@ func TestReadBufConfig(t *testing.T) {
|
||||
func TestIsSet(t *testing.T) {
|
||||
v := New()
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
/* config and defaults */
|
||||
v.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
v.SetDefault("clothing.shoes", "sneakers")
|
||||
|
||||
assert.True(t, v.IsSet("clothing"))
|
||||
assert.True(t, v.IsSet("clothing.jacket"))
|
||||
assert.False(t, v.IsSet("clothing.jackets"))
|
||||
assert.True(t, v.IsSet("clothing.shoes"))
|
||||
|
||||
/* state change */
|
||||
assert.False(t, v.IsSet("helloworld"))
|
||||
v.Set("helloworld", "fubar")
|
||||
assert.True(t, v.IsSet("helloworld"))
|
||||
|
||||
/* env */
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.BindEnv("eyes")
|
||||
v.BindEnv("foo")
|
||||
v.BindEnv("clothing.hat")
|
||||
v.BindEnv("clothing.hats")
|
||||
os.Setenv("FOO", "bar")
|
||||
os.Setenv("CLOTHING_HAT", "bowler")
|
||||
|
||||
assert.True(t, v.IsSet("eyes")) // in the config file
|
||||
assert.True(t, v.IsSet("foo")) // in the environment
|
||||
assert.True(t, v.IsSet("clothing.hat")) // in the environment
|
||||
assert.False(t, v.IsSet("clothing.hats")) // not defined
|
||||
|
||||
/* flags */
|
||||
flagset := pflag.NewFlagSet("testisset", pflag.ContinueOnError)
|
||||
flagset.Bool("foobaz", false, "foobaz")
|
||||
flagset.Bool("barbaz", false, "barbaz")
|
||||
foobaz, barbaz := flagset.Lookup("foobaz"), flagset.Lookup("barbaz")
|
||||
v.BindPFlag("foobaz", foobaz)
|
||||
v.BindPFlag("barbaz", barbaz)
|
||||
barbaz.Value.Set("true")
|
||||
barbaz.Changed = true // hack for pflag usage
|
||||
|
||||
assert.False(t, v.IsSet("foobaz"))
|
||||
assert.True(t, v.IsSet("barbaz"))
|
||||
}
|
||||
|
||||
func TestDirsSearch(t *testing.T) {
|
||||
|
||||
root, config, cleanup := initDirs(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -859,6 +1166,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,11 +1176,10 @@ 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) {
|
||||
|
||||
_, config, cleanup := initDirs(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -892,7 +1199,6 @@ func TestWrongDirsSearchNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWrongDirsSearchNotFoundForMerge(t *testing.T) {
|
||||
|
||||
_, config, cleanup := initDirs(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -1077,6 +1383,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:
|
||||
@@ -1113,10 +1456,73 @@ func TestWriteConfigYAML(t *testing.T) {
|
||||
assert.Equal(t, yamlWriteExpected, read)
|
||||
}
|
||||
|
||||
func TestSafeWriteConfig(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.AddConfigPath("/test")
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("yaml")
|
||||
require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample)))
|
||||
require.NoError(t, v.SafeWriteConfig())
|
||||
read, err := afero.ReadFile(fs, "/test/c.yaml")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, yamlWriteExpected, read)
|
||||
}
|
||||
|
||||
func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("yaml")
|
||||
require.EqualError(t, v.SafeWriteConfig(), "missing configuration for 'configPath'")
|
||||
}
|
||||
|
||||
func TestSafeWriteConfigWithExistingFile(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
fs.Create("/test/c.yaml")
|
||||
v.SetFs(fs)
|
||||
v.AddConfigPath("/test")
|
||||
v.SetConfigName("c")
|
||||
v.SetConfigType("yaml")
|
||||
err := v.SafeWriteConfig()
|
||||
require.Error(t, err)
|
||||
_, ok := err.(ConfigFileAlreadyExistsError)
|
||||
assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
|
||||
}
|
||||
|
||||
func TestSafeWriteAsConfig(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
v.SetFs(fs)
|
||||
err := v.ReadConfig(bytes.NewBuffer(yamlExample))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, v.SafeWriteConfigAs("/test/c.yaml"))
|
||||
if _, err = afero.ReadFile(fs, "/test/c.yaml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeWriteConfigAsWithExistingFile(t *testing.T) {
|
||||
v := New()
|
||||
fs := afero.NewMemMapFs()
|
||||
fs.Create("/test/c.yaml")
|
||||
v.SetFs(fs)
|
||||
err := v.SafeWriteConfigAs("/test/c.yaml")
|
||||
require.Error(t, err)
|
||||
_, ok := err.(ConfigFileAlreadyExistsError)
|
||||
assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
|
||||
}
|
||||
|
||||
var yamlMergeExampleTgt = []byte(`
|
||||
hello:
|
||||
pop: 37890
|
||||
lagrenum: 765432101234567
|
||||
largenum: 765432101234567
|
||||
num2pow63: 9223372036854775808
|
||||
world:
|
||||
- us
|
||||
- uk
|
||||
@@ -1127,10 +1533,13 @@ hello:
|
||||
var yamlMergeExampleSrc = []byte(`
|
||||
hello:
|
||||
pop: 45000
|
||||
lagrenum: 7654321001234567
|
||||
largenum: 7654321001234567
|
||||
universe:
|
||||
- mw
|
||||
- ad
|
||||
ints:
|
||||
- 1
|
||||
- 2
|
||||
fu: bar
|
||||
`)
|
||||
|
||||
@@ -1149,8 +1558,20 @@ func TestMergeConfig(t *testing.T) {
|
||||
t.Fatalf("pop != 37890, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt64("hello.lagrenum"); pop != int64(765432101234567) {
|
||||
t.Fatalf("int64 lagrenum != 765432101234567, = %d", pop)
|
||||
if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) {
|
||||
t.Fatalf("int64 largenum != 765432101234567, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetUint("hello.pop"); pop != 37890 {
|
||||
t.Fatalf("uint pop != 37890, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetUint32("hello.pop"); pop != 37890 {
|
||||
t.Fatalf("uint32 pop != 37890, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 {
|
||||
t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop)
|
||||
}
|
||||
|
||||
if world := v.GetStringSlice("hello.world"); len(world) != 4 {
|
||||
@@ -1173,8 +1594,8 @@ func TestMergeConfig(t *testing.T) {
|
||||
t.Fatalf("pop != 45000, = %d", pop)
|
||||
}
|
||||
|
||||
if pop := v.GetInt64("hello.lagrenum"); pop != int64(7654321001234567) {
|
||||
t.Fatalf("int64 lagrenum != 7654321001234567, = %d", pop)
|
||||
if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) {
|
||||
t.Fatalf("int64 largenum != 7654321001234567, = %d", pop)
|
||||
}
|
||||
|
||||
if world := v.GetStringSlice("hello.world"); len(world) != 4 {
|
||||
@@ -1185,6 +1606,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)
|
||||
}
|
||||
@@ -1225,6 +1650,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)
|
||||
}
|
||||
@@ -1238,7 +1667,7 @@ func TestMergeConfigMap(t *testing.T) {
|
||||
}
|
||||
|
||||
assert := func(i int) {
|
||||
large := v.GetInt("hello.lagrenum")
|
||||
large := v.GetInt64("hello.largenum")
|
||||
pop := v.GetInt("hello.pop")
|
||||
if large != 765432101234567 {
|
||||
t.Fatal("Got large num:", large)
|
||||
@@ -1252,8 +1681,11 @@ func TestMergeConfigMap(t *testing.T) {
|
||||
assert(37890)
|
||||
|
||||
update := map[string]interface{}{
|
||||
"hello": map[string]interface{}{
|
||||
"pop": 1234,
|
||||
"Hello": map[string]interface{}{
|
||||
"Pop": 1234,
|
||||
},
|
||||
"World": map[interface{}]interface{}{
|
||||
"Rock": 345,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1261,8 +1693,11 @@ func TestMergeConfigMap(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert(1234)
|
||||
if rock := v.GetInt("world.rock"); rock != 345 {
|
||||
t.Fatal("Got rock:", rock)
|
||||
}
|
||||
|
||||
assert(1234)
|
||||
}
|
||||
|
||||
func TestUnmarshalingWithAliases(t *testing.T) {
|
||||
@@ -1301,7 +1736,6 @@ func TestSetConfigNameClearsFileCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShadowedNestedValue(t *testing.T) {
|
||||
|
||||
config := `name: steve
|
||||
clothing:
|
||||
jacket: leather
|
||||
@@ -1481,7 +1915,6 @@ func doTestCaseInsensitive(t *testing.T, typ, config string) {
|
||||
assert.Equal(t, 3, cast.ToInt(Get("ef.ijk")))
|
||||
assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no")))
|
||||
assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q")))
|
||||
|
||||
}
|
||||
|
||||
func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) {
|
||||
@@ -1586,7 +2019,90 @@ func TestWatchFile(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "baz", v.Get("foo"))
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var yamlExampleWithDot = []byte(`Hacker: true
|
||||
name: steve
|
||||
hobbies:
|
||||
- skateboarding
|
||||
- snowboarding
|
||||
- go
|
||||
clothing:
|
||||
jacket: leather
|
||||
trousers: denim
|
||||
pants:
|
||||
size: large
|
||||
age: 35
|
||||
eyes : brown
|
||||
beard: true
|
||||
emails:
|
||||
steve@hacker.com:
|
||||
created: 01/02/03
|
||||
active: true
|
||||
`)
|
||||
|
||||
func TestKeyDelimiter(t *testing.T) {
|
||||
v := NewWithOptions(KeyDelimiter("::"))
|
||||
v.SetConfigType("yaml")
|
||||
r := strings.NewReader(string(yamlExampleWithDot))
|
||||
|
||||
err := v.unmarshalReader(r, v.config)
|
||||
require.NoError(t, err)
|
||||
|
||||
values := map[string]interface{}{
|
||||
"image": map[string]interface{}{
|
||||
"repository": "someImage",
|
||||
"tag": "1.0.0",
|
||||
},
|
||||
"ingress": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{
|
||||
"traefik.frontend.rule.type": "PathPrefix",
|
||||
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
v.SetDefault("charts::values", values)
|
||||
|
||||
assert.Equal(t, "leather", v.GetString("clothing::jacket"))
|
||||
assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created"))
|
||||
|
||||
type config struct {
|
||||
Charts struct {
|
||||
Values map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
expected := config{
|
||||
Charts: struct {
|
||||
Values map[string]interface{}
|
||||
}{
|
||||
Values: values,
|
||||
},
|
||||
}
|
||||
|
||||
var actual config
|
||||
|
||||
assert.NoError(t, v.Unmarshal(&actual))
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func BenchmarkGetBool(b *testing.B) {
|
||||
@@ -1613,7 +2129,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"
|
||||
|
||||
Reference in New Issue
Block a user