go-config
An extensible go configuration. The default parsers can parse the CLI arguments and the ini file. You can implement and register your parser, and the configuration engine will call the parser to parse the configuration.
The inspiration is from oslo.config, which is a OpenStack
library for config.
The current version is v9
. See DOC.
The supported Go version: 1.x
.
Principle of Work
- Create a
Config
engine. - (Optional) Add the CLI and non-CLI parsers into the
Config
. - Register the common options into
Config
. - Call the method
Parse()
to parse the options.
- Start to parse the configuration.
- Call the CLI parser if exists, and the CLI parser parses the CLI arguments.
- Call each other parsers according to the order that they are registered.
- Call the method
Parse()
of the parser. - The parser parses the options and sets values.
- Check whether some required options have neither the parsed value nor the default value.
Notice: when setting the parsed value, it will calling the validators to validate it if setting the validators for the option.
Parser
In order to deveplop a new parser, you just need to implement the interface Parser
. But Config
distinguishes the CLI parser and the common parser, which have the same interface Parser
. But Config
must have no more than one CLI parser set by ResetCLIParser()
and maybe have many common parsers added by AddParser()
. See the example above.
Read and Modify the value from Config
It's thread-safe for the application to read the configuration value from the Config
, but you must not modify it.
If you want to the value of a certain configuration, you should call the method SetOptValue(groupName, optName, newOptValue)
. For the default group, groupName
may be ""
. If the setting fails, it will return an error. Moreover, SetOptValue
is thread-safe. During the running, therefore, you can get and set the configuration value between goroutines dynamically.
For the modifiable type, such as slice or map, in order to modify them, you should clone them firstly, then modify the cloned value and call SetOptValue
with the cloned one.
Watch the changed configuration
You can use the method Watch(callback func(groupName, optName string, optValue interface{}))
to monitor what the configuration is modified to: when a certain configuration is modified, the callback function will be called.
Notice: the callback should finish as soon as possible because the callback is called synchronously at when the configuration is modified.
Usage
package main
import (
"flag"
"fmt"
"os"
config "github.com/xgfone/go-config"
)
func main() {
cliParser := config.NewDefaultFlagCliParser(true)
iniParser := config.NewSimpleIniParser("config-file")
conf := config.NewConfig(cliParser).AddParser(iniParser)
ipOpt := config.StrOpt("", "ip", "", "the ip address").SetValidators(NewIPValidator())
conf.RegisterCliOpt("", ipOpt)
conf.RegisterCliOpt("", config.IntOpt("", "port", 80, "the port"))
conf.RegisterCliOpt("", config.StrOpt("", "config-file", "", "The path of the ini config file."))
conf.RegisterCliOpt("redis", config.StrOpt("", "conn", "redis://127.0.0.1:6379/0", "the redis connection url"))
conf.SetAddVersion("1.0.0")
if err := conf.Parse(); err != nil {
conf.Audit()
fmt.Println(err)
return
}
fmt.Println(conf.String("ip"))
fmt.Println(conf.Int("port"))
fmt.Println(conf.Group("redis").String("conn"))
fmt.Println(conf.Args)
}
You can also create a new Config
by the NewDefault()
, which will use NewDefaultFlagCliParser(true)
as the CLI parser, add the ini parser NewSimpleIniParser()
and register the CLI option config-file
, which you change it by modifying the value of the variable IniParserOptName
. Notice: NewDefault()
does not add the environment variable parser, and you need to add it by hand, such as NewDefault().AddParser(NewEnvVarParser(""))
.
The package has created a global default Config
created by NewDefault()
like doing above, which is Conf
. You can use it, like the global variable CONF
in oslo.config
. For example,
package main
import (
"fmt"
config "github.com/xgfone/go-config"
)
var opts = []config.Opt{
config.Str("ip", "", "the ip address").AddValidators(NewIPValidator()),
config.Int("port", 80, "the port").AddValidators(NewPortValidator()),
}
func main() {
config.Conf.RegisterCliOpts("", opts)
config.Conf.Parse("-ip", "0.0.0.0")
fmt.Println(config.Conf.String("ip"))
fmt.Println(config.Conf.Int("port"))
}
You also register a struct then use it.
package main
import (
"fmt"
config "github.com/xgfone/go-config"
)
func main() {
type Address struct {
Address []string `default:""`
}
type S struct {
Name string `name:"name" cli:"1" default:"Aaron" help:"The user name"`
Age int8 `cli:"t" default:"123"`
Addr1 Address `group:"group" cli:"true"`
Addr2 Address `cli:"true"`
Addr3 Address
Ignore string `name:"-"`
}
args := []string{"--age", "18", "--group-address", "abc,def", "--addr2-address", "xyz"}
s := S{}
config.Conf.RegisterStruct("", &s)
if err := config.Conf.Parse(args...); err != nil {
fmt.Println(err)
return
}
fmt.Printf("Name: %s\n", s.Name)
fmt.Printf("Age: %d\n", s.Age)
fmt.Printf("Address: %v\n", s.Addr1.Address)
fmt.Printf("Address: %v\n", s.Addr2.Address)
fmt.Printf("Address: %v\n", s.Addr3.Address)
fmt.Printf("Name: %s\n", config.Conf.String("name"))
fmt.Printf("Age: %d\n", config.Conf.Int8("age"))
fmt.Printf("Address: %v\n", config.Conf.Group("group").Strings("address"))
fmt.Printf("Address: %v\n", config.Conf.Group("addr2").Strings("address"))
fmt.Printf("Address: %v\n", config.Conf.Group("addr3").Strings("address"))
}