etcdv3 0.9.0 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2c90b3d66d75efdf3c6c608cc1ccaf9673731070
4
- data.tar.gz: 7698e02e69968d2e6348dd01a7e6813d27d5d50e
2
+ SHA256:
3
+ metadata.gz: 18d76403d57c3e56b75e603931345900a4583b2e94d773b33ca04aa1547e304d
4
+ data.tar.gz: f1d1f9336f62f18b32fb9d3f055e0043bddb6076022b922b097c0104a7467c8b
5
5
  SHA512:
6
- metadata.gz: 634ec36c4eccf0b93dd69b51a25e6515c06a26a1677264fb02d035c793cf905feb739ad9bde6e40ae30617416aebe7b85706bb4a1060cbb3d26d8b68e42a702f
7
- data.tar.gz: 7043f69c71837cefc8168ff3d5c0225afc9109c4a1c5e9aab1bec7b5fcf56999c9866abac23c3a7b13e9de5422b1983ffae57448f85dafd65f5d369837d7d286
6
+ metadata.gz: 9633a47e982b7777ccda7e5472a663a661e4cf6368689cfbbd8d06a6ca862be47e7c3a75d075919b645611879513d4c88c7f59686b2a0eac15cc160e3ea7520f
7
+ data.tar.gz: 5a330de810e02ebdec73c279e33b7c2ece0c2aa7c4c877da89a2b285976ce68578ca20b3c51206d9a14c9fccbf18ef6310254010053eaefa59b06b898e1c195a
data/.gitignore CHANGED
@@ -1,9 +1,10 @@
1
- Gemfile.lock
2
- Guardfile
3
- *.sh
4
1
  *.gem
5
- /tmp/
2
+ *.sh
3
+ *.swp
4
+ /.bundle
6
5
  /.idea
