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 +5 -5
- data/.gitignore +6 -5
- data/.travis.yml +4 -5
- data/README.md +45 -2
- data/Rakefile +3 -2
- data/lib/etcdv3/connection.rb +26 -5
- data/lib/etcdv3/connection_wrapper.rb +22 -7
- 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/watch.rb +12 -4
- data/lib/etcdv3.rb +18 -4
- data/spec/etcdv3/connection_spec.rb +3 -3
- data/spec/etcdv3/connection_wrapper_spec.rb +39 -8
- data/spec/etcdv3/namespace/kv_spec.rb +82 -0
- data/spec/etcdv3/namespace/lock_spec.rb +23 -0
- data/spec/etcdv3/namespace/watch_spec.rb +36 -0
- data/spec/etcdv3/watch_spec.rb +24 -0
- data/spec/etcdv3_spec.rb +271 -1
- data/spec/helpers/connections.rb +10 -2
- data/spec/helpers/shared_examples_for_timeout.rb +4 -4
- data/spec/spec_helper.rb +6 -4
- 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: 18d76403d57c3e56b75e603931345900a4583b2e94d773b33ca04aa1547e304d
|
4
|
+
data.tar.gz: f1d1f9336f62f18b32fb9d3f055e0043bddb6076022b922b097c0104a7467c8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9633a47e982b7777ccda7e5472a663a661e4cf6368689cfbbd8d06a6ca862be47e7c3a75d075919b645611879513d4c88c7f59686b2a0eac15cc160e3ea7520f
|
7
|
+
data.tar.gz: 5a330de810e02ebdec73c279e33b7c2ece0c2aa7c4c877da89a2b285976ce68578ca20b3c51206d9a14c9fccbf18ef6310254010053eaefa59b06b898e1c195a
|
data/.gitignore
CHANGED
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
|
-
-
|
9
|
-
-
|
10
|
-
|
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["
|
4
|
-
ETCD_URL = "https://github.com/coreos/etcd/releases/download
|
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
|
|
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
|
@@ -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
|
data/lib/etcdv3/version.rb
CHANGED
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,
|
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
|
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
|
-
@
|
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:
|
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)
|