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

basicflag Provider does not exclude default values

jkroepke opened this issue · comments

Describe the bug
I would like to use koanf with multiple providers in a chain. In my case

defaults (struct) -> file -> env -> flags

The flagset is enriched with default values, because then --help will display all default values for each option.

With posflag Provider, everything work fine. Since it will only include changed values. However the basicflag provider does not have an behavior (see #254)

To Reproduce

package main

import (
	"flag"
	"fmt"
	"github.com/knadh/koanf/providers/basicflag"
	"log"
	"os"
	"strings"

	"github.com/knadh/koanf/providers/env"
	"github.com/knadh/koanf/providers/structs"
	"github.com/knadh/koanf/v2"
)

// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")

type parentStruct struct {
	Name string `koanf:"name"`
	ID   int    `koanf:"id"`
}

func init() {
	os.Setenv("MYVAR_ID", "5000")
}

func main() {
	defaults := parentStruct{
		Name: "parent1",
		ID:   1234,
	}

	k.Load(structs.Provider(defaults, "koanf"), nil)

	k.Load(env.Provider("MYVAR_", ".", func(s string) string {
		return strings.Replace(strings.ToLower(
			strings.TrimPrefix(s, "MYVAR_")), "_", ".", -1)
	}), nil)

	f := flag.NewFlagSet("config", flag.ContinueOnError)
	f.String("name", defaults.Name, "")
	f.Int("id", defaults.ID, "")
	f.Parse([]string{"--name", "changed-from-flags"})

	if err := k.Load(basicflag.Provider(f, "."), nil); err != nil {
		log.Fatalf("error loading config: %v", err)
	}
	
	fmt.Printf("id is = `%d`\n", k.Int("id"))
	fmt.Printf("name is = `%s`\n", k.String("name"))
}

Output

id is = `1234`
name is = `changed-from-flags`

Expected behavior
A clear and concise description of what you expected to happen.

Defaults values from flagset are not used in final config,

Output

id is = `5000`
name is = `changed-from-flags`

Please provide the following information):

  • OS: [e.g. linux/osx/windows]
  • Koanf Version [e.g. v1.0.0]

Additional context
Add any other context about the problem here.

With posflag, everything works as expected:

package main

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/knadh/koanf/providers/env"
	"github.com/knadh/koanf/providers/posflag"
	"github.com/knadh/koanf/providers/structs"
	"github.com/knadh/koanf/v2"
	flag "github.com/spf13/pflag"
)

// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")

type parentStruct struct {
	Name string `koanf:"name"`
	ID   int    `koanf:"id"`
}

func init() {
	os.Setenv("MYVAR_ID", "5000")
}

func main() {
	defaults := parentStruct{
		Name: "parent1",
		ID:   1234,
	}

	k.Load(structs.Provider(defaults, "koanf"), nil)

	k.Load(env.Provider("MYVAR_", ".", func(s string) string {
		return strings.Replace(strings.ToLower(
			strings.TrimPrefix(s, "MYVAR_")), "_", ".", -1)
	}), nil)

	f := flag.NewFlagSet("config", flag.ContinueOnError)
	f.String("name", defaults.Name, "")
	f.Int("id", defaults.ID, "")
	f.Parse([]string{"--name", "changed-from-flags"})

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

	fmt.Printf("id is = `%d`\n", k.Int("id"))
	fmt.Printf("name is = `%s`\n", k.String("name"))
}

# id is = `5000`
# name is = `changed-from-flags`

Hi @jkroepke. Isn't this the same behaviour as described in #254?

Correct. But my proposal was closed, but the bug still exists.

I took a slightly different approach here without having to break Provider()'s backwards compatibility. It's a bit hacky, but preserves old behaviour and can take more options and config in the future. Please take a look at #259

basicflag.Provider(flags, ".", &basicflag.Opt{KeyMap: existingKoanfInstance}), nil)