1
0
Fork 0

Add KV store providers (dynamic configuration only)

Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-11-28 21:56:04 +01:00 committed by Traefiker Bot
parent 028683666d
commit 9b9f4be6a4
61 changed files with 5825 additions and 70 deletions

View file

@ -53,6 +53,22 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
p.quietAddProvider(conf.ConsulCatalog)
}
if conf.Consul != nil {
p.quietAddProvider(conf.Consul)
}
if conf.Etcd != nil {
p.quietAddProvider(conf.Etcd)
}
if conf.ZooKeeper != nil {
p.quietAddProvider(conf.ZooKeeper)
}
if conf.Redis != nil {
p.quietAddProvider(conf.Redis)
}
return p
}

View file

@ -0,0 +1,25 @@
package consul
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:8500"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.CONSUL, "consul")
}

View file

@ -0,0 +1,25 @@
package etcd
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:2379"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.ETCDV3, "etcd")
}

191
pkg/provider/kv/kv.go Normal file
View file

@ -0,0 +1,191 @@
package kv
import (
"context"
"errors"
"fmt"
"path"
"time"
"github.com/abronan/valkeyrie"
"github.com/abronan/valkeyrie/store"
"github.com/abronan/valkeyrie/store/consul"
etcdv3 "github.com/abronan/valkeyrie/store/etcd/v3"
"github.com/abronan/valkeyrie/store/redis"
"github.com/abronan/valkeyrie/store/zookeeper"
"github.com/cenkalti/backoff/v3"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/kv"
"github.com/containous/traefik/v2/pkg/job"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/types"
)
// Provider holds configurations of the provider.
type Provider struct {
RootKey string `description:"Root key used for KV store" export:"true" json:"rootKey,omitempty" toml:"rootKey,omitempty" yaml:"rootKey,omitempty"`
Endpoints []string `description:"KV store endpoints" json:"endpoints,omitempty" toml:"endpoints,omitempty" yaml:"endpoints,omitempty"`
Username string `description:"KV Username" json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty"`
Password string `description:"KV Password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty"`
TLS *types.ClientTLS `description:"Enable TLS support" export:"true" json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"`
storeType store.Backend
kvClient store.Store
name string
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.RootKey = "traefik"
}
// Init the provider
func (p *Provider) Init(storeType store.Backend, name string) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, string(storeType)))
p.storeType = storeType
p.name = name
kvClient, err := p.createKVClient(ctx)
if err != nil {
return fmt.Errorf("failed to Connect to KV store: %w", err)
}
p.kvClient = kvClient
return nil
}
// Provide allows the docker provider to provide configurations to traefik using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, string(p.storeType)))
logger := log.FromContext(ctx)
operation := func() error {
if _, err := p.kvClient.Exists(path.Join(p.RootKey, "qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"), nil); err != nil {
return fmt.Errorf("KV store connection error: %w", err)
}
return nil
}
notify := func(err error, time time.Duration) {
logger.Errorf("KV connection error: %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
return fmt.Errorf("cannot connect to KV server: %w", err)
}
configuration, err := p.buildConfiguration()
if err != nil {
logger.Errorf("Cannot build the configuration: %v", err)
} else {
configurationChan <- dynamic.Message{
ProviderName: p.name,
Configuration: configuration,
}
}
pool.Go(func(stop chan bool) {
err := p.watchKv(ctx, configurationChan, p.RootKey, stop)
if err != nil {
logger.Errorf("Cannot watch KV store: %v", err)
}
})
return nil
}
func (p *Provider) watchKv(ctx context.Context, configurationChan chan<- dynamic.Message, prefix string, stop chan bool) error {
operation := func() error {
events, err := p.kvClient.WatchTree(p.RootKey, make(chan struct{}), nil)
if err != nil {
return fmt.Errorf("failed to watch KV: %w", err)
}
for {
select {
case <-stop:
return nil
case _, ok := <-events:
if !ok {
return errors.New("the WatchTree channel is closed")
}
configuration, errC := p.buildConfiguration()
if errC != nil {
return errC
}
if configuration != nil {
configurationChan <- dynamic.Message{
ProviderName: string(p.storeType),
Configuration: configuration,
}
}
}
}
}
notify := func(err error, time time.Duration) {
log.FromContext(ctx).Errorf("KV connection error: %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
return fmt.Errorf("cannot connect to KV server: %w", err)
}
return nil
}
func (p *Provider) buildConfiguration() (*dynamic.Configuration, error) {
pairs, err := p.kvClient.List(p.RootKey, nil)
if err != nil {
return nil, err
}
cfg := &dynamic.Configuration{}
err = kv.Decode(pairs, cfg, p.RootKey)
if err != nil {
return nil, err
}
return cfg, nil
}
func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) {
storeConfig := &store.Config{
ConnectionTimeout: 3 * time.Second,
Bucket: "traefik",
Username: p.Username,
Password: p.Password,
}
if p.TLS != nil {
var err error
storeConfig.TLS, err = p.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, err
}
}
switch p.storeType {
case store.CONSUL:
consul.Register()
case store.ETCDV3:
etcdv3.Register()
case store.ZK:
zookeeper.Register()
case store.REDIS:
redis.Register()
}
kvStore, err := valkeyrie.NewStore(p.storeType, p.Endpoints, storeConfig)
if err != nil {
return nil, err
}
return &storeWrapper{Store: kvStore}, nil
}

