python-etcd3-async 0.1.0__py3-none-any.whl
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.
- etcd3/__init__.py +90 -0
- etcd3/async_client.py +1495 -0
- etcd3/async_client_extensions.py +878 -0
- etcd3/async_endpoint.py +140 -0
- etcd3/async_managers.py +498 -0
- etcd3/async_monitor.py +16 -0
- etcd3/async_watch.py +462 -0
- etcd3/client.py +2021 -0
- etcd3/endpoint.py +79 -0
- etcd3/events.py +40 -0
- etcd3/exceptions.py +47 -0
- etcd3/exporters/__init__.py +18 -0
- etcd3/exporters/prometheus.py +365 -0
- etcd3/leases.py +106 -0
- etcd3/locks.py +593 -0
- etcd3/log.py +146 -0
- etcd3/log_formatters.py +228 -0
- etcd3/log_sampling.py +221 -0
- etcd3/members.py +44 -0
- etcd3/observability.py +431 -0
- etcd3/shared.py +486 -0
- etcd3/trace.py +231 -0
- etcd3/trace_attributes.py +434 -0
- etcd3/transactions.py +280 -0
- etcd3/utils.py +158 -0
- etcd3/watch.py +270 -0
- etcdrpc/__init__.py +16 -0
- etcdrpc/auth.proto +49 -0
- etcdrpc/auth_pb2.py +43 -0
- etcdrpc/auth_pb2.pyi +44 -0
- etcdrpc/auth_pb2_grpc.py +24 -0
- etcdrpc/kv.proto +43 -0
- etcdrpc/kv_pb2.py +40 -0
- etcdrpc/kv_pb2.pyi +39 -0
- etcdrpc/kv_pb2_grpc.py +24 -0
- etcdrpc/rpc.proto +924 -0
- etcdrpc/rpc_pb2.py +237 -0
- etcdrpc/rpc_pb2.pyi +811 -0
- etcdrpc/rpc_pb2_grpc.py +1893 -0
- python_etcd3_async-0.1.0.dist-info/METADATA +328 -0
- python_etcd3_async-0.1.0.dist-info/RECORD +42 -0
- python_etcd3_async-0.1.0.dist-info/WHEEL +4 -0
etcd3/utils.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Utility functions for etcd3 operations."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, AsyncIterator, Iterator, Union
|
|
5
|
+
|
|
6
|
+
import grpc
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Watch filter types - matching etcd protobuf FilterType
|
|
10
|
+
# Use these constants with the filters parameter in watch operations
|
|
11
|
+
#
|
|
12
|
+
# Example usage:
|
|
13
|
+
# events, cancel = await etcd.watch('/key', filters=[FILTER_DELETE])
|
|
14
|
+
# # Only PUT events will be received, DELETE events are filtered out
|
|
15
|
+
#
|
|
16
|
+
FILTER_PUT = 0 # Filter out PUT events (only DELETE events will be received)
|
|
17
|
+
FILTER_DELETE = 1 # Filter out DELETE events (only PUT events will be received)
|
|
18
|
+
FILTER_COMMIT = 2 # Filter out all events (no events will be received)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LoadBalancerStrategy(Enum):
|
|
22
|
+
"""Load balancing strategies for multi-endpoint clients.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
PRIORITY: Priority-based routing - prefer healthy nodes, failover on failure
|
|
26
|
+
ROUND_ROBIN: Round-robin - cycle through endpoints sequentially
|
|
27
|
+
RANDOM: Random - select random healthy endpoint
|
|
28
|
+
STICKY: Sticky - maintain connection to the same endpoint
|
|
29
|
+
"""
|
|
30
|
+
PRIORITY = "priority"
|
|
31
|
+
ROUND_ROBIN = "round_robin"
|
|
32
|
+
RANDOM = "random"
|
|
33
|
+
STICKY = "sticky"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_connection_canceled(error: grpc.RpcError) -> bool:
|
|
37
|
+
"""Check if a connection error indicates the connection was canceled.
|
|
38
|
+
|
|
39
|
+
This is similar to Go's clientv3.IsConnCanceled function.
|
|
40
|
+
|
|
41
|
+
:param error: The gRPC error to check
|
|
42
|
+
:type error: grpc.RpcError
|
|
43
|
+
:returns: True if the connection was canceled
|
|
44
|
+
:rtype: bool
|
|
45
|
+
"""
|
|
46
|
+
code = error.code()
|
|
47
|
+
if code == grpc.StatusCode.UNAVAILABLE:
|
|
48
|
+
return True
|
|
49
|
+
if code == grpc.StatusCode.INTERNAL:
|
|
50
|
+
return True
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_channel_ready(channel) -> bool:
|
|
55
|
+
"""Check if a gRPC channel is ready.
|
|
56
|
+
|
|
57
|
+
:param channel: The gRPC channel to check
|
|
58
|
+
:type channel: grpc.aio.Channel
|
|
59
|
+
:returns: True if the channel is ready
|
|
60
|
+
:rtype: bool
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
state = channel.get_state()
|
|
64
|
+
return state == grpc.ChannelConnectivity.READY
|
|
65
|
+
except Exception:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def prefix_range_end(prefix: bytes) -> bytes:
|
|
70
|
+
"""Create a bytestring that can be used as a range_end for a prefix query.
|
|
71
|
+
|
|
72
|
+
This function calculates the end of a key range that includes all keys
|
|
73
|
+
starting with the given prefix.
|
|
74
|
+
|
|
75
|
+
:param prefix: The key prefix to match
|
|
76
|
+
:type prefix: bytes
|
|
77
|
+
:returns: The range end bytes for prefix matching
|
|
78
|
+
:rtype: bytes
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
>>> prefix_range_end(b'/foo/')
|
|
82
|
+
b'/foo0' # All keys starting with /foo/ will match
|
|
83
|
+
"""
|
|
84
|
+
if not prefix:
|
|
85
|
+
# Empty prefix returns empty range_end (matches nothing)
|
|
86
|
+
return b""
|
|
87
|
+
s = bytearray(prefix)
|
|
88
|
+
for i in reversed(range(len(s))):
|
|
89
|
+
if s[i] < 0xFF:
|
|
90
|
+
s[i] = s[i] + 1
|
|
91
|
+
break
|
|
92
|
+
return bytes(s)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def to_bytes(maybe_bytestring: Union[str, bytes]) -> bytes:
|
|
96
|
+
"""Encode string to bytes.
|
|
97
|
+
|
|
98
|
+
Convenience function to do a simple encode('utf-8') if the input is not
|
|
99
|
+
already bytes. Returns the data unmodified if the input is bytes.
|
|
100
|
+
|
|
101
|
+
:param maybe_bytestring: The string or bytes to convert
|
|
102
|
+
:type maybe_bytestring: str or bytes
|
|
103
|
+
:returns: The input encoded as bytes
|
|
104
|
+
:rtype: bytes
|
|
105
|
+
"""
|
|
106
|
+
if isinstance(maybe_bytestring, bytes):
|
|
107
|
+
return maybe_bytestring
|
|
108
|
+
else:
|
|
109
|
+
return maybe_bytestring.encode("utf-8")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def lease_to_id(lease: Any) -> int:
|
|
113
|
+
"""Extract lease ID from a Lease object or return the ID directly.
|
|
114
|
+
|
|
115
|
+
:param lease: A Lease object with an 'id' attribute, or a lease ID
|
|
116
|
+
:type lease: Any (with 'id' attribute) or int
|
|
117
|
+
:returns: The lease ID as an integer
|
|
118
|
+
:rtype: int
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> lease_to_id(my_lease) # Lease object
|
|
122
|
+
12345
|
|
123
|
+
>>> lease_to_id(12345) # Direct ID
|
|
124
|
+
12345
|
|
125
|
+
"""
|
|
126
|
+
lease_id = 0
|
|
127
|
+
if hasattr(lease, "id"):
|
|
128
|
+
lease_id = lease.id
|
|
129
|
+
else:
|
|
130
|
+
try:
|
|
131
|
+
lease_id = int(lease)
|
|
132
|
+
except TypeError:
|
|
133
|
+
pass
|
|
134
|
+
return lease_id
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def response_to_event_iterator(response_iterator: Iterator[Any]) -> Iterator[Any]:
|
|
138
|
+
"""Convert a watch response iterator to an event iterator.
|
|
139
|
+
|
|
140
|
+
:param response_iterator: Iterator of watch responses
|
|
141
|
+
:type response_iterator: Iterator
|
|
142
|
+
:yields: Individual events from the watch responses
|
|
143
|
+
:rtype: Any
|
|
144
|
+
"""
|
|
145
|
+
for response in response_iterator:
|
|
146
|
+
for event in response.events:
|
|
147
|
+
yield event
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def async_response_to_event_iterator(response_iterator) -> AsyncIterator[Any]:
|
|
151
|
+
"""Convert an async watch response iterator to an async event iterator.
|
|
152
|
+
|
|
153
|
+
:param response_iterator: Async iterator of watch responses
|
|
154
|
+
:yields: Individual events from the watch responses
|
|
155
|
+
"""
|
|
156
|
+
async for response in response_iterator:
|
|
157
|
+
for event in response.events:
|
|
158
|
+
yield event
|
etcd3/watch.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import grpc
|
|
6
|
+
import queue
|
|
7
|
+
|
|
8
|
+
import etcdrpc
|
|
9
|
+
import etcd3.events as events
|
|
10
|
+
import etcd3.exceptions as exceptions
|
|
11
|
+
import etcd3.utils as utils
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Watch(object):
|
|
18
|
+
def __init__(self, watch_id, iterator=None, etcd_client=None):
|
|
19
|
+
self.watch_id = watch_id
|
|
20
|
+
self.etcd_client = etcd_client
|
|
21
|
+
self.iterator = iterator
|
|
22
|
+
|
|
23
|
+
def cancel(self):
|
|
24
|
+
self.etcd_client.cancel_watch(self.watch_id)
|
|
25
|
+
|
|
26
|
+
def iterator(self):
|
|
27
|
+
if self.iterator is not None:
|
|
28
|
+
return self.iterator
|
|
29
|
+
|
|
30
|
+
raise ValueError("Undefined iterator")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Watcher(object):
|
|
34
|
+
def __init__(self, watchstub, timeout=None, call_credentials=None, metadata=None):
|
|
35
|
+
self.timeout = timeout
|
|
36
|
+
self._watch_stub = watchstub
|
|
37
|
+
self._credentials = call_credentials
|
|
38
|
+
self._metadata = metadata
|
|
39
|
+
|
|
40
|
+
self._lock = threading.Lock()
|
|
41
|
+
self._request_queue = queue.Queue(maxsize=10)
|
|
42
|
+
self._callbacks = {}
|
|
43
|
+
self._callback_thread = None
|
|
44
|
+
self._new_watch_cond = threading.Condition(lock=self._lock)
|
|
45
|
+
self._new_watch = None
|
|
46
|
+
self._stopping = False
|
|
47
|
+
|
|
48
|
+
def _create_watch_request(
|
|
49
|
+
self,
|
|
50
|
+
key,
|
|
51
|
+
range_end=None,
|
|
52
|
+
start_revision=None,
|
|
53
|
+
progress_notify=False,
|
|
54
|
+
filters=None,
|
|
55
|
+
prev_kv=False,
|
|
56
|
+
):
|
|
57
|
+
create_watch = etcdrpc.WatchCreateRequest()
|
|
58
|
+
create_watch.key = utils.to_bytes(key)
|
|
59
|
+
if range_end is not None:
|
|
60
|
+
create_watch.range_end = utils.to_bytes(range_end)
|
|
61
|
+
if start_revision is not None:
|
|
62
|
+
create_watch.start_revision = start_revision
|
|
63
|
+
if progress_notify:
|
|
64
|
+
create_watch.progress_notify = progress_notify
|
|
65
|
+
if filters is not None:
|
|
66
|
+
create_watch.filters.extend(filters)
|
|
67
|
+
if prev_kv:
|
|
68
|
+
create_watch.prev_kv = prev_kv
|
|
69
|
+
return etcdrpc.WatchRequest(create_request=create_watch)
|
|
70
|
+
|
|
71
|
+
def add_callback(
|
|
72
|
+
self,
|
|
73
|
+
key,
|
|
74
|
+
callback,
|
|
75
|
+
range_end=None,
|
|
76
|
+
start_revision=None,
|
|
77
|
+
progress_notify=False,
|
|
78
|
+
filters=None,
|
|
79
|
+
prev_kv=False,
|
|
80
|
+
):
|
|
81
|
+
rq = self._create_watch_request(
|
|
82
|
+
key,
|
|
83
|
+
range_end=range_end,
|
|
84
|
+
start_revision=start_revision,
|
|
85
|
+
progress_notify=progress_notify,
|
|
86
|
+
filters=filters,
|
|
87
|
+
prev_kv=prev_kv,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
with self._lock:
|
|
91
|
+
# Wait for exiting thread to close
|
|
92
|
+
if self._stopping:
|
|
93
|
+
self._callback_thread.join()
|
|
94
|
+
self._callback_thread = None
|
|
95
|
+
self._stopping = False
|
|
96
|
+
|
|
97
|
+
# Start the callback thread if it is not yet running.
|
|
98
|
+
if not self._callback_thread:
|
|
99
|
+
thread_name = "etcd3_watch_%x" % (id(self),)
|
|
100
|
+
self._callback_thread = threading.Thread(
|
|
101
|
+
name=thread_name, target=self._run
|
|
102
|
+
)
|
|
103
|
+
self._callback_thread.daemon = True
|
|
104
|
+
self._callback_thread.start()
|
|
105
|
+
|
|
106
|
+
# Only one create watch request can be pending at a time, so if
|
|
107
|
+
# there one already, then wait for it to complete first.
|
|
108
|
+
while self._new_watch:
|
|
109
|
+
self._new_watch_cond.wait()
|
|
110
|
+
|
|
111
|
+
# Submit a create watch request.
|
|
112
|
+
new_watch = _NewWatch(callback)
|
|
113
|
+
self._request_queue.put(rq)
|
|
114
|
+
self._new_watch = new_watch
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
# Wait for the request to be completed, or timeout.
|
|
118
|
+
# Use a loop to handle spurious wakeups (Mesa semantics)
|
|
119
|
+
wait_deadline = time.time() + self.timeout if self.timeout else None
|
|
120
|
+
while new_watch.id is None and new_watch.err is None:
|
|
121
|
+
if wait_deadline is not None:
|
|
122
|
+
remaining = wait_deadline - time.time()
|
|
123
|
+
if remaining <= 0:
|
|
124
|
+
break
|
|
125
|
+
self._new_watch_cond.wait(timeout=remaining)
|
|
126
|
+
else:
|
|
127
|
+
self._new_watch_cond.wait()
|
|
128
|
+
|
|
129
|
+
# If the request not completed yet, then raise a timeout
|
|
130
|
+
# exception.
|
|
131
|
+
if new_watch.id is None and new_watch.err is None:
|
|
132
|
+
raise exceptions.WatchTimedOut()
|
|
133
|
+
|
|
134
|
+
# Raise an exception if the watch request failed.
|
|
135
|
+
if new_watch.err:
|
|
136
|
+
raise new_watch.err
|
|
137
|
+
finally:
|
|
138
|
+
# Wake up threads stuck on add_callback call if any.
|
|
139
|
+
self._new_watch = None
|
|
140
|
+
self._new_watch_cond.notify_all()
|
|
141
|
+
|
|
142
|
+
return new_watch.id
|
|
143
|
+
|
|
144
|
+
def cancel(self, watch_id):
|
|
145
|
+
with self._lock:
|
|
146
|
+
callback = self._callbacks.pop(watch_id, None)
|
|
147
|
+
if not callback:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
self._cancel_no_lock(watch_id)
|
|
151
|
+
|
|
152
|
+
def _run(self):
|
|
153
|
+
callback_err = None
|
|
154
|
+
try:
|
|
155
|
+
response_iter = self._watch_stub.Watch(
|
|
156
|
+
_new_request_iter(self._request_queue),
|
|
157
|
+
credentials=self._credentials,
|
|
158
|
+
metadata=self._metadata,
|
|
159
|
+
)
|
|
160
|
+
for rs in response_iter:
|
|
161
|
+
self._handle_response(rs)
|
|
162
|
+
|
|
163
|
+
except grpc.RpcError as err:
|
|
164
|
+
callback_err = err
|
|
165
|
+
|
|
166
|
+
finally:
|
|
167
|
+
with self._lock:
|
|
168
|
+
self._stopping = True
|
|
169
|
+
if self._new_watch:
|
|
170
|
+
self._new_watch.err = callback_err
|
|
171
|
+
self._new_watch_cond.notify_all()
|
|
172
|
+
|
|
173
|
+
callbacks = self._callbacks
|
|
174
|
+
self._callbacks = {}
|
|
175
|
+
|
|
176
|
+
# Rotate request queue. This way we can terminate one gRPC
|
|
177
|
+
# stream and initiate another one whilst avoiding a race
|
|
178
|
+
# between them over requests in the queue.
|
|
179
|
+
self._request_queue.put(None)
|
|
180
|
+
self._request_queue = queue.Queue(maxsize=10)
|
|
181
|
+
|
|
182
|
+
for callback in callbacks.values():
|
|
183
|
+
_safe_callback(callback, callback_err)
|
|
184
|
+
|
|
185
|
+
def _handle_response(self, rs):
|
|
186
|
+
with self._lock:
|
|
187
|
+
if rs.created:
|
|
188
|
+
# Store reference locally to avoid race with other threads
|
|
189
|
+
new_watch = self._new_watch
|
|
190
|
+
|
|
191
|
+
# If the new watch request has already expired then cancel the
|
|
192
|
+
# created watch right away.
|
|
193
|
+
if not new_watch:
|
|
194
|
+
self._cancel_no_lock(rs.watch_id)
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
if rs.compact_revision != 0:
|
|
198
|
+
new_watch.err = exceptions.RevisionCompactedError(
|
|
199
|
+
rs.compact_revision
|
|
200
|
+
)
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
self._callbacks[rs.watch_id] = new_watch.callback
|
|
204
|
+
new_watch.id = rs.watch_id
|
|
205
|
+
self._new_watch_cond.notify_all()
|
|
206
|
+
|
|
207
|
+
callback = self._callbacks.get(rs.watch_id)
|
|
208
|
+
|
|
209
|
+
# Ignore leftovers from canceled watches.
|
|
210
|
+
if not callback:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
# The watcher can be safely reused, but adding a new event
|
|
214
|
+
# to indicate that the revision is already compacted
|
|
215
|
+
# requires api change which would break all users of this
|
|
216
|
+
# module. So, raising an exception if a watcher is still
|
|
217
|
+
# alive.
|
|
218
|
+
if rs.compact_revision != 0:
|
|
219
|
+
err = exceptions.RevisionCompactedError(rs.compact_revision)
|
|
220
|
+
_safe_callback(callback, err)
|
|
221
|
+
self.cancel(rs.watch_id)
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
# Call the callback even when there are no events in the watch
|
|
225
|
+
# response so as not to ignore progress notify responses.
|
|
226
|
+
if rs.events or not (rs.created or rs.canceled):
|
|
227
|
+
new_events = [events.new_event(event) for event in rs.events]
|
|
228
|
+
response = WatchResponse(rs.header, new_events)
|
|
229
|
+
_safe_callback(callback, response)
|
|
230
|
+
|
|
231
|
+
def _cancel_no_lock(self, watch_id):
|
|
232
|
+
cancel_watch = etcdrpc.WatchCancelRequest()
|
|
233
|
+
cancel_watch.watch_id = watch_id
|
|
234
|
+
rq = etcdrpc.WatchRequest(cancel_request=cancel_watch)
|
|
235
|
+
self._request_queue.put(rq)
|
|
236
|
+
|
|
237
|
+
def close(self):
|
|
238
|
+
with self._lock:
|
|
239
|
+
if self._callback_thread and not self._stopping:
|
|
240
|
+
self._request_queue.put(None)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class WatchResponse(object):
|
|
244
|
+
def __init__(self, header, events):
|
|
245
|
+
self.header = header
|
|
246
|
+
self.events = events
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class _NewWatch(object):
|
|
250
|
+
def __init__(self, callback):
|
|
251
|
+
self.callback = callback
|
|
252
|
+
self.id = None
|
|
253
|
+
self.err = None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _new_request_iter(_request_queue):
|
|
257
|
+
while True:
|
|
258
|
+
rq = _request_queue.get()
|
|
259
|
+
if rq is None:
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
yield rq
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _safe_callback(callback, response_or_err):
|
|
266
|
+
try:
|
|
267
|
+
callback(response_or_err)
|
|
268
|
+
|
|
269
|
+
except Exception:
|
|
270
|
+
_log.exception("Watch callback failed")
|
etcdrpc/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""etcdrpc package - uses pre-generated protobuf files."""
|
|
2
|
+
|
|
3
|
+
# 简单的检查,如果文件不存在则报错提示(防御性编程)
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
_current_dir = Path(__file__).parent
|
|
6
|
+
if not (_current_dir / "rpc_pb2.py").exists():
|
|
7
|
+
raise ImportError(
|
|
8
|
+
"Protobuf modules not found. This package might be incorrectly installed."
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# 扁平化导出
|
|
12
|
+
from etcdrpc.rpc_pb2 import *
|
|
13
|
+
from etcdrpc.rpc_pb2_grpc import *
|
|
14
|
+
|
|
15
|
+
# 获取所有非私有成员
|
|
16
|
+
__all__ = [name for name in globals().keys() if not name.startswith('_')]
|
etcdrpc/auth.proto
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2016-2021 The jetcd authors
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
//
|
|
16
|
+
|
|
17
|
+
syntax = "proto3";
|
|
18
|
+
|
|
19
|
+
package authpb;
|
|
20
|
+
|
|
21
|
+
option java_multiple_files = true;
|
|
22
|
+
option java_package = "io.etcd.jetcd.api";
|
|
23
|
+
|
|
24
|
+
// User is a single entry in the bucket authUsers
|
|
25
|
+
message User {
|
|
26
|
+
bytes name = 1;
|
|
27
|
+
bytes password = 2;
|
|
28
|
+
repeated string roles = 3;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Permission is a single entity
|
|
32
|
+
message Permission {
|
|
33
|
+
enum Type {
|
|
34
|
+
READ = 0;
|
|
35
|
+
WRITE = 1;
|
|
36
|
+
READWRITE = 2;
|
|
37
|
+
}
|
|
38
|
+
Type permType = 1;
|
|
39
|
+
|
|
40
|
+
bytes key = 2;
|
|
41
|
+
bytes range_end = 3;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Role is a single entry in the bucket authRoles
|
|
45
|
+
message Role {
|
|
46
|
+
bytes name = 1;
|
|
47
|
+
|
|
48
|
+
repeated Permission keyPermission = 2;
|
|
49
|
+
}
|
etcdrpc/auth_pb2.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: etcdrpc/auth.proto
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
31,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'etcdrpc/auth.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x65tcdrpc/auth.proto\x12\x06\x61uthpb\"5\n\x04User\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x10\n\x08password\x18\x02 \x01(\x0c\x12\r\n\x05roles\x18\x03 \x03(\t\"\x83\x01\n\nPermission\x12)\n\x08permType\x18\x01 \x01(\x0e\x32\x17.authpb.Permission.Type\x12\x0b\n\x03key\x18\x02 \x01(\x0c\x12\x11\n\trange_end\x18\x03 \x01(\x0c\"*\n\x04Type\x12\x08\n\x04READ\x10\x00\x12\t\n\x05WRITE\x10\x01\x12\r\n\tREADWRITE\x10\x02\"?\n\x04Role\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12)\n\rkeyPermission\x18\x02 \x03(\x0b\x32\x12.authpb.PermissionB\x15\n\x11io.etcd.jetcd.apiP\x01\x62\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'etcdrpc.auth_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
_globals['DESCRIPTOR']._loaded_options = None
|
|
34
|
+
_globals['DESCRIPTOR']._serialized_options = b'\n\021io.etcd.jetcd.apiP\001'
|
|
35
|
+
_globals['_USER']._serialized_start=30
|
|
36
|
+
_globals['_USER']._serialized_end=83
|
|
37
|
+
_globals['_PERMISSION']._serialized_start=86
|
|
38
|
+
_globals['_PERMISSION']._serialized_end=217
|
|
39
|
+
_globals['_PERMISSION_TYPE']._serialized_start=175
|
|
40
|
+
_globals['_PERMISSION_TYPE']._serialized_end=217
|
|
41
|
+
_globals['_ROLE']._serialized_start=219
|
|
42
|
+
_globals['_ROLE']._serialized_end=282
|
|
43
|
+
# @@protoc_insertion_point(module_scope)
|
etcdrpc/auth_pb2.pyi
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from google.protobuf.internal import containers as _containers
|
|
2
|
+
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
|
3
|
+
from google.protobuf import descriptor as _descriptor
|
|
4
|
+
from google.protobuf import message as _message
|
|
5
|
+
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
|
|
6
|
+
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
|
7
|
+
|
|
8
|
+
DESCRIPTOR: _descriptor.FileDescriptor
|
|
9
|
+
|
|
10
|
+
class User(_message.Message):
|
|
11
|
+
__slots__ = ("name", "password", "roles")
|
|
12
|
+
NAME_FIELD_NUMBER: _ClassVar[int]
|
|
13
|
+
PASSWORD_FIELD_NUMBER: _ClassVar[int]
|
|
14
|
+
ROLES_FIELD_NUMBER: _ClassVar[int]
|
|
15
|
+
name: bytes
|
|
16
|
+
password: bytes
|
|
17
|
+
roles: _containers.RepeatedScalarFieldContainer[str]
|
|
18
|
+
def __init__(self, name: _Optional[bytes] = ..., password: _Optional[bytes] = ..., roles: _Optional[_Iterable[str]] = ...) -> None: ...
|
|
19
|
+
|
|
20
|
+
class Permission(_message.Message):
|
|
21
|
+
__slots__ = ("permType", "key", "range_end")
|
|
22
|
+
class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
23
|
+
__slots__ = ()
|
|
24
|
+
READ: _ClassVar[Permission.Type]
|
|
25
|
+
WRITE: _ClassVar[Permission.Type]
|
|
26
|
+
READWRITE: _ClassVar[Permission.Type]
|
|
27
|
+
READ: Permission.Type
|
|
28
|
+
WRITE: Permission.Type
|
|
29
|
+
READWRITE: Permission.Type
|
|
30
|
+
PERMTYPE_FIELD_NUMBER: _ClassVar[int]
|
|
31
|
+
KEY_FIELD_NUMBER: _ClassVar[int]
|
|
32
|
+
RANGE_END_FIELD_NUMBER: _ClassVar[int]
|
|
33
|
+
permType: Permission.Type
|
|
34
|
+
key: bytes
|
|
35
|
+
range_end: bytes
|
|
36
|
+
def __init__(self, permType: _Optional[_Union[Permission.Type, str]] = ..., key: _Optional[bytes] = ..., range_end: _Optional[bytes] = ...) -> None: ...
|
|
37
|
+
|
|
38
|
+
class Role(_message.Message):
|
|
39
|
+
__slots__ = ("name", "keyPermission")
|
|
40
|
+
NAME_FIELD_NUMBER: _ClassVar[int]
|
|
41
|
+
KEYPERMISSION_FIELD_NUMBER: _ClassVar[int]
|
|
42
|
+
name: bytes
|
|
43
|
+
keyPermission: _containers.RepeatedCompositeFieldContainer[Permission]
|
|
44
|
+
def __init__(self, name: _Optional[bytes] = ..., keyPermission: _Optional[_Iterable[_Union[Permission, _Mapping]]] = ...) -> None: ...
|
etcdrpc/auth_pb2_grpc.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
2
|
+
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
|
+
import grpc
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
GRPC_GENERATED_VERSION = '1.76.0'
|
|
8
|
+
GRPC_VERSION = grpc.__version__
|
|
9
|
+
_version_not_supported = False
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from grpc._utilities import first_version_is_lower
|
|
13
|
+
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
|
14
|
+
except ImportError:
|
|
15
|
+
_version_not_supported = True
|
|
16
|
+
|
|
17
|
+
if _version_not_supported:
|
|
18
|
+
raise RuntimeError(
|
|
19
|
+
f'The grpc package installed is at version {GRPC_VERSION},'
|
|
20
|
+
+ ' but the generated code in etcdrpc/auth_pb2_grpc.py depends on'
|
|
21
|
+
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
|
22
|
+
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
|
23
|
+
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
|
24
|
+
)
|
etcdrpc/kv.proto
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package mvccpb;
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
message KeyValue {
|
|
7
|
+
// key is the key in bytes. An empty key is not allowed.
|
|
8
|
+
bytes key = 1;
|
|
9
|
+
// create_revision is the revision of last creation on this key.
|
|
10
|
+
int64 create_revision = 2;
|
|
11
|
+
// mod_revision is the revision of last modification on this key.
|
|
12
|
+
int64 mod_revision = 3;
|
|
13
|
+
// version is the version of the key. A deletion resets
|
|
14
|
+
// the version to zero and any modification of the key
|
|
15
|
+
// increases its version.
|
|
16
|
+
int64 version = 4;
|
|
17
|
+
// value is the value held by the key, in bytes.
|
|
18
|
+
bytes value = 5;
|
|
19
|
+
// lease is the ID of the lease that attached to key.
|
|
20
|
+
// When the attached lease expires, the key will be deleted.
|
|
21
|
+
// If lease is 0, then no lease is attached to the key.
|
|
22
|
+
int64 lease = 6;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
message Event {
|
|
26
|
+
enum EventType {
|
|
27
|
+
PUT = 0;
|
|
28
|
+
DELETE = 1;
|
|
29
|
+
}
|
|
30
|
+
// type is the kind of event. If type is a PUT, it indicates
|
|
31
|
+
// new data has been stored to the key. If type is a DELETE,
|
|
32
|
+
// it indicates the key was deleted.
|
|
33
|
+
EventType type = 1;
|
|
34
|
+
// kv holds the KeyValue for the event.
|
|
35
|
+
// A PUT event contains current kv pair.
|
|
36
|
+
// A PUT event with kv.Version=1 indicates the creation of a key.
|
|
37
|
+
// A DELETE/EXPIRE event contains the deleted key with
|
|
38
|
+
// its modification revision set to the revision of deletion.
|
|
39
|
+
KeyValue kv = 2;
|
|
40
|
+
|
|
41
|
+
// prev_kv holds the key-value pair before the event happens.
|
|
42
|
+
KeyValue prev_kv = 3;
|
|
43
|
+
}
|
etcdrpc/kv_pb2.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: etcdrpc/kv.proto
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
31,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'etcdrpc/kv.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x65tcdrpc/kv.proto\x12\x06mvccpb\"u\n\x08KeyValue\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\x17\n\x0f\x63reate_revision\x18\x02 \x01(\x03\x12\x14\n\x0cmod_revision\x18\x03 \x01(\x03\x12\x0f\n\x07version\x18\x04 \x01(\x03\x12\r\n\x05value\x18\x05 \x01(\x0c\x12\r\n\x05lease\x18\x06 \x01(\x03\"\x91\x01\n\x05\x45vent\x12%\n\x04type\x18\x01 \x01(\x0e\x32\x17.mvccpb.Event.EventType\x12\x1c\n\x02kv\x18\x02 \x01(\x0b\x32\x10.mvccpb.KeyValue\x12!\n\x07prev_kv\x18\x03 \x01(\x0b\x32\x10.mvccpb.KeyValue\" \n\tEventType\x12\x07\n\x03PUT\x10\x00\x12\n\n\x06\x44\x45LETE\x10\x01\x62\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'etcdrpc.kv_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
DESCRIPTOR._loaded_options = None
|
|
34
|
+
_globals['_KEYVALUE']._serialized_start=28
|
|
35
|
+
_globals['_KEYVALUE']._serialized_end=145
|
|
36
|
+
_globals['_EVENT']._serialized_start=148
|
|
37
|
+
_globals['_EVENT']._serialized_end=293
|
|
38
|
+
_globals['_EVENT_EVENTTYPE']._serialized_start=261
|
|
39
|
+
_globals['_EVENT_EVENTTYPE']._serialized_end=293
|
|
40
|
+
# @@protoc_insertion_point(module_scope)
|