Browse code

Add TLS support for discovery backend

This leverages recent additions to libkv enabling client
authentication via TLS so the discovery back-end can be locked
down with mutual TLS. Example usage:

docker daemon [other args] \
--cluster-advertise 192.168.122.168:2376 \
--cluster-store etcd://192.168.122.168:2379 \
--cluster-store-opt kv.cacertfile=/path/to/ca.pem \
--cluster-store-opt kv.certfile=/path/to/cert.pem \
--cluster-store-opt kv.keyfile=/path/to/key.pem

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>

Daniel Hiltgen authored on 2015/09/29 08:22:57
Showing 13 changed files
... ...
@@ -39,6 +39,10 @@ type CommonConfig struct {
39 39
 	// mechanism.
40 40
 	ClusterStore string
41 41
 
42
+	// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
43
+	// as TLS configuration settings.
44
+	ClusterOpts map[string]string
45
+
42 46
 	// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
43 47
 	// discovery. This should be a 'host:port' combination on which that daemon instance is
44 48
 	// reachable by other hosts.
... ...
@@ -68,4 +72,5 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string)
68 68
 	cmd.Var(opts.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
69 69
 	cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address of the daemon instance to advertise"))
70 70
 	cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
71
+	cmd.Var(opts.NewMapOpts(config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
71 72
 }
... ...
@@ -756,7 +756,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
756 756
 	// DiscoveryWatcher version.
757 757
 	if config.ClusterStore != "" && config.ClusterAdvertise != "" {
758 758
 		var err error
759
-		if d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise); err != nil {
759
+		if d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts); err != nil {
760 760
 			return nil, fmt.Errorf("discovery initialization failed (%v)", err)
761 761
 		}
762 762
 	}
... ...
@@ -20,12 +20,12 @@ const (
20 20
 
21 21
 // initDiscovery initialized the nodes discovery subsystem by connecting to the specified backend
22 22
 // and start a registration loop to advertise the current node under the specified address.
23
-func initDiscovery(backend, address string) (discovery.Backend, error) {
23
+func initDiscovery(backend, address string, clusterOpts map[string]string) (discovery.Backend, error) {
24 24
 	var (
25 25
 		discoveryBackend discovery.Backend
26 26
 		err              error
27 27
 	)
28
-	if discoveryBackend, err = discovery.New(backend, defaultDiscoveryHeartbeat, defaultDiscoveryTTL); err != nil {
28
+	if discoveryBackend, err = discovery.New(backend, defaultDiscoveryHeartbeat, defaultDiscoveryTTL, clusterOpts); err != nil {
29 29
 		return nil, err
30 30
 	}
31 31
 
... ...
@@ -71,6 +71,7 @@ func NewDaemonCli() *DaemonCli {
71 71
 	// TODO(tiborvass): remove InstallFlags?
72 72
 	daemonConfig := new(daemon.Config)
73 73
 	daemonConfig.LogConfig.Config = make(map[string]string)
74
+	daemonConfig.ClusterOpts = make(map[string]string)
74 75
 	daemonConfig.InstallFlags(daemonFlags, presentInHelp)
75 76
 	daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)
76 77
 	registryOptions := new(registry.Options)
... ...
@@ -24,6 +24,7 @@ weight=1
24 24
       --default-gateway-v6=""                Container default gateway IPv6 address
25 25
       --cluster-store=""                     URL of the distributed storage backend
26 26
       --cluster-advertise=""                 Address of the daemon instance to advertise
27
+      --cluster-store-opt=map[]              Set cluster options
27 28
       --dns=[]                               DNS server to use
28 29
       --dns-opt=[]                           DNS options to use
29 30
       --dns-search=[]                        DNS search domains to use
... ...
@@ -537,6 +538,20 @@ please check the [run](run.md) reference.
537 537
 daemon instance should use when advertising itself to the cluster. The daemon
538 538
 should be reachable by remote hosts on this 'host:port' combination.
539 539
 
540
+The daemon uses [libkv](https://github.com/docker/libkv/) to advertise
541
+the node within the cluster.  Some Key/Value backends support mutual
542
+TLS, and the client TLS settings used by the daemon can be configured
543
+using the `--cluster-store-opt` flag, specifying the paths to PEM encoded
544
+files. For example:
545
+
546
+```bash
547
+    --cluster-advertise 192.168.1.2:2376 \
548
+    --cluster-store etcd://192.168.1.2:2379 \
549
+    --cluster-store-opt kv.cacertfile=/path/to/ca.pem \
550
+    --cluster-store-opt kv.certfile=/path/to/cert.pem \
551
+    --cluster-store-opt kv.keyfile=/path/to/key.pem
552
+```
553
+
540 554
 ## Miscellaneous options
541 555
 
542 556
 IP masquerading uses address translation to allow containers without a public
... ...
@@ -41,11 +41,11 @@ func parse(rawurl string) (string, string) {
41 41
 
42 42
 // New returns a new Discovery given a URL, heartbeat and ttl settings.
43 43
 // Returns an error if the URL scheme is not supported.
44
-func New(rawurl string, heartbeat time.Duration, ttl time.Duration) (Backend, error) {
44
+func New(rawurl string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) (Backend, error) {
45 45
 	scheme, uri := parse(rawurl)
46 46
 	if backend, exists := backends[scheme]; exists {
47 47
 		log.WithFields(log.Fields{"name": scheme, "uri": uri}).Debug("Initializing discovery service")
48
-		err := backend.Initialize(uri, heartbeat, ttl)
48
+		err := backend.Initialize(uri, heartbeat, ttl, clusterOpts)
49 49
 		return backend, err
50 50
 	}
51 51
 
... ...
@@ -27,8 +27,8 @@ type Backend interface {
27 27
 	// Watcher must be provided by every backend.
28 28
 	Watcher
29 29
 
30
-	// Initialize the discovery with URIs, a heartbeat and a ttl.
31
-	Initialize(string, time.Duration, time.Duration) error
30
+	// Initialize the discovery with URIs, a heartbeat, a ttl and optional settings.
31
+	Initialize(string, time.Duration, time.Duration, map[string]string) error
32 32
 
33 33
 	// Register to the discovery.
34 34
 	Register(string) error
... ...
@@ -25,7 +25,7 @@ func Init() {
25 25
 }
26 26
 
27 27
 // Initialize is exported
28
-func (s *Discovery) Initialize(path string, heartbeat time.Duration, ttl time.Duration) error {
28
+func (s *Discovery) Initialize(path string, heartbeat time.Duration, ttl time.Duration, _ map[string]string) error {
29 29
 	s.path = path
30 30
 	s.heartbeat = heartbeat
31 31
 	return nil
... ...
@@ -19,12 +19,12 @@ var _ = check.Suite(&DiscoverySuite{})
19 19
 
20 20
 func (s *DiscoverySuite) TestInitialize(c *check.C) {
21 21
 	d := &Discovery{}
22
-	d.Initialize("/path/to/file", 1000, 0)
22
+	d.Initialize("/path/to/file", 1000, 0, nil)
23 23
 	c.Assert(d.path, check.Equals, "/path/to/file")
24 24
 }
25 25
 
26 26
 func (s *DiscoverySuite) TestNew(c *check.C) {
27
-	d, err := discovery.New("file:///path/to/file", 0, 0)
27
+	d, err := discovery.New("file:///path/to/file", 0, 0, nil)
28 28
 	c.Assert(err, check.IsNil)
29 29
 	c.Assert(d.(*Discovery).path, check.Equals, "/path/to/file")
30 30
 }
... ...
@@ -81,7 +81,7 @@ func (s *DiscoverySuite) TestWatch(c *check.C) {
81 81
 
82 82
 	// Set up file discovery.
83 83
 	d := &Discovery{}
84
-	d.Initialize(tmp.Name(), 1000, 0)
84
+	d.Initialize(tmp.Name(), 1000, 0, nil)
85 85
 	stopCh := make(chan struct{})
86 86
 	ch, errCh := d.Watch(stopCh)
87 87
 
... ...
@@ -8,6 +8,7 @@ import (
8 8
 
9 9
 	log "github.com/Sirupsen/logrus"
10 10
 	"github.com/docker/docker/pkg/discovery"
11
+	"github.com/docker/docker/pkg/tlsconfig"
11 12
 	"github.com/docker/libkv"
12 13
 	"github.com/docker/libkv/store"
13 14
 	"github.com/docker/libkv/store/consul"
... ...
@@ -47,7 +48,7 @@ func Init() {
47 47
 }
48 48
 
49 49
 // Initialize is exported
50
-func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration) error {
50
+func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) error {
51 51
 	var (
52 52
 		parts = strings.SplitN(uris, "/", 2)
53 53
 		addrs = strings.Split(parts[0], ",")
... ...
@@ -63,9 +64,34 @@ func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Du
63 63
 	s.ttl = ttl
64 64
 	s.path = path.Join(s.prefix, discoveryPath)
65 65
 
66
+	var config *store.Config
67
+	if clusterOpts["kv.cacertfile"] != "" && clusterOpts["kv.certfile"] != "" && clusterOpts["kv.keyfile"] != "" {
68
+		log.Info("Initializing discovery with TLS")
69
+		tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
70
+			CAFile:   clusterOpts["kv.cacertfile"],
71
+			CertFile: clusterOpts["kv.certfile"],
72
+			KeyFile:  clusterOpts["kv.keyfile"],
73
+		})
74
+		if err != nil {
75
+			return err
76
+		}
77
+		config = &store.Config{
78
+			// Set ClientTLS to trigger https (bug in libkv/etcd)
79
+			ClientTLS: &store.ClientTLSConfig{
80
+				CACertFile: clusterOpts["kv.cacertfile"],
81
+				CertFile:   clusterOpts["kv.certfile"],
82
+				KeyFile:    clusterOpts["kv.keyfile"],
83
+			},
84
+			// The actual TLS config that will be used
85
+			TLS: tlsConfig,
86
+		}
87
+	} else {
88
+		log.Info("Initializing discovery without TLS")
89
+	}
90
+
66 91
 	// Creates a new store, will ignore options given
67 92
 	// if not supported by the chosen store
68
-	s.store, err = libkv.NewStore(s.backend, addrs, nil)
93
+	s.store, err = libkv.NewStore(s.backend, addrs, config)
69 94
 	return err
70 95
 }
71 96
 
... ...
@@ -2,11 +2,14 @@ package kv
2 2
 
3 3
 import (
4 4
 	"errors"
5
+	"io/ioutil"
6
+	"os"
5 7
 	"path"
6 8
 	"testing"
7 9
 	"time"
8 10
 
9 11
 	"github.com/docker/docker/pkg/discovery"
12
+	"github.com/docker/libkv"
10 13
 	"github.com/docker/libkv/store"
11 14
 
12 15
 	"github.com/go-check/check"
... ...
@@ -24,7 +27,7 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
24 24
 		Endpoints: []string{"127.0.0.1"},
25 25
 	}
26 26
 	d := &Discovery{backend: store.CONSUL}
27
-	d.Initialize("127.0.0.1", 0, 0)
27
+	d.Initialize("127.0.0.1", 0, 0, nil)
28 28
 	d.store = storeMock
29 29
 
30 30
 	s := d.store.(*FakeStore)
... ...
@@ -36,7 +39,7 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
36 36
 		Endpoints: []string{"127.0.0.1:1234"},
37 37
 	}
38 38
 	d = &Discovery{backend: store.CONSUL}
39
-	d.Initialize("127.0.0.1:1234/path", 0, 0)
39
+	d.Initialize("127.0.0.1:1234/path", 0, 0, nil)
40 40
 	d.store = storeMock
41 41
 
42 42
 	s = d.store.(*FakeStore)
... ...
@@ -48,7 +51,7 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
48 48
 		Endpoints: []string{"127.0.0.1:1234", "127.0.0.2:1234", "127.0.0.3:1234"},
49 49
 	}
50 50
 	d = &Discovery{backend: store.CONSUL}
51
-	d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0, 0)
51
+	d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0, 0, nil)
52 52
 	d.store = storeMock
53 53
 
54 54
 	s = d.store.(*FakeStore)
... ...
@@ -60,6 +63,150 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
60 60
 	c.Assert(d.path, check.Equals, "path/"+discoveryPath)
61 61
 }
62 62
 
63
+// Extremely limited mock store so we can test initialization
64
+type Mock struct {
65
+	// Endpoints passed to InitializeMock
66
+	Endpoints []string
67
+
68
+	// Options passed to InitializeMock
69
+	Options *store.Config
70
+}
71
+
72
+func NewMock(endpoints []string, options *store.Config) (store.Store, error) {
73
+	s := &Mock{}
74
+	s.Endpoints = endpoints
75
+	s.Options = options
76
+	return s, nil
77
+}
78
+func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
79
+	return errors.New("Put not supported")
80
+}
81
+func (s *Mock) Get(key string) (*store.KVPair, error) {
82
+	return nil, errors.New("Get not supported")
83
+}
84
+func (s *Mock) Delete(key string) error {
85
+	return errors.New("Delete not supported")
86
+}
87
+
88
+// Exists mock
89
+func (s *Mock) Exists(key string) (bool, error) {
90
+	return false, errors.New("Exists not supported")
91
+}
92
+
93
+// Watch mock
94
+func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
95
+	return nil, errors.New("Watch not supported")
96
+}
97
+
98
+// WatchTree mock
99
+func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
100
+	return nil, errors.New("WatchTree not supported")
101
+}
102
+
103
+// NewLock mock
104
+func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
105
+	return nil, errors.New("NewLock not supported")
106
+}
107
+
108
+// List mock
109
+func (s *Mock) List(prefix string) ([]*store.KVPair, error) {
110
+	return nil, errors.New("List not supported")
111
+}
112
+
113
+// DeleteTree mock
114
+func (s *Mock) DeleteTree(prefix string) error {
115
+	return errors.New("DeleteTree not supported")
116
+}
117
+
118
+// AtomicPut mock
119
+func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
120
+	return false, nil, errors.New("AtomicPut not supported")
121
+}
122
+
123
+// AtomicDelete mock
124
+func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
125
+	return false, errors.New("AtomicDelete not supported")
126
+}
127
+
128
+// Close mock
129
+func (s *Mock) Close() {
130
+	return
131
+}
132
+
133
+func (ds *DiscoverySuite) TestInitializeWithCerts(c *check.C) {
134
+	cert := `-----BEGIN CERTIFICATE-----
135
+MIIDCDCCAfKgAwIBAgIICifG7YeiQOEwCwYJKoZIhvcNAQELMBIxEDAOBgNVBAMT
136
+B1Rlc3QgQ0EwHhcNMTUxMDAxMjMwMDAwWhcNMjAwOTI5MjMwMDAwWjASMRAwDgYD
137
+VQQDEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wRC
138
+O+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4+zE9h80aC4hz+6caRpds
139
++J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhRSoSi3nY+B7F2E8cuz14q
140
+V2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZrpXUyXxAvzXfpFXo1RhSb
141
+UywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUerVYrCPq8vqfn//01qz55
142
+Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHojxOpXTBepUCIJLbtNnWFT
143
+V44t9gh5IqIWtoBReQIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAAYwEgYDVR0TAQH/
144
+BAgwBgEB/wIBAjAdBgNVHQ4EFgQUZKUI8IIjIww7X/6hvwggQK4bD24wHwYDVR0j
145
+BBgwFoAUZKUI8IIjIww7X/6hvwggQK4bD24wCwYJKoZIhvcNAQELA4IBAQDES2cz
146
+7sCQfDCxCIWH7X8kpi/JWExzUyQEJ0rBzN1m3/x8ySRxtXyGekimBqQwQdFqlwMI
147
+xzAQKkh3ue8tNSzRbwqMSyH14N1KrSxYS9e9szJHfUasoTpQGPmDmGIoRJuq1h6M
148
+ej5x1SCJ7GWCR6xEXKUIE9OftXm9TdFzWa7Ja3OHz/mXteii8VXDuZ5ACq6EE5bY
149
+8sP4gcICfJ5fTrpTlk9FIqEWWQrCGa5wk95PGEj+GJpNogjXQ97wVoo/Y3p1brEn
150
+t5zjN9PAq4H1fuCMdNNA+p1DHNwd+ELTxcMAnb2ajwHvV6lKPXutrTFc4umJToBX
151
+FpTxDmJHEV4bzUzh
152
+-----END CERTIFICATE-----
153
+`
154
+	key := `-----BEGIN RSA PRIVATE KEY-----
155
+MIIEpQIBAAKCAQEA1wRCO+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4
156
++zE9h80aC4hz+6caRpds+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhR
157
+SoSi3nY+B7F2E8cuz14qV2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZr
158
+pXUyXxAvzXfpFXo1RhSbUywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUe
159
+rVYrCPq8vqfn//01qz55Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHoj
160
+xOpXTBepUCIJLbtNnWFTV44t9gh5IqIWtoBReQIDAQABAoIBAHSWipORGp/uKFXj
161
+i/mut776x8ofsAxhnLBARQr93ID+i49W8H7EJGkOfaDjTICYC1dbpGrri61qk8sx
162
+qX7p3v/5NzKwOIfEpirgwVIqSNYe/ncbxnhxkx6tXtUtFKmEx40JskvSpSYAhmmO
163
+1XSx0E/PWaEN/nLgX/f1eWJIlxlQkk3QeqL+FGbCXI48DEtlJ9+MzMu4pAwZTpj5
164
+5qtXo5JJ0jRGfJVPAOznRsYqv864AhMdMIWguzk6EGnbaCWwPcfcn+h9a5LMdony
165
+MDHfBS7bb5tkF3+AfnVY3IBMVx7YlsD9eAyajlgiKu4zLbwTRHjXgShy+4Oussz0
166
+ugNGnkECgYEA/hi+McrZC8C4gg6XqK8+9joD8tnyDZDz88BQB7CZqABUSwvjDqlP
167
+L8hcwo/lzvjBNYGkqaFPUICGWKjeCtd8pPS2DCVXxDQX4aHF1vUur0uYNncJiV3N
168
+XQz4Iemsa6wnKf6M67b5vMXICw7dw0HZCdIHD1hnhdtDz0uVpeevLZ8CgYEA2KCT
169
+Y43lorjrbCgMqtlefkr3GJA9dey+hTzCiWEOOqn9RqGoEGUday0sKhiLofOgmN2B
170
+LEukpKIey8s+Q/cb6lReajDVPDsMweX8i7hz3Wa4Ugp4Xa5BpHqu8qIAE2JUZ7bU
171
+t88aQAYE58pUF+/Lq1QzAQdrjjzQBx6SrBxieecCgYEAvukoPZEC8mmiN1VvbTX+
172
+QFHmlZha3QaDxChB+QUe7bMRojEUL/fVnzkTOLuVFqSfxevaI/km9n0ac5KtAchV
173
+xjp2bTnBb5EUQFqjopYktWA+xO07JRJtMfSEmjZPbbay1kKC7rdTfBm961EIHaRj
174
+xZUf6M+rOE8964oGrdgdLlECgYEA046GQmx6fh7/82FtdZDRQp9tj3SWQUtSiQZc
175
+qhO59Lq8mjUXz+MgBuJXxkiwXRpzlbaFB0Bca1fUoYw8o915SrDYf/Zu2OKGQ/qa
176
+V81sgiVmDuEgycR7YOlbX6OsVUHrUlpwhY3hgfMe6UtkMvhBvHF/WhroBEIJm1pV
177
+PXZ/CbMCgYEApNWVktFBjOaYfY6SNn4iSts1jgsQbbpglg3kT7PLKjCAhI6lNsbk
178
+dyT7ut01PL6RaW4SeQWtrJIVQaM6vF3pprMKqlc5XihOGAmVqH7rQx9rtQB5TicL
179
+BFrwkQE4HQtQBV60hYQUzzlSk44VFDz+jxIEtacRHaomDRh2FtOTz+I=
180
+-----END RSA PRIVATE KEY-----
181
+`
182
+	certFile, err := ioutil.TempFile("", "cert")
183
+	c.Assert(err, check.IsNil)
184
+	defer os.Remove(certFile.Name())
185
+	certFile.Write([]byte(cert))
186
+	certFile.Close()
187
+	keyFile, err := ioutil.TempFile("", "key")
188
+	c.Assert(err, check.IsNil)
189
+	defer os.Remove(keyFile.Name())
190
+	keyFile.Write([]byte(key))
191
+	keyFile.Close()
192
+
193
+	libkv.AddStore("mock", NewMock)
194
+	d := &Discovery{backend: "mock"}
195
+	err = d.Initialize("127.0.0.3:1234", 0, 0, map[string]string{
196
+		"kv.cacertfile": certFile.Name(),
197
+		"kv.certfile":   certFile.Name(),
198
+		"kv.keyfile":    keyFile.Name(),
199
+	})
200
+	c.Assert(err, check.IsNil)
201
+	s := d.store.(*Mock)
202
+	c.Assert(s.Options.TLS, check.NotNil)
203
+	c.Assert(s.Options.TLS.RootCAs, check.NotNil)
204
+	c.Assert(s.Options.TLS.Certificates, check.HasLen, 1)
205
+}
206
+
63 207
 func (ds *DiscoverySuite) TestWatch(c *check.C) {
64 208
 	mockCh := make(chan []*store.KVPair)
65 209
 
... ...
@@ -69,7 +216,7 @@ func (ds *DiscoverySuite) TestWatch(c *check.C) {
69 69
 	}
70 70
 
71 71
 	d := &Discovery{backend: store.CONSUL}
72
-	d.Initialize("127.0.0.1:1234/path", 0, 0)
72
+	d.Initialize("127.0.0.1:1234/path", 0, 0, nil)
73 73
 	d.store = storeMock
74 74
 
75 75
 	expected := discovery.Entries{
... ...
@@ -23,7 +23,7 @@ func Init() {
23 23
 }
24 24
 
25 25
 // Initialize is exported
26
-func (s *Discovery) Initialize(uris string, _ time.Duration, _ time.Duration) error {
26
+func (s *Discovery) Initialize(uris string, _ time.Duration, _ time.Duration, _ map[string]string) error {
27 27
 	for _, input := range strings.Split(uris, ",") {
28 28
 		for _, ip := range discovery.Generate(input) {
29 29
 			entry, err := discovery.NewEntry(ip)
... ...
@@ -17,7 +17,7 @@ var _ = check.Suite(&DiscoverySuite{})
17 17
 
18 18
 func (s *DiscoverySuite) TestInitialize(c *check.C) {
19 19
 	d := &Discovery{}
20
-	d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0)
20
+	d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0, nil)
21 21
 	c.Assert(len(d.entries), check.Equals, 2)
22 22
 	c.Assert(d.entries[0].String(), check.Equals, "1.1.1.1:1111")
23 23
 	c.Assert(d.entries[1].String(), check.Equals, "2.2.2.2:2222")
... ...
@@ -25,7 +25,7 @@ func (s *DiscoverySuite) TestInitialize(c *check.C) {
25 25
 
26 26
 func (s *DiscoverySuite) TestInitializeWithPattern(c *check.C) {
27 27
 	d := &Discovery{}
28
-	d.Initialize("1.1.1.[1:2]:1111,2.2.2.[2:4]:2222", 0, 0)
28
+	d.Initialize("1.1.1.[1:2]:1111,2.2.2.[2:4]:2222", 0, 0, nil)
29 29
 	c.Assert(len(d.entries), check.Equals, 5)
30 30
 	c.Assert(d.entries[0].String(), check.Equals, "1.1.1.1:1111")
31 31
 	c.Assert(d.entries[1].String(), check.Equals, "1.1.1.2:1111")
... ...
@@ -36,7 +36,7 @@ func (s *DiscoverySuite) TestInitializeWithPattern(c *check.C) {
36 36
 
37 37
 func (s *DiscoverySuite) TestWatch(c *check.C) {
38 38
 	d := &Discovery{}
39
-	d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0)
39
+	d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0, nil)
40 40
 	expected := discovery.Entries{
41 41
 		&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
42 42
 		&discovery.Entry{Host: "2.2.2.2", Port: "2222"},