feat: introduced apollo for paladin with driver registration.

pull/340/head
colstuwjx 5 years ago
parent 526c532bba
commit c90108b14f
  1. 2
      go.mod
  2. 2
      go.sum
  3. 74
      pkg/conf/paladin/README.md
  4. 273
      pkg/conf/paladin/apollo/apollo.go
  5. 73
      pkg/conf/paladin/apollo/apollo_test.go
  6. 6
      pkg/conf/paladin/apollo/const.go
  7. 149
      pkg/conf/paladin/apollo/internal/mockserver/mockserver.go
  8. 25
      pkg/conf/paladin/default.go
  9. 9
      pkg/conf/paladin/driver.go
  10. 39
      pkg/conf/paladin/example_test.go
  11. 4
      pkg/conf/paladin/file.go
  12. 10
      pkg/conf/paladin/map.go
  13. 55
      pkg/conf/paladin/register.go
  14. 2
      pkg/conf/paladin/toml.go
  15. 11
      pkg/conf/paladin/value.go

@ -26,9 +26,11 @@ require (
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/leodido/go-urn v1.1.0 // indirect github.com/leodido/go-urn v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-colorable v0.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/montanaflynn/stats v0.5.0 github.com/montanaflynn/stats v0.5.0
github.com/openzipkin/zipkin-go v0.2.1 github.com/openzipkin/zipkin-go v0.2.1
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3
github.com/philchia/agollo v2.3.1+incompatible
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_golang v1.1.0
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect

@ -171,6 +171,8 @@ github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 h1:zjmNboC3QFuMdJSaZJ7Qvi3HUxWXPdj7wb3rc4jH5HI= github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 h1:zjmNboC3QFuMdJSaZJ7Qvi3HUxWXPdj7wb3rc4jH5HI=
github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3/go.mod h1:pLR8n2aimFxvvDJ6n8JuQWthMGezCYMjuhlaTjPTZf0= github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3/go.mod h1:pLR8n2aimFxvvDJ6n8JuQWthMGezCYMjuhlaTjPTZf0=
github.com/philchia/agollo v2.3.1+incompatible h1:C4zDDuOcP1Qynikz2rSJQSMjwexv4GfDpwBHJRinhPc=
github.com/philchia/agollo v2.3.1+incompatible/go.mod h1:EXNdWdQkS+QBi0nb/Xm+sBBuQ1PM7/NIPr1JDzOlt8A=
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

@ -2,17 +2,18 @@
##### 项目简介 ##### 项目简介
paladin 是一个config SDK客户端,包括了file、mock几个抽象功能,方便使用本地文件或者sven配置中心,并且集成了对象自动reload功能。 paladin 是一个config SDK客户端,包括了file、mock几个抽象功能,方便使用本地文件或者sven\apollo配置中心,并且集成了对象自动reload功能。
local files: local files:
``` ```
demo -conf=/data/conf/app/msm-servie.toml demo -conf=/data/conf/app/msm-servie.toml
// or dir // or dir
demo -conf=/data/conf/app/ demo -conf=/data/conf/app/
``` ```
example:
*注:使用远程配置中心的用户在执行应用,如这里的`demo`时务必**不要**带上`-conf`参数,具体见下文远程配置中心的例子*
local file example:
``` ```
type exampleConf struct { type exampleConf struct {
Bool bool Bool bool
@ -65,6 +66,71 @@ func ExampleClient() {
} }
``` ```
remote config center example:
```
type exampleConf struct {
Bool bool
Int int64
Float float64
String string
}
func (e *exampleConf) Set(text string) error {
var ec exampleConf
if err := yaml.Unmarshal([]byte(text), &ec); err != nil {
return err
}
*e = ec
return nil
}
func ExampleApolloClient() {
/*
pass flags or set envs that apollo needs, for example:
```
export APOLLO_APP_ID=SampleApp
export APOLLO_CLUSTER=default
export APOLLO_CACHE_DIR=/tmp
export APOLLO_META_ADDR=localhost:8080
export APOLLO_NAMESPACES=example.yml
```
*/
if err := paladin.Init(apollo.PaladinDriverApollo); err != nil {
panic(err)
}
var (
ec exampleConf
eo exampleConf
m paladin.Map
strs []string
)
// config unmarshal
if err := paladin.Get("example.yml").UnmarshalYAML(&ec); err != nil {
panic(err)
}
// config setter
if err := paladin.Watch("example.yml", &ec); err != nil {
panic(err)
}
// paladin map
if err := paladin.Watch("example.yml", &m); err != nil {
panic(err)
}
s, err := m.Value("key").String()
b, err := m.Value("key").Bool()
i, err := m.Value("key").Int64()
f, err := m.Value("key").Float64()
// value slice
err = m.Value("strings").Slice(&strs)
// watch key
for event := range paladin.WatchEvent(context.TODO(), "key") {
fmt.Println(event)
}
}
```
##### 编译环境 ##### 编译环境
- **请只用 Golang v1.12.x 以上版本编译执行** - **请只用 Golang v1.12.x 以上版本编译执行**

@ -0,0 +1,273 @@
package apollo
import (
"context"
"errors"
"flag"
"log"
"os"
"strings"
"sync"
"time"
"github.com/philchia/agollo"
"github.com/bilibili/kratos/pkg/conf/paladin"
)
var (
_ paladin.Client = &apollo{}
defaultValue = ""
)
type apolloWatcher struct {
keys []string // in apollo, they're called namespaces
C chan paladin.Event
}
func newApolloWatcher(keys []string) *apolloWatcher {
return &apolloWatcher{keys: keys, C: make(chan paladin.Event, 5)}
}
func (aw *apolloWatcher) HasKey(key string) bool {
if len(aw.keys) == 0 {
return true
}
for _, k := range aw.keys {
if k == key {
return true
}
}
return false
}
func (aw *apolloWatcher) Handle(event paladin.Event) {
select {
case aw.C <- event:
default:
log.Printf("paladin: event channel full discard ns %s update event", event.Key)
}
}
// apollo is apollo config client.
type apollo struct {
client *agollo.Client
values *paladin.Map
wmu sync.RWMutex
watchers map[*apolloWatcher]struct{}
}
// Config is apollo config client config.
type Config struct {
AppID string `json:"app_id"`
Cluster string `json:"cluster"`
CacheDir string `json:"cache_dir"`
MetaAddr string `json:"meta_addr"`
Namespaces []string `json:"namespaces"`
}
type apolloDriver struct{}
var (
confAppID, confCluster, confCacheDir, confMetaAddr, confNamespaces string
)
func init() {
addApolloFlags()
paladin.Register(PaladinDriverApollo, &apolloDriver{})
}
func addApolloFlags() {
flag.StringVar(&confAppID, "apollo.appid", "", "apollo app id")
flag.StringVar(&confCluster, "apollo.cluster", "", "apollo cluster")
flag.StringVar(&confCacheDir, "apollo.cachedir", "/tmp", "apollo cache dir")
flag.StringVar(&confMetaAddr, "apollo.metaaddr", "", "apollo meta server addr, e.g. localhost:8080")
flag.StringVar(&confNamespaces, "apollo.namespaces", "", "subscribed apollo namespaces, comma separated, e.g. app.yml,mysql.yml")
}
func buildConfigForApollo() (c *Config, err error) {
if appidFromEnv := os.Getenv("APOLLO_APP_ID"); appidFromEnv != "" {
confAppID = appidFromEnv
}
if confAppID == "" {
err = errors.New("invalid apollo appid, pass it via APOLLO_APP_ID=xxx with env or --apollo.appid=xxx with flag")
return
}
if clusterFromEnv := os.Getenv("APOLLO_CLUSTER"); clusterFromEnv != "" {
confCluster = clusterFromEnv
}
if confAppID == "" {
err = errors.New("invalid apollo cluster, pass it via APOLLO_CLUSTER=xxx with env or --apollo.cluster=xxx with flag")
return
}
if cacheDirFromEnv := os.Getenv("APOLLO_CACHE_DIR"); cacheDirFromEnv != "" {
confCacheDir = cacheDirFromEnv
}
if metaAddrFromEnv := os.Getenv("APOLLO_META_ADDR"); metaAddrFromEnv != "" {
confMetaAddr = metaAddrFromEnv
}
if confMetaAddr == "" {
err = errors.New("invalid apollo meta addr, pass it via APOLLO_META_ADDR=xxx with env or --apollo.metaaddr=xxx with flag")
return
}
if namespacesFromEnv := os.Getenv("APOLLO_NAMESPACES"); namespacesFromEnv != "" {
confNamespaces = namespacesFromEnv
}
namespaceNames := strings.Split(confNamespaces, ",")
if len(namespaceNames) == 0 {
err = errors.New("invalid apollo namespaces, pass it via APOLLO_NAMESPACES=xxx with env or --apollo.namespaces=xxx with flag")
return
}
c = &Config{
AppID: confAppID,
Cluster: confCluster,
CacheDir: confCacheDir,
MetaAddr: confMetaAddr,
Namespaces: namespaceNames,
}
return
}
// New new an apollo config client.
// it watches apollo namespaces changes and updates local cache.
// BTW, in our context, namespaces in apollo means keys in paladin.
func (ad *apolloDriver) New() (paladin.Client, error) {
c, err := buildConfigForApollo()
if err != nil {
return nil, err
}
return ad.new(c)
}
func (ad *apolloDriver) new(conf *Config) (paladin.Client, error) {
if conf == nil {
err := errors.New("invalid apollo conf")
return nil, err
}
client := agollo.NewClient(&agollo.Conf{
AppID: conf.AppID,
Cluster: conf.Cluster,
NameSpaceNames: conf.Namespaces, // these namespaces will be subscribed at init
CacheDir: conf.CacheDir,
IP: conf.MetaAddr,
})
err := client.Start()
if err != nil {
return nil, err
}
a := &apollo{
client: client,
values: new(paladin.Map),
watchers: make(map[*apolloWatcher]struct{}),
}
raws, err := a.loadValues(conf.Namespaces)
if err != nil {
return nil, err
}
a.values.Store(raws)
// watch namespaces by default.
a.WatchEvent(context.TODO(), conf.Namespaces...)
go a.watchproc(conf.Namespaces)
return a, nil
}
// loadValues load values from apollo namespaces to values
func (a *apollo) loadValues(keys []string) (values map[string]*paladin.Value, err error) {
values = make(map[string]*paladin.Value, len(keys))
for _, k := range keys {
if values[k], err = a.loadValue(k); err != nil {
return
}
}
return
}
// loadValue load value from apollo namespace content to value
func (a *apollo) loadValue(key string) (*paladin.Value, error) {
content := a.client.GetNameSpaceContent(key, defaultValue)
return paladin.NewValue(content, content), nil
}
// reloadValue reload value by key and send event
func (a *apollo) reloadValue(key string) (err error) {
// NOTE: in some case immediately read content from client after receive event
// will get old content due to cache, sleep 100ms make sure get correct content.
time.Sleep(100 * time.Millisecond)
var (
value *paladin.Value
rawValue string
)
value, err = a.loadValue(key)
if err != nil {
return
}
rawValue, err = value.Raw()
if err != nil {
return
}
raws := a.values.Load()
raws[key] = value
a.values.Store(raws)
a.wmu.RLock()
n := 0
for w := range a.watchers {
if w.HasKey(key) {
n++
// FIXME(Colstuwjx): check change event and send detail type like EventAdd\Update\Delete.
w.Handle(paladin.Event{Event: paladin.EventUpdate, Key: key, Value: rawValue})
}
}
a.wmu.RUnlock()
log.Printf("paladin: reload config: %s events: %d\n", key, n)
return
}
// apollo config daemon to watch remote apollo notifications
func (a *apollo) watchproc(keys []string) {
events := a.client.WatchUpdate()
for {
select {
case event := <-events:
if err := a.reloadValue(event.Namespace); err != nil {
log.Printf("paladin: load key: %s error: %s, skipped", event.Namespace, err)
}
}
}
}
// Get return value by key.
func (a *apollo) Get(key string) *paladin.Value {
return a.values.Get(key)
}
// GetAll return value map.
func (a *apollo) GetAll() *paladin.Map {
return a.values
}
// WatchEvent watch with the specified keys.
func (a *apollo) WatchEvent(ctx context.Context, keys ...string) <-chan paladin.Event {
aw := newApolloWatcher(keys)
err := a.client.SubscribeToNamespaces(keys...)
if err != nil {
log.Printf("subscribe namespaces %v failed, %v", keys, err)
return aw.C
}
a.wmu.Lock()
a.watchers[aw] = struct{}{}
a.wmu.Unlock()
return aw.C
}
// Close close watcher.
func (a *apollo) Close() (err error) {
if err = a.client.Stop(); err != nil {
return
}
a.wmu.RLock()
for w := range a.watchers {
close(w.C)
}
a.wmu.RUnlock()
return
}

@ -0,0 +1,73 @@
package apollo
import (
"context"
"fmt"
"log"
"os"
"testing"
"time"
"github.com/bilibili/kratos/pkg/conf/paladin/apollo/internal/mockserver"
)
func TestMain(m *testing.M) {
setup()
code := m.Run()
teardown()
os.Exit(code)
}
func setup() {
go func() {
if err := mockserver.Run(); err != nil {
log.Fatal(err)
}
}()
// wait for mock server to run
time.Sleep(time.Millisecond * 500)
}
func teardown() {
mockserver.Close()
}
func TestApollo(t *testing.T) {
var (
testAppYAML = "app.yml"
testAppYAMLContent1 = "test: test12234\ntest2: test333"
testAppYAMLContent2 = "test: 1111"
testClientJSON = "client.json"
testClientJSONContent = `{"name":"agollo"}`
)
os.Setenv("APOLLO_APP_ID", "SampleApp")
os.Setenv("APOLLO_CLUSTER", "default")
os.Setenv("APOLLO_CACHE_DIR", "/tmp")
os.Setenv("APOLLO_META_ADDR", "localhost:8080")
os.Setenv("APOLLO_NAMESPACES", fmt.Sprintf("%s,%s", testAppYAML, testClientJSON))
mockserver.Set(testAppYAML, "content", testAppYAMLContent1)
mockserver.Set(testClientJSON, "content", testClientJSONContent)
ad := &apolloDriver{}
apollo, err := ad.New()
if err != nil {
t.Fatalf("new apollo error, %v", err)
}
value := apollo.Get(testAppYAML)
if content, _ := value.String(); content != testAppYAMLContent1 {
t.Fatalf("got app.yml unexpected value %s", content)
}
value = apollo.Get(testClientJSON)
if content, _ := value.String(); content != testClientJSONContent {
t.Fatalf("got app.yml unexpected value %s", content)
}
mockserver.Set(testAppYAML, "content", testAppYAMLContent2)
updates := apollo.WatchEvent(context.TODO(), testAppYAML)
select {
case <-updates:
case <-time.After(time.Millisecond * 30000):
}
value = apollo.Get(testAppYAML)
if content, _ := value.String(); content != testAppYAMLContent2 {
t.Fatalf("got app.yml unexpected updated value %s", content)
}
}

@ -0,0 +1,6 @@
package apollo
const (
// PaladinDriverApollo ...
PaladinDriverApollo = "apollo"
)

@ -0,0 +1,149 @@
package mockserver
import (
"context"
"encoding/json"
"net/http"
"strings"
"sync"
"time"
)
type notification struct {
NamespaceName string `json:"namespaceName,omitempty"`
NotificationID int `json:"notificationId,omitempty"`
}
type result struct {
// AppID string `json:"appId"`
// Cluster string `json:"cluster"`
NamespaceName string `json:"namespaceName"`
Configurations map[string]string `json:"configurations"`
ReleaseKey string `json:"releaseKey"`
}
type mockServer struct {
server http.Server
lock sync.Mutex
notifications map[string]int
config map[string]map[string]string
}
func (s *mockServer) NotificationHandler(rw http.ResponseWriter, req *http.Request) {
s.lock.Lock()
defer s.lock.Unlock()
req.ParseForm()
var notifications []notification
if err := json.Unmarshal([]byte(req.FormValue("notifications")), &notifications); err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
}
var changes []notification
for _, noti := range notifications {
if currentID := s.notifications[noti.NamespaceName]; currentID != noti.NotificationID {
changes = append(changes, notification{NamespaceName: noti.NamespaceName, NotificationID: currentID})
}
}
if len(changes) == 0 {
rw.WriteHeader(http.StatusNotModified)
return
}
bts, err := json.Marshal(&changes)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
}
rw.Write(bts)
}
func (s *mockServer) ConfigHandler(rw http.ResponseWriter, req *http.Request) {
req.ParseForm()
strs := strings.Split(req.RequestURI, "/")
var namespace, releaseKey = strings.Split(strs[4], "?")[0], req.FormValue("releaseKey")
config := s.Get(namespace)
var result = result{NamespaceName: namespace, Configurations: config, ReleaseKey: releaseKey}
bts, err := json.Marshal(&result)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
}
rw.Write(bts)
}
var server *mockServer
func (s *mockServer) Set(namespace, key, value string) {
server.lock.Lock()
defer server.lock.Unlock()
notificationID := s.notifications[namespace]
notificationID++
s.notifications[namespace] = notificationID
if kv, ok := s.config[namespace]; ok {
kv[key] = value
return
}
kv := map[string]string{key: value}
s.config[namespace] = kv
}
func (s *mockServer) Get(namespace string) map[string]string {
server.lock.Lock()
defer server.lock.Unlock()
return s.config[namespace]
}
func (s *mockServer) Delete(namespace, key string) {
server.lock.Lock()
defer server.lock.Unlock()
if kv, ok := s.config[namespace]; ok {
delete(kv, key)
}
notificationID := s.notifications[namespace]
notificationID++
s.notifications[namespace] = notificationID
}
// Set namespace's key value
func Set(namespace, key, value string) {
server.Set(namespace, key, value)
}
// Delete namespace's key
func Delete(namespace, key string) {
server.Delete(namespace, key)
}
// Run mock server
func Run() error {
initServer()
return server.server.ListenAndServe()
}
func initServer() {
server = &mockServer{
notifications: map[string]int{},
config: map[string]map[string]string{},
}
mux := http.NewServeMux()
mux.Handle("/notifications/", http.HandlerFunc(server.NotificationHandler))
mux.Handle("/configs/", http.HandlerFunc(server.ConfigHandler))
server.server.Handler = mux
server.server.Addr = ":8080"
}
// Close mock server
func Close() error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second))
defer cancel()
return server.server.Shutdown(ctx)
}

