fix(config/apollo): apollo namespace (#1516)
* fix(config/apollo): support multiple namespace * fix(config/apollo): modify example and test * fix(config/apollo): recoding watcher * styl(config/apollo): package sort; use log instead of fmt * styl(config/apollo): use kratos/log package instead of fmt * styl(config/apollo): optimise code with reviewer advises; fix some edge cases on genKey function. * styl(config/apollo): rename `convertProperties` as `resolve`pull/1576/head
parent
eb0730a1b0
commit
eec45a3d0a
@ -0,0 +1,172 @@ |
||||
package apollo |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func Test_genKey(t *testing.T) { |
||||
type args struct { |
||||
ns string |
||||
sub string |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want string |
||||
}{ |
||||
{ |
||||
name: "case 1", |
||||
args: args{ |
||||
ns: "", |
||||
sub: "has_no_ns", |
||||
}, |
||||
want: "has_no_ns", |
||||
}, |
||||
{ |
||||
name: "case 2", |
||||
args: args{ |
||||
ns: "ns.ext", |
||||
sub: "sub", |
||||
}, |
||||
want: "ns.sub", |
||||
}, |
||||
{ |
||||
name: "case 3", |
||||
args: args{ |
||||
ns: "", |
||||
sub: "", |
||||
}, |
||||
want: "", |
||||
}, |
||||
{ |
||||
name: "case 4", |
||||
args: args{ |
||||
ns: "ns.ext", |
||||
sub: "sub.sub2.sub3", |
||||
}, |
||||
want: "ns.sub.sub2.sub3", |
||||
}, |
||||
{ |
||||
name: "case 5", |
||||
args: args{ |
||||
ns: "ns.more.ext", |
||||
sub: "sub.sub2.sub3", |
||||
}, |
||||
want: "ns.more.sub.sub2.sub3", |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := genKey(tt.args.ns, tt.args.sub); got != tt.want { |
||||
t.Errorf("genKey() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_format(t *testing.T) { |
||||
type args struct { |
||||
ns string |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want string |
||||
}{ |
||||
{ |
||||
name: "case 0", |
||||
args: args{ |
||||
ns: "ns.yaml", |
||||
}, |
||||
want: "yaml", |
||||
}, |
||||
{ |
||||
name: "case 1", |
||||
args: args{ |
||||
ns: "ns", |
||||
}, |
||||
want: "json", |
||||
}, |
||||
{ |
||||
name: "case 2", |
||||
args: args{ |
||||
ns: "ns.more.json", |
||||
}, |
||||
want: "json", |
||||
}, |
||||
{ |
||||
name: "case 3", |
||||
args: args{ |
||||
ns: "", |
||||
}, |
||||
want: "json", |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := format(tt.args.ns); got != tt.want { |
||||
t.Errorf("format() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_convertProperties(t *testing.T) { |
||||
type args struct { |
||||
key string |
||||
value interface{} |
||||
target map[string]interface{} |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want map[string]interface{} |
||||
}{ |
||||
{ |
||||
name: "case 0", |
||||
args: args{ |
||||
key: "application.name", |
||||
value: "app name", |
||||
target: map[string]interface{}{}, |
||||
}, |
||||
want: map[string]interface{}{ |
||||
"application": map[string]interface{}{ |
||||
"name": "app name", |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
name: "case 1", |
||||
args: args{ |
||||
key: "application", |
||||
value: []string{"1", "2", "3"}, |
||||
target: map[string]interface{}{}, |
||||
}, |
||||
want: map[string]interface{}{ |
||||
"application": []string{"1", "2", "3"}, |
||||
}, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
resolve(tt.args.key, tt.args.value, tt.args.target) |
||||
assert.Equal(t, tt.want, tt.args.target) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_convertProperties_duplicate(t *testing.T) { |
||||
target := map[string]interface{}{} |
||||
resolve("application.name", "name", target) |
||||
assert.Contains(t, target, "application") |
||||
assert.Contains(t, target["application"], "name") |
||||
assert.Equal(t, "name", target["application"].(map[string]interface{})["name"]) |
||||
|
||||
// cause duplicate, the oldest value will be kept
|
||||
resolve("application.name.first", "first name", target) |
||||
assert.Contains(t, target, "application") |
||||
assert.Contains(t, target["application"], "name") |
||||
assert.Equal(t, "name", target["application"].(map[string]interface{})["name"]) |
||||
} |
@ -0,0 +1,25 @@ |
||||
package apollo |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
|
||||
"github.com/apolloconfig/agollo/v4/constant" |
||||
"github.com/apolloconfig/agollo/v4/extension" |
||||
) |
||||
|
||||
type jsonExtParser struct{} |
||||
|
||||
func (j jsonExtParser) Parse(configContent interface{}) (map[string]interface{}, error) { |
||||
v, ok := configContent.(string) |
||||
if !ok { |
||||
return nil, nil |
||||
} |
||||
out := make(map[string]interface{}, 4) |
||||
err := json.Unmarshal([]byte(v), &out) |
||||
return out, err |
||||
} |
||||
|
||||
func init() { |
||||
// add json format
|
||||
extension.AddFormatParser(constant.JSON, &jsonExtParser{}) |
||||
} |
@ -1,53 +1,90 @@ |
||||
package apollo |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/go-kratos/kratos/v2/config" |
||||
"github.com/go-kratos/kratos/v2/encoding" |
||||
"github.com/go-kratos/kratos/v2/log" |
||||
|
||||
"github.com/apolloconfig/agollo/v4/storage" |
||||
) |
||||
|
||||
type watcher struct { |
||||
event chan []*config.KeyValue |
||||
out <-chan []*config.KeyValue |
||||
cancelFn func() |
||||
} |
||||
|
||||
type customChangeListener struct { |
||||
event chan []*config.KeyValue |
||||
in chan<- []*config.KeyValue |
||||
logger log.Logger |
||||
} |
||||
|
||||
func (c *customChangeListener) OnChange(changeEvent *storage.ChangeEvent) { |
||||
kv := make([]*config.KeyValue, 0) |
||||
for key, value := range changeEvent.Changes { |
||||
kv = append(kv, &config.KeyValue{ |
||||
Key: key, |
||||
Value: []byte(value.NewValue.(string)), |
||||
}) |
||||
func (c *customChangeListener) onChange( |
||||
namespace string, changes map[string]*storage.ConfigChange) []*config.KeyValue { |
||||
|
||||
kv := make([]*config.KeyValue, 0, 2) |
||||
next := make(map[string]interface{}) |
||||
|
||||
for key, change := range changes { |
||||
resolve(genKey(namespace, key), change.NewValue, next) |
||||
} |
||||
c.event <- kv |
||||
|
||||
f := format(namespace) |
||||
codec := encoding.GetCodec(f) |
||||
val, err := codec.Marshal(next) |
||||
if err != nil { |
||||
_ = c.logger.Log(log.LevelWarn, |
||||
"msg", |
||||
fmt.Sprintf("apollo could not handle namespace %s: %v", namespace, err), |
||||
) |
||||
return nil |
||||
} |
||||
kv = append(kv, &config.KeyValue{ |
||||
Key: namespace, |
||||
Value: val, |
||||
Format: f, |
||||
}) |
||||
|
||||
return kv |
||||
} |
||||
|
||||
func (c *customChangeListener) OnNewestChange(changeEvent *storage.FullChangeEvent) { |
||||
kv := make([]*config.KeyValue, 0) |
||||
for key, value := range changeEvent.Changes { |
||||
kv = append(kv, &config.KeyValue{ |
||||
Key: key, |
||||
Value: []byte(value.(string)), |
||||
}) |
||||
func (c *customChangeListener) OnChange(changeEvent *storage.ChangeEvent) { |
||||
change := c.onChange(changeEvent.Namespace, changeEvent.Changes) |
||||
if len(change) == 0 { |
||||
return |
||||
} |
||||
c.event <- kv |
||||
|
||||
c.in <- change |
||||
} |
||||
|
||||
func NewWatcher(a *apollo) (config.Watcher, error) { |
||||
e := make(chan []*config.KeyValue) |
||||
a.client.AddChangeListener(&customChangeListener{event: e}) |
||||
return &watcher{event: e}, nil |
||||
func (c *customChangeListener) OnNewestChange(changeEvent *storage.FullChangeEvent) {} |
||||
|
||||
func newWatcher(a *apollo, logger log.Logger) (config.Watcher, error) { |
||||
if logger == nil { |
||||
logger = log.DefaultLogger |
||||
} |
||||
|
||||
changeCh := make(chan []*config.KeyValue) |
||||
a.client.AddChangeListener(&customChangeListener{in: changeCh, logger: logger}) |
||||
|
||||
return &watcher{ |
||||
out: changeCh, |
||||
cancelFn: func() { |
||||
close(changeCh) |
||||
}, |
||||
}, nil |
||||
} |
||||
|
||||
// Next will be blocked until the Stop method is called
|
||||
func (w *watcher) Next() ([]*config.KeyValue, error) { |
||||
return <-w.event, nil |
||||
return <-w.out, nil |
||||
} |
||||
|
||||
func (w *watcher) Stop() error { |
||||
close(w.event) |
||||
if w.cancelFn != nil { |
||||
w.cancelFn() |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
Loading…
Reference in new issue