13 | 13 |
)
|
14 | 14 |
|
15 | 15 |
var (
|
16 | |
ErrNoKey = errors.New("no key provided")
|
|
16 |
// ErrNoKey indicates a client method needs a key but receives none.
|
|
17 |
ErrNoKey = errors.New("no key provided")
|
|
18 |
|
|
19 |
// ErrNoValue indicates a client method needs a value but receives none.
|
17 | 20 |
ErrNoValue = errors.New("no value provided")
|
18 | 21 |
)
|
19 | 22 |
|
20 | 23 |
// Client is a wrapper around the etcd client.
|
21 | 24 |
type Client interface {
|
22 | |
// GetEntries will query the given prefix in etcd and returns a set of entries.
|
|
25 |
// GetEntries queries the given prefix in etcd and returns a slice
|
|
26 |
// containing the values of all keys found, recursively, underneath that
|
|
27 |
// prefix.
|
23 | 28 |
GetEntries(prefix string) ([]string, error)
|
24 | 29 |
|
25 | |
// WatchPrefix starts watching every change for given prefix in etcd. When an
|
26 | |
// change is detected it will populate the responseChan when an *etcd.Response.
|
27 | |
WatchPrefix(prefix string, responseChan chan *etcd.Response)
|
|
30 |
// WatchPrefix watches the given prefix in etcd for changes. When a change
|
|
31 |
// is detected, it will signal on the passed channel. Clients are expected
|
|
32 |
// to call GetEntries to update themselves with the latest set of complete
|
|
33 |
// values. WatchPrefix will always send an initial sentinel value on the
|
|
34 |
// channel after establishing the watch, to ensure that clients always
|
|
35 |
// receive the latest set of values. WatchPrefix will block until the
|
|
36 |
// context passed to the NewClient constructor is terminated.
|
|
37 |
WatchPrefix(prefix string, ch chan struct{})
|
28 | 38 |
|
29 | 39 |
// Register a service with etcd.
|
30 | 40 |
Register(s Service) error
|
|
41 |
|
31 | 42 |
// Deregister a service with etcd.
|
32 | 43 |
Deregister(s Service) error
|
33 | 44 |
}
|
|
41 | 52 |
type ClientOptions struct {
|
42 | 53 |
Cert string
|
43 | 54 |
Key string
|
44 | |
CaCert string
|
|
55 |
CACert string
|
45 | 56 |
DialTimeout time.Duration
|
46 | 57 |
DialKeepAlive time.Duration
|
47 | 58 |
HeaderTimeoutPerRequest time.Duration
|
|
52 | 63 |
// The parameter machines needs to be a full URL with schemas.
|
53 | 64 |
// e.g. "http://localhost:2379" will work, but "localhost:2379" will not.
|
54 | 65 |
func NewClient(ctx context.Context, machines []string, options ClientOptions) (Client, error) {
|
55 | |
var (
|
56 | |
c etcd.KeysAPI
|
57 | |
err error
|
58 | |
caCertCt []byte
|
59 | |
tlsCert tls.Certificate
|
60 | |
)
|
61 | |
|
|
66 |
transport := etcd.DefaultTransport
|
62 | 67 |
if options.Cert != "" && options.Key != "" {
|
63 | |
tlsCert, err = tls.LoadX509KeyPair(options.Cert, options.Key)
|
|
68 |
tlsCert, err := tls.LoadX509KeyPair(options.Cert, options.Key)
|
64 | 69 |
if err != nil {
|
65 | 70 |
return nil, err
|
66 | 71 |
}
|
67 | |
|
68 | |
caCertCt, err = ioutil.ReadFile(options.CaCert)
|
|
72 |
caCertCt, err := ioutil.ReadFile(options.CACert)
|
69 | 73 |
if err != nil {
|
70 | 74 |
return nil, err
|
71 | 75 |
}
|
72 | 76 |
caCertPool := x509.NewCertPool()
|
73 | 77 |
caCertPool.AppendCertsFromPEM(caCertCt)
|
74 | |
|
75 | |
tlsConfig := &tls.Config{
|
76 | |
Certificates: []tls.Certificate{tlsCert},
|
77 | |
RootCAs: caCertPool,
|
78 | |
}
|
79 | |
|
80 | |
transport := &http.Transport{
|
81 | |
TLSClientConfig: tlsConfig,
|
82 | |
Dial: func(network, addr string) (net.Conn, error) {
|
83 | |
dial := &net.Dialer{
|
|
78 |
transport = &http.Transport{
|
|
79 |
TLSClientConfig: &tls.Config{
|
|
80 |
Certificates: []tls.Certificate{tlsCert},
|
|
81 |
RootCAs: caCertPool,
|
|
82 |
},
|
|
83 |
Dial: func(network, address string) (net.Conn, error) {
|
|
84 |
return (&net.Dialer{
|
84 | 85 |
Timeout: options.DialTimeout,
|
85 | 86 |
KeepAlive: options.DialKeepAlive,
|
86 | |
}
|
87 | |
return dial.Dial(network, addr)
|
|
87 |
}).Dial(network, address)
|
88 | 88 |
},
|
89 | 89 |
}
|
90 | |
|
91 | |
cfg := etcd.Config{
|
92 | |
Endpoints: machines,
|
93 | |
Transport: transport,
|
94 | |
HeaderTimeoutPerRequest: options.HeaderTimeoutPerRequest,
|
95 | |
}
|
96 | |
ce, err := etcd.New(cfg)
|
97 | |
if err != nil {
|
98 | |
return nil, err
|
99 | |
}
|
100 | |
c = etcd.NewKeysAPI(ce)
|
101 | |
} else {
|
102 | |
cfg := etcd.Config{
|
103 | |
Endpoints: machines,
|
104 | |
Transport: etcd.DefaultTransport,
|
105 | |
HeaderTimeoutPerRequest: options.HeaderTimeoutPerRequest,
|
106 | |
}
|
107 | |
ce, err := etcd.New(cfg)
|
108 | |
if err != nil {
|
109 | |
return nil, err
|
110 | |
}
|
111 | |
c = etcd.NewKeysAPI(ce)
|
112 | 90 |
}
|
113 | 91 |
|
114 | |
return &client{c, ctx}, nil
|
|
92 |
ce, err := etcd.New(etcd.Config{
|
|
93 |
Endpoints: machines,
|
|
94 |
Transport: transport,
|
|
95 |
HeaderTimeoutPerRequest: options.HeaderTimeoutPerRequest,
|
|
96 |
})
|
|
97 |
if err != nil {
|
|
98 |
return nil, err
|
|
99 |
}
|
|
100 |
|
|
101 |
return &client{
|
|
102 |
keysAPI: etcd.NewKeysAPI(ce),
|
|
103 |
ctx: ctx,
|
|
104 |
}, nil
|
115 | 105 |
}
|
116 | 106 |
|
117 | 107 |
// GetEntries implements the etcd Client interface.
|
|
136 | 126 |
}
|
137 | 127 |
|
138 | 128 |
// WatchPrefix implements the etcd Client interface.
|
139 | |
func (c *client) WatchPrefix(prefix string, responseChan chan *etcd.Response) {
|
|
129 |
func (c *client) WatchPrefix(prefix string, ch chan struct{}) {
|
140 | 130 |
watch := c.keysAPI.Watcher(prefix, &etcd.WatcherOptions{AfterIndex: 0, Recursive: true})
|
141 | |
responseChan <- nil // TODO(pb) explain this
|
|
131 |
ch <- struct{}{} // make sure caller invokes GetEntries
|
142 | 132 |
for {
|
143 | |
res, err := watch.Next(c.ctx)
|
144 | |
if err != nil {
|
|
133 |
if _, err := watch.Next(c.ctx); err != nil {
|
145 | 134 |
return
|
146 | 135 |
}
|
147 | |
responseChan <- res
|
|
136 |
ch <- struct{}{}
|
148 | 137 |
}
|
149 | 138 |
}
|
150 | 139 |
|