knadh / koanf

Simple, extremely lightweight, extensible, configuration management library for Go. Support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Merging nested configs doesn't work as expected

michaeladler opened this issue · comments

Describe the bug

I'm having two sources for my configuration:

  1. a config file (yaml)
  2. pflags

The idea is that pflags wins over the values set in the config file (unless pflag is the default value).
In this particular example, I will try to set --log-level using the config file.

To Reproduce

  1. Create config.yaml:
# uncommenting the following line fixes the bug:
# log-level: debug
log:
  level: debug
  1. Create this as main.go and run it:
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/knadh/koanf/parsers/yaml"
	"github.com/knadh/koanf/providers/file"
	"github.com/knadh/koanf/providers/posflag"
	"github.com/knadh/koanf/v2"
	flag "github.com/spf13/pflag"
)

var k = koanf.New(".")

func main() {
	if err := k.Load(file.Provider("config.yaml"), yaml.Parser()); err != nil {
		log.Fatalf("error loading config: %v", err)
	}
	// log.level is debug
	k.Print()

	f := flag.NewFlagSet("config", flag.ContinueOnError)
	f.Usage = func() {
		fmt.Println(f.FlagUsages())
		os.Exit(0)
	}
	f.String("log-level", "info", "set log level")
	f.Parse(os.Args[1:])

	if err := k.Load(posflag.Provider(f, "-", k), nil); err != nil {
		log.Fatalf("error loading config: %v", err)
	}

	// now log.level is info
	k.Print()

	level := k.String("log.level")
	fmt.Println("log level is:", level)
}

Expected behavior

The final log level is info but it should be debug because I set it in the config file and didn't specify any flags. The output should be:

log.level -> debug
log.level -> debug
log level is: debug

Please provide the following information):

  • OS: linux
  • Koanf Version: v2.0.0

Additional context

If I change delim of posflag.Provider to . (and thus use the flag --log.level) it works as expected. However, I prefer the style --log-level. I guess koanf doesn't like that the string starts with --.

This has to do with how pflag handles default values. This is documented here

// It takes an optional (but recommended) Koanf instance to see if the

-- in flags is irrelevant. The flag library strips that away and only returns the key.

Yes, I understand that, but: using the default pflag value is wrong because I have a value set in my config file.

I think you need to provide a cb function that replaces your flag delimiter - with the koanf delimiter (usually .).

Yes, I understand that, but: using the default pflag value is wrong because I have a value set in my config file.

Please refer to the comments on the function I'd linked in the previous comment. This behaviour can be overridden by passing an additional config interface.

Got it, thanks. This is the fix for the above code:

if err := k.Load(posflag.ProviderWithValue(f, ".", k, func(key, value string) (string, interface{}) {
		return strings.ReplaceAll(key, "-", "."), value
	}), nil); err != nil {
		log.Fatalf("error loading config: %v", err)
	}