1.9.16

viper, go-flag, and pflag: How they intersect, and how to use them.


I recently dove into golang configuration management

And i learned a lot about how flag and pflag have evolved.

go FLAG is the standard go library.  It binds flags immediately at runtime into structs, typically, someone will call flag.Parse() to do so.  Ideally, and library will *not* call flag.Parse, since that should be done at the highest level of the application.  HOWEVER thats not always the case.

go TEST case in point: Go test. Go test is the standard go testing library.  There are tools like ginkgo which use it.  Since go TEST parses FLAG, there is no way to get around go test flag parsing.

PFLAG is a drop in replacement for flag.  It basically allows you to import it as
 ("flag" github.com/spf13/flag) so that your code is backward compatible (i.e. you don't have to modify every reference to go FLAG).  HOWEVER, if you're code depends on code that imports "flag" (plain old flag), you will then be using both Pflag AND flag .  THIS IS VERY BAD ! Because FLAG doesn't support unrecognized flags, meaning you have to add all flags to flag (even if you're using flag to parse the flags and "flag" is not using them at all.  Someone please the "flag" folks to accept unrecognized flags https://github.com/golang/go/issues/6112.

In any case: What the hell should you do if you want to redo flags in a poly got app that might or might not be supporting flag in a way that is flexible ? 

1) Dont rely on flag if you can help it.  Try to rely on flag.

2) If you depend on a library (like go test) that uses "flag", then file an issue.  Library code shouldn't be parsing flags !

3) Stop using flags all together and try VIPER!

THE SOLUTION

Viper is a cool config manager for golang.  It supports a declarative configuration style, where you can have parameters that are nested in json or yaml, or even etcd or other sources.  It scans these sources and can bind to a struct as necessary.  It also allows you to iterate through all config options and have an associated custom action, for example, setting a 'flag' value if you're dealing with legacy code that depends on flag.

The only thing you need is a viper config name.  For example, if the name is "my-configs" it will look for my-configs.json, my-configs.yaml, and so on.

EXAMPLE

Here is a full blown viper unit test that shows how to use hierarchical parameters, env variables, and so on to create a unified configuration object.  This assumes a test.son file is provided with corresponding json:

➜  jayunit100  cat test.json
{
        "cat food":"purina",
        "purina":{
                "ingredients":["lamb","corn"]
        }
}

And, to leverage it with viper, just do this:

// First lets do a simple test that Viper can read env vars.
viper.SetEnvPrefix("viper")
os.Setenv("VIPER_DOGFOOD""puppy chow")
// Viper: Read it in as "dogfood"
viper.BindEnv("dogfood")
AssertKV(t, "dogfood","puppy chow")
 // Now, viper will find the test.json config automatically if you name it accordingly.
viper.SetConfigName("test")
viper.AddConfigPath(".")
viper.ReadInConfig()
// Finally lets show that the env var, as well as nested parameters found in the json are all available to the unified viper object.
AssertKV(t, "dogfood","puppy chow")
AssertSlice(t, "purina.ingredients","lamb",0)
AssertSlice(t, "purina.ingredients","corn",1)
}

Yipppeee !

No comments:

Post a Comment