View file

@ -0,0 +1,122 @@
package kv
import (
"errors"
"strings"
"github.com/abronan/valkeyrie/store"
)
func newProviderMock(kvPairs []*store.KVPair) *Provider {
return &Provider{
RootKey: "traefik",
kvClient: newKvClientMock(kvPairs, nil),
}
}
// Override Get/List to return a error
type KvError struct {
Get error
List error
}
// Extremely limited mock store so we can test initialization
type Mock struct {
Error KvError
KVPairs []*store.KVPair
WatchTreeMethod func() <-chan []*store.KVPair
}
func newKvClientMock(kvPairs []*store.KVPair, err error) *Mock {
mock := &Mock{
KVPairs: kvPairs,
}
if err != nil {
mock.Error = KvError{
Get: err,
List: err,
}
}
return mock
}
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
return errors.New("method Put not supported")
}
func (s *Mock) Get(key string, options *store.ReadOptions) (*store.KVPair, error) {
if err := s.Error.Get; err != nil {
return nil, err
}
for _, kvPair := range s.KVPairs {
if kvPair.Key == key {
return kvPair, nil
}
}
return nil, store.ErrKeyNotFound
}
func (s *Mock) Delete(key string) error {
return errors.New("method Delete not supported")
}
// Exists mock
func (s *Mock) Exists(key string, options *store.ReadOptions) (bool, error) {
if err := s.Error.Get; err != nil {
return false, err
}
for _, kvPair := range s.KVPairs {
if strings.HasPrefix(kvPair.Key, key) {
return true, nil
}
}
return false, store.ErrKeyNotFound
}
// Watch mock
func (s *Mock) Watch(key string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan *store.KVPair, error) {
return nil, errors.New("method Watch not supported")
}
// WatchTree mock
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan []*store.KVPair, error) {
return s.WatchTreeMethod(), nil
}
// NewLock mock
func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
return nil, errors.New("method NewLock not supported")
}
// List mock
func (s *Mock) List(prefix string, options *store.ReadOptions) ([]*store.KVPair, error) {
if err := s.Error.List; err != nil {
return nil, err
}
var kv []*store.KVPair
for _, kvPair := range s.KVPairs {
if strings.HasPrefix(kvPair.Key, prefix) { // FIXME && !strings.ContainsAny(strings.TrimPrefix(kvPair.Key, prefix), "/") {
kv = append(kv, kvPair)
}
}
return kv, nil
}
// DeleteTree mock
func (s *Mock) DeleteTree(prefix string) error {
return errors.New("method DeleteTree not supported")
}
// AtomicPut mock
func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
return false, nil, errors.New("method AtomicPut not supported")
}
// AtomicDelete mock
func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
return false, errors.New("method AtomicDelete not supported")
}
// Close mock
func (s *Mock) Close() {}

892
pkg/provider/kv/kv_test.go Normal file
View file