7
6
  /coverage/*
8
7
  /doc/
9
- *.swp
8
+ /tmp/
9
+ Gemfile.lock
10
+ Guardfile
data/.travis.yml CHANGED
@@ -1,15 +1,14 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 3.0.2
3
4
  - 2.5.3
4
5
  - 2.4.5
5
6
  - 2.3.8
6
7
 
7
8
  env:
8
- - ETCD_VERSION=v3.1.20
9
- - ETCD_VERSION=v3.2.25
10
- # v3.3.10 is not working for whatever reason (at least in travis, spec passes
11
- # locally for me)
12
- # - ETCD_VERSION=v3.3.10
9
+ - ETCD_VERSION_TO_TEST=v3.1.20
10
+ - ETCD_VERSION_TO_TEST=v3.2.25
11
+ - ETCD_VERSION_TO_TEST=v3.3.10
13
12
 
14
13
  install:
15
14
  - bundle install
data/README.md CHANGED
@@ -26,6 +26,9 @@ conn = Etcdv3.new(endpoints: 'https://hostname:port')
26
26
  # Secure connection with Auth
27
27
  conn = Etcdv3.new(endpoints: 'https://hostname:port', user: 'root', password: 'mysecretpassword')
28
28
 
29
+ # Scope CRUD operations to a specific keyspace.
30
+ conn = Etcdv3.new(endpoints: 'https://hostname:port', namespace: "/target_keyspace/")
31
+
29
32
  # Secure connection specifying custom certificates
30
33
  # Coming soon...
31
34
 
@@ -34,6 +37,43 @@ conn = Etcdv3.new(endpoints: 'https://hostname:port', user: 'root', password: 'm
34
37
 
35
38
  In the event of a failure, the client will work to restore connectivity by cycling through the specified endpoints until a connection can be established. With that being said, it is encouraged to specify multiple endpoints when available.
36
39
 
40
+ However, sometimes this is not what you want. If you need more control over
41
+ failures, you can suppress this mechanism by using
42
+
43
+ ```ruby
44
+ conn = Etcdv3.new(endpoints: 'https://hostname:port', allow_reconnect: false)
45
+ ```
46
+
47
+ This will still rotate the endpoints, but it will raise an exception so you can
48
+ handle the failure yourself. On next call to the new endpoint (since they were
49
+ rotated) is tried. One thing you need to keep in mind if auth is enabled, you
50
+ need to take care of `GRPC::Unauthenticated` exception and manually re-authenticate
51
+ when token expires. To reiterate, you are responsible for handling the errors, so
52
+ some understanding of how this gem and etcd works is recommended.
53
+
54
+ ## Namespace support
55
+
56
+ Namespacing is a convenience feature used to scope CRUD based operations to a specific keyspace.
57
+
58
+ ```ruby
59
+ # Establish connection
60
+ conn = Etcdv3.new(endpoints: 'https://hostname:port', namespace: '/service-a/')
61
+
62
+ # Write key to /service-a/test_key
63
+ conn.put("test_key", "value").
64
+
65
+ # Get the key we just wrote.
66
+ conn.get("test_key")
67
+
68
+ # Retrieve everything under the namespace.
69
+ conn.get("", "\0")
70
+
71
+ # Delete everything under the namespace.
72
+ conn.del("", "\0")
73
+ ```
74
+
75
+ _Note: Namespaces are stripped from responses._
76
+
37
77
 
38
78
  ## Adding, Fetching and Deleting Keys
39
79
  ```ruby
@@ -149,12 +189,15 @@ end
149
189
 
150
190
  ## Watch
151
191
  ```ruby
152
- # Watch for changes on a specified key and return
153
- events = conn.watch('foo')
192
+ # Watch for changes on a specified key for at most 10 seconds and return
193
+ events = conn.watch('foo', timeout: 10)
154
194
 
155
195
  # Watch for changes on a specified key range and return
156
196
  events = conn.watch('foo', range_end: 'fop')
157
197
 
198
+ # Watch for changes since a given revision
199
+ events = conn.watch('foo', start_revision: 42)
200
+
158
201
  # Watches for changes continuously until killed.
159
202
  event_count = 0
160
203
  conn.watch('boom') do |events|
data/Rakefile CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ETCD_VERSION = ENV["ETCD_VERSION"] || "v3.2.0"
4
- ETCD_URL = "https://github.com/coreos/etcd/releases/download/#{ETCD_VERSION}/etcd-#{ETCD_VERSION}-linux-amd64.tar.gz"
3
+ ETCD_VERSION = ENV["ETCD_VERSION_TO_TEST"] || "v3.2.0"
4
+ ETCD_URL = "https://github.com/coreos/etcd/releases/download/" \
5
+ "#{ETCD_VERSION}/etcd-#{ETCD_VERSION}-linux-amd64.tar.gz"
5
6
 
6
7
  require "tmpdir"
7
8
 
@@ -1,6 +1,12 @@
1
1
  class Etcdv3
2
2
  class Connection
3
3
 
4
+ NAMESPACE_HANDLERS = {
5
+ kv: Etcdv3::Namespace::KV,
6
+ watch: Etcdv3::Namespace::Watch,
7
+ lock: Etcdv3::Namespace::Lock,
8
+ }
9
+
4
10
  HANDLERS = {
5
11
  auth: Etcdv3::Auth,
6
12
  kv: Etcdv3::KV,
@@ -10,18 +16,25 @@ class Etcdv3
10
16
  lock: Etcdv3::Lock,
11
17
  }
12
18
 
13
- attr_reader :endpoint, :hostname, :handlers, :credentials
19
+ attr_reader :endpoint, :hostname, :handlers, :credentials, :namespace
14
20
 
15
- def initialize(url, timeout, metadata={})
21
+ def initialize(url, timeout, namespace, metadata={})
16
22
  @endpoint = URI(url)
17
23
  @hostname = "#{@endpoint.hostname}:#{@endpoint.port}"
24
+ @namespace = namespace
18
25
  @credentials = resolve_credentials
19
26
  @timeout = timeout
20
27
  @handlers = handler_map(metadata)
21
28
  end
22
29
 
23
30
  def call(stub, method, method_args=[])
24
- @handlers.fetch(stub).send(method, *method_args)
31
+ *method_args, method_kwargs = method_args if method_args.last.class == Hash
32
+
33
+ if method_kwargs.nil?
34
+ @handlers.fetch(stub).send(method, *method_args)
35
+ else
36
+ @handlers.fetch(stub).send(method, *method_args, **method_kwargs)
37
+ end
25
38
  end
26
39
 
27
40
  def refresh_metadata(metadata)
@@ -31,11 +44,19 @@ class Etcdv3
31
44
  private
32
45
 
33
46
  def handler_map(metadata={})
34
- Hash[
47
+ handlers = Hash[
35
48
  HANDLERS.map do |key, klass|
36
- [key, klass.new("#{@hostname}", @credentials, @timeout, metadata)]
49
+ [key, klass.new(@hostname, @credentials, @timeout, metadata)]
37
50
  end
38
51
  ]
52
+ # Override any handlers that are namespace compatable.
53
+ if @namespace
54
+ NAMESPACE_HANDLERS.each do |key, klass|
55
+ handlers[key] = klass.new(@hostname, @credentials, @timeout, @namespace, metadata)
56
+ end
57
+ end
58
+
59
+ handlers
39
60
  end
40
61
 
41
62
  def resolve_credentials
@@ -3,31 +3,46 @@ class Etcdv3
3
3
 
4
4
  attr_accessor :connection, :endpoints, :user, :password, :token, :timeout
5
5
 
6
- def initialize(timeout, *endpoints)
6
+ def initialize(timeout, *endpoints, namespace, allow_reconnect)
7
7
  @user, @password, @token = nil, nil, nil
8
8
  @timeout = timeout
9
- @endpoints = endpoints.map{|endpoint| Etcdv3::Connection.new(endpoint, @timeout) }
9
+ @namespace = namespace
10
+ @endpoints = endpoints.map{|endpoint| Etcdv3::Connection.new(endpoint, @timeout, @namespace) }
11
+ @allow_reconnect = allow_reconnect
10
12
  @connection = @endpoints.first
11
13
  end
12
14
 
15
+ private def retry_or_raise(*args)
16
+ if @allow_reconnect
17
+ *args, kwargs = args if args.last.class == Hash
18
+
19
+ if kwargs.nil?
20
+ handle(*args)
21
+ else
22
+ handle(*args, **kwargs)
23
+ end
24
+ else
25
+ raise
26
+ end
27
+ end
28
+
13
29
  def handle(stub, method, method_args=[], retries: 1)
14
30
  @connection.call(stub, method, method_args)
15
31
 
16
- rescue GRPC::Unavailable, GRPC::Core::CallError => exception
32
+ rescue GRPC::Unavailable, GRPC::Core::CallError
17
33
  $stderr.puts("Failed to connect to endpoint '#{@connection.hostname}'")
18
34
  if @endpoints.size > 1
19
35
  rotate_connection_endpoint
20
- $stderr.puts("Failover event triggered. Failing over to '#{@connection.hostname}'")
21
- return handle(stub, method, method_args)
36
+ return retry_or_raise(stub, method, method_args)
22
37
  else
23
- return handle(stub, method, method_args)
38
+ return retry_or_raise(stub, method, method_args)
24
39
  end
25
40
  rescue GRPC::Unauthenticated => exception
26
41
  # Regenerate token in the event it expires.
27
42
  if exception.details == 'etcdserver: invalid auth token'
28
43
  if retries > 0
29
44
  authenticate(@user, @password)
30
- return handle(stub, method, method_args, retries: retries - 1)
45
+ return retry_or_raise(stub, method, method_args, retries: retries - 1)
31
46
  end
32
47
  end
33
48
  raise exception
@@ -0,0 +1,54 @@
1
+ class Etcdv3::Namespace::KV
2
+ module Requests
3
+
4
+ SORT_TARGET = {
5
+ key: 0,
6
+ version: 1,
7
+ create: 2,
8
+ mod: 3,
9
+ value: 4
10
+ }
11
+
12
+ SORT_ORDER = {
13
+ none: 0,
14
+ ascend: 1,
15
+ descend: 2
16
+ }
17
+
18
+ def get_request(key, opts)
19
+ key = prepend_prefix(@namespace, key)
20
+ # In order to enforce the scope of the specified namespace, we are going to
21
+ # intercept the zero-byte reference and re-target everything under the given namespace.
22
+ if opts[:range_end] =~ /\x00/
23
+ opts[:range_end] = (@namespace[0..-2] + (@namespace[-1].ord + 1).chr)
24
+ else
25
+ opts[:range_end] = prepend_prefix(@namespace, opts[:range_end]) if opts[:range_end]
26
+ end
27
+ opts[:sort_order] = SORT_ORDER[opts[:sort_order]] \
28
+ if opts[:sort_order]
29
+ opts[:sort_target] = SORT_TARGET[opts[:sort_target]] \
30
+ if opts[:sort_target]
31
+ opts[:key] = key
32
+ Etcdserverpb::RangeRequest.new(opts)
33
+ end
34
+
35
+ def del_request(key, range_end=nil)
36
+ key = prepend_prefix(@namespace, key)
37
+ # In order to enforce the scope of the specified namespace, we are going to
38
+ # intercept the zero-byte reference and re-target everything under the given namespace.
39
+ if range_end =~ /\x00/
40
+ range_end = (@namespace[0..-2] + (@namespace[-1].ord + 1).chr)
41
+ else
42
+ range_end = prepend_prefix(@namespace, range_end) if range_end
43
+ end
44
+ Etcdserverpb::DeleteRangeRequest.new(key: key, range_end: range_end)
45
+ end
46
+
47
+ def put_request(key, value, lease=nil)
48
+ key = prepend_prefix(@namespace, key)
49
+ kv = Etcdserverpb::PutRequest.new(key: key, value: value)
50
+ kv.lease = lease if lease
51
+ kv
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,91 @@
1
+ class Etcdv3::Namespace::KV
2
+ class Transaction
3
+ include Etcdv3::Namespace::Utilities
4
+ include Etcdv3::Namespace::KV::Requests
5
+
6
+ # Available comparison identifiers.
7
+ COMPARISON_IDENTIFIERS = {
8
+ equal: 0,
9
+ greater: 1,
10
+ less: 2,
11
+ not_equal: 3
12
+ }
13
+
14
+ # Available targets to compare with.
15
+ TARGETS = {
16
+ version: 0,
17
+ create_revision: 1,
18
+ mod_revision: 2,
19
+ value: 3
20
+ }
21
+
22
+ attr_writer :compare, :success, :failure
23
+
24
+ def initialize(namespace)
25
+ @namespace = namespace
26
+ end
27
+
28
+ def compare
29
+ @compare ||= []
30
+ end
31
+
32
+ def success
33
+ @success ||= []
34
+ end
35
+
36
+ def failure
37
+ @failure ||=[]
38
+ end
39
+
40
+ # Request Operations
41
+
42
+ # txn.put('my', 'key', lease_id: 1)
43
+ def put(key, value, lease=nil)
44
+ put_request(key, value, lease)
45
+ end
46
+
47
+ # txn.get('key')
48
+ def get(key, opts={})
49
+ get_request(key, opts)
50
+ end
51
+
52
+ # txn.del('key')
53
+ def del(key, range_end='')
54
+ del_request(key, range_end)
55
+ end
56
+
57
+ ### Compare Operations
58
+
59
+ # txn.version('names', :greater, 0 )
60
+ def version(key, compare_type, value)
61
+ generate_compare(:version, key, compare_type, value)
62
+ end
63
+
64
+ # txn.value('names', :equal, 'wowza' )
65
+ def value(key, compare_type, value)
66
+ generate_compare(:value, key, compare_type, value)
67
+ end
68
+
69
+ # txn.mod_revision('names', :not_equal, 0)
70
+ def mod_revision(key, compare_type, value)
71
+ generate_compare(:mod_revision, key, compare_type, value)
72
+ end
73
+
74
+ # txn.create_revision('names', :less, 10)
75
+ def create_revision(key, compare_type, value)
76
+ generate_compare(:create_revision, key, compare_type, value)
77
+ end
78
+
79
+ private
80
+
81
+ def generate_compare(target_union, key, compare_type, value)
82
+ key = prepend_prefix(@namespace, key)
83
+ Etcdserverpb::Compare.new(
84
+ key: key,
85
+ result: COMPARISON_IDENTIFIERS[compare_type],
86
+ target: TARGETS[target_union],
87
+ target_union => value
88
+ )
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,62 @@
1
+ class Etcdv3::Namespace
2
+ class KV
3
+ include Etcdv3::Namespace::KV::Requests
4
+ include Etcdv3::Namespace::Utilities
5
+ include GRPC::Core::TimeConsts
6
+
7
+ def initialize(hostname, credentials, timeout, namespace, metadata={})
8
+ @stub = Etcdserverpb::KV::Stub.new(hostname, credentials)
9
+ @timeout = timeout
10
+ @namespace = namespace
11
+ @metadata = metadata
12
+ end
13
+
14
+ def get(key, opts={})
15
+ timeout = opts.delete(:timeout)
16
+ resp = @stub.range(get_request(key, opts), metadata: @metadata, deadline: deadline(timeout))
17
+ strip_prefix(@namespace, resp)
18
+ end
19
+
20
+ def del(key, range_end: '', timeout: nil)
21
+ resp = @stub.delete_range(del_request(key, range_end), metadata: @metadata, deadline: deadline(timeout))
22
+ strip_prefix(@namespace, resp)
23
+ end
24
+
25
+ def put(key, value, lease: nil, timeout: nil)
26
+ resp = @stub.put(put_request(key, value, lease), metadata: @metadata, deadline: deadline(timeout))
27
+ strip_prefix(@namespace, resp)
28
+ end
29
+
30
+ def transaction(block, timeout: nil)
31
+ txn = Etcdv3::Namespace::KV::Transaction.new(@namespace)
32
+ block.call(txn)
33
+ request = Etcdserverpb::TxnRequest.new(
34
+ compare: txn.compare,
35
+ success: generate_request_ops(txn.success),
36
+ failure: generate_request_ops(txn.failure),
37
+ )
38
+ resp = @stub.txn(request, metadata: @metadata, deadline: deadline(timeout))
39
+ strip_prefix(@namespace, resp)
40
+ end
41
+
42
+ private
43
+
44
+ def deadline(timeout)
45
+ from_relative_time(timeout || @timeout)
46
+ end
47
+
48
+ def generate_request_ops(requests)
49
+ requests.map do |request|
50
+ if request.is_a?(Etcdserverpb::RangeRequest)
51
+ Etcdserverpb::RequestOp.new(request_range: request)
52
+ elsif request.is_a?(Etcdserverpb::PutRequest)
53
+ Etcdserverpb::RequestOp.new(request_put: request)
54
+ elsif request.is_a?(Etcdserverpb::DeleteRangeRequest)
55
+ Etcdserverpb::RequestOp.new(request_delete_range: request)
56
+ else
57
+ raise "Invalid command. Not sure how you got here!"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,32 @@
1
+ class Etcdv3::Namespace
2
+ class Lock
3
+ include GRPC::Core::TimeConsts
4
+ include Etcdv3::Namespace::Utilities
5
+
6
+ def initialize(hostname, credentials, timeout, namespace, metadata = {})
7
+ @stub = V3lockpb::Lock::Stub.new(hostname, credentials)
8
+ @timeout = timeout
9
+ @namespace = namespace
10
+ @metadata = metadata
11
+ end
12
+
13
+ def lock(name, lease_id, timeout: nil)
14
+ name = prepend_prefix(@namespace, name)
15
+ request = V3lockpb::LockRequest.new(name: name, lease: lease_id)
16
+ resp = @stub.lock(request, deadline: deadline(timeout))
17
+ strip_prefix_from_lock(@namespace, resp)
18
+ end
19
+
20
+ def unlock(key, timeout: nil)
21
+ key = prepend_prefix(@namespace, key)
22
+ request = V3lockpb::UnlockRequest.new(key: key)
23
+ @stub.unlock(request, deadline: deadline(timeout))
24
+ end
25
+
26
+ private
27
+
28
+ def deadline(timeout)
29
+ from_relative_time(timeout || @timeout)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ class Etcdv3::Namespace
2
+ module Utilities
3
+
4
+ def prepend_prefix(prefix, key)
5
+ key = key.dup if key.frozen?
6
+ key.prepend(prefix)
7
+ end
8
+
9
+ def strip_prefix(prefix, resp)
10
+ [:kvs, :prev_kvs].each do |field|
11
+ if resp.respond_to?(field)
12
+ resp.send(field).each do |kv|
13
+ kv.key = delete_prefix(prefix, kv.key)
14
+ end
15
+ end
16
+ end
17
+ resp
18
+ end
19
+
20
+ def strip_prefix_from_lock(prefix, resp)
21
+ if resp.key
22
+ resp.key = delete_prefix(prefix, resp.key)
23
+ end
24
+ resp
25
+ end
26
+
27
+ def strip_prefix_from_events(prefix, events)
28
+ events.each do |event|
29
+ if event.kv
30
+ event.kv.key = delete_prefix(prefix, event.kv.key)
31
+ end
32
+ if event.prev_kv
33
+ event.prev_kv.key = delete_prefix(prefix, event.prev_kv.key)
34
+ end
35
+ event
36
+ end
37
+ end
38
+
39
+ def delete_prefix(prefix, str)
40
+ str.sub(/\A#{prefix}/, '')
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ class Etcdv3::Namespace
2
+ class Watch
3
+ include GRPC::Core::TimeConsts
4
+ include Etcdv3::Namespace::Utilities
5
+
6
+ def initialize(hostname, credentials, timeout, namespace, metadata = {})
7
+ @stub = Etcdserverpb::Watch::Stub.new(hostname, credentials)
8
+ @timeout = timeout
9
+ @namespace = namespace
10
+ @metadata = metadata
11
+ end
12
+
13
+ def watch(key, range_end, start_revision, block, timeout: nil)
14
+ key = prepend_prefix(@namespace, key)
15
+ range_end = prepend_prefix(@namespace, range_end) if range_end
16
+ create_req = Etcdserverpb::WatchCreateRequest.new(key: key)
17
+ create_req.range_end = range_end if range_end
18
+ create_req.start_revision = start_revision if start_revision
19
+ watch_req = Etcdserverpb::WatchRequest.new(create_request: create_req)
20
+ events = nil
21
+ @stub.watch([watch_req], metadata: @metadata, deadline: deadline(timeout)).each do |resp|
22
+ next if resp.events.empty?
23
+ if block
24
+ block.call(strip_prefix_from_events(@namespace, resp.events))
25
+ else
26
+ events = strip_prefix_from_events(@namespace, resp.events)
27
+ break
28
+ end
29
+ end
30
+ events
31
+ end
32
+
33
+ def deadline(timeout)
34
+ from_relative_time(timeout || @timeout)
35
+ end
36
+
37
+ end
38
+ end
@@ -1,3 +1,3 @@
1
1
  class Etcdv3
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '0.11.4'.freeze
3
3
  end
data/lib/etcdv3/watch.rb CHANGED
@@ -1,16 +1,20 @@
1
1
  class Etcdv3
2
2
  class Watch
3
+ include GRPC::Core::TimeConsts
3
4
 
4
- def initialize(hostname, credentials, _timeout, metadata = {})
5
+ def initialize(hostname, credentials, timeout, metadata = {})
5
6
  @stub = Etcdserverpb::Watch::Stub.new(hostname, credentials)
7
+ @timeout = timeout
6
8
  @metadata = metadata
7
9
  end
8
10
 
9
- def watch(key, range_end, block)
10
- create_req = Etcdserverpb::WatchCreateRequest.new(key: key, range_end: range_end)
11
+ def watch(key, range_end, start_revision, block, timeout: nil)
12
+ create_req = Etcdserverpb::WatchCreateRequest.new(key: key)
13
+ create_req.range_end = range_end if range_end
14
+ create_req.start_revision = start_revision if start_revision
11
15
  watch_req = Etcdserverpb::WatchRequest.new(create_request: create_req)
12
16
  events = nil
13
- @stub.watch([watch_req], metadata: @metadata).each do |resp|
17
+ @stub.watch([watch_req], metadata: @metadata, deadline: deadline(timeout)).each do |resp|
14
18
  next if resp.events.empty?
15
19
  if block
16
20
  block.call(resp.events)
@@ -21,5 +25,9 @@ class Etcdv3
21
25
  end
22
26
  events
23
27
  end
28
+
29
+ def deadline(timeout)
30
+ from_relative_time(timeout || @timeout)
31
+ end
24
32
  end
25
33
  end
data/lib/etcdv3.rb CHANGED
@@ -7,6 +7,14 @@ require 'etcdv3/auth'
7
7
  require 'etcdv3/kv/requests'
8
8
  require 'etcdv3/kv/transaction'
9
9
  require 'etcdv3/kv'
10
+
11
+ require 'etcdv3/namespace/utilities'
12
+ require 'etcdv3/namespace/kv/requests'
13
+ require 'etcdv3/namespace/kv/transaction'
14
+ require 'etcdv3/namespace/lock'
15
+ require 'etcdv3/namespace/kv'
16
+ require 'etcdv3/namespace/watch'
17
+
10
18
  require 'etcdv3/maintenance'
11
19
  require 'etcdv3/lease'
12
20
  require 'etcdv3/watch'
@@ -21,10 +29,16 @@ class Etcdv3
21
29
  attr_reader :conn, :credentials, :options
22
30
  DEFAULT_TIMEOUT = 120
23
31
 
24
- def initialize(options = {})
32
+ def initialize(**options)
25
33
  @options = options
26
34
  @timeout = options[:command_timeout] || DEFAULT_TIMEOUT
27
- @conn = ConnectionWrapper.new(@timeout, *sanitized_endpoints)
35
+ @namespace = options[:namespace]
36
+ @conn = ConnectionWrapper.new(
37
+ @timeout,
38
+ *sanitized_endpoints,
39
+ @namespace,
40
+ @options.fetch(:allow_reconnect, true),
41
+ )
28
42
  warn "WARNING: `url` is deprecated. Please use `endpoints` instead." if @options.key?(:url)
29
43
  authenticate(@options[:user], @options[:password]) if @options.key?(:user)
30
44
  end
@@ -219,8 +233,8 @@ class Etcdv3
219
233
  end
220
234
 
221
235
  # Watches for changes on a specified key range.
222
- def watch(key, range_end: '', &block)
223
- @conn.handle(:watch, 'watch', [key, range_end, block])
236
+ def watch(key, range_end: nil, start_revision: nil, timeout: nil, &block)
237
+ @conn.handle(:watch, 'watch', [key, range_end, start_revision, block, timeout: timeout])
224
238
  end
225
239
 
226
240
  def transaction(timeout: nil, &block)