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 |
package apollo |
||||||
|
|
||||||
import ( |
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/config" |
"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" |
"github.com/apolloconfig/agollo/v4/storage" |
||||||
) |
) |
||||||
|
|
||||||
type watcher struct { |
type watcher struct { |
||||||
event chan []*config.KeyValue |
out <-chan []*config.KeyValue |
||||||
|
cancelFn func() |
||||||
} |
} |
||||||
|
|
||||||
type customChangeListener struct { |
type customChangeListener struct { |
||||||
event chan []*config.KeyValue |
in chan<- []*config.KeyValue |
||||||
|
logger log.Logger |
||||||
} |
} |
||||||
|
|
||||||
func (c *customChangeListener) OnChange(changeEvent *storage.ChangeEvent) { |
func (c *customChangeListener) onChange( |
||||||
kv := make([]*config.KeyValue, 0) |
namespace string, changes map[string]*storage.ConfigChange) []*config.KeyValue { |
||||||
for key, value := range changeEvent.Changes { |
|
||||||
kv = append(kv, &config.KeyValue{ |
kv := make([]*config.KeyValue, 0, 2) |
||||||
Key: key, |
next := make(map[string]interface{}) |
||||||
Value: []byte(value.NewValue.(string)), |
|
||||||
}) |
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) { |
func (c *customChangeListener) OnChange(changeEvent *storage.ChangeEvent) { |
||||||
kv := make([]*config.KeyValue, 0) |
change := c.onChange(changeEvent.Namespace, changeEvent.Changes) |
||||||
for key, value := range changeEvent.Changes { |
if len(change) == 0 { |
||||||
kv = append(kv, &config.KeyValue{ |
return |
||||||
Key: key, |
|
||||||
Value: []byte(value.(string)), |
|
||||||
}) |
|
||||||
} |
} |
||||||
c.event <- kv |
|
||||||
|
c.in <- change |
||||||
} |
} |
||||||
|
|
||||||
func NewWatcher(a *apollo) (config.Watcher, error) { |
func (c *customChangeListener) OnNewestChange(changeEvent *storage.FullChangeEvent) {} |
||||||
e := make(chan []*config.KeyValue) |
|
||||||
a.client.AddChangeListener(&customChangeListener{event: e}) |
func newWatcher(a *apollo, logger log.Logger) (config.Watcher, error) { |
||||||
return &watcher{event: e}, nil |
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
|
// Next will be blocked until the Stop method is called
|
||||||
func (w *watcher) Next() ([]*config.KeyValue, error) { |
func (w *watcher) Next() ([]*config.KeyValue, error) { |
||||||
return <-w.event, nil |
return <-w.out, nil |
||||||
} |
} |
||||||
|
|
||||||
func (w *watcher) Stop() error { |
func (w *watcher) Stop() error { |
||||||
close(w.event) |
if w.cancelFn != nil { |
||||||
|
w.cancelFn() |
||||||
|
} |
||||||
|
|
||||||
return nil |
return nil |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue