Open dee-kryvenko opened 5 years ago
The reason why it is not working is that there is no such key as config
, but something like config.key
, config.key2
, etc.
What you can do is fetching all keys from a viper instance using AllKeys
and setting all keys starting with config.
to null, but I think that wouldn't work either.
Another option is getting all configuration with AllSettings
and writing it to a file manually.
Please note that neither options will work with AutomaticEnv
and env vars as it only works with direct access (using Get*
functions).
Unfortunately implementing the feature you want is not trivial, because "unsetting" would trigger the next configuration source.
config
is a valid root key in my case, something like:
config: /foo/bar
xxx: yyy
Basically I'm trying to maintain in-memory config based on inputs from viper and cobra, i.e. from CLI arguments, env variables and multiple config files. As part of CLI arguments or env variables you would be able to pass a path to the config file that later needs to be considered as part of the rest of the input.
When I want to change something in a config, such as similar to kubectl config use-context
- I want to take what's in-memory, filter out some keys (such as why config path needs to be in the config itself?), and dump it to the file.
I were able to set the key to null, but when I try to save the file - the key ends up in the yaml with an empty value. Expected behavior would be to not to have that key at all.
Writing to file manually is the only option atm but it would be nice if I can reuse what's already in the library and not reinventing the wheel.
Theres two potential ways I can see this can be implemented. One is to go through every map in memory (config
, override
, defaults
, etc...) and delete a key from there. Second is to improve AllSettings
and interpret null value as "no key". It seems to break backward compatibility so might require a feature flag / option to be introduced.
Writing to file manually is the only option atm but it would be nice if I can reuse what's already in the library and not reinventing the wheel.
Personally I think the current writing mechanism is flawed in so many ways, so I avoid using it whenever I can. I can only suggest doing the same. 😕
Could you explain please why do you think so? Any suggestions/alternatives? I'm pretty new to viper, well actually I'm pretty new to golang...
Yay!! There's already a patch for this, see https://github.com/spf13/viper/pull/519
@llibicpep sorry, missed your answer.
Viper reads configuration from a number of sources. When you write the configuration, everything is written to a file. This poses several issues in itself.
For example, if you configure Viper to read secrets from a secret store and the rest from file, your secrets would be written too file as well. That's not what you usually want.
So using Viper for writing to file only makes sense, of you only use a file as source as well. Even then, you have limited access to the configuration itself (the issue itself proves that).
Based on what the use case is, I usually prefer using Viper when I need to configure an application, and use custom logic when I need to write files. Eg. use a separate Viper instance for file config. Or just read the config file manually, merge in viper, and write back the original config manually.
Take a look at my suggestions in my first comment as well. My advice: avoid writing with Viper. I'm actually going to propose removing config writing if a v2 Viper ever becomes a thing.
@llibicpep As a hack (if you are only using a config file) if you really want to use viper.WriteConfig
you could do something as following:
func Unset(key string) error {
configMap := viper.AllSettings()
delete(configMap, key)
encodedConfig, _ := json.MarshalIndent(configMap, "", " ")
err := viper.ReadConfig(encodedConfig)
if err != nil {
return err
}
viper.WriteConfig()
}
Note: do proper error handling
@llibicpep As a hack (if you are only using a config file) if you really want to use
viper.WriteConfig
you could do something as following:func Unset(key string) error { configMap := viper.AllSettings() delete(configMap, key) encodedConfig, _ := json.MarshalIndent(configMap, "", " ") err := viper.ReadConfig(encodedConfig) if err != nil { return err } viper.WriteConfig() }
Note: do proper error handling
I had to convert the byte[] here to a reader, otherwise, you receive:
cannot use (type []byte) as type io.Reader in argument to viper.ReadConfig
Here's the modified hack:
func Unset(key string) error {
configMap := viper.AllSettings()
delete(configMap, key)
encodedConfig, _ := json.MarshalIndent(configMap, "", " ")
err := viper.ReadConfig(bytes.NewReader(encodedConfig))
if err != nil {
return err
}
viper.WriteConfig()
}
A more complete version of Unset which deals with more than just root level items.
It's still not perfect as it will save default's and has no concept of where values came from so could save values from ENV
to the config including secure vars, which isn't desireable.
func Unset(vars ...string) error {
cfg := viper.AllSettings()
vals := cfg
for _, v := range vars {
parts := strings.Split(v, ".")
for i, k := range parts {
v, ok := vals[k]
if !ok {
// Doesn't exist no action needed
break
}
switch len(parts) {
case i + 1:
// Last part so delete.
delete(vals, k)
default:
m, ok := v.(map[string]interface{})
if !ok {
return fmt.Errorf("unsupported type: %T for %q", v, strings.Join(parts[0:i], "."))
}
vals = m
}
}
}
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
if err = viper.ReadConfig(bytes.NewReader(b)); err != nil {
return err
}
return viper.WriteConfig()
}
this is a really annoying thing, say I have a key which is a map, I want to remove it. How do I do that? What worked for me is when deleting to write map[string]string{}
value and also create a custom getValue
function that does this check to see if the value is there !viper.IsSet(key) || len(maps.Keys(viper.GetStringMapString(key))) == 0
A hack method:
delABC := viper.New()
delABC.MergeConfigMap(source.AllSettings())
delABC.Set("a.b.c", struct{}{})
noABC := viper.New()
noABC.MergeConfigMap(delABC.AllSettings())
Sometimes it might be necessary to delete/unset existing keys. For instance in my case, I want to use
viper.WriteConfig
to dump config to file, but I do want to filter out certain keys: I've tried the following:But it is interpreted as empty string in resulted config file. I also ran into https://stackoverflow.com/questions/52339336/removal-of-key-value-pair-from-viper-config-file but I wasn't able to make it work for a root level keys.