diff --git a/README.md b/README.md index 0ee60e2..1d145de 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ We developed [Flæg](https://github.com/containous/flaeg) and Stært in order to - Three native sources : - Command line arguments using [Flæg](https://github.com/containous/flaeg) package - TOML config file using [TOML](http://github.com/BurntSushi/toml) package - - [Key-Value Store](#kvstore) using [libkv](https://github.com/docker/libkv) and [mapstructure](https://github.com/mitchellh/mapstructure) packages + - [Key-Value Store](#kvstore) using [valkeyrie](https://github.com/abronan/valkeyrie) and [mapstructure](https://github.com/mitchellh/mapstructure) packages - An interface to add your own sources - Handle pointers field : - You can give a structure of default values for pointers @@ -211,7 +211,7 @@ Thank you [@debovema](https://github.com/debovema) for this work :) ## KvStore As with Flæg and TOML sources, the configuration structure can be loaded from a Key-Value Store. -The package [libkv](https://github.com/docker/libkv) provides connection to many KV Store like `Consul`, `Etcd` or `Zookeeper`. +The package [valkeyrie](https://github.com/abronan/valkeyrie) provides connection to many KV Store like `Consul`, `Etcd` or `Zookeeper`. The whole configuration structure is stored, using architecture like this pattern: diff --git a/kv.go b/kv.go index 60ad3c2..d681d84 100644 --- a/kv.go +++ b/kv.go @@ -381,6 +381,34 @@ func (kv *KvSource) ListValuedPairWithPrefix(key string) (map[string][]byte, err return pairs, nil } +// ReplaceConfig will replace the existing config in the KV store with +// the new config. Unused (stale) keys will be removed from the kv +// store, while existing keys remain and get updated. +func (kv *KvSource) ReplaceConfig(config interface{}) error { + existingConfigPairs, err := kv.ListValuedPairWithPrefix(kv.Prefix) + + if err != nil { + return err + } + + newConfigPairs := map[string]string{} + if err = collateKvRecursive(reflect.ValueOf(config), newConfigPairs, kv.Prefix); err != nil { + return err + } + + // Loop over the keys in the store and remove the keys that are not present in the new config + for existingKey := range existingConfigPairs { + if _, ok := newConfigPairs[existingKey]; !ok { + err = kv.Delete(existingKey) + if err != nil { + return err + } + } + } + + return kv.StoreConfig(config) +} + func convertPairs(pairs map[string][]byte) []*store.KVPair { slicePairs := make([]*store.KVPair, len(pairs)) i := 0 diff --git a/kv_mock_test.go b/kv_mock_test.go index 343014e..fb5a9bd 100644 --- a/kv_mock_test.go +++ b/kv_mock_test.go @@ -40,7 +40,16 @@ func (s *Mock) Get(key string, options *store.ReadOptions) (*store.KVPair, error } func (s *Mock) Delete(key string) error { - return errors.New("delete not supported") + pairs := make([]*store.KVPair, 0) + for _, pair := range s.KVPairs { + if pair.Key != key { + pairs = append(pairs, pair) + } + } + + s.KVPairs = pairs + + return nil } // Exists mock diff --git a/kv_test.go b/kv_test.go index 7f43793..f3aba5d 100644 --- a/kv_test.go +++ b/kv_test.go @@ -1238,3 +1238,74 @@ func TestDecodeHookCustomMarshaller(t *testing.T) { assert.Exactly(t, data, output) } + +func TestReplaceConfig(t *testing.T) { + kv := &KvSource{ + &Mock{ + KVPairs: []*store.KVPair{ + {Key: "prefix/foo", Value: []byte("foo")}, + {Key: "prefix/bar/baz", Value: []byte("foo")}, + {Key: "prefix/bar/boo", Value: []byte("foo")}, + }, + WatchTreeMethod: nil, + }, + "prefix", + } + + config := &struct { + Foo string + }{ + Foo: "bar", + } + + err := kv.ReplaceConfig(config) + require.NoError(t, err) + + results, err := kv.ListValuedPairWithPrefix("prefix") + require.NoError(t, err) + + require.EqualValues(t, map[string][]byte{ + "prefix/foo": []byte("bar"), + }, results) +} + +func TestReplaceNestedConfig(t *testing.T) { + kv := &KvSource{ + &Mock{ + KVPairs: []*store.KVPair{ + {Key: "prefix/foo", Value: []byte("foo")}, + {Key: "prefix/bar/baz", Value: []byte("foo")}, + {Key: "prefix/bar/boo", Value: []byte("foo")}, + {Key: "prefix/bar/far/baz", Value: []byte("foo")}, + }, + WatchTreeMethod: nil, + }, + "prefix", + } + + config := &struct { + Foo string + Bar map[string]map[string]string + New string + }{ + Foo: "bar", + Bar: map[string]map[string]string{ + "far": { + "baz": "faz", + }, + }, + New: "foo", + } + + err := kv.ReplaceConfig(config) + require.NoError(t, err) + + results, err := kv.ListValuedPairWithPrefix("prefix") + require.NoError(t, err) + + require.EqualValues(t, map[string][]byte{ + "prefix/foo": []byte("bar"), + "prefix/bar/far/baz": []byte("faz"), + "prefix/new": []byte("foo"), + }, results) +}