mirror of
https://github.com/go-kratos/kratos.git
synced 2025-01-24 03:46:37 +02:00
feat: introduced apollo for paladin with driver registration.
This commit is contained in:
parent
526c532bba
commit
c90108b14f
2
go.mod
2
go.mod
@ -26,9 +26,11 @@ require (
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/leodido/go-urn v1.1.0 // 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/openzipkin/zipkin-go v0.2.1
|
||||
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/prometheus/client_golang v1.1.0
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -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/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 h1:zjmNboC3QFuMdJSaZJ7Qvi3HUxWXPdj7wb3rc4jH5HI=
|
||||
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 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
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:
|
||||
```
|
||||
demo -conf=/data/conf/app/msm-servie.toml
|
||||
// or dir
|
||||
demo -conf=/data/conf/app/
|
||||
|
||||
```
|
||||
example:
|
||||
|
||||
*注:使用远程配置中心的用户在执行应用,如这里的`demo`时务必**不要**带上`-conf`参数,具体见下文远程配置中心的例子*
|
||||
|
||||
local file example:
|
||||
```
|
||||
type exampleConf struct {
|
||||
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 以上版本编译执行**
|
||||
|
273
pkg/conf/paladin/apollo/apollo.go
Normal file
273
pkg/conf/paladin/apollo/apollo.go
Normal file
@ -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
|
||||
}
|
73
pkg/conf/paladin/apollo/apollo_test.go
Normal file
73
pkg/conf/paladin/apollo/apollo_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
6
pkg/conf/paladin/apollo/const.go
Normal file
6
pkg/conf/paladin/apollo/const.go
Normal file
@ -0,0 +1,6 @@
|
||||
package apollo
|
||||
|
||||
const (
|
||||
// PaladinDriverApollo ...
|
||||
PaladinDriverApollo = "apollo"
|
||||
)
|
149
pkg/conf/paladin/apollo/internal/mockserver/mockserver.go
Normal file
149
pkg/conf/paladin/apollo/internal/mockserver/mockserver.go
Normal file
@ -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")), ¬ifications); 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
)
|
||||
|
||||
@ -16,12 +17,30 @@ func init() {
|
||||
}
|
||||
|
||||
// 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 != "" {
|
||||
DefaultClient, err = NewFile(confPath)
|
||||
} else {
|
||||
// TODO: Get the configuration from the remote service
|
||||
panic("Please specify a file or dir name by -conf flag.")
|
||||
var (
|
||||
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 {
|
||||
return
|
||||
|
9
pkg/conf/paladin/driver.go
Normal file
9
pkg/conf/paladin/driver.go
Normal file
@ -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"
|
||||
|
||||
"github.com/bilibili/kratos/pkg/conf/paladin"
|
||||
"github.com/bilibili/kratos/pkg/conf/paladin/apollo"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
@ -26,7 +27,7 @@ func (e *exampleConf) Set(text string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExampleClient is a example client usage.
|
||||
// ExampleClient is an example client usage.
|
||||
// exmaple.toml:
|
||||
/*
|
||||
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:
|
||||
/*
|
||||
bool = true
|
||||
|
@ -31,7 +31,7 @@ func (w *watcher) HasKey(key string) bool {
|
||||
return true
|
||||
}
|
||||
for _, k := range w.keys {
|
||||
if keyNamed(k) == key {
|
||||
if KeyNamed(k) == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -138,7 +138,7 @@ func (f *file) reloadFile(fpath string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key := keyNamed(path.Base(fpath))
|
||||
key := KeyNamed(path.Base(fpath))
|
||||
raws := f.values.Load()
|
||||
raws[key] = value
|
||||
f.values.Store(raws)
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// keyNamed key naming to lower case.
|
||||
func keyNamed(key string) string {
|
||||
// KeyNamed key naming to lower case.
|
||||
func KeyNamed(key string) string {
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ type Map struct {
|
||||
func (m *Map) Store(values map[string]*Value) {
|
||||
dst := make(map[string]*Value, len(values))
|
||||
for k, v := range values {
|
||||
dst[keyNamed(k)] = v
|
||||
dst[KeyNamed(k)] = v
|
||||
}
|
||||
m.values.Store(dst)
|
||||
}
|
||||
@ -36,13 +36,13 @@ func (m *Map) Load() map[string]*Value {
|
||||
|
||||
// Exist check if values map exist a key.
|
||||
func (m *Map) Exist(key string) bool {
|
||||
_, ok := m.Load()[keyNamed(key)]
|
||||
_, ok := m.Load()[KeyNamed(key)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Get return get value by key.
|
||||
func (m *Map) Get(key string) *Value {
|
||||
v, ok := m.Load()[keyNamed(key)]
|
||||
v, ok := m.Load()[KeyNamed(key)]
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
|
55
pkg/conf/paladin/register.go
Normal file
55
pkg/conf/paladin/register.go
Normal file
@ -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{}
|
||||
for k, v := range raws {
|
||||
k = keyNamed(k)
|
||||
k = KeyNamed(k)
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Map:
|
||||
|
@ -25,6 +25,14 @@ type Value struct {
|
||||
raw string
|
||||
}
|
||||
|
||||
// NewValue new a value
|
||||
func NewValue(val interface{}, raw string) *Value {
|
||||
return &Value{
|
||||
val: val,
|
||||
raw: raw,
|
||||
}
|
||||
}
|
||||
|
||||
// Bool return bool value.
|
||||
func (v *Value) Bool() (bool, error) {
|
||||
if v.val == nil {
|
||||
@ -112,7 +120,7 @@ func (v *Value) Raw() (string, error) {
|
||||
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 {
|
||||
// NOTE: val is []interface{}, slice is []type
|
||||
if v.val == nil {
|
||||
@ -167,6 +175,7 @@ func (v *Value) UnmarshalJSON(dst interface{}) error {
|
||||
return json.Unmarshal([]byte(text), dst)
|
||||
}
|
||||
|
||||
// UnmarshalYAML unmarshal yaml to struct.
|
||||
func (v *Value) UnmarshalYAML(dst interface{}) error {
|
||||
text, err := v.Raw()
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user