koanf是一个库,用于在GO应用程序中以不同格式的不同来源读取配置。它是SPF13/Viper的更清洁,更轻松的替代品,具有更好的抽象和可扩展性,依赖性较少。
koanf V2具有用于从各种来源读取配置的模块(提供商),例如文件,命令线标志,环境变量,库库和S3,以及用于解析(解析器)格式,例如JSON,YAML,TOML,TOML,Hashicorp HCl。很容易插入自定义解析器和提供商。
提供者和解析器中的所有外部依赖项均与核心分离,并且可以根据需要单独安装。
安装
# Install the core.
go get -u github.com/knadh/ koanf /v2
# Install the necessary Provider(s).
# Available: file, env/v2, posflag, basicflag, confmap, rawbytes,
# structs, fs, s3, appconfig/v2, consul/v2, etcd/v2, vault/v2, parameterstore/v2
# eg: go get -u github.com/knadh/ koanf /providers/s3
# eg: go get -u github.com/knadh/ koanf /providers/consul/v2
go get -u github.com/knadh/ koanf /providers/file
# Install the necessary Parser(s).
# Available: toml, toml/v2, json, yaml, dotenv, hcl, hjson, nestedtext
# go get -u github.com/knadh/ koanf /parsers/$parser
go get -u github.com/knadh/ koanf /parsers/toml
请参阅所有捆绑的提供商和解析器的列表。
内容
- 概念
- 从文件读取配置
- 观看文件以进行更改
- 从命令行读取
- 阅读环境变量
- 读取原始字节
- 从地图和结构阅读
- 解除和编组
- 合并和关键案例灵敏度的顺序
- 定制提供商和解析器
- 定制合并策略
- 可安装的提供商和解析器列表
概念
-
koanf .Provider是一个通用接口,可提供配置,例如,来自文件,环境变量,HTTP源或任何地方。配置可以是解析器可以解析的原始字节,也可以是可以直接加载的嵌套map[string]interface{}。 -
koanf .Parser是一个通用接口,可以采用原始字节,解析并返回一个嵌套的map[string]interface{}。例如,JSON和YAML解析器。 - 一旦加载到koanf中,配置就会由界定的关键路径语法查询。例如:
app.server.port。可以选择任何定界符。 - 可以将来自多个源的配置加载并合并到koanf实例中,例如,首先从文件加载,并用命令行的标志覆盖某些值。
通过这两个接口实现, koanf可以从任何源以任何格式获得配置,并将其解析,并使应用程序可用于应用程序。
从文件读取配置
package main
import (
"fmt"
"log"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /parsers/yaml"
"github.com/knadh/ koanf /providers/file"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf . New ( "." )
func main () {
// Load JSON config.
if err := k . Load ( file . Provider ( "mock/mock.json" ), json . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
// Load YAML config and merge into the previously loaded config (because we can).
k . Load ( file . Provider ( "mock/mock.yml" ), yaml . Parser ())
fmt . Println ( "parent's name is = " , k . String ( "parent1.name" ))
fmt . Println ( "parent's ID is = " , k . Int ( "parent1.id" ))
}
观看文件以进行更改
一些提供商揭示了一种Watch()方法,该方法使提供商手表更改配置并触发回调以重新加载配置。如果在koanf对象上执行Load()时发生同时发生的*Get()调用,这不是goroutine安全的。这种情况将需要静音锁定。
file, appconfig, vault, consul提供商具有Watch()方法。
package main
import (
"fmt"
"log"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /parsers/yaml"
"github.com/knadh/ koanf /providers/file"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf . New ( "." )
func main () {
// Load JSON config.
f := file . Provider ( "mock/mock.json" )
if err := k . Load ( f , json . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
// Load YAML config and merge into the previously loaded config (because we can).
k . Load ( file . Provider ( "mock/mock.yml" ), yaml . Parser ())
fmt . Println ( "parent's name is = " , k . String ( "parent1.name" ))
fmt . Println ( "parent's ID is = " , k . Int ( "parent1.id" ))
// Watch the file and get a callback on change. The callback can do whatever,
// like re-load the configuration.
// File provider always returns a nil `event`.
f . Watch ( func ( event interface {}, err error ) {
if err != nil {
log . Printf ( "watch error: %v" , err )
return
}
// Throw away the old config and load a fresh copy.
log . Println ( "config changed. Reloading ..." )
k = koanf . New ( "." )
k . Load ( f , json . Parser ())
k . Print ()
})
// To stop a file watcher, call:
// f.Unwatch()
// Block forever (and manually make a change to mock/mock.json) to
// reload the config.
log . Println ( "waiting forever. Try making a change to mock/mock.json to live reload" )
<- make ( chan bool )
}
从命令行读取
下面的示例显示了posflag.Provider的使用,这是一个由SPF13/PFLAG库(高级命令行Lib)上的包装器。对于GO内置的flag软件包,请使用basicflag.Provider 。
package main
import (
"fmt"
"log"
"os"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /parsers/toml"
// TOML version 2 is available at:
// "github.com/knadh/ koanf /parsers/toml/v2"
"github.com/knadh/ koanf /providers/file"
"github.com/knadh/ koanf /providers/posflag"
flag "github.com/spf13/pflag"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf . New ( "." )
func main () {
// Use the POSIX compliant pflag lib instead of Go's flag lib.
f := flag . NewFlagSet ( "config" , flag . ContinueOnError )
f . Usage = func () {
fmt . Println ( f . FlagUsages ())
os . Exit ( 0 )
}
// Path to one or more config files to load into koanf along with some config params.
f . StringSlice ( "conf" , [] string { "mock/mock.toml" }, "path to one or more .toml config files" )
f . String ( "time" , "2020-01-01" , "a time string" )
f . String ( "type" , "xxx" , "type of the app" )
f . Parse ( os . Args [ 1 :])
// Load the config files provided in the commandline.
cFiles , _ := f . GetStringSlice ( "conf" )
for _ , c := range cFiles {
if err := k . Load ( file . Provider ( c ), toml . Parser ()); err != nil {
log . Fatalf ( "error loading file: %v" , err )
}
}
// "time" and "type" may have been loaded from the config file, but
// they can still be overridden with the values from the command line.
// The bundled posflag.Provider takes a flagset from the spf13/pflag lib.
// Passing the koanf instance to posflag helps it deal with default command
// line flag values that are not present in conf maps from previously loaded
// providers.
if err := k . Load ( posflag . Provider ( f , "." , k ), nil ); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
fmt . Println ( "time is = " , k . String ( "time" ))
}
阅读环境变量
package main
import (
"fmt"
"log"
"strings"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /providers/env/v2"
"github.com/knadh/ koanf /providers/file"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var k = koanf . New ( "." )
func main () {
// Load JSON config.
if err := k . Load ( file . Provider ( "mock/mock.json" ), json . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
// Load only environment variables with prefix "MYVAR_" and merge into config.
// Transform var names by:
// 1. Converting to lowercase
// 2. Removing "MYVAR_" prefix
// 3. Replacing "_" with "." to representing nesting using the . delimiter.
// Example: MYVAR_PARENT1_CHILD1_NAME becomes "parent1.child1.name"
k . Load ( env . Provider ( "." , env. Opt {
Prefix : "MYVAR_" ,
TransformFunc : func ( k , v string ) ( string , any ) {
// Transform the key.
k = strings . ReplaceAll ( strings . ToLower ( strings . TrimPrefix ( k , "MYVAR_" )), "_" , "." )
// Transform the value into slices, if they contain spaces.
// Eg: MYVAR_TAGS="foo bar baz" -> tags: ["foo", "bar", "baz"]
// This is to demonstrate that string values can be transformed to any type
// where necessary.
if strings . Contains ( v , " " ) {
return k , strings . Split ( v , " " )
}
return k , v
},
}), nil )
fmt . Println ( "name is =" , k . String ( "parent1.child1.name" ))
fmt . Println ( "time is =" , k . Time ( "time" , time . DateOnly ))
fmt . Println ( "ids are =" , k . Strings ( "parent1.child1.grandchild1.ids" ))
} `` `
### Reading from an S3 bucket
` `` go
// Load JSON config from s3.
if err := k . Load ( s3 . Provider (s3. Config {
AccessKey : os . Getenv ( "AWS_S3_ACCESS_KEY" ),
SecretKey : os . Getenv ( "AWS_S3_SECRET_KEY" ),
Region : os . Getenv ( "AWS_S3_REGION" ),
Bucket : os . Getenv ( "AWS_S3_BUCKET" ),
ObjectKey : "dir/config.json" ,
}), json . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
读取原始字节
捆绑的rawbytes提供商可用于从源中读取任意字节,例如数据库或HTTP调用。
package main
import (
"fmt"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /providers/rawbytes"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var k = koanf . New ( "." )
func main () {
b := [] byte ( `{"type": "rawbytes", "parent1": {"child1": {"type": "rawbytes"}}}` )
k . Load ( rawbytes . Provider ( b ), json . Parser ())
fmt . Println ( "type is = " , k . String ( "parent1.child1.type" ))
}
解除和编组
Parser可用于根据字段标签将koanf实例中的值和扫描值扫描到一个结构中,并将koanf实例元用回到序列化字节中,例如json或yaml文件
package main
import (
"fmt"
"log"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /providers/file"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var (
k = koanf . New ( "." )
parser = json . Parser ()
)
func main () {
// Load JSON config.
if err := k . Load ( file . Provider ( "mock/mock.json" ), parser ); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
// Structure to unmarshal nested conf to.
type childStruct struct {
Name string ` koanf :"name"`
Type string ` koanf :"type"`
Empty map [ string ] string ` koanf :"empty"`
GrandChild struct {
Ids [] int ` koanf :"ids"`
On bool ` koanf :"on"`
} ` koanf :"grandchild1"`
}
var out childStruct
// Quick unmarshal.
k . Unmarshal ( "parent1.child1" , & out )
fmt . Println ( out )
// Unmarshal with advanced config.
out = childStruct {}
k . UnmarshalWithConf ( "parent1.child1" , & out , koanf . UnmarshalConf { Tag : " koanf " })
fmt . Println ( out )
// Marshal the instance back to JSON.
// The parser instance can be anything, eg: json.Parser(), yaml.Parser() etc.
b , _ := k . Marshal ( parser )
fmt . Println ( string ( b ))
}
用平坦的路径拆开
有时,有必要将各种钥匙从各种嵌套结构转换为平坦的目标结构。 UnmarshalConf.FlatPaths标志是可能的。
package main
import (
"fmt"
"log"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /providers/file"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var k = koanf . New ( "." )
func main () {
// Load JSON config.
if err := k . Load ( file . Provider ( "mock/mock.json" ), json . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
type rootFlat struct {
Type string ` koanf :"type"`
Empty map [ string ] string ` koanf :"empty"`
Parent1Name string ` koanf :"parent1.name"`
Parent1ID int ` koanf :"parent1.id"`
Parent1Child1Name string ` koanf :"parent1.child1.name"`
Parent1Child1Type string ` koanf :"parent1.child1.type"`
Parent1Child1Empty map [ string ] string ` koanf :"parent1.child1.empty"`
Parent1Child1Grandchild1IDs [] int ` koanf :"parent1.child1.grandchild1.ids"`
Parent1Child1Grandchild1On bool ` koanf :"parent1.child1.grandchild1.on"`
}
// Unmarshal the whole root with FlatPaths: True.
var o1 rootFlat
k . UnmarshalWithConf ( "" , & o1 , koanf . UnmarshalConf { Tag : " koanf " , FlatPaths : true })
fmt . Println ( o1 )
// Unmarshal a child structure of "parent1".
type subFlat struct {
Name string ` koanf :"name"`
ID int ` koanf :"id"`
Child1Name string ` koanf :"child1.name"`
Child1Type string ` koanf :"child1.type"`
Child1Empty map [ string ] string ` koanf :"child1.empty"`
Child1Grandchild1IDs [] int ` koanf :"child1.grandchild1.ids"`
Child1Grandchild1On bool ` koanf :"child1.grandchild1.on"`
}
var o2 subFlat
k . UnmarshalWithConf ( "parent1" , & o2 , koanf . UnmarshalConf { Tag : " koanf " , FlatPaths : true })
fmt . Println ( o2 )
}
从嵌套地图上读取
捆绑的confmap提供商采用可以加载到koanf实例中的map[string]interface{} 。
package main
import (
"fmt"
"log"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /providers/confmap"
"github.com/knadh/ koanf /providers/file"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /parsers/yaml"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf . New ( "." )
func main () {
// Load default values using the confmap provider.
// We provide a flat map with the "." delimiter.
// A nested map can be loaded by setting the delimiter to an empty string "".
k . Load ( confmap . Provider ( map [ string ] interface {}{
"parent1.name" : "Default Name" ,
"parent3.name" : "New name here" ,
}, "." ), nil )
// Load JSON config on top of the default values.
if err := k . Load ( file . Provider ( "mock/mock.json" ), json . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
// Load YAML config and merge into the previously loaded config (because we can).
k . Load ( file . Provider ( "mock/mock.yml" ), yaml . Parser ())
fmt . Println ( "parent's name is = " , k . String ( "parent1.name" ))
fmt . Println ( "parent's ID is = " , k . Int ( "parent1.id" ))
}
从结构阅读
捆绑的structs提供商可用于读取从结构的数据,以加载到koanf实例中。
package main
import (
"fmt"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /providers/structs"
)
// 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"`
Child1 childStruct ` koanf :"child1"`
}
type childStruct struct {
Name string ` koanf :"name"`
Type string ` koanf :"type"`
Empty map [ string ] string ` koanf :"empty"`
Grandchild1 grandchildStruct ` koanf :"grandchild1"`
}
type grandchildStruct struct {
Ids [] int ` koanf :"ids"`
On bool ` koanf :"on"`
}
type sampleStruct struct {
Type string ` koanf :"type"`
Empty map [ string ] string ` koanf :"empty"`
Parent1 parentStruct ` koanf :"parent1"`
}
func main () {
// Load default values using the structs provider.
// We provide a struct along with the struct tag ` koanf ` to the
// provider.
k . Load ( structs . Provider ( sampleStruct {
Type : "json" ,
Empty : make ( map [ string ] string ),
Parent1 : parentStruct {
Name : "parent1" ,
ID : 1234 ,
Child1 : childStruct {
Name : "child1" ,
Type : "json" ,
Empty : make ( map [ string ] string ),
Grandchild1 : grandchildStruct {
Ids : [] int { 1 , 2 , 3 },
On : true ,
},
},
},
}, " koanf " ), nil )
fmt . Printf ( "name is = `%s` \n " , k . String ( "parent1.child1.name" ))
}
合并行为
默认行为
当您以这种方式创建koanf时,默认行为是: koanf .New(delim) ,最新的加载配置将与上一个合并。
例如: first.yml
key : [1,2,3] second.yml
key : ' string '加载second.yml时,它将覆盖first.yml类型。
如果不需要此行为,则可以“严格”合并。在同一情况下, Load将返回错误。
package main
import (
"errors"
"log"
"github.com/knadh/ koanf /v2"
"github.com/knadh/ koanf /maps"
"github.com/knadh/ koanf /parsers/json"
"github.com/knadh/ koanf /parsers/yaml"
"github.com/knadh/ koanf /providers/file"
)
var conf = koanf . Conf {
Delim : "." ,
StrictMerge : true ,
}
var k = koanf . NewWithConf ( conf )
func main () {
yamlPath := "mock/mock.yml"
if err := k . Load ( file . Provider ( yamlPath ), yaml . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
jsonPath := "mock/mock.json"
if err := k . Load ( file . Provider ( jsonPath ), json . Parser ()); err != nil {
log . Fatalf ( "error loading config: %v" , err )
}
}
注意:当合并不同的扩展名时,每个解析器都可以对其类型的处理方式有所不同,这意味着即使您的负载相同类型,也可能会在StrictMerge: true 。
例如:合并JSON和YAML很可能会失败,因为JSON将整数视为Float64,而Yaml将其视为整数。
合并和关键案例灵敏度的顺序
- config键在koanf中对大小写。例如,
app.server.port和APP.SERVER.port不一样。 - koanf不会在来自各个提供商的加载配置上施加任何订购。每个连续的
Load()或Merge()将新的配置合并到现有配置中。也就是说,可以先加载环境变量,然后在其顶部加载文件,然后在其顶部或任何此类订单上加载命令行变量。
定制提供商和解析器
提供商返回一个可以直接加载到koanf koanf .Load()中的嵌套map[string]interface{}配置,或者它可以返回可以用解析器解析的原始字节(再次使用koanf .Load()加载。
定制合并策略
默认情况下,当使用Load()合并两个配置源时, koanf递归合并了嵌套地图( map[string]interface{} )的键,而静态值则被覆盖(切片,字符串等)。可以通过使用WithMergeFunc选项提供自定义合并函数来更改此行为。
通过命令行克隆项目: