etcdv3 0.10.1 → 0.11.5
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 +5 -5
- data/.gitignore +6 -5
- data/.travis.yml +1 -0
- data/README.md +40 -0
- data/lib/etcdv3/connection.rb +26 -5
- data/lib/etcdv3/connection_wrapper.rb +22 -7
- data/lib/etcdv3/lock.rb +2 -2
- data/lib/etcdv3/namespace/kv/requests.rb +54 -0
- data/lib/etcdv3/namespace/kv/transaction.rb +91 -0
- data/lib/etcdv3/namespace/kv.rb +62 -0
- data/lib/etcdv3/namespace/lock.rb +32 -0
- data/lib/etcdv3/namespace/utilities.rb +44 -0
- data/lib/etcdv3/namespace/watch.rb +38 -0
- data/lib/etcdv3/version.rb +1 -1
- data/lib/etcdv3.rb +16 -2
- data/spec/etcdv3/connection_spec.rb +3 -3
- data/spec/etcdv3/connection_wrapper_spec.rb +39 -8
- data/spec/etcdv3/lock_spec.rb +19 -5
- data/spec/etcdv3/namespace/kv_spec.rb +82 -0
- data/spec/etcdv3/namespace/lock_spec.rb +42 -0
- data/spec/etcdv3/namespace/watch_spec.rb +36 -0
- data/spec/etcdv3/watch_spec.rb +12 -5
- data/spec/etcdv3_spec.rb +257 -1
- data/spec/helpers/connections.rb +18 -2
- data/spec/helpers/metadata_passthrough.rb +21 -0
- data/spec/helpers/shared_examples_for_timeout.rb +4 -4
- data/spec/helpers/test_instance.rb +2 -0
- data/spec/spec_helper.rb +4 -0
- metadata +17 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 03fa84076fc6d31e895bc80d55abb4501398a0be305cefa1f492b5e217e52277
|
4
|
+
data.tar.gz: 020aa912711d848ceb60a842a2d04bc1d5d3aacbf2714a380782cefd37c3b9c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7541f20326b9577fa7b595e371a0e94713b0b0d83ced62eaae120b5fe77c853625020425258ed941f49b345d40025f7c9d8398ba7bbb136e666f13d1b64e181b
|
7
|
+
data.tar.gz: 3e1226dcf9a6f7a3fcfce148148fcdcabe3f07bf92f24335a7a624b8af3e3b07176162a8f52f606b80dc9df864574ce8e430b95c456a54e79444f6466d3b37b4
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
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
|
data/lib/etcdv3/connection.rb
CHANGED
@@ -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
|
-
|
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(
|
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
|
-
@
|
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
|
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
|
-
|
21
|
-
return handle(stub, method, method_args)
|
36
|
+
return retry_or_raise(stub, method, method_args)
|
22
37
|
else
|
23
|
-
return
|
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
|
45
|
+
return retry_or_raise(stub, method, method_args, retries: retries - 1)
|
31
46
|
end
|
32
47
|
end
|
33
48
|
raise exception
|
data/lib/etcdv3/lock.rb
CHANGED
@@ -10,12 +10,12 @@ class Etcdv3
|
|
10
10
|
|
11
11
|
def lock(name, lease_id, timeout: nil)
|
12
12
|
request = V3lockpb::LockRequest.new(name: name, lease: lease_id)
|
13
|
-
@stub.lock(request, deadline: deadline(timeout))
|
13
|
+
@stub.lock(request, metadata: @metadata, deadline: deadline(timeout))
|
14
14
|
end
|
15
15
|
|
16
16
|
def unlock(key, timeout: nil)
|
17
17
|
request = V3lockpb::UnlockRequest.new(key: key)
|
18
|
-
@stub.unlock(request, deadline: deadline(timeout))
|
18
|
+
@stub.unlock(request, metadata: @metadata, deadline: deadline(timeout))
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
@@ -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, metadata: @metadata, 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, metadata: @metadata, 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
|
data/lib/etcdv3/version.rb
CHANGED
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
|
-
@
|
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
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Etcdv3::Connection do
|
4
4
|
|
5
5
|
describe '#initialize - without metadata' do
|
6
|
-
subject { Etcdv3::Connection.new('http://localhost:2379', 10) }
|
6
|
+
subject { Etcdv3::Connection.new('http://localhost:2379', 10, nil) }
|
7
7
|
|
8
8
|
it { is_expected.to have_attributes(endpoint: URI('http://localhost:2379')) }
|
9
9
|
it { is_expected.to have_attributes(credentials: :this_channel_is_insecure) }
|
@@ -22,7 +22,7 @@ describe Etcdv3::Connection do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe '#initialize - with metadata' do
|
25
|
-
subject { Etcdv3::Connection.new('http://localhost:2379', 10, token: 'token123') }
|
25
|
+
subject { Etcdv3::Connection.new('http://localhost:2379', 10, nil, token: 'token123') }
|
26
26
|
|
27
27
|
[:kv, :maintenance, :lease, :watch, :auth].each do |handler|
|
28
28
|
let(:handler_stub) { subject.handlers[handler].instance_variable_get(:@stub) }
|
@@ -37,7 +37,7 @@ describe Etcdv3::Connection do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
describe '#refresh_metadata' do
|
40
|
-
subject { Etcdv3::Connection.new('http://localhost:2379', token: 'token123') }
|
40
|
+
subject { Etcdv3::Connection.new('http://localhost:2379', nil, token: 'token123') }
|
41
41
|
before { subject.refresh_metadata(token: 'newtoken') }
|
42
42
|
[:kv, :maintenance, :lease, :watch, :auth].each do |handler|
|
43
43
|
let(:handler_metadata) { subject.handlers[handler].instance_variable_get(:@metadata) }
|