Dead simple loading YAMLs as config files
First, define your config struct:
type MyConfig struct {
Address string
LoginName string `yaml:"username"`
}
then, load:
import "github.com/XANi/go-yamlcfg"
cfgFiles := []string{
"$HOME/.config/my/cnf.yaml",
"./cfg/config.yaml",
"/etc/my/cnf.yaml",
}
var cfg MyConfig
err := yamlcfg.LoadConfig(cfgFiles, &cfg)
It will err out on:
- no readable file in config file list
- first file found was unparseable
Adding method GetSecret(string) string
to a struct enables template parsing via text/template
before parsing the YAML
Aside from standard text/template
functions additional ones are available:
{{ secret "secretname"}}
will callGetSecret("secretname") string
method on the config struct. Connect any serial retrieval there. The method should report errors separately as there is not really sensible way to push errors up{{ env "USER"}}
will callos.Getenv
Both outputs are string only so lack of key should be signalled with empty string if you want to base config logic on it. But do try to avoid making config into an application, this is not a library for that and this function is designed so loading vars from k8s or environment is easier.
Be warned, the value is inserted into raw text of YAML so it is entirely possible to have undesirable config injected via ENV (if say it contains newlines), so it should only be used when inputs are secure.
If you need to have more flexible config format, say a plugin list with each plugin having its own separate config definition,
you might want to use yaml.Node
(from yaml.v3 module) to specify a part of config as to be parsed later, like
type PluginConfig struct {
Name string `yaml:"name"`
Plugin string `yaml:"plugin"`
Config yaml.Node `yaml:"config"`
}
// pass your config struct to this function, it will fill it
func (p *PluginConfig) GetConfig(i interface{}) error{
if p.Config.Kind != 0 {
return p.Config.Decode(i)
} else {
// no changes to the struct
// make sure your plugin handles that and loads the default config or errors out if applicable
return nil
}
}
type Config struct {
Plugins []PluginConfig
}
and then re-parse that fragment when initializing plugin:
func (p *Plugin) initPlugin(cfg PluginConfig) error {
pluginCfg := pluginConfig{
Default: "values"
}
err := cfg.GetConfig()
if err != nil { return err }
}
Define method SetConfigPath(string)
on config struct like that:
func (c *testCfg1)SetConfigPath(s string) {
log.Infof("Loaded config file from %s",s)
}
Define method GetDefaultConfig() string
that returns default config, like that:
var testCfg3Default = `---
test3: testing
`
func (c *testCfg3) GetDefaultConfig() string {
return testCfg3Default
}
or, just return yaml directly if you do not need comments: {
func (c *testCfg) GetDefaultConfig() string {
defaultCfg := testCfg{
User: "root",
Pass: yamlcfg.RandomString(yamlcfg.CharsetAlphanumeric, 32)
}
out, err := yaml.Marshal(&defaultCfg)
if err != nil {panic(fmt.Errorf("can't marshal [%T- %+v] into YAML: %s",defaultCfg,defaultCfg,err))}
return string(out)
}
if GetDefaultConfig()
returns empty string, config will not be created
Add Validate() error
method. It will be called at the end and err returned will be returned from LoadConfig
.
It is also a good place to put any default value handling.
Default config (and any sub-dirs leading to it) will be created at first entry of cfgFiles, then loaded
- basic include support
- validation and default values (altho preinializing config struct kinda does that now )