2022-07-07 20:11:50 +00:00
// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// 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.
// Each item takes precedence over the item below it:
// overrides
// flag
// env
// config
// key/value store
// default
package viper
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
2024-10-16 10:54:40 +00:00
"strconv"
2022-07-07 20:11:50 +00:00
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/mitchellh/mapstructure"
2024-10-16 10:54:40 +00:00
slog "github.com/sagikazarmark/slog-shim"
2022-07-07 20:11:50 +00:00
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/pflag"
2024-10-16 10:54:40 +00:00
"github.com/spf13/viper/internal/encoding"
"github.com/spf13/viper/internal/encoding/dotenv"
"github.com/spf13/viper/internal/encoding/hcl"
"github.com/spf13/viper/internal/encoding/ini"
"github.com/spf13/viper/internal/encoding/javaproperties"
"github.com/spf13/viper/internal/encoding/json"
"github.com/spf13/viper/internal/encoding/toml"
"github.com/spf13/viper/internal/encoding/yaml"
"github.com/spf13/viper/internal/features"
2022-07-07 20:11:50 +00:00
)
// ConfigMarshalError happens when failing to marshal the configuration.
type ConfigMarshalError struct {
err error
}
// Error returns the formatted configuration error.
func ( e ConfigMarshalError ) Error ( ) string {
return fmt . Sprintf ( "While marshaling config: %s" , e . err . Error ( ) )
}
var v * Viper
type RemoteResponse struct {
Value [ ] byte
Error error
}
func init ( ) {
v = New ( )
}
type remoteConfigFactory interface {
Get ( rp RemoteProvider ) ( io . Reader , error )
Watch ( rp RemoteProvider ) ( io . Reader , error )
WatchChannel ( rp RemoteProvider ) ( <- chan * RemoteResponse , chan bool )
}
2024-10-16 10:54:40 +00:00
// RemoteConfig is optional, see the remote package.
2022-07-07 20:11:50 +00:00
var RemoteConfig remoteConfigFactory
// UnsupportedConfigError denotes encountering an unsupported
// configuration filetype.
type UnsupportedConfigError string
// Error returns the formatted configuration error.
func ( str UnsupportedConfigError ) Error ( ) string {
return fmt . Sprintf ( "Unsupported Config Type %q" , string ( str ) )
}
// UnsupportedRemoteProviderError denotes encountering an unsupported remote
// provider. Currently only etcd and Consul are supported.
type UnsupportedRemoteProviderError string
// Error returns the formatted remote provider error.
func ( str UnsupportedRemoteProviderError ) Error ( ) string {
return fmt . Sprintf ( "Unsupported Remote Provider Type %q" , string ( str ) )
}
// RemoteConfigError denotes encountering an error while trying to
// pull the configuration from the remote provider.
type RemoteConfigError string
2024-10-16 10:54:40 +00:00
// Error returns the formatted remote provider error.
2022-07-07 20:11:50 +00:00
func ( rce RemoteConfigError ) Error ( ) string {
return fmt . Sprintf ( "Remote Configurations Error: %s" , string ( rce ) )
}
// ConfigFileNotFoundError denotes failing to find configuration file.
type ConfigFileNotFoundError struct {
name , locations string
}
// Error returns the formatted configuration error.
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
2024-10-16 10:54:40 +00:00
// mapstructure.DecoderConfig options.
2022-07-07 20:11:50 +00:00
type DecoderConfigOption func ( * mapstructure . DecoderConfig )
// DecodeHook returns a DecoderConfigOption which overrides the default
// DecoderConfig.DecodeHook value, the default is:
//
2024-10-16 10:54:40 +00:00
// mapstructure.ComposeDecodeHookFunc(
// mapstructure.StringToTimeDurationHookFunc(),
// mapstructure.StringToSliceHookFunc(","),
// )
2022-07-07 20:11:50 +00:00
func DecodeHook ( hook mapstructure . DecodeHookFunc ) DecoderConfigOption {
return func ( c * mapstructure . DecoderConfig ) {
c . DecodeHook = hook
}
}
// Viper is a prioritized configuration registry. It
// maintains a set of configuration sources, fetches
// values to populate those, and provides them according
// to the source's priority.
// The priority of the sources is the following:
// 1. overrides
// 2. flags
// 3. env. variables
// 4. config file
// 5. key/value store
// 6. defaults
//
// For example, if values from the following sources were loaded:
//
2024-10-16 10:54:40 +00:00
// Defaults : {
// "secret": "",
// "user": "default",
// "endpoint": "https://localhost"
// }
// Config : {
// "user": "root"
// "secret": "defaultsecret"
// }
// Env : {
// "secret": "somesecretkey"
// }
2022-07-07 20:11:50 +00:00
//
// The resulting config will have the following values:
//
// {
// "secret": "somesecretkey",
// "user": "root",
// "endpoint": "https://localhost"
// }
2024-10-16 10:54:40 +00:00
//
// Note: Vipers are not safe for concurrent Get() and Set() operations.
2022-07-07 20:11:50 +00:00
type Viper struct {
// Delimiter that separates a list of keys
// used to access a nested value in one go
keyDelim string
// A set of paths to look for the config file in
configPaths [ ] string
// The filesystem to read config from.
fs afero . Fs
// A set of remote providers to search for the configuration
remoteProviders [ ] * defaultRemoteProvider
// Name of file to look for inside the path
configName string
configFile string
configType string
configPermissions os . FileMode
envPrefix string
2024-10-16 10:54:40 +00:00
// Specific commands for ini parsing
iniLoadOptions ini . LoadOptions
2022-07-07 20:11:50 +00:00
automaticEnvApplied bool
envKeyReplacer StringReplacer
allowEmptyEnv bool
2024-10-16 10:54:40 +00:00
parents [ ] string
config map [ string ] any
override map [ string ] any
defaults map [ string ] any
kvstore map [ string ] any
2022-07-07 20:11:50 +00:00
pflags map [ string ] FlagValue
2024-10-16 10:54:40 +00:00
env map [ string ] [ ] string
2022-07-07 20:11:50 +00:00
aliases map [ string ] string
typeByDefValue bool
onConfigChange func ( fsnotify . Event )
2024-10-16 10:54:40 +00:00
logger * slog . Logger
// TODO: should probably be protected with a mutex
encoderRegistry * encoding . EncoderRegistry
decoderRegistry * encoding . DecoderRegistry
2022-07-07 20:11:50 +00:00
}
// New returns an initialized Viper instance.
func New ( ) * Viper {
v := new ( Viper )
v . keyDelim = "."
v . configName = "config"
2024-10-16 10:54:40 +00:00
v . configPermissions = os . FileMode ( 0 o644 )
2022-07-07 20:11:50 +00:00
v . fs = afero . NewOsFs ( )
2024-10-16 10:54:40 +00:00
v . config = make ( map [ string ] any )
v . parents = [ ] string { }
v . override = make ( map [ string ] any )
v . defaults = make ( map [ string ] any )
v . kvstore = make ( map [ string ] any )
2022-07-07 20:11:50 +00:00
v . pflags = make ( map [ string ] FlagValue )
2024-10-16 10:54:40 +00:00
v . env = make ( map [ string ] [ ] string )
2022-07-07 20:11:50 +00:00
v . aliases = make ( map [ string ] string )
v . typeByDefValue = false
2024-10-16 10:54:40 +00:00
v . logger = slog . New ( & discardHandler { } )
v . resetEncoding ( )
2022-07-07 20:11:50 +00:00
return v
}
// 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 )
}
2024-10-16 10:54:40 +00:00
v . resetEncoding ( )
2022-07-07 20:11:50 +00:00
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 ( )
2024-10-16 10:54:40 +00:00
SupportedExts = [ ] string { "json" , "toml" , "yaml" , "yml" , "properties" , "props" , "prop" , "hcl" , "tfvars" , "dotenv" , "env" , "ini" }
SupportedRemoteProviders = [ ] string { "etcd" , "etcd3" , "consul" , "firestore" , "nats" }
}
// TODO: make this lazy initialization instead.
func ( v * Viper ) resetEncoding ( ) {
encoderRegistry := encoding . NewEncoderRegistry ( )
decoderRegistry := encoding . NewDecoderRegistry ( )
{
codec := yaml . Codec { }
encoderRegistry . RegisterEncoder ( "yaml" , codec )
decoderRegistry . RegisterDecoder ( "yaml" , codec )
encoderRegistry . RegisterEncoder ( "yml" , codec )
decoderRegistry . RegisterDecoder ( "yml" , codec )
}
{
codec := json . Codec { }
encoderRegistry . RegisterEncoder ( "json" , codec )
decoderRegistry . RegisterDecoder ( "json" , codec )
}
{
codec := toml . Codec { }
encoderRegistry . RegisterEncoder ( "toml" , codec )
decoderRegistry . RegisterDecoder ( "toml" , codec )
}
{
codec := hcl . Codec { }
encoderRegistry . RegisterEncoder ( "hcl" , codec )
decoderRegistry . RegisterDecoder ( "hcl" , codec )
encoderRegistry . RegisterEncoder ( "tfvars" , codec )
decoderRegistry . RegisterDecoder ( "tfvars" , codec )
}
{
codec := ini . Codec {
KeyDelimiter : v . keyDelim ,
LoadOptions : v . iniLoadOptions ,
}
encoderRegistry . RegisterEncoder ( "ini" , codec )
decoderRegistry . RegisterDecoder ( "ini" , codec )
}
{
codec := & javaproperties . Codec {
KeyDelimiter : v . keyDelim ,
}
encoderRegistry . RegisterEncoder ( "properties" , codec )
decoderRegistry . RegisterDecoder ( "properties" , codec )
encoderRegistry . RegisterEncoder ( "props" , codec )
decoderRegistry . RegisterDecoder ( "props" , codec )
encoderRegistry . RegisterEncoder ( "prop" , codec )
decoderRegistry . RegisterDecoder ( "prop" , codec )
}
{
codec := & dotenv . Codec { }
encoderRegistry . RegisterEncoder ( "dotenv" , codec )
decoderRegistry . RegisterDecoder ( "dotenv" , codec )
encoderRegistry . RegisterEncoder ( "env" , codec )
decoderRegistry . RegisterDecoder ( "env" , codec )
}
v . encoderRegistry = encoderRegistry
v . decoderRegistry = decoderRegistry
2022-07-07 20:11:50 +00:00
}
type defaultRemoteProvider struct {
provider string
endpoint string
path string
secretKeyring string
}
func ( rp defaultRemoteProvider ) Provider ( ) string {
return rp . provider
}
func ( rp defaultRemoteProvider ) Endpoint ( ) string {
return rp . endpoint
}
func ( rp defaultRemoteProvider ) Path ( ) string {
return rp . path
}
func ( rp defaultRemoteProvider ) SecretKeyring ( ) string {
return rp . secretKeyring
}
// RemoteProvider stores the configuration necessary
// to connect to a remote key/value store.
// Optional secretKeyring to unencrypt encrypted values
// can be provided.
type RemoteProvider interface {
Provider ( ) string
Endpoint ( ) string
Path ( ) string
SecretKeyring ( ) string
}
// SupportedExts are universally supported extensions.
2024-10-16 10:54:40 +00:00
var SupportedExts = [ ] string { "json" , "toml" , "yaml" , "yml" , "properties" , "props" , "prop" , "hcl" , "tfvars" , "dotenv" , "env" , "ini" }
2022-07-07 20:11:50 +00:00
// SupportedRemoteProviders are universally supported remote providers.
2024-10-16 10:54:40 +00:00
var SupportedRemoteProviders = [ ] string { "etcd" , "etcd3" , "consul" , "firestore" , "nats" }
2022-07-07 20:11:50 +00:00
2024-10-16 10:54:40 +00:00
// OnConfigChange sets the event handler that is called when a config file changes.
2022-07-07 20:11:50 +00:00
func OnConfigChange ( run func ( in fsnotify . Event ) ) { v . OnConfigChange ( run ) }
2024-10-16 10:54:40 +00:00
// OnConfigChange sets the event handler that is called when a config file changes.
2022-07-07 20:11:50 +00:00
func ( v * Viper ) OnConfigChange ( run func ( in fsnotify . Event ) ) {
v . onConfigChange = run
}
2024-10-16 10:54:40 +00:00
// WatchConfig starts watching a config file for changes.
2022-07-07 20:11:50 +00:00
func WatchConfig ( ) { v . WatchConfig ( ) }
2024-10-16 10:54:40 +00:00
// WatchConfig starts watching a config file for changes.
2022-07-07 20:11:50 +00:00
func ( v * Viper ) WatchConfig ( ) {
initWG := sync . WaitGroup { }
initWG . Add ( 1 )
go func ( ) {
watcher , err := fsnotify . NewWatcher ( )
if err != nil {
2024-10-16 10:54:40 +00:00
v . logger . Error ( fmt . Sprintf ( "failed to create watcher: %s" , err ) )
os . Exit ( 1 )
2022-07-07 20:11:50 +00:00
}
defer watcher . Close ( )
// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
filename , err := v . getConfigFile ( )
if err != nil {
2024-10-16 10:54:40 +00:00
v . logger . Error ( fmt . Sprintf ( "get config file: %s" , err ) )
2022-07-07 20:11:50 +00:00
initWG . Done ( )
return
}
configFile := filepath . Clean ( filename )
configDir , _ := filepath . Split ( configFile )
realConfigFile , _ := filepath . EvalSymlinks ( filename )
eventsWG := sync . WaitGroup { }
eventsWG . Add ( 1 )
go func ( ) {
for {
select {
case event , ok := <- watcher . Events :
if ! ok { // 'Events' channel is closed
eventsWG . Done ( )
return
}
currentConfigFile , _ := filepath . EvalSymlinks ( filename )
// we only care about the config file with the following cases:
// 1 - if the config file was modified or created
// 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)
if ( filepath . Clean ( event . Name ) == configFile &&
2024-10-16 10:54:40 +00:00
( event . Has ( fsnotify . Write ) || event . Has ( fsnotify . Create ) ) ) ||
2022-07-07 20:11:50 +00:00
( currentConfigFile != "" && currentConfigFile != realConfigFile ) {
realConfigFile = currentConfigFile
err := v . ReadInConfig ( )
if err != nil {
2024-10-16 10:54:40 +00:00
v . logger . Error ( fmt . Sprintf ( "read config file: %s" , err ) )
2022-07-07 20:11:50 +00:00
}
if v . onConfigChange != nil {
v . onConfigChange ( event )
}
2024-10-16 10:54:40 +00:00
} else if filepath . Clean ( event . Name ) == configFile && event . Has ( fsnotify . Remove ) {
2022-07-07 20:11:50 +00:00
eventsWG . Done ( )
return
}
case err , ok := <- watcher . Errors :
if ok { // 'Errors' channel is not closed
2024-10-16 10:54:40 +00:00
v . logger . Error ( fmt . Sprintf ( "watcher error: %s" , err ) )
2022-07-07 20:11:50 +00:00
}
eventsWG . Done ( )
return
}
}
} ( )
watcher . Add ( configDir )
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
}
// SetConfigFile explicitly defines the path, name and extension of the config file.
// Viper will use this and not check any of the config paths.
func SetConfigFile ( in string ) { v . SetConfigFile ( in ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetConfigFile ( in string ) {
if in != "" {
v . configFile = in
}
}
// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use.
// E.g. if your prefix is "spf", the env registry will look for env
// variables that start with "SPF_".
func SetEnvPrefix ( in string ) { v . SetEnvPrefix ( in ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetEnvPrefix ( in string ) {
if in != "" {
v . envPrefix = in
}
}
2024-10-16 10:54:40 +00:00
func GetEnvPrefix ( ) string { return v . GetEnvPrefix ( ) }
func ( v * Viper ) GetEnvPrefix ( ) string {
return v . envPrefix
}
2022-07-07 20:11:50 +00:00
func ( v * Viper ) mergeWithEnvPrefix ( in string ) string {
if v . envPrefix != "" {
return strings . ToUpper ( v . envPrefix + "_" + in )
}
return strings . ToUpper ( in )
}
// AllowEmptyEnv tells Viper to consider set,
// but empty environment variables as valid values instead of falling back.
// For backward compatibility reasons this is false by default.
func AllowEmptyEnv ( allowEmptyEnv bool ) { v . AllowEmptyEnv ( allowEmptyEnv ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) AllowEmptyEnv ( allowEmptyEnv bool ) {
v . allowEmptyEnv = allowEmptyEnv
}
// TODO: should getEnv logic be moved into find(). Can generalize the use of
// rewriting keys many things, Ex: Get('someKey') -> some_key
// (camel case to snake case for JSON keys perhaps)
// getEnv is a wrapper around os.Getenv which replaces characters in the original
// key. This allows env vars which have different keys than the config object
// keys.
func ( v * Viper ) getEnv ( key string ) ( string , bool ) {
if v . envKeyReplacer != nil {
key = v . envKeyReplacer . Replace ( key )
}
val , ok := os . LookupEnv ( key )
return val , ok && ( v . allowEmptyEnv || val != "" )
}
// ConfigFileUsed returns the file used to populate the config registry.
func ConfigFileUsed ( ) string { return v . ConfigFileUsed ( ) }
func ( v * Viper ) ConfigFileUsed ( ) string { return v . configFile }
// AddConfigPath adds a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths.
func AddConfigPath ( in string ) { v . AddConfigPath ( in ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) AddConfigPath ( in string ) {
if in != "" {
2024-10-16 10:54:40 +00:00
absin := absPathify ( v . logger , in )
v . logger . Info ( "adding path to search paths" , "path" , absin )
2022-07-07 20:11:50 +00:00
if ! stringInSlice ( absin , v . configPaths ) {
v . configPaths = append ( v . configPaths , absin )
}
}
}
// AddRemoteProvider adds a remote configuration source.
// Remote Providers are searched in the order they are added.
2024-10-16 10:54:40 +00:00
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
// endpoint is the url. etcd requires http://ip:port, consul requires ip:port, nats requires nats://ip:port
2022-07-07 20:11:50 +00:00
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
// you should set path to /configs and set config name (SetConfigName()) to
2024-10-16 10:54:40 +00:00
// "myapp".
2022-07-07 20:11:50 +00:00
func AddRemoteProvider ( provider , endpoint , path string ) error {
return v . AddRemoteProvider ( provider , endpoint , path )
}
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) AddRemoteProvider ( provider , endpoint , path string ) error {
if ! stringInSlice ( provider , SupportedRemoteProviders ) {
return UnsupportedRemoteProviderError ( provider )
}
if provider != "" && endpoint != "" {
2024-10-16 10:54:40 +00:00
v . logger . Info ( "adding remote provider" , "provider" , provider , "endpoint" , endpoint )
2022-07-07 20:11:50 +00:00
rp := & defaultRemoteProvider {
endpoint : endpoint ,
provider : provider ,
path : path ,
}
if ! v . providerPathExists ( rp ) {
v . remoteProviders = append ( v . remoteProviders , rp )
}
}
return nil
}
// AddSecureRemoteProvider adds a remote configuration source.
// Secure Remote Providers are searched in the order they are added.
2024-10-16 10:54:40 +00:00
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
2022-07-07 20:11:50 +00:00
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
// you should set path to /configs and set config name (SetConfigName()) to
2024-10-16 10:54:40 +00:00
// "myapp".
// Secure Remote Providers are implemented with github.com/sagikazarmark/crypt.
2022-07-07 20:11:50 +00:00
func AddSecureRemoteProvider ( provider , endpoint , path , secretkeyring string ) error {
return v . AddSecureRemoteProvider ( provider , endpoint , path , secretkeyring )
}
func ( v * Viper ) AddSecureRemoteProvider ( provider , endpoint , path , secretkeyring string ) error {
if ! stringInSlice ( provider , SupportedRemoteProviders ) {
return UnsupportedRemoteProviderError ( provider )
}
if provider != "" && endpoint != "" {
2024-10-16 10:54:40 +00:00
v . logger . Info ( "adding remote provider" , "provider" , provider , "endpoint" , endpoint )
2022-07-07 20:11:50 +00:00
rp := & defaultRemoteProvider {
endpoint : endpoint ,
provider : provider ,
path : path ,
secretKeyring : secretkeyring ,
}
if ! v . providerPathExists ( rp ) {
v . remoteProviders = append ( v . remoteProviders , rp )
}
}
return nil
}
func ( v * Viper ) providerPathExists ( p * defaultRemoteProvider ) bool {
for _ , y := range v . remoteProviders {
if reflect . DeepEqual ( y , p ) {
return true
}
}
return false
}
// searchMap recursively searches for a value for path in source map.
// Returns nil if not found.
// Note: This assumes that the path entries and map keys are lower cased.
2024-10-16 10:54:40 +00:00
func ( v * Viper ) searchMap ( source map [ string ] any , path [ ] string ) any {
2022-07-07 20:11:50 +00:00
if len ( path ) == 0 {
return source
}
next , ok := source [ path [ 0 ] ]
if ok {
// Fast path
if len ( path ) == 1 {
return next
}
// Nested case
2024-10-16 10:54:40 +00:00
switch next := next . ( type ) {
case map [ any ] any :
2022-07-07 20:11:50 +00:00
return v . searchMap ( cast . ToStringMap ( next ) , path [ 1 : ] )
2024-10-16 10:54:40 +00:00
case map [ string ] any :
2022-07-07 20:11:50 +00:00
// Type assertion is safe here since it is only reached
// if the type of `next` is the same as the type being asserted
2024-10-16 10:54:40 +00:00
return v . searchMap ( next , path [ 1 : ] )
2022-07-07 20:11:50 +00:00
default :
// got a value but nested key expected, return "nil" for not found
return nil
}
}
return nil
}
2024-10-16 10:54:40 +00:00
// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice.
2022-07-07 20:11:50 +00:00
//
2024-10-16 10:54:40 +00:00
// While searchMap() considers each path element as a single map key or slice index, this
2022-07-07 20:11:50 +00:00
// function searches for, and prioritizes, merged path elements.
// e.g., if in the source, "foo" is defined with a sub-key "bar", and "foo.bar"
// is also defined, this latter value is returned for path ["foo", "bar"].
//
// This should be useful only at config level (other maps may not contain dots
// in their keys).
//
// Note: This assumes that the path entries and map keys are lower cased.
2024-10-16 10:54:40 +00:00
func ( v * Viper ) searchIndexableWithPathPrefixes ( source any , path [ ] string ) any {
2022-07-07 20:11:50 +00:00
if len ( path ) == 0 {
return source
}
// search for path prefixes, starting from the longest one
for i := len ( path ) ; i > 0 ; i -- {
prefixKey := strings . ToLower ( strings . Join ( path [ 0 : i ] , v . keyDelim ) )
2024-10-16 10:54:40 +00:00
var val any
switch sourceIndexable := source . ( type ) {
case [ ] any :
val = v . searchSliceWithPathPrefixes ( sourceIndexable , prefixKey , i , path )
case map [ string ] any :
val = v . searchMapWithPathPrefixes ( sourceIndexable , prefixKey , i , path )
}
if val != nil {
return val
2022-07-07 20:11:50 +00:00
}
}
// not found
return nil
}
2024-10-16 10:54:40 +00:00
// searchSliceWithPathPrefixes searches for a value for path in sourceSlice
//
// This function is part of the searchIndexableWithPathPrefixes recurring search and
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
func ( v * Viper ) searchSliceWithPathPrefixes (
sourceSlice [ ] any ,
prefixKey string ,
pathIndex int ,
path [ ] string ,
) any {
// if the prefixKey is not a number or it is out of bounds of the slice
index , err := strconv . Atoi ( prefixKey )
if err != nil || len ( sourceSlice ) <= index {
return nil
}
next := sourceSlice [ index ]
// Fast path
if pathIndex == len ( path ) {
return next
}
switch n := next . ( type ) {
case map [ any ] any :
return v . searchIndexableWithPathPrefixes ( cast . ToStringMap ( n ) , path [ pathIndex : ] )
case map [ string ] any , [ ] any :
return v . searchIndexableWithPathPrefixes ( n , path [ pathIndex : ] )
default :
// got a value but nested key expected, do nothing and look for next prefix
}
// not found
return nil
}
// searchMapWithPathPrefixes searches for a value for path in sourceMap
//
// This function is part of the searchIndexableWithPathPrefixes recurring search and
// should not be called directly from functions other than searchIndexableWithPathPrefixes.
func ( v * Viper ) searchMapWithPathPrefixes (
sourceMap map [ string ] any ,
prefixKey string ,
pathIndex int ,
path [ ] string ,
) any {
next , ok := sourceMap [ prefixKey ]
if ! ok {
return nil
}
// Fast path
if pathIndex == len ( path ) {
return next
}
// Nested case
switch n := next . ( type ) {
case map [ any ] any :
return v . searchIndexableWithPathPrefixes ( cast . ToStringMap ( n ) , path [ pathIndex : ] )
case map [ string ] any , [ ] any :
return v . searchIndexableWithPathPrefixes ( n , path [ pathIndex : ] )
default :
// got a value but nested key expected, do nothing and look for next prefix
}
// not found
return nil
}
2022-07-07 20:11:50 +00:00
// isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere
// on its path in the map.
// e.g., if "foo.bar" has a value in the given map, it “shadows”
2024-10-16 10:54:40 +00:00
//
// "foo.bar.baz" in a lower-priority map
func ( v * Viper ) isPathShadowedInDeepMap ( path [ ] string , m map [ string ] any ) string {
var parentVal any
2022-07-07 20:11:50 +00:00
for i := 1 ; i < len ( path ) ; i ++ {
parentVal = v . searchMap ( m , path [ 0 : i ] )
if parentVal == nil {
// not found, no need to add more path elements
return ""
}
switch parentVal . ( type ) {
2024-10-16 10:54:40 +00:00
case map [ any ] any :
2022-07-07 20:11:50 +00:00
continue
2024-10-16 10:54:40 +00:00
case map [ string ] any :
2022-07-07 20:11:50 +00:00
continue
default :
// parentVal is a regular value which shadows "path"
return strings . Join ( path [ 0 : i ] , v . keyDelim )
}
}
return ""
}
// isPathShadowedInFlatMap makes sure the given path is not shadowed somewhere
// in a sub-path of the map.
// e.g., if "foo.bar" has a value in the given map, it “shadows”
2024-10-16 10:54:40 +00:00
//
// "foo.bar.baz" in a lower-priority map
func ( v * Viper ) isPathShadowedInFlatMap ( path [ ] string , mi any ) string {
2022-07-07 20:11:50 +00:00
// unify input map
var m map [ string ] interface { }
2024-10-16 10:54:40 +00:00
switch miv := mi . ( type ) {
case map [ string ] string :
m = castMapStringToMapInterface ( miv )
case map [ string ] FlagValue :
m = castMapFlagToMapInterface ( miv )
2022-07-07 20:11:50 +00:00
default :
return ""
}
// scan paths
var parentKey string
for i := 1 ; i < len ( path ) ; i ++ {
parentKey = strings . Join ( path [ 0 : i ] , v . keyDelim )
if _ , ok := m [ parentKey ] ; ok {
return parentKey
}
}
return ""
}
// isPathShadowedInAutoEnv makes sure the given path is not shadowed somewhere
// in the environment, when automatic env is on.
// e.g., if "foo.bar" has a value in the environment, it “shadows”
2024-10-16 10:54:40 +00:00
//
// "foo.bar.baz" in a lower-priority map
2022-07-07 20:11:50 +00:00
func ( v * Viper ) isPathShadowedInAutoEnv ( path [ ] string ) string {
var parentKey string
for i := 1 ; i < len ( path ) ; i ++ {
parentKey = strings . Join ( path [ 0 : i ] , v . keyDelim )
if _ , ok := v . getEnv ( v . mergeWithEnvPrefix ( parentKey ) ) ; ok {
return parentKey
}
}
return ""
}
// SetTypeByDefaultValue enables or disables the inference of a key value's
// type when the Get function is used based upon a key's default value as
// opposed to the value returned based on the normal fetch logic.
//
// For example, if a key has a default value of []string{} and the same key
// is set via an environment variable to "a b c", a call to the Get function
// would return a string slice for the key if the key's type is inferred by
// the default value and the Get function would return:
//
2024-10-16 10:54:40 +00:00
// []string {"a", "b", "c"}
2022-07-07 20:11:50 +00:00
//
// Otherwise the Get function would return:
//
2024-10-16 10:54:40 +00:00
// "a b c"
2022-07-07 20:11:50 +00:00
func SetTypeByDefaultValue ( enable bool ) { v . SetTypeByDefaultValue ( enable ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetTypeByDefaultValue ( enable bool ) {
v . typeByDefValue = enable
}
// GetViper gets the global Viper instance.
func GetViper ( ) * Viper {
return v
}
// Get can retrieve any value given the key to use.
// Get is case-insensitive for a key.
// Get has the behavior of returning the value associated with the first
// place from where it is set. Viper will check in the following order:
// override, flag, env, config file, key/value store, default
//
// Get returns an interface. For a specific value use one of the Get____ methods.
2024-10-16 10:54:40 +00:00
func Get ( key string ) any { return v . Get ( key ) }
func ( v * Viper ) Get ( key string ) any {
2022-07-07 20:11:50 +00:00
lcaseKey := strings . ToLower ( key )
val := v . find ( lcaseKey , true )
if val == nil {
return nil
}
if v . typeByDefValue {
// TODO(bep) this branch isn't covered by a single test.
valType := val
path := strings . Split ( lcaseKey , v . keyDelim )
defVal := v . searchMap ( v . defaults , path )
if defVal != nil {
valType = defVal
}
switch valType . ( type ) {
case bool :
return cast . ToBool ( val )
case string :
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 :
return cast . ToFloat64 ( val )
case time . Time :
return cast . ToTime ( val )
case time . Duration :
return cast . ToDuration ( val )
case [ ] string :
return cast . ToStringSlice ( val )
case [ ] int :
return cast . ToIntSlice ( val )
2024-10-16 10:54:40 +00:00
case [ ] time . Duration :
return cast . ToDurationSlice ( val )
2022-07-07 20:11:50 +00:00
}
}
return val
}
// Sub returns new Viper instance representing a sub tree of this instance.
// Sub is case-insensitive for a key.
func Sub ( key string ) * Viper { return v . Sub ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) Sub ( key string ) * Viper {
subv := New ( )
data := v . Get ( key )
if data == nil {
return nil
}
if reflect . TypeOf ( data ) . Kind ( ) == reflect . Map {
2024-10-16 10:54:40 +00:00
subv . parents = append ( [ ] string ( nil ) , v . parents ... )
subv . parents = append ( subv . parents , strings . ToLower ( key ) )
subv . automaticEnvApplied = v . automaticEnvApplied
subv . envPrefix = v . envPrefix
subv . envKeyReplacer = v . envKeyReplacer
2022-07-07 20:11:50 +00:00
subv . config = cast . ToStringMap ( data )
return subv
}
return nil
}
// GetString returns the value associated with the key as a string.
func GetString ( key string ) string { return v . GetString ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetString ( key string ) string {
return cast . ToString ( v . Get ( key ) )
}
// GetBool returns the value associated with the key as a boolean.
func GetBool ( key string ) bool { return v . GetBool ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetBool ( key string ) bool {
return cast . ToBool ( v . Get ( key ) )
}
// GetInt returns the value associated with the key as an integer.
func GetInt ( key string ) int { return v . GetInt ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetInt ( key string ) int {
return cast . ToInt ( v . Get ( key ) )
}
// GetInt32 returns the value associated with the key as an integer.
func GetInt32 ( key string ) int32 { return v . GetInt32 ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetInt32 ( key string ) int32 {
return cast . ToInt32 ( v . Get ( key ) )
}
// GetInt64 returns the value associated with the key as an integer.
func GetInt64 ( key string ) int64 { return v . GetInt64 ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
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 ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetUint ( key string ) uint {
return cast . ToUint ( v . Get ( key ) )
}
2024-10-16 10:54:40 +00:00
// GetUint16 returns the value associated with the key as an unsigned integer.
func GetUint16 ( key string ) uint16 { return v . GetUint16 ( key ) }
func ( v * Viper ) GetUint16 ( key string ) uint16 {
return cast . ToUint16 ( v . Get ( key ) )
}
2022-07-07 20:11:50 +00:00
// GetUint32 returns the value associated with the key as an unsigned integer.
func GetUint32 ( key string ) uint32 { return v . GetUint32 ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
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 ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
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 ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetFloat64 ( key string ) float64 {
return cast . ToFloat64 ( v . Get ( key ) )
}
// GetTime returns the value associated with the key as time.
func GetTime ( key string ) time . Time { return v . GetTime ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetTime ( key string ) time . Time {
return cast . ToTime ( v . Get ( key ) )
}
// GetDuration returns the value associated with the key as a duration.
func GetDuration ( key string ) time . Duration { return v . GetDuration ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
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 ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
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 ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetStringSlice ( key string ) [ ] string {
return cast . ToStringSlice ( v . Get ( key ) )
}
// GetStringMap returns the value associated with the key as a map of interfaces.
2024-10-16 10:54:40 +00:00
func GetStringMap ( key string ) map [ string ] any { return v . GetStringMap ( key ) }
func ( v * Viper ) GetStringMap ( key string ) map [ string ] any {
2022-07-07 20:11:50 +00:00
return cast . ToStringMap ( v . Get ( key ) )
}
// GetStringMapString returns the value associated with the key as a map of strings.
func GetStringMapString ( key string ) map [ string ] string { return v . GetStringMapString ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetStringMapString ( key string ) map [ string ] string {
return cast . ToStringMapString ( v . Get ( key ) )
}
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func GetStringMapStringSlice ( key string ) map [ string ] [ ] string { return v . GetStringMapStringSlice ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetStringMapStringSlice ( key string ) map [ string ] [ ] string {
return cast . ToStringMapStringSlice ( v . Get ( key ) )
}
// GetSizeInBytes returns the size of the value associated with the given key
// in bytes.
func GetSizeInBytes ( key string ) uint { return v . GetSizeInBytes ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) GetSizeInBytes ( key string ) uint {
sizeStr := cast . ToString ( v . Get ( key ) )
return parseSizeInBytes ( sizeStr )
}
// UnmarshalKey takes a single key and unmarshals it into a Struct.
2024-10-16 10:54:40 +00:00
func UnmarshalKey ( key string , rawVal any , opts ... DecoderConfigOption ) error {
2022-07-07 20:11:50 +00:00
return v . UnmarshalKey ( key , rawVal , opts ... )
}
2024-10-16 10:54:40 +00:00
func ( v * Viper ) UnmarshalKey ( key string , rawVal any , opts ... DecoderConfigOption ) error {
2022-07-07 20:11:50 +00:00
return decode ( v . Get ( key ) , defaultDecoderConfig ( rawVal , opts ... ) )
}
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
// on the fields of the structure are properly set.
2024-10-16 10:54:40 +00:00
func Unmarshal ( rawVal any , opts ... DecoderConfigOption ) error {
2022-07-07 20:11:50 +00:00
return v . Unmarshal ( rawVal , opts ... )
}
2024-10-16 10:54:40 +00:00
func ( v * Viper ) Unmarshal ( rawVal any , opts ... DecoderConfigOption ) error {
keys := v . AllKeys ( )
if features . BindStruct {
// TODO: make this optional?
structKeys , err := v . decodeStructKeys ( rawVal , opts ... )
if err != nil {
return err
}
keys = append ( keys , structKeys ... )
}
// TODO: struct keys should be enough?
return decode ( v . getSettings ( keys ) , defaultDecoderConfig ( rawVal , opts ... ) )
}
func ( v * Viper ) decodeStructKeys ( input any , opts ... DecoderConfigOption ) ( [ ] string , error ) {
var structKeyMap map [ string ] any
err := decode ( input , defaultDecoderConfig ( & structKeyMap , opts ... ) )
if err != nil {
return nil , err
}
flattenedStructKeyMap := v . flattenAndMergeMap ( map [ string ] bool { } , structKeyMap , "" )
r := make ( [ ] string , 0 , len ( flattenedStructKeyMap ) )
for v := range flattenedStructKeyMap {
r = append ( r , v )
}
return r , nil
2022-07-07 20:11:50 +00:00
}
2024-10-16 10:54:40 +00:00
// defaultDecoderConfig returns default mapstructure.DecoderConfig with support
// of time.Duration values & string slices.
func defaultDecoderConfig ( output any , opts ... DecoderConfigOption ) * mapstructure . DecoderConfig {
2022-07-07 20:11:50 +00:00
c := & mapstructure . DecoderConfig {
Metadata : nil ,
Result : output ,
WeaklyTypedInput : true ,
DecodeHook : mapstructure . ComposeDecodeHookFunc (
mapstructure . StringToTimeDurationHookFunc ( ) ,
mapstructure . StringToSliceHookFunc ( "," ) ,
) ,
}
for _ , opt := range opts {
opt ( c )
}
return c
}
2024-10-16 10:54:40 +00:00
// decode is a wrapper around mapstructure.Decode that mimics the WeakDecode functionality.
func decode ( input any , config * mapstructure . DecoderConfig ) error {
2022-07-07 20:11:50 +00:00
decoder , err := mapstructure . NewDecoder ( config )
if err != nil {
return err
}
return decoder . Decode ( input )
}
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
// in the destination struct.
2024-10-16 10:54:40 +00:00
func UnmarshalExact ( rawVal any , opts ... DecoderConfigOption ) error {
2022-07-07 20:11:50 +00:00
return v . UnmarshalExact ( rawVal , opts ... )
}
2024-10-16 10:54:40 +00:00
func ( v * Viper ) UnmarshalExact ( rawVal any , opts ... DecoderConfigOption ) error {
2022-07-07 20:11:50 +00:00
config := defaultDecoderConfig ( rawVal , opts ... )
config . ErrorUnused = true
2024-10-16 10:54:40 +00:00
keys := v . AllKeys ( )
if features . BindStruct {
// TODO: make this optional?
structKeys , err := v . decodeStructKeys ( rawVal , opts ... )
if err != nil {
return err
}
keys = append ( keys , structKeys ... )
}
// TODO: struct keys should be enough?
return decode ( v . getSettings ( keys ) , config )
2022-07-07 20:11:50 +00:00
}
// BindPFlags binds a full flag set to the configuration, using each flag's long
// name as the config key.
func BindPFlags ( flags * pflag . FlagSet ) error { return v . BindPFlags ( flags ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) BindPFlags ( flags * pflag . FlagSet ) error {
return v . BindFlagValues ( pflagValueSet { flags } )
}
// BindPFlag binds a specific key to a pflag (as used by cobra).
// Example (where serverCmd is a Cobra instance):
//
2024-10-16 10:54:40 +00:00
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
2022-07-07 20:11:50 +00:00
func BindPFlag ( key string , flag * pflag . Flag ) error { return v . BindPFlag ( key , flag ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) BindPFlag ( key string , flag * pflag . Flag ) error {
2024-10-16 10:54:40 +00:00
if flag == nil {
return fmt . Errorf ( "flag for %q is nil" , key )
}
2022-07-07 20:11:50 +00:00
return v . BindFlagValue ( key , pflagValue { flag } )
}
// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long
// name as the config key.
func BindFlagValues ( flags FlagValueSet ) error { return v . BindFlagValues ( flags ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) BindFlagValues ( flags FlagValueSet ) ( err error ) {
flags . VisitAll ( func ( flag FlagValue ) {
if err = v . BindFlagValue ( flag . Name ( ) , flag ) ; err != nil {
return
}
} )
return nil
}
// BindFlagValue binds a specific key to a FlagValue.
func BindFlagValue ( key string , flag FlagValue ) error { return v . BindFlagValue ( key , flag ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) BindFlagValue ( key string , flag FlagValue ) error {
if flag == nil {
return fmt . Errorf ( "flag for %q is nil" , key )
}
v . pflags [ strings . ToLower ( key ) ] = flag
return nil
}
// BindEnv binds a Viper key to a ENV variable.
// ENV variables are case sensitive.
// If only a key is provided, it will use the env key matching the key, uppercased.
2024-10-16 10:54:40 +00:00
// If more arguments are provided, they will represent the env variable names that
// should bind to this key and will be taken in the specified order.
2022-07-07 20:11:50 +00:00
// EnvPrefix will be used when set when env name is not provided.
func BindEnv ( input ... string ) error { return v . BindEnv ( input ... ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) BindEnv ( input ... string ) error {
if len ( input ) == 0 {
return fmt . Errorf ( "missing key to bind to" )
}
2024-10-16 10:54:40 +00:00
key := strings . ToLower ( input [ 0 ] )
2022-07-07 20:11:50 +00:00
if len ( input ) == 1 {
2024-10-16 10:54:40 +00:00
v . env [ key ] = append ( v . env [ key ] , v . mergeWithEnvPrefix ( key ) )
2022-07-07 20:11:50 +00:00
} else {
2024-10-16 10:54:40 +00:00
v . env [ key ] = append ( v . env [ key ] , input [ 1 : ] ... )
2022-07-07 20:11:50 +00:00
}
return nil
}
2024-10-16 10:54:40 +00:00
// MustBindEnv wraps BindEnv in a panic.
// If there is an error binding an environment variable, MustBindEnv will
// panic.
func MustBindEnv ( input ... string ) { v . MustBindEnv ( input ... ) }
func ( v * Viper ) MustBindEnv ( input ... string ) {
if err := v . BindEnv ( input ... ) ; err != nil {
panic ( fmt . Sprintf ( "error while binding environment variable: %v" , err ) )
}
}
2022-07-07 20:11:50 +00:00
// Given a key, find the value.
//
// 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.
2024-10-16 10:54:40 +00:00
func ( v * Viper ) find ( lcaseKey string , flagDefault bool ) any {
2022-07-07 20:11:50 +00:00
var (
2024-10-16 10:54:40 +00:00
val any
2022-07-07 20:11:50 +00:00
exists bool
path = strings . Split ( lcaseKey , v . keyDelim )
nested = len ( path ) > 1
)
// compute the path through the nested maps to the nested value
if nested && v . isPathShadowedInDeepMap ( path , castMapStringToMapInterface ( v . aliases ) ) != "" {
return nil
}
// if the requested key is an alias, then return the proper key
lcaseKey = v . realKey ( lcaseKey )
path = strings . Split ( lcaseKey , v . keyDelim )
nested = len ( path ) > 1
// Set() override first
val = v . searchMap ( v . override , path )
if val != nil {
return val
}
if nested && v . isPathShadowedInDeepMap ( path , v . override ) != "" {
return nil
}
// PFlag override next
flag , exists := v . pflags [ lcaseKey ]
if exists && flag . HasChanged ( ) {
switch flag . ValueType ( ) {
case "int" , "int8" , "int16" , "int32" , "int64" :
return cast . ToInt ( flag . ValueString ( ) )
case "bool" :
return cast . ToBool ( flag . ValueString ( ) )
2024-10-16 10:54:40 +00:00
case "stringSlice" , "stringArray" :
2022-07-07 20:11:50 +00:00
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 )
2024-10-16 10:54:40 +00:00
case "durationSlice" :
s := strings . TrimPrefix ( flag . ValueString ( ) , "[" )
s = strings . TrimSuffix ( s , "]" )
slice := strings . Split ( s , "," )
return cast . ToDurationSlice ( slice )
2022-07-07 20:11:50 +00:00
case "stringToString" :
return stringToStringConv ( flag . ValueString ( ) )
2024-10-16 10:54:40 +00:00
case "stringToInt" :
return stringToIntConv ( flag . ValueString ( ) )
2022-07-07 20:11:50 +00:00
default :
return flag . ValueString ( )
}
}
if nested && v . isPathShadowedInFlatMap ( path , v . pflags ) != "" {
return nil
}
// Env override next
if v . automaticEnvApplied {
2024-10-16 10:54:40 +00:00
envKey := strings . Join ( append ( v . parents , lcaseKey ) , "." )
2022-07-07 20:11:50 +00:00
// even if it hasn't been registered, if automaticEnv is used,
// check any Get request
2024-10-16 10:54:40 +00:00
if val , ok := v . getEnv ( v . mergeWithEnvPrefix ( envKey ) ) ; ok {
2022-07-07 20:11:50 +00:00
return val
}
if nested && v . isPathShadowedInAutoEnv ( path ) != "" {
return nil
}
}
2024-10-16 10:54:40 +00:00
envkeys , exists := v . env [ lcaseKey ]
2022-07-07 20:11:50 +00:00
if exists {
2024-10-16 10:54:40 +00:00
for _ , envkey := range envkeys {
if val , ok := v . getEnv ( envkey ) ; ok {
return val
}
2022-07-07 20:11:50 +00:00
}
}
if nested && v . isPathShadowedInFlatMap ( path , v . env ) != "" {
return nil
}
// Config file next
2024-10-16 10:54:40 +00:00
val = v . searchIndexableWithPathPrefixes ( v . config , path )
2022-07-07 20:11:50 +00:00
if val != nil {
return val
}
if nested && v . isPathShadowedInDeepMap ( path , v . config ) != "" {
return nil
}
// K/V store next
val = v . searchMap ( v . kvstore , path )
if val != nil {
return val
}
if nested && v . isPathShadowedInDeepMap ( path , v . kvstore ) != "" {
return nil
}
// Default next
val = v . searchMap ( v . defaults , path )
if val != nil {
return val
}
if nested && v . isPathShadowedInDeepMap ( path , v . defaults ) != "" {
return nil
}
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 ( ) )
2024-10-16 10:54:40 +00:00
case "stringSlice" , "stringArray" :
2022-07-07 20:11:50 +00:00
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 )
case "stringToString" :
return stringToStringConv ( flag . ValueString ( ) )
2024-10-16 10:54:40 +00:00
case "stringToInt" :
return stringToIntConv ( flag . ValueString ( ) )
case "durationSlice" :
s := strings . TrimPrefix ( flag . ValueString ( ) , "[" )
s = strings . TrimSuffix ( s , "]" )
slice := strings . Split ( s , "," )
return cast . ToDurationSlice ( slice )
2022-07-07 20:11:50 +00:00
default :
return flag . ValueString ( )
}
}
// last item, no need to check shadowing
}
return nil
}
func readAsCSV ( val string ) ( [ ] string , error ) {
if val == "" {
return [ ] string { } , nil
}
stringReader := strings . NewReader ( val )
csvReader := csv . NewReader ( stringReader )
return csvReader . Read ( )
}
// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L79
2024-10-16 10:54:40 +00:00
// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap.
func stringToStringConv ( val string ) any {
2022-07-07 20:11:50 +00:00
val = strings . Trim ( val , "[]" )
// An empty string would cause an empty map
2024-10-16 10:54:40 +00:00
if val == "" {
return map [ string ] any { }
2022-07-07 20:11:50 +00:00
}
r := csv . NewReader ( strings . NewReader ( val ) )
ss , err := r . Read ( )
if err != nil {
return nil
}
2024-10-16 10:54:40 +00:00
out := make ( map [ string ] any , len ( ss ) )
for _ , pair := range ss {
k , vv , found := strings . Cut ( pair , "=" )
if ! found {
return nil
}
out [ k ] = vv
}
return out
}
// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/string_to_int.go#L68
// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap.
func stringToIntConv ( val string ) any {
val = strings . Trim ( val , "[]" )
// An empty string would cause an empty map
if val == "" {
return map [ string ] any { }
}
ss := strings . Split ( val , "," )
out := make ( map [ string ] any , len ( ss ) )
2022-07-07 20:11:50 +00:00
for _ , pair := range ss {
2024-10-16 10:54:40 +00:00
k , vv , found := strings . Cut ( pair , "=" )
if ! found {
return nil
}
var err error
out [ k ] , err = strconv . Atoi ( vv )
if err != nil {
2022-07-07 20:11:50 +00:00
return nil
}
}
return out
}
// IsSet checks to see if the key has been set in any of the data locations.
// IsSet is case-insensitive for a key.
func IsSet ( key string ) bool { return v . IsSet ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) IsSet ( key string ) bool {
lcaseKey := strings . ToLower ( key )
val := v . find ( lcaseKey , false )
return val != nil
}
2024-10-16 10:54:40 +00:00
// AutomaticEnv makes Viper check if environment variables match any of the existing keys
// (config, default or flags). If matching env vars are found, they are loaded into Viper.
2022-07-07 20:11:50 +00:00
func AutomaticEnv ( ) { v . AutomaticEnv ( ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) AutomaticEnv ( ) {
v . automaticEnvApplied = true
}
// SetEnvKeyReplacer sets the strings.Replacer on the viper object
// Useful for mapping an environmental variable to a key that does
// not match it.
func SetEnvKeyReplacer ( r * strings . Replacer ) { v . SetEnvKeyReplacer ( r ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetEnvKeyReplacer ( r * strings . Replacer ) {
v . envKeyReplacer = r
}
// RegisterAlias creates an alias that provides another accessor for the same key.
// This enables one to change a name without breaking the application.
2024-10-16 10:54:40 +00:00
func RegisterAlias ( alias , key string ) { v . RegisterAlias ( alias , key ) }
func ( v * Viper ) RegisterAlias ( alias , key string ) {
2022-07-07 20:11:50 +00:00
v . registerAlias ( alias , strings . ToLower ( key ) )
}
2024-10-16 10:54:40 +00:00
func ( v * Viper ) registerAlias ( alias , key string ) {
2022-07-07 20:11:50 +00:00
alias = strings . ToLower ( alias )
if alias != key && alias != v . realKey ( key ) {
_ , exists := v . aliases [ alias ]
if ! exists {
// if we alias something that exists in one of the maps to another
// name, we'll never be able to get that value using the original
// name, so move the config value to the new realkey.
if val , ok := v . config [ alias ] ; ok {
delete ( v . config , alias )
v . config [ key ] = val
}
if val , ok := v . kvstore [ alias ] ; ok {
delete ( v . kvstore , alias )
v . kvstore [ key ] = val
}
if val , ok := v . defaults [ alias ] ; ok {
delete ( v . defaults , alias )
v . defaults [ key ] = val
}
if val , ok := v . override [ alias ] ; ok {
delete ( v . override , alias )
v . override [ key ] = val
}
v . aliases [ alias ] = key
}
} else {
2024-10-16 10:54:40 +00:00
v . logger . Warn ( "creating circular reference alias" , "alias" , alias , "key" , key , "real_key" , v . realKey ( key ) )
2022-07-07 20:11:50 +00:00
}
}
func ( v * Viper ) realKey ( key string ) string {
newkey , exists := v . aliases [ key ]
if exists {
2024-10-16 10:54:40 +00:00
v . logger . Debug ( "key is an alias" , "alias" , key , "to" , newkey )
2022-07-07 20:11:50 +00:00
return v . realKey ( newkey )
}
return key
}
// InConfig checks to see if the given key (or an alias) is in the config file.
func InConfig ( key string ) bool { return v . InConfig ( key ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) InConfig ( key string ) bool {
2024-10-16 10:54:40 +00:00
lcaseKey := strings . ToLower ( key )
2022-07-07 20:11:50 +00:00
// if the requested key is an alias, then return the proper key
2024-10-16 10:54:40 +00:00
lcaseKey = v . realKey ( lcaseKey )
path := strings . Split ( lcaseKey , v . keyDelim )
2022-07-07 20:11:50 +00:00
2024-10-16 10:54:40 +00:00
return v . searchIndexableWithPathPrefixes ( v . config , path ) != nil
2022-07-07 20:11:50 +00:00
}
// SetDefault sets the default value for this key.
// SetDefault is case-insensitive for a key.
// Default only used when no value is provided by the user via flag, config or ENV.
2024-10-16 10:54:40 +00:00
func SetDefault ( key string , value any ) { v . SetDefault ( key , value ) }
func ( v * Viper ) SetDefault ( key string , value any ) {
2022-07-07 20:11:50 +00:00
// If alias passed in, then set the proper default
key = v . realKey ( strings . ToLower ( key ) )
value = toCaseInsensitiveValue ( value )
path := strings . Split ( key , v . keyDelim )
lastKey := strings . ToLower ( path [ len ( path ) - 1 ] )
deepestMap := deepSearch ( v . defaults , path [ 0 : len ( path ) - 1 ] )
// set innermost value
deepestMap [ lastKey ] = value
}
// Set sets the value for the key in the override register.
// Set is case-insensitive for a key.
// Will be used instead of values obtained via
// flags, config file, ENV, default, or key/value store.
2024-10-16 10:54:40 +00:00
func Set ( key string , value any ) { v . Set ( key , value ) }
func ( v * Viper ) Set ( key string , value any ) {
2022-07-07 20:11:50 +00:00
// If alias passed in, then set the proper override
key = v . realKey ( strings . ToLower ( key ) )
value = toCaseInsensitiveValue ( value )
path := strings . Split ( key , v . keyDelim )
lastKey := strings . ToLower ( path [ len ( path ) - 1 ] )
deepestMap := deepSearch ( v . override , path [ 0 : len ( path ) - 1 ] )
// set innermost value
deepestMap [ lastKey ] = value
}
// ReadInConfig will discover and load the configuration file from disk
// and key/value stores, searching in one of the defined paths.
func ReadInConfig ( ) error { return v . ReadInConfig ( ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) ReadInConfig ( ) error {
2024-10-16 10:54:40 +00:00
v . logger . Info ( "attempting to read in config file" )
2022-07-07 20:11:50 +00:00
filename , err := v . getConfigFile ( )
if err != nil {
return err
}
if ! stringInSlice ( v . getConfigType ( ) , SupportedExts ) {
return UnsupportedConfigError ( v . getConfigType ( ) )
}
2024-10-16 10:54:40 +00:00
v . logger . Debug ( "reading file" , "file" , filename )
2022-07-07 20:11:50 +00:00
file , err := afero . ReadFile ( v . fs , filename )
if err != nil {
return err
}
2024-10-16 10:54:40 +00:00
config := make ( map [ string ] any )
2022-07-07 20:11:50 +00:00
err = v . unmarshalReader ( bytes . NewReader ( file ) , config )
if err != nil {
return err
}
v . config = config
return nil
}
// MergeInConfig merges a new configuration with an existing config.
func MergeInConfig ( ) error { return v . MergeInConfig ( ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) MergeInConfig ( ) error {
2024-10-16 10:54:40 +00:00
v . logger . Info ( "attempting to merge in config file" )
2022-07-07 20:11:50 +00:00
filename , err := v . getConfigFile ( )
if err != nil {
return err
}
if ! stringInSlice ( v . getConfigType ( ) , SupportedExts ) {
return UnsupportedConfigError ( v . getConfigType ( ) )
}
file , err := afero . ReadFile ( v . fs , filename )
if err != nil {
return err
}
return v . MergeConfig ( bytes . NewReader ( file ) )
}
// ReadConfig will read a configuration file, setting existing keys to nil if the
// key does not exist in the file.
func ReadConfig ( in io . Reader ) error { return v . ReadConfig ( in ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) ReadConfig ( in io . Reader ) error {
2024-10-16 10:54:40 +00:00
v . config = make ( map [ string ] any )
2022-07-07 20:11:50 +00:00
return v . unmarshalReader ( in , v . config )
}
// MergeConfig merges a new configuration with an existing config.
func MergeConfig ( in io . Reader ) error { return v . MergeConfig ( in ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) MergeConfig ( in io . Reader ) error {
2024-10-16 10:54:40 +00:00
cfg := make ( map [ string ] any )
2022-07-07 20:11:50 +00:00
if err := v . unmarshalReader ( in , cfg ) ; err != nil {
return err
}
return v . MergeConfigMap ( cfg )
}
// MergeConfigMap merges the configuration from the map given with an existing config.
// Note that the map given may be modified.
2024-10-16 10:54:40 +00:00
func MergeConfigMap ( cfg map [ string ] any ) error { return v . MergeConfigMap ( cfg ) }
func ( v * Viper ) MergeConfigMap ( cfg map [ string ] any ) error {
2022-07-07 20:11:50 +00:00
if v . config == nil {
2024-10-16 10:54:40 +00:00
v . config = make ( map [ string ] any )
2022-07-07 20:11:50 +00:00
}
insensitiviseMap ( cfg )
mergeMaps ( cfg , v . config , nil )
return nil
}
// WriteConfig writes the current configuration to a file.
func WriteConfig ( ) error { return v . WriteConfig ( ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) WriteConfig ( ) error {
filename , err := v . getConfigFile ( )
if err != nil {
return err
}
return v . writeConfig ( filename , true )
}
// SafeWriteConfig writes current configuration to file only if the file does not exist.
func SafeWriteConfig ( ) error { return v . SafeWriteConfig ( ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SafeWriteConfig ( ) error {
if len ( v . configPaths ) < 1 {
return errors . New ( "missing configuration for 'configPath'" )
}
return v . SafeWriteConfigAs ( filepath . Join ( v . configPaths [ 0 ] , v . configName + "." + v . configType ) )
}
// WriteConfigAs writes current configuration to a given filename.
func WriteConfigAs ( filename string ) error { return v . WriteConfigAs ( filename ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) WriteConfigAs ( filename string ) error {
return v . writeConfig ( filename , true )
}
// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.
func SafeWriteConfigAs ( filename string ) error { return v . SafeWriteConfigAs ( filename ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
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 ( v * Viper ) writeConfig ( filename string , force bool ) error {
2024-10-16 10:54:40 +00:00
v . logger . Info ( "attempting to write configuration to file" )
2022-07-07 20:11:50 +00:00
var configType string
ext := filepath . Ext ( filename )
2024-10-16 10:54:40 +00:00
if ext != "" && ext != filepath . Base ( filename ) {
2022-07-07 20:11:50 +00:00
configType = ext [ 1 : ]
} else {
configType = v . configType
}
if configType == "" {
return fmt . Errorf ( "config type could not be determined for %s" , filename )
}
if ! stringInSlice ( configType , SupportedExts ) {
return UnsupportedConfigError ( configType )
}
if v . config == nil {
2024-10-16 10:54:40 +00:00
v . config = make ( map [ string ] any )
2022-07-07 20:11:50 +00:00
}
flags := os . O_CREATE | os . O_TRUNC | os . O_WRONLY
if ! force {
flags |= os . O_EXCL
}
f , err := v . fs . OpenFile ( filename , flags , v . configPermissions )
if err != nil {
return err
}
defer f . Close ( )
if err := v . marshalWriter ( f , configType ) ; err != nil {
return err
}
return f . Sync ( )
}
2024-10-16 10:54:40 +00:00
func ( v * Viper ) unmarshalReader ( in io . Reader , c map [ string ] any ) error {
2022-07-07 20:11:50 +00:00
buf := new ( bytes . Buffer )
buf . ReadFrom ( in )
2024-10-16 10:54:40 +00:00
switch format := strings . ToLower ( v . getConfigType ( ) ) ; format {
case "yaml" , "yml" , "json" , "toml" , "hcl" , "tfvars" , "ini" , "properties" , "props" , "prop" , "dotenv" , "env" :
err := v . decoderRegistry . Decode ( format , buf . Bytes ( ) , c )
2022-07-07 20:11:50 +00:00
if err != nil {
return ConfigParseError { err }
}
}
insensitiviseMap ( c )
return nil
}
// Marshal a map into Writer.
func ( v * Viper ) marshalWriter ( f afero . File , configType string ) error {
c := v . AllSettings ( )
switch configType {
2024-10-16 10:54:40 +00:00
case "yaml" , "yml" , "json" , "toml" , "hcl" , "tfvars" , "ini" , "prop" , "props" , "properties" , "dotenv" , "env" :
b , err := v . encoderRegistry . Encode ( configType , c )
2022-07-07 20:11:50 +00:00
if err != nil {
return ConfigMarshalError { err }
}
2024-10-16 10:54:40 +00:00
_ , err = f . WriteString ( string ( b ) )
2022-07-07 20:11:50 +00:00
if err != nil {
return ConfigMarshalError { err }
}
}
return nil
}
2024-10-16 10:54:40 +00:00
func keyExists ( k string , m map [ string ] any ) string {
2022-07-07 20:11:50 +00:00
lk := strings . ToLower ( k )
for mk := range m {
lmk := strings . ToLower ( mk )
if lmk == lk {
return mk
}
}
return ""
}
func castToMapStringInterface (
2024-10-16 10:54:40 +00:00
src map [ any ] any ,
) map [ string ] any {
tgt := map [ string ] any { }
2022-07-07 20:11:50 +00:00
for k , v := range src {
tgt [ fmt . Sprintf ( "%v" , k ) ] = v
}
return tgt
}
2024-10-16 10:54:40 +00:00
func castMapStringSliceToMapInterface ( src map [ string ] [ ] string ) map [ string ] any {
tgt := map [ string ] any { }
for k , v := range src {
tgt [ k ] = v
}
return tgt
}
func castMapStringToMapInterface ( src map [ string ] string ) map [ string ] any {
tgt := map [ string ] any { }
2022-07-07 20:11:50 +00:00
for k , v := range src {
tgt [ k ] = v
}
return tgt
}
2024-10-16 10:54:40 +00:00
func castMapFlagToMapInterface ( src map [ string ] FlagValue ) map [ string ] any {
tgt := map [ string ] any { }
2022-07-07 20:11:50 +00:00
for k , v := range src {
tgt [ k ] = v
}
return tgt
}
// mergeMaps merges two maps. The `itgt` parameter is for handling go-yaml's
2024-10-16 10:54:40 +00:00
// insistence on parsing nested structures as `map[any]any`
2022-07-07 20:11:50 +00:00
// instead of using a `string` as the key for nest structures beyond one level
// deep. Both map types are supported as there is a go-yaml fork that uses
2024-10-16 10:54:40 +00:00
// `map[string]any` instead.
func mergeMaps ( src , tgt map [ string ] any , itgt map [ any ] any ) {
2022-07-07 20:11:50 +00:00
for sk , sv := range src {
tk := keyExists ( sk , tgt )
if tk == "" {
2024-10-16 10:54:40 +00:00
v . logger . Debug ( "" , "tk" , "\"\"" , fmt . Sprintf ( "tgt[%s]" , sk ) , sv )
2022-07-07 20:11:50 +00:00
tgt [ sk ] = sv
if itgt != nil {
itgt [ sk ] = sv
}
continue
}
tv , ok := tgt [ tk ]
if ! ok {
2024-10-16 10:54:40 +00:00
v . logger . Debug ( "" , fmt . Sprintf ( "ok[%s]" , tk ) , false , fmt . Sprintf ( "tgt[%s]" , sk ) , sv )
2022-07-07 20:11:50 +00:00
tgt [ sk ] = sv
if itgt != nil {
itgt [ sk ] = sv
}
continue
}
svType := reflect . TypeOf ( sv )
tvType := reflect . TypeOf ( tv )
2024-10-16 10:54:40 +00:00
v . logger . Debug (
"processing" ,
"key" , sk ,
"st" , svType ,
"tt" , tvType ,
"sv" , sv ,
"tv" , tv ,
)
2022-07-07 20:11:50 +00:00
switch ttv := tv . ( type ) {
2024-10-16 10:54:40 +00:00
case map [ any ] any :
v . logger . Debug ( "merging maps (must convert)" )
tsv , ok := sv . ( map [ any ] any )
if ! ok {
v . logger . Error (
"Could not cast sv to map[any]any" ,
"key" , sk ,
"st" , svType ,
"tt" , tvType ,
"sv" , sv ,
"tv" , tv ,
)
continue
}
2022-07-07 20:11:50 +00:00
ssv := castToMapStringInterface ( tsv )
stv := castToMapStringInterface ( ttv )
mergeMaps ( ssv , stv , ttv )
2024-10-16 10:54:40 +00:00
case map [ string ] any :
v . logger . Debug ( "merging maps" )
tsv , ok := sv . ( map [ string ] any )
if ! ok {
v . logger . Error (
"Could not cast sv to map[string]any" ,
"key" , sk ,
"st" , svType ,
"tt" , tvType ,
"sv" , sv ,
"tv" , tv ,
)
continue
}
mergeMaps ( tsv , ttv , nil )
2022-07-07 20:11:50 +00:00
default :
2024-10-16 10:54:40 +00:00
v . logger . Debug ( "setting value" )
2022-07-07 20:11:50 +00:00
tgt [ tk ] = sv
if itgt != nil {
itgt [ tk ] = sv
}
}
}
}
// ReadRemoteConfig attempts to get configuration from a remote source
// and read it in the remote configuration registry.
func ReadRemoteConfig ( ) error { return v . ReadRemoteConfig ( ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) ReadRemoteConfig ( ) error {
return v . getKeyValueConfig ( )
}
func WatchRemoteConfig ( ) error { return v . WatchRemoteConfig ( ) }
func ( v * Viper ) WatchRemoteConfig ( ) error {
return v . watchKeyValueConfig ( )
}
func ( v * Viper ) WatchRemoteConfigOnChannel ( ) error {
return v . watchKeyValueConfigOnChannel ( )
}
// Retrieve the first found remote configuration.
func ( v * Viper ) getKeyValueConfig ( ) error {
if RemoteConfig == nil {
return RemoteConfigError ( "Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'" )
}
2024-10-16 10:54:40 +00:00
if len ( v . remoteProviders ) == 0 {
return RemoteConfigError ( "No Remote Providers" )
}
2022-07-07 20:11:50 +00:00
for _ , rp := range v . remoteProviders {
val , err := v . getRemoteConfig ( rp )
if err != nil {
2024-10-16 10:54:40 +00:00
v . logger . Error ( fmt . Errorf ( "get remote config: %w" , err ) . Error ( ) )
2022-07-07 20:11:50 +00:00
continue
}
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
v . kvstore = val
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
return nil
}
return RemoteConfigError ( "No Files Found" )
}
2024-10-16 10:54:40 +00:00
func ( v * Viper ) getRemoteConfig ( provider RemoteProvider ) ( map [ string ] any , error ) {
2022-07-07 20:11:50 +00:00
reader , err := RemoteConfig . Get ( provider )
if err != nil {
return nil , err
}
err = v . unmarshalReader ( reader , v . kvstore )
return v . kvstore , err
}
// Retrieve the first found remote configuration.
func ( v * Viper ) watchKeyValueConfigOnChannel ( ) error {
2024-10-16 10:54:40 +00:00
if len ( v . remoteProviders ) == 0 {
return RemoteConfigError ( "No Remote Providers" )
}
2022-07-07 20:11:50 +00:00
for _ , rp := range v . remoteProviders {
respc , _ := RemoteConfig . WatchChannel ( rp )
// Todo: Add quit channel
go func ( rc <- chan * RemoteResponse ) {
for {
b := <- rc
reader := bytes . NewReader ( b . Value )
v . unmarshalReader ( reader , v . kvstore )
}
} ( respc )
return nil
}
return RemoteConfigError ( "No Files Found" )
}
// Retrieve the first found remote configuration.
func ( v * Viper ) watchKeyValueConfig ( ) error {
2024-10-16 10:54:40 +00:00
if len ( v . remoteProviders ) == 0 {
return RemoteConfigError ( "No Remote Providers" )
}
2022-07-07 20:11:50 +00:00
for _ , rp := range v . remoteProviders {
val , err := v . watchRemoteConfig ( rp )
if err != nil {
2024-10-16 10:54:40 +00:00
v . logger . Error ( fmt . Errorf ( "watch remote config: %w" , err ) . Error ( ) )
2022-07-07 20:11:50 +00:00
continue
}
v . kvstore = val
return nil
}
return RemoteConfigError ( "No Files Found" )
}
2024-10-16 10:54:40 +00:00
func ( v * Viper ) watchRemoteConfig ( provider RemoteProvider ) ( map [ string ] any , error ) {
2022-07-07 20:11:50 +00:00
reader , err := RemoteConfig . Watch ( provider )
if err != nil {
return nil , err
}
err = v . unmarshalReader ( reader , v . kvstore )
return v . kvstore , err
}
// AllKeys returns all keys holding a value, regardless of where they are set.
2024-10-16 10:54:40 +00:00
// Nested keys are returned with a v.keyDelim separator.
2022-07-07 20:11:50 +00:00
func AllKeys ( ) [ ] string { return v . AllKeys ( ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) AllKeys ( ) [ ] string {
m := map [ string ] bool { }
// add all paths, by order of descending priority to ensure correct shadowing
m = v . flattenAndMergeMap ( m , castMapStringToMapInterface ( v . aliases ) , "" )
m = v . flattenAndMergeMap ( m , v . override , "" )
m = v . mergeFlatMap ( m , castMapFlagToMapInterface ( v . pflags ) )
2024-10-16 10:54:40 +00:00
m = v . mergeFlatMap ( m , castMapStringSliceToMapInterface ( v . env ) )
2022-07-07 20:11:50 +00:00
m = v . flattenAndMergeMap ( m , v . config , "" )
m = v . flattenAndMergeMap ( m , v . kvstore , "" )
m = v . flattenAndMergeMap ( m , v . defaults , "" )
// convert set of paths to list
a := make ( [ ] string , 0 , len ( m ) )
for x := range m {
a = append ( a , x )
}
return a
}
// flattenAndMergeMap recursively flattens the given map into a map[string]bool
// of key paths (used as a set, easier to manipulate than a []string):
2024-10-16 10:54:40 +00:00
// - 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.
//
2022-07-07 20:11:50 +00:00
// The resulting set of paths is merged to the given shadow set at the same time.
2024-10-16 10:54:40 +00:00
func ( v * Viper ) flattenAndMergeMap ( shadow map [ string ] bool , m map [ string ] any , prefix string ) map [ string ] bool {
2022-07-07 20:11:50 +00:00
if shadow != nil && prefix != "" && shadow [ prefix ] {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make ( map [ string ] bool )
}
2024-10-16 10:54:40 +00:00
var m2 map [ string ] any
2022-07-07 20:11:50 +00:00
if prefix != "" {
prefix += v . keyDelim
}
for k , val := range m {
fullKey := prefix + k
2024-10-16 10:54:40 +00:00
switch val := val . ( type ) {
case map [ string ] any :
m2 = val
case map [ any ] any :
2022-07-07 20:11:50 +00:00
m2 = cast . ToStringMap ( val )
default :
// immediate value
shadow [ strings . ToLower ( fullKey ) ] = true
continue
}
// recursively merge to shadow map
shadow = v . flattenAndMergeMap ( shadow , m2 , fullKey )
}
return shadow
}
// mergeFlatMap merges the given maps, excluding values of the second map
// shadowed by values from the first map.
2024-10-16 10:54:40 +00:00
func ( v * Viper ) mergeFlatMap ( shadow map [ string ] bool , m map [ string ] any ) map [ string ] bool {
2022-07-07 20:11:50 +00:00
// scan keys
outer :
for k := range m {
path := strings . Split ( k , v . keyDelim )
// scan intermediate paths
var parentKey string
for i := 1 ; i < len ( path ) ; i ++ {
parentKey = strings . Join ( path [ 0 : i ] , v . keyDelim )
if shadow [ parentKey ] {
// path is shadowed, continue
continue outer
}
}
// add key
shadow [ strings . ToLower ( k ) ] = true
}
return shadow
}
2024-10-16 10:54:40 +00:00
// AllSettings merges all settings and returns them as a map[string]any.
func AllSettings ( ) map [ string ] any { return v . AllSettings ( ) }
func ( v * Viper ) AllSettings ( ) map [ string ] any {
return v . getSettings ( v . AllKeys ( ) )
}
func ( v * Viper ) getSettings ( keys [ ] string ) map [ string ] any {
m := map [ string ] any { }
2022-07-07 20:11:50 +00:00
// start from the list of keys, and construct the map one value at a time
2024-10-16 10:54:40 +00:00
for _ , k := range keys {
2022-07-07 20:11:50 +00:00
value := v . Get ( k )
if value == nil {
// should not happen, since AllKeys() returns only keys holding a value,
// check just in case anything changes
continue
}
path := strings . Split ( k , v . keyDelim )
lastKey := strings . ToLower ( path [ len ( path ) - 1 ] )
deepestMap := deepSearch ( m , path [ 0 : len ( path ) - 1 ] )
// set innermost value
deepestMap [ lastKey ] = value
}
return m
}
// SetFs sets the filesystem to use to read configuration.
func SetFs ( fs afero . Fs ) { v . SetFs ( fs ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetFs ( fs afero . Fs ) {
v . fs = fs
}
// SetConfigName sets name for the config file.
// Does not include extension.
func SetConfigName ( in string ) { v . SetConfigName ( in ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetConfigName ( in string ) {
if in != "" {
v . configName = in
v . configFile = ""
}
}
// SetConfigType sets the type of the configuration returned by the
// remote source, e.g. "json".
func SetConfigType ( in string ) { v . SetConfigType ( in ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetConfigType ( in string ) {
if in != "" {
v . configType = in
}
}
// SetConfigPermissions sets the permissions for the config file.
func SetConfigPermissions ( perm os . FileMode ) { v . SetConfigPermissions ( perm ) }
2024-10-16 10:54:40 +00:00
2022-07-07 20:11:50 +00:00
func ( v * Viper ) SetConfigPermissions ( perm os . FileMode ) {
v . configPermissions = perm . Perm ( )
}
2024-10-16 10:54:40 +00:00
// IniLoadOptions sets the load options for ini parsing.
func IniLoadOptions ( in ini . LoadOptions ) Option {
return optionFunc ( func ( v * Viper ) {
v . iniLoadOptions = in
} )
}
2022-07-07 20:11:50 +00:00
func ( v * Viper ) getConfigType ( ) string {
if v . configType != "" {
return v . configType
}
cf , err := v . getConfigFile ( )
if err != nil {
return ""
}
ext := filepath . Ext ( cf )
if len ( ext ) > 1 {
return ext [ 1 : ]
}
return ""
}
func ( v * Viper ) getConfigFile ( ) ( string , error ) {
if v . configFile == "" {
cf , err := v . findConfigFile ( )
if err != nil {
return "" , err
}
v . configFile = cf
}
return v . configFile , nil
}
// Debug prints all configuration registries for debugging
// purposes.
2024-10-16 10:54:40 +00:00
func Debug ( ) { v . Debug ( ) }
func DebugTo ( w io . Writer ) { v . DebugTo ( w ) }
func ( v * Viper ) Debug ( ) { v . DebugTo ( os . Stdout ) }
func ( v * Viper ) DebugTo ( w io . Writer ) {
fmt . Fprintf ( w , "Aliases:\n%#v\n" , v . aliases )
fmt . Fprintf ( w , "Override:\n%#v\n" , v . override )
fmt . Fprintf ( w , "PFlags:\n%#v\n" , v . pflags )
fmt . Fprintf ( w , "Env:\n%#v\n" , v . env )
fmt . Fprintf ( w , "Key/Value Store:\n%#v\n" , v . kvstore )
fmt . Fprintf ( w , "Config:\n%#v\n" , v . config )
fmt . Fprintf ( w , "Defaults:\n%#v\n" , v . defaults )
2022-07-07 20:11:50 +00:00
}