@ -0,0 +1,892 @@
package kv
import (
"context"
"errors"
"testing"
"time"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/tls"
"github.com/containous/traefik/v2/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_buildConfiguration(t *testing.T) {
provider := newProviderMock(mapToPairs(map[string]string{
"traefik/http/routers/Router0/entryPoints/0": "foobar",
"traefik/http/routers/Router0/entryPoints/1": "foobar",
"traefik/http/routers/Router0/middlewares/0": "foobar",
"traefik/http/routers/Router0/middlewares/1": "foobar",
"traefik/http/routers/Router0/service": "foobar",
"traefik/http/routers/Router0/rule": "foobar",
"traefik/http/routers/Router0/priority": "42",
"traefik/http/routers/Router0/tls": "",
"traefik/http/routers/Router1/rule": "foobar",
"traefik/http/routers/Router1/priority": "42",
"traefik/http/routers/Router1/tls/domains/0/main": "foobar",
"traefik/http/routers/Router1/tls/domains/0/sans/0": "foobar",
"traefik/http/routers/Router1/tls/domains/0/sans/1": "foobar",
"traefik/http/routers/Router1/tls/domains/1/main": "foobar",
"traefik/http/routers/Router1/tls/domains/1/sans/0": "foobar",
"traefik/http/routers/Router1/tls/domains/1/sans/1": "foobar",
"traefik/http/routers/Router1/tls/options": "foobar",
"traefik/http/routers/Router1/tls/certResolver": "foobar",
"traefik/http/routers/Router1/entryPoints/0": "foobar",
"traefik/http/routers/Router1/entryPoints/1": "foobar",
"traefik/http/routers/Router1/middlewares/0": "foobar",
"traefik/http/routers/Router1/middlewares/1": "foobar",
"traefik/http/routers/Router1/service": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/path": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/port": "42",
"traefik/http/services/Service01/loadBalancer/healthCheck/interval": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/timeout": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/hostname": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/scheme": "foobar",
"traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval": "foobar",
"traefik/http/services/Service01/loadBalancer/passHostHeader": "true",
"traefik/http/services/Service01/loadBalancer/sticky/cookie/name": "foobar",
"traefik/http/services/Service01/loadBalancer/sticky/cookie/secure": "true",
"traefik/http/services/Service01/loadBalancer/sticky/cookie/httpOnly": "true",
"traefik/http/services/Service01/loadBalancer/servers/0/url": "foobar",
"traefik/http/services/Service01/loadBalancer/servers/1/url": "foobar",
"traefik/http/services/Service02/mirroring/service": "foobar",
"traefik/http/services/Service02/mirroring/mirrors/0/name": "foobar",
"traefik/http/services/Service02/mirroring/mirrors/0/percent": "42",
"traefik/http/services/Service02/mirroring/mirrors/1/name": "foobar",
"traefik/http/services/Service02/mirroring/mirrors/1/percent": "42",
"traefik/http/services/Service03/weighted/sticky/cookie/name": "foobar",
"traefik/http/services/Service03/weighted/sticky/cookie/secure": "true",
"traefik/http/services/Service03/weighted/sticky/cookie/httpOnly": "true",
"traefik/http/services/Service03/weighted/services/0/name": "foobar",
"traefik/http/services/Service03/weighted/services/0/weight": "42",
"traefik/http/services/Service03/weighted/services/1/name": "foobar",
"traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/0": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/1": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/key": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/ca": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/caOptional": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/cert": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/address": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/trustForwardHeader": "true",
"traefik/http/middlewares/Middleware15/redirectScheme/scheme": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/port": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/permanent": "true",
"traefik/http/middlewares/Middleware17/replacePathRegex/regex": "foobar",
"traefik/http/middlewares/Middleware17/replacePathRegex/replacement": "foobar",
"traefik/http/middlewares/Middleware14/redirectRegex/regex": "foobar",
"traefik/http/middlewares/Middleware14/redirectRegex/replacement": "foobar",
"traefik/http/middlewares/Middleware14/redirectRegex/permanent": "true",
"traefik/http/middlewares/Middleware16/replacePath/path": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/removeHeader": "true",
"traefik/http/middlewares/Middleware06/digestAuth/realm": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/headerField": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/users/0": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/users/1": "foobar",
"traefik/http/middlewares/Middleware06/digestAuth/usersFile": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowOrigin": "foobar",
"traefik/http/middlewares/Middleware09/headers/contentTypeNosniff": "true",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowCredentials": "true",
"traefik/http/middlewares/Middleware09/headers/featurePolicy": "foobar",
"traefik/http/middlewares/Middleware09/headers/forceSTSHeader": "true",
"traefik/http/middlewares/Middleware09/headers/sslRedirect": "true",
"traefik/http/middlewares/Middleware09/headers/sslHost": "foobar",
"traefik/http/middlewares/Middleware09/headers/sslForceHost": "true",
"traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name1": "foobar",
"traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name0": "foobar",
"traefik/http/middlewares/Middleware09/headers/allowedHosts/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/allowedHosts/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/stsPreload": "true",
"traefik/http/middlewares/Middleware09/headers/frameDeny": "true",
"traefik/http/middlewares/Middleware09/headers/isDevelopment": "true",
"traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name1": "foobar",
"traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/stsSeconds": "42",
"traefik/http/middlewares/Middleware09/headers/stsIncludeSubdomains": "true",
"traefik/http/middlewares/Middleware09/headers/customFrameOptionsValue": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlMaxAge": "42",
"traefik/http/middlewares/Middleware09/headers/addVaryHeader": "true",
"traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/sslTemporaryRedirect": "true",
"traefik/http/middlewares/Middleware09/headers/customBrowserXSSValue": "foobar",
"traefik/http/middlewares/Middleware09/headers/referrerPolicy": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/contentSecurityPolicy": "foobar",
"traefik/http/middlewares/Middleware09/headers/publicKey": "foobar",
"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name0": "foobar",
"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name1": "foobar",
"traefik/http/middlewares/Middleware09/headers/browserXssFilter": "true",
"traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/0": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/1": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/0": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/1": "foobar",
"traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/depth": "42",
"traefik/http/middlewares/Middleware11/inFlightReq/amount": "42",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHost": "true",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/depth": "42",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0": "foobar",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1": "foobar",
"traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHeaderName": "foobar",
"traefik/http/middlewares/Middleware12/passTLSClientCert/pem": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/notAfter": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/notBefore": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/sans": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/country": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/province": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/locality": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/organization": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/commonName": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/serialNumber": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/domainComponent": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/country": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/province": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/locality": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/organization": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/commonName": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/serialNumber": "true",
"traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/domainComponent": "true",
"traefik/http/middlewares/Middleware00/addPrefix/prefix": "foobar",
"traefik/http/middlewares/Middleware03/chain/middlewares/0": "foobar",
"traefik/http/middlewares/Middleware03/chain/middlewares/1": "foobar",
"traefik/http/middlewares/Middleware04/circuitBreaker/expression": "foobar",
"traefik/http/middlewares/Middleware07/errors/status/0": "foobar",
"traefik/http/middlewares/Middleware07/errors/status/1": "foobar",
"traefik/http/middlewares/Middleware07/errors/service": "foobar",
"traefik/http/middlewares/Middleware07/errors/query": "foobar",
"traefik/http/middlewares/Middleware13/rateLimit/average": "42",
"traefik/http/middlewares/Middleware13/rateLimit/burst": "42",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHeaderName": "foobar",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHost": "true",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/depth": "42",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0": "foobar",
"traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1": "foobar",
"traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/0": "foobar",
"traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/1": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/users/0": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/users/1": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/usersFile": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/realm": "foobar",
"traefik/http/middlewares/Middleware01/basicAuth/removeHeader": "true",
"traefik/http/middlewares/Middleware01/basicAuth/headerField": "foobar",
"traefik/http/middlewares/Middleware02/buffering/maxResponseBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/memResponseBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/retryExpression": "foobar",
"traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware05/compress": "",
"traefik/http/middlewares/Middleware18/retry/attempts": "42",
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar",
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/1": "foobar",
"traefik/http/middlewares/Middleware19/stripPrefix/forceSlash": "true",
"traefik/tcp/routers/TCPRouter0/entryPoints/0": "foobar",
"traefik/tcp/routers/TCPRouter0/entryPoints/1": "foobar",
"traefik/tcp/routers/TCPRouter0/service": "foobar",
"traefik/tcp/routers/TCPRouter0/rule": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/options": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/certResolver": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/0/main": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/1/main": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter0/tls/passthrough": "true",
"traefik/tcp/routers/TCPRouter1/entryPoints/0": "foobar",
"traefik/tcp/routers/TCPRouter1/entryPoints/1": "foobar",
"traefik/tcp/routers/TCPRouter1/service": "foobar",
"traefik/tcp/routers/TCPRouter1/rule": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/0/main": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/1/main": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/0": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/1": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/passthrough": "true",
"traefik/tcp/routers/TCPRouter1/tls/options": "foobar",
"traefik/tcp/routers/TCPRouter1/tls/certResolver": "foobar",
"traefik/tcp/services/TCPService01/loadBalancer/terminationDelay": "42",
"traefik/tcp/services/TCPService01/loadBalancer/servers/0/address": "foobar",
"traefik/tcp/services/TCPService01/loadBalancer/servers/1/address": "foobar",
"traefik/tcp/services/TCPService02/weighted/services/0/name": "foobar",
"traefik/tcp/services/TCPService02/weighted/services/0/weight": "42",
"traefik/tcp/services/TCPService02/weighted/services/1/name": "foobar",
"traefik/tcp/services/TCPService02/weighted/services/1/weight": "43",
"traefik/tls/options/Options0/minVersion": "foobar",
"traefik/tls/options/Options0/maxVersion": "foobar",
"traefik/tls/options/Options0/cipherSuites/0": "foobar",
"traefik/tls/options/Options0/cipherSuites/1": "foobar",
"traefik/tls/options/Options0/sniStrict": "true",
"traefik/tls/options/Options0/curvePreferences/0": "foobar",
"traefik/tls/options/Options0/curvePreferences/1": "foobar",
"traefik/tls/options/Options0/clientAuth/caFiles/0": "foobar",
"traefik/tls/options/Options0/clientAuth/caFiles/1": "foobar",
"traefik/tls/options/Options0/clientAuth/clientAuthType": "foobar",
"traefik/tls/options/Options1/sniStrict": "true",
"traefik/tls/options/Options1/curvePreferences/0": "foobar",
"traefik/tls/options/Options1/curvePreferences/1": "foobar",
"traefik/tls/options/Options1/clientAuth/caFiles/0": "foobar",
"traefik/tls/options/Options1/clientAuth/caFiles/1": "foobar",
"traefik/tls/options/Options1/clientAuth/clientAuthType": "foobar",
"traefik/tls/options/Options1/minVersion": "foobar",
"traefik/tls/options/Options1/maxVersion": "foobar",
"traefik/tls/options/Options1/cipherSuites/0": "foobar",
"traefik/tls/options/Options1/cipherSuites/1": "foobar",
"traefik/tls/stores/Store0/defaultCertificate/certFile": "foobar",
"traefik/tls/stores/Store0/defaultCertificate/keyFile": "foobar",
"traefik/tls/stores/Store1/defaultCertificate/certFile": "foobar",
"traefik/tls/stores/Store1/defaultCertificate/keyFile": "foobar",
"traefik/tls/certificates/0/certFile": "foobar",
"traefik/tls/certificates/0/keyFile": "foobar",
"traefik/tls/certificates/0/stores/0": "foobar",
"traefik/tls/certificates/0/stores/1": "foobar",
"traefik/tls/certificates/1/certFile": "foobar",
"traefik/tls/certificates/1/keyFile": "foobar",
"traefik/tls/certificates/1/stores/0": "foobar",
"traefik/tls/certificates/1/stores/1": "foobar",
}))
cfg, err := provider.buildConfiguration()
require.NoError(t, err)
expected := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
EntryPoints: []string{
"foobar",
"foobar",
},
Middlewares: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
Priority: 42,
TLS: &dynamic.RouterTLSConfig{
Options: "foobar",
CertResolver: "foobar",
Domains: []types.Domain{
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
},
},
},
"Router0": {
EntryPoints: []string{
"foobar",
"foobar",
},
Middlewares: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
Priority: 42,
TLS: &dynamic.RouterTLSConfig{},
},
},
Middlewares: map[string]*dynamic.Middleware{
"Middleware10": {
IPWhiteList: &dynamic.IPWhiteList{
SourceRange: []string{
"foobar",
"foobar",
},
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{
"foobar",
"foobar",
},
},
},
},
"Middleware13": {
RateLimit: &dynamic.RateLimit{
Average: 42,
Burst: 42,
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{
"foobar",
"foobar",
},
},
RequestHeaderName: "foobar",
RequestHost: true,
},
},
},
"Middleware19": {
StripPrefix: &dynamic.StripPrefix{
Prefixes: []string{
"foobar",
"foobar",
},
ForceSlash: true,
},
},
"Middleware00": {
AddPrefix: &dynamic.AddPrefix{
Prefix: "foobar",
},
},
"Middleware02": {
Buffering: &dynamic.Buffering{
MaxRequestBodyBytes: 42,
MemRequestBodyBytes: 42,
MaxResponseBodyBytes: 42,
MemResponseBodyBytes: 42,
RetryExpression: "foobar",
},
},
"Middleware04": {
CircuitBreaker: &dynamic.CircuitBreaker{
Expression: "foobar",
},
},
"Middleware05": {
Compress: &dynamic.Compress{},
},
"Middleware08": {
ForwardAuth: &dynamic.ForwardAuth{
Address: "foobar",
TLS: &dynamic.ClientTLS{
CA: "foobar",
CAOptional: true,
Cert: "foobar",
Key: "foobar",
InsecureSkipVerify: true,
},
TrustForwardHeader: true,
AuthResponseHeaders: []string{
"foobar",
"foobar",
},
},
},
"Middleware06": {
DigestAuth: &dynamic.DigestAuth{
Users: dynamic.Users{
"foobar",
"foobar",
},
UsersFile: "foobar",
RemoveHeader: true,
Realm: "foobar",
HeaderField: "foobar",
},
},
"Middleware18": {
Retry: &dynamic.Retry{
Attempts: 42,
},
},
"Middleware16": {
ReplacePath: &dynamic.ReplacePath{
Path: "foobar",
},
},
"Middleware20": {
StripPrefixRegex: &dynamic.StripPrefixRegex{
Regex: []string{
"foobar",
"foobar",
},
},
},
"Middleware03": {
Chain: &dynamic.Chain{
Middlewares: []string{
"foobar",
"foobar",
},
},
},
"Middleware11": {
InFlightReq: &dynamic.InFlightReq{
Amount: 42,
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
ExcludedIPs: []string{
"foobar",
"foobar",
},
},
RequestHeaderName: "foobar",
RequestHost: true,
},
},
},
"Middleware12": {
PassTLSClientCert: &dynamic.PassTLSClientCert{
PEM: true,
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
NotBefore: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateDNInfo{
Country: true,
Province: true,
Locality: true,
Organization: true,
CommonName: true,
SerialNumber: true,
DomainComponent: true,
},
},
},
},
"Middleware14": {
RedirectRegex: &dynamic.RedirectRegex{
Regex: "foobar",
Replacement: "foobar",
Permanent: true,
},
},
"Middleware15": {
RedirectScheme: &dynamic.RedirectScheme{
Scheme: "foobar",
Port: "foobar",
Permanent: true,
},
},
"Middleware01": {
BasicAuth: &dynamic.BasicAuth{
Users: dynamic.Users{
"foobar",
"foobar",
},
UsersFile: "foobar",
Realm: "foobar",
RemoveHeader: true,
HeaderField: "foobar",
},
},
"Middleware07": {
Errors: &dynamic.ErrorPage{
Status: []string{
"foobar",
"foobar",
},
Service: "foobar",
Query: "foobar",
},
},
"Middleware09": {
Headers: &dynamic.Headers{
CustomRequestHeaders: map[string]string{
"name0": "foobar",
"name1": "foobar",
},
CustomResponseHeaders: map[string]string{
"name0": "foobar",
"name1": "foobar",
},
AccessControlAllowCredentials: true,
AccessControlAllowHeaders: []string{
"foobar",
"foobar",
},
AccessControlAllowMethods: []string{
"foobar",
"foobar",
},
AccessControlAllowOrigin: "foobar",
AccessControlExposeHeaders: []string{
"foobar",
"foobar",
},
AccessControlMaxAge: 42,
AddVaryHeader: true,
AllowedHosts: []string{
"foobar",
"foobar",
},
HostsProxyHeaders: []string{
"foobar",
"foobar",
},
SSLRedirect: true,
SSLTemporaryRedirect: true,
SSLHost: "foobar",
SSLProxyHeaders: map[string]string{
"name1": "foobar",
"name0": "foobar",
},
SSLForceHost: true,
STSSeconds: 42,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
CustomFrameOptionsValue: "foobar",
ContentTypeNosniff: true,
BrowserXSSFilter: true,
CustomBrowserXSSValue: "foobar",
ContentSecurityPolicy: "foobar",
PublicKey: "foobar",
ReferrerPolicy: "foobar",
FeaturePolicy: "foobar",
IsDevelopment: true,
},
},
"Middleware17": {
ReplacePathRegex: &dynamic.ReplacePathRegex{
Regex: "foobar",
Replacement: "foobar",
},
},
},
Services: map[string]*dynamic.Service{
"Service01": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{
Name: "foobar",
Secure: true,
HTTPOnly: true,
},
},
Servers: []dynamic.Server{
{
URL: "foobar",
Scheme: "http",
},
{
URL: "foobar",
Scheme: "http",
},
},
HealthCheck: &dynamic.HealthCheck{
Scheme: "foobar",
Path: "foobar",
Port: 42,
Interval: "foobar",
Timeout: "foobar",
Hostname: "foobar",
Headers: map[string]string{
"name0": "foobar",
"name1": "foobar",
},
},
PassHostHeader: func(v bool) *bool { return &v }(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: "foobar",
},
},
},
"Service02": {
Mirroring: &dynamic.Mirroring{
Service: "foobar",
Mirrors: []dynamic.MirrorService{
{
Name: "foobar",
Percent: 42,
},
{
Name: "foobar",
Percent: 42,
},
},
},
},
"Service03": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "foobar",
Weight: func(v int) *int { return &v }(42),
},
{
Name: "foobar",
Weight: func(v int) *int { return &v }(42),
},
},
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{
Name: "foobar",
Secure: true,
HTTPOnly: true,
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"TCPRouter0": {
EntryPoints: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
TLS: &dynamic.RouterTCPTLSConfig{
Passthrough: true,
Options: "foobar",
CertResolver: "foobar",
Domains: []types.Domain{
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
},
},
},
"TCPRouter1": {
EntryPoints: []string{
"foobar",
"foobar",
},
Service: "foobar",
Rule: "foobar",
TLS: &dynamic.RouterTCPTLSConfig{
Passthrough: true,
Options: "foobar",
CertResolver: "foobar",
Domains: []types.Domain{
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
{
Main: "foobar",
SANs: []string{
"foobar",
"foobar",
},
},
},
},
},
},
Services: map[string]*dynamic.TCPService{
"TCPService01": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
TerminationDelay: func(v int) *int { return &v }(42),
Servers: []dynamic.TCPServer{
{Address: "foobar"},
{Address: "foobar"},
},
},
},
"TCPService02": {
Weighted: &dynamic.TCPWeightedRoundRobin{
Services: []dynamic.TCPWRRService{
{
Name: "foobar",
Weight: func(v int) *int { return &v }(42),
},
{
Name: "foobar",
Weight: func(v int) *int { return &v }(43),
},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{
Certificates: []*tls.CertAndStores{
{
Certificate: tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
Stores: []string{
"foobar",
"foobar",
},
},
{
Certificate: tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
Stores: []string{
"foobar",
"foobar",
},
},
},
Options: map[string]tls.Options{
"Options0": {
MinVersion: "foobar",
MaxVersion: "foobar",
CipherSuites: []string{
"foobar",
"foobar",
},
CurvePreferences: []string{
"foobar",
"foobar",
},
ClientAuth: tls.ClientAuth{
CAFiles: []tls.FileOrContent{
tls.FileOrContent("foobar"),
tls.FileOrContent("foobar"),
},
ClientAuthType: "foobar",
},
SniStrict: true,
},
"Options1": {
MinVersion: "foobar",
MaxVersion: "foobar",
CipherSuites: []string{
"foobar",
"foobar",
},
CurvePreferences: []string{
"foobar",
"foobar",
},
ClientAuth: tls.ClientAuth{
CAFiles: []tls.FileOrContent{
tls.FileOrContent("foobar"),
tls.FileOrContent("foobar"),
},
ClientAuthType: "foobar",
},
SniStrict: true,
},
},
Stores: map[string]tls.Store{
"Store0": {
DefaultCertificate: &tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
},
"Store1": {
DefaultCertificate: &tls.Certificate{
CertFile: tls.FileOrContent("foobar"),
KeyFile: tls.FileOrContent("foobar"),
},
},
},
},
}
assert.Equal(t, expected, cfg)
}
func Test_buildConfiguration_KV_error(t *testing.T) {
provider := &Provider{
RootKey: "traefik",
kvClient: &Mock{
Error: KvError{
List: errors.New("OOPS"),
},
KVPairs: mapToPairs(map[string]string{
"traefik/foo": "bar",
}),
},
}
cfg, err := provider.buildConfiguration()
require.Error(t, err)
assert.Nil(t, cfg)
}
func TestKvWatchTree(t *testing.T) {
returnedChans := make(chan chan []*store.KVPair)
provider := Provider{
kvClient: &Mock{
WatchTreeMethod: func() <-chan []*store.KVPair {
c := make(chan []*store.KVPair, 10)
returnedChans <- c
return c
},
},
}
configChan := make(chan dynamic.Message)
go func() {
err := provider.watchKv(context.Background(), configChan, "prefix", make(chan bool, 1))
require.NoError(t, err)
}()
select {
case c1 := <-returnedChans:
c1 <- []*store.KVPair{}
<-configChan
close(c1) // WatchTree chans can close due to error
case <-time.After(1 * time.Second):
t.Fatalf("Failed to create a new WatchTree chan")
}
select {
case c2 := <-returnedChans:
c2 <- []*store.KVPair{}
<-configChan
case <-time.After(1 * time.Second):
t.Fatalf("Failed to create a new WatchTree chan")
}
select {
case <-configChan:
t.Fatalf("configChan should be empty")
default:
}
}
func mapToPairs(in map[string]string) []*store.KVPair {
var out []*store.KVPair
for k, v := range in {
out = append(out, &store.KVPair{Key: k, Value: []byte(v)})
}
return out
}

View file

@ -0,0 +1,25 @@
package redis
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:6379"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.REDIS, "redis")
}