@ -2,6 +2,7 @@ package paladin
import ( import (
"context" "context"
"errors"
"flag" "flag"
) )
@ -16,12 +17,30 @@ func init() {
} }
// Init init config client. // Init init config client.
func Init() (err error) { // If confPath is set, it inits file client by default
// Otherwise we could pass args to init remote client
// args[0]: driver name, string type
func Init(args ...interface{}) (err error) {
if confPath != "" { if confPath != "" {
DefaultClient, err = NewFile(confPath) DefaultClient, err = NewFile(confPath)
} else { } else {
// TODO: Get the configuration from the remote service var (
panic("Please specify a file or dir name by -conf flag.") driver Driver
)
argsLackErr := errors.New("lack of remote config center args")
if len(args) == 0 {
panic(argsLackErr.Error())
}
argsInvalidErr := errors.New("invalid remote config center args")
driverName, ok := args[0].(string)
if !ok {
panic(argsInvalidErr.Error())
}
driver, err = GetDriver(driverName)
if err != nil {
return
}
DefaultClient, err = driver.New()
} }
if err != nil { if err != nil {
return return

@ -0,0 +1,9 @@
package paladin
// Driver defined paladin remote client impl
// each remote config center driver must do
// 1. implements `New` method
// 2. call `Register` to register itself
type Driver interface {
New() (Client, error)
}

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/bilibili/kratos/pkg/conf/paladin" "github.com/bilibili/kratos/pkg/conf/paladin"
"github.com/bilibili/kratos/pkg/conf/paladin/apollo"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
@ -26,7 +27,7 @@ func (e *exampleConf) Set(text string) error {
return nil return nil
} }
// ExampleClient is a example client usage. // ExampleClient is an example client usage.
// exmaple.toml: // exmaple.toml:
/* /*
bool = true bool = true
@ -56,7 +57,41 @@ func ExampleClient() {
}() }()
} }
// ExampleMap is a example map usage. // ExampleApolloClient is an example client for apollo driver usage.
func ExampleApolloClient() {
/*
pass flags or set envs that apollo needs, for example:
```
export APOLLO_APP_ID=SampleApp
export APOLLO_CLUSTER=default
export APOLLO_CACHE_DIR=/tmp
export APOLLO_META_ADDR=localhost:8080
export APOLLO_NAMESPACES=example.yml
```
*/
if err := paladin.Init(apollo.PaladinDriverApollo); err != nil {
panic(err)
}
var ec exampleConf
// var setter
if err := paladin.Watch("example.yml", &ec); err != nil {
panic(err)
}
if err := paladin.Get("example.yml").UnmarshalYAML(&ec); err != nil {
panic(err)
}
// use exampleConf
// watch event key
go func() {
for event := range paladin.WatchEvent(context.TODO(), "key") {
fmt.Println(event)
}
}()
}
// ExampleMap is an example map usage.
// exmaple.toml: // exmaple.toml:
/* /*
bool = true bool = true

@ -31,7 +31,7 @@ func (w *watcher) HasKey(key string) bool {
return true return true
} }
for _, k := range w.keys { for _, k := range w.keys {
if keyNamed(k) == key { if KeyNamed(k) == key {
return true return true
} }
} }
@ -138,7 +138,7 @@ func (f *file) reloadFile(fpath string) (err error) {
if err != nil { if err != nil {
return return
} }
key := keyNamed(path.Base(fpath)) key := KeyNamed(path.Base(fpath))
raws := f.values.Load() raws := f.values.Load()
raws[key] = value raws[key] = value
f.values.Store(raws) f.values.Store(raws)

@ -5,8 +5,8 @@ import (
"sync/atomic" "sync/atomic"
) )
// keyNamed key naming to lower case. // KeyNamed key naming to lower case.
func keyNamed(key string) string { func KeyNamed(key string) string {
return strings.ToLower(key) return strings.ToLower(key)
} }
@ -19,7 +19,7 @@ type Map struct {
func (m *Map) Store(values map[string]*Value) { func (m *Map) Store(values map[string]*Value) {
dst := make(map[string]*Value, len(values)) dst := make(map[string]*Value, len(values))
for k, v := range values { for k, v := range values {
dst[keyNamed(k)] = v dst[KeyNamed(k)] = v
} }
m.values.Store(dst) m.values.Store(dst)
} }
@ -36,13 +36,13 @@ func (m *Map) Load() map[string]*Value {
// Exist check if values map exist a key. // Exist check if values map exist a key.
func (m *Map) Exist(key string) bool { func (m *Map) Exist(key string) bool {
_, ok := m.Load()[keyNamed(key)] _, ok := m.Load()[KeyNamed(key)]
return ok return ok
} }
// Get return get value by key. // Get return get value by key.
func (m *Map) Get(key string) *Value { func (m *Map) Get(key string) *Value {
v, ok := m.Load()[keyNamed(key)] v, ok := m.Load()[KeyNamed(key)]
if ok { if ok {
return v return v
} }

@ -0,0 +1,55 @@
package paladin
import (
"fmt"
"sort"
"sync"
)
var (
driversMu sync.RWMutex
drivers = make(map[string]Driver)
)
// Register makes a paladin driver available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, driver Driver) {
driversMu.Lock()
defer driversMu.Unlock()
if driver == nil {
panic("paladin: driver is nil")
}
if _, dup := drivers[name]; dup {
panic("paladin: Register called twice for driver " + name)
}
drivers[name] = driver
}
// Drivers returns a sorted list of the names of the registered paladin driver.
func Drivers() []string {
driversMu.RLock()
defer driversMu.RUnlock()
var list []string
for name := range drivers {
list = append(list, name)
}
sort.Strings(list)
return list
}
// GetDriver returns a driver implement by name.
func GetDriver(name string) (Driver, error) {
driversMu.RLock()
driveri, ok := drivers[name]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("paladin: unknown driver %q (forgotten import?)", name)
}
return driveri, nil
}

@ -28,7 +28,7 @@ func (m *TOML) UnmarshalText(text []byte) error {
} }
values := map[string]*Value{} values := map[string]*Value{}
for k, v := range raws { for k, v := range raws {
k = keyNamed(k) k = KeyNamed(k)
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
switch rv.Kind() { switch rv.Kind() {
case reflect.Map: case reflect.Map:

@ -25,6 +25,14 @@ type Value struct {
raw string raw string
} }
// NewValue new a value
func NewValue(val interface{}, raw string) *Value {
return &Value{
val: val,
raw: raw,
}
}
// Bool return bool value. // Bool return bool value.
func (v *Value) Bool() (bool, error) { func (v *Value) Bool() (bool, error) {
if v.val == nil { if v.val == nil {
@ -112,7 +120,7 @@ func (v *Value) Raw() (string, error) {
return v.raw, nil return v.raw, nil
} }
// Slice scan a slcie interface, if slice has element it will be discard. // Slice scan a slice interface, if slice has element it will be discard.
func (v *Value) Slice(dst interface{}) error { func (v *Value) Slice(dst interface{}) error {
// NOTE: val is []interface{}, slice is []type // NOTE: val is []interface{}, slice is []type
if v.val == nil { if v.val == nil {
@ -167,6 +175,7 @@ func (v *Value) UnmarshalJSON(dst interface{}) error {
return json.Unmarshal([]byte(text), dst) return json.Unmarshal([]byte(text), dst)
} }
// UnmarshalYAML unmarshal yaml to struct.
func (v *Value) UnmarshalYAML(dst interface{}) error { func (v *Value) UnmarshalYAML(dst interface{}) error {
text, err := v.Raw() text, err := v.Raw()
if err != nil { if err != nil {

Loading…
Cancel
Save