View file

@ -0,0 +1,118 @@
package kv
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/log"
)
type storeWrapper struct {
store.Store
}
func (s *storeWrapper) Put(key string, value []byte, options *store.WriteOptions) error {
log.WithoutContext().Debugf("Put: %s", key, string(value))
if s.Store == nil {
return nil
}
return s.Store.Put(key, value, options)
}
func (s *storeWrapper) Get(key string, options *store.ReadOptions) (*store.KVPair, error) {
log.WithoutContext().Debugf("Get: %s", key)
if s.Store == nil {
return nil, nil
}
return s.Store.Get(key, options)
}
func (s *storeWrapper) Delete(key string) error {
log.WithoutContext().Debugf("Delete: %s", key)
if s.Store == nil {
return nil
}
return s.Store.Delete(key)
}
func (s *storeWrapper) Exists(key string, options *store.ReadOptions) (bool, error) {
log.WithoutContext().Debugf("Exists: %s", key)
if s.Store == nil {
return true, nil
}
return s.Store.Exists(key, options)
}
func (s *storeWrapper) Watch(key string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan *store.KVPair, error) {
log.WithoutContext().Debugf("Watch: %s", key)
if s.Store == nil {
return nil, nil
}
return s.Store.Watch(key, stopCh, options)
}
func (s *storeWrapper) WatchTree(directory string, stopCh <-chan struct{}, options *store.ReadOptions) (<-chan []*store.KVPair, error) {
log.WithoutContext().Debugf("WatchTree: %s", directory)
if s.Store == nil {
return nil, nil
}
return s.Store.WatchTree(directory, stopCh, options)
}
func (s *storeWrapper) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
log.WithoutContext().Debugf("NewLock: %s", key)
if s.Store == nil {
return nil, nil
}
return s.Store.NewLock(key, options)
}
func (s *storeWrapper) List(directory string, options *store.ReadOptions) ([]*store.KVPair, error) {
log.WithoutContext().Debugf("List: %s", directory)
if s.Store == nil {
return nil, nil
}
return s.Store.List(directory, options)
}
func (s *storeWrapper) DeleteTree(directory string) error {
log.WithoutContext().Debugf("DeleteTree: %s", directory)
if s.Store == nil {
return nil
}
return s.Store.DeleteTree(directory)
}
func (s *storeWrapper) AtomicPut(key string, value []byte, previous *store.KVPair, options *store.WriteOptions) (bool, *store.KVPair, error) {
log.WithoutContext().Debugf("AtomicPut: %s", key, string(value), previous)
if s.Store == nil {
return true, nil, nil
}
return s.Store.AtomicPut(key, value, previous, options)
}
func (s *storeWrapper) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
log.WithoutContext().Debugf("AtomicDelete: %s", key, previous)
if s.Store == nil {
return true, nil
}
return s.Store.AtomicDelete(key, previous)
}
func (s *storeWrapper) Close() {
log.WithoutContext().Debugf("Close")
if s.Store == nil {
return
}
s.Store.Close()
}

25
pkg/provider/kv/zk/zk.go Normal file
View file

@ -0,0 +1,25 @@
package zk
import (
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/provider/kv"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:2181"}
}
// Init the provider
func (p *Provider) Init() error {
return p.Provider.Init(store.ZK, "zookeeper")
}