sensu-plugins-mongodb-mrtrotl 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +22 -0
  4. data/README.md +27 -0
  5. data/bin/check-mongodb-metric.rb +144 -0
  6. data/bin/check-mongodb-query-count.rb +267 -0
  7. data/bin/check-mongodb.py +1644 -0
  8. data/bin/check-mongodb.rb +5 -0
  9. data/bin/metrics-mongodb-replication.rb +254 -0
  10. data/bin/metrics-mongodb.rb +133 -0
  11. data/lib/bson/__init__.py +1347 -0
  12. data/lib/bson/__pycache__/__init__.cpython-310.pyc +0 -0
  13. data/lib/bson/__pycache__/_helpers.cpython-310.pyc +0 -0
  14. data/lib/bson/__pycache__/binary.cpython-310.pyc +0 -0
  15. data/lib/bson/__pycache__/code.cpython-310.pyc +0 -0
  16. data/lib/bson/__pycache__/codec_options.cpython-310.pyc +0 -0
  17. data/lib/bson/__pycache__/dbref.cpython-310.pyc +0 -0
  18. data/lib/bson/__pycache__/decimal128.cpython-310.pyc +0 -0
  19. data/lib/bson/__pycache__/errors.cpython-310.pyc +0 -0
  20. data/lib/bson/__pycache__/int64.cpython-310.pyc +0 -0
  21. data/lib/bson/__pycache__/json_util.cpython-310.pyc +0 -0
  22. data/lib/bson/__pycache__/max_key.cpython-310.pyc +0 -0
  23. data/lib/bson/__pycache__/min_key.cpython-310.pyc +0 -0
  24. data/lib/bson/__pycache__/objectid.cpython-310.pyc +0 -0
  25. data/lib/bson/__pycache__/raw_bson.cpython-310.pyc +0 -0
  26. data/lib/bson/__pycache__/regex.cpython-310.pyc +0 -0
  27. data/lib/bson/__pycache__/son.cpython-310.pyc +0 -0
  28. data/lib/bson/__pycache__/timestamp.cpython-310.pyc +0 -0
  29. data/lib/bson/__pycache__/tz_util.cpython-310.pyc +0 -0
  30. data/lib/bson/_cbson.cpython-310-x86_64-linux-gnu.so +0 -0
  31. data/lib/bson/_helpers.py +41 -0
  32. data/lib/bson/binary.py +364 -0
  33. data/lib/bson/code.py +101 -0
  34. data/lib/bson/codec_options.py +414 -0
  35. data/lib/bson/codec_options.pyi +100 -0
  36. data/lib/bson/dbref.py +133 -0
  37. data/lib/bson/decimal128.py +314 -0
  38. data/lib/bson/errors.py +35 -0
  39. data/lib/bson/int64.py +39 -0
  40. data/lib/bson/json_util.py +874 -0
  41. data/lib/bson/max_key.py +55 -0
  42. data/lib/bson/min_key.py +55 -0
  43. data/lib/bson/objectid.py +286 -0
  44. data/lib/bson/py.typed +2 -0
  45. data/lib/bson/raw_bson.py +175 -0
  46. data/lib/bson/regex.py +135 -0
  47. data/lib/bson/son.py +208 -0
  48. data/lib/bson/timestamp.py +124 -0
  49. data/lib/bson/tz_util.py +52 -0
  50. data/lib/gridfs/__init__.py +1015 -0
  51. data/lib/gridfs/__pycache__/__init__.cpython-310.pyc +0 -0
  52. data/lib/gridfs/__pycache__/errors.cpython-310.pyc +0 -0
  53. data/lib/gridfs/__pycache__/grid_file.cpython-310.pyc +0 -0
  54. data/lib/gridfs/errors.py +33 -0
  55. data/lib/gridfs/grid_file.py +907 -0
  56. data/lib/gridfs/py.typed +2 -0
  57. data/lib/pymongo/__init__.py +185 -0
  58. data/lib/pymongo/__pycache__/__init__.cpython-310.pyc +0 -0
  59. data/lib/pymongo/__pycache__/_csot.cpython-310.pyc +0 -0
  60. data/lib/pymongo/__pycache__/aggregation.cpython-310.pyc +0 -0
  61. data/lib/pymongo/__pycache__/auth.cpython-310.pyc +0 -0
  62. data/lib/pymongo/__pycache__/auth_aws.cpython-310.pyc +0 -0
  63. data/lib/pymongo/__pycache__/bulk.cpython-310.pyc +0 -0
  64. data/lib/pymongo/__pycache__/change_stream.cpython-310.pyc +0 -0
  65. data/lib/pymongo/__pycache__/client_options.cpython-310.pyc +0 -0
  66. data/lib/pymongo/__pycache__/client_session.cpython-310.pyc +0 -0
  67. data/lib/pymongo/__pycache__/collation.cpython-310.pyc +0 -0
  68. data/lib/pymongo/__pycache__/collection.cpython-310.pyc +0 -0
  69. data/lib/pymongo/__pycache__/command_cursor.cpython-310.pyc +0 -0
  70. data/lib/pymongo/__pycache__/common.cpython-310.pyc +0 -0
  71. data/lib/pymongo/__pycache__/compression_support.cpython-310.pyc +0 -0
  72. data/lib/pymongo/__pycache__/cursor.cpython-310.pyc +0 -0
  73. data/lib/pymongo/__pycache__/daemon.cpython-310.pyc +0 -0
  74. data/lib/pymongo/__pycache__/database.cpython-310.pyc +0 -0
  75. data/lib/pymongo/__pycache__/driver_info.cpython-310.pyc +0 -0
  76. data/lib/pymongo/__pycache__/encryption.cpython-310.pyc +0 -0
  77. data/lib/pymongo/__pycache__/encryption_options.cpython-310.pyc +0 -0
  78. data/lib/pymongo/__pycache__/errors.cpython-310.pyc +0 -0
  79. data/lib/pymongo/__pycache__/event_loggers.cpython-310.pyc +0 -0
  80. data/lib/pymongo/__pycache__/hello.cpython-310.pyc +0 -0
  81. data/lib/pymongo/__pycache__/helpers.cpython-310.pyc +0 -0
  82. data/lib/pymongo/__pycache__/max_staleness_selectors.cpython-310.pyc +0 -0
  83. data/lib/pymongo/__pycache__/message.cpython-310.pyc +0 -0
  84. data/lib/pymongo/__pycache__/mongo_client.cpython-310.pyc +0 -0
  85. data/lib/pymongo/__pycache__/monitor.cpython-310.pyc +0 -0
  86. data/lib/pymongo/__pycache__/monitoring.cpython-310.pyc +0 -0
  87. data/lib/pymongo/__pycache__/network.cpython-310.pyc +0 -0
  88. data/lib/pymongo/__pycache__/ocsp_cache.cpython-310.pyc +0 -0
  89. data/lib/pymongo/__pycache__/ocsp_support.cpython-310.pyc +0 -0
  90. data/lib/pymongo/__pycache__/operations.cpython-310.pyc +0 -0
  91. data/lib/pymongo/__pycache__/periodic_executor.cpython-310.pyc +0 -0
  92. data/lib/pymongo/__pycache__/pool.cpython-310.pyc +0 -0
  93. data/lib/pymongo/__pycache__/pyopenssl_context.cpython-310.pyc +0 -0
  94. data/lib/pymongo/__pycache__/read_concern.cpython-310.pyc +0 -0
  95. data/lib/pymongo/__pycache__/read_preferences.cpython-310.pyc +0 -0
  96. data/lib/pymongo/__pycache__/response.cpython-310.pyc +0 -0
  97. data/lib/pymongo/__pycache__/results.cpython-310.pyc +0 -0
  98. data/lib/pymongo/__pycache__/saslprep.cpython-310.pyc +0 -0
  99. data/lib/pymongo/__pycache__/server.cpython-310.pyc +0 -0
  100. data/lib/pymongo/__pycache__/server_api.cpython-310.pyc +0 -0
  101. data/lib/pymongo/__pycache__/server_description.cpython-310.pyc +0 -0
  102. data/lib/pymongo/__pycache__/server_selectors.cpython-310.pyc +0 -0
  103. data/lib/pymongo/__pycache__/server_type.cpython-310.pyc +0 -0
  104. data/lib/pymongo/__pycache__/settings.cpython-310.pyc +0 -0
  105. data/lib/pymongo/__pycache__/socket_checker.cpython-310.pyc +0 -0
  106. data/lib/pymongo/__pycache__/srv_resolver.cpython-310.pyc +0 -0
  107. data/lib/pymongo/__pycache__/ssl_context.cpython-310.pyc +0 -0
  108. data/lib/pymongo/__pycache__/ssl_support.cpython-310.pyc +0 -0
  109. data/lib/pymongo/__pycache__/topology.cpython-310.pyc +0 -0
  110. data/lib/pymongo/__pycache__/topology_description.cpython-310.pyc +0 -0
  111. data/lib/pymongo/__pycache__/typings.cpython-310.pyc +0 -0
  112. data/lib/pymongo/__pycache__/uri_parser.cpython-310.pyc +0 -0
  113. data/lib/pymongo/__pycache__/write_concern.cpython-310.pyc +0 -0
  114. data/lib/pymongo/_cmessage.cpython-310-x86_64-linux-gnu.so +0 -0
  115. data/lib/pymongo/_csot.py +118 -0
  116. data/lib/pymongo/aggregation.py +229 -0
  117. data/lib/pymongo/auth.py +549 -0
  118. data/lib/pymongo/auth_aws.py +94 -0
  119. data/lib/pymongo/bulk.py +513 -0
  120. data/lib/pymongo/change_stream.py +457 -0
  121. data/lib/pymongo/client_options.py +302 -0
  122. data/lib/pymongo/client_session.py +1112 -0
  123. data/lib/pymongo/collation.py +224 -0
  124. data/lib/pymongo/collection.py +3204 -0
  125. data/lib/pymongo/command_cursor.py +353 -0
  126. data/lib/pymongo/common.py +984 -0
  127. data/lib/pymongo/compression_support.py +149 -0
  128. data/lib/pymongo/cursor.py +1345 -0
  129. data/lib/pymongo/daemon.py +141 -0
  130. data/lib/pymongo/database.py +1202 -0
  131. data/lib/pymongo/driver_info.py +42 -0
  132. data/lib/pymongo/encryption.py +884 -0
  133. data/lib/pymongo/encryption_options.py +221 -0
  134. data/lib/pymongo/errors.py +365 -0
  135. data/lib/pymongo/event_loggers.py +221 -0
  136. data/lib/pymongo/hello.py +219 -0
  137. data/lib/pymongo/helpers.py +259 -0
  138. data/lib/pymongo/max_staleness_selectors.py +114 -0
  139. data/lib/pymongo/message.py +1440 -0
  140. data/lib/pymongo/mongo_client.py +2144 -0
  141. data/lib/pymongo/monitor.py +440 -0
  142. data/lib/pymongo/monitoring.py +1801 -0
  143. data/lib/pymongo/network.py +311 -0
  144. data/lib/pymongo/ocsp_cache.py +87 -0
  145. data/lib/pymongo/ocsp_support.py +372 -0
  146. data/lib/pymongo/operations.py +507 -0
  147. data/lib/pymongo/periodic_executor.py +183 -0
  148. data/lib/pymongo/pool.py +1660 -0
  149. data/lib/pymongo/py.typed +2 -0
  150. data/lib/pymongo/pyopenssl_context.py +383 -0
  151. data/lib/pymongo/read_concern.py +75 -0
  152. data/lib/pymongo/read_preferences.py +609 -0
  153. data/lib/pymongo/response.py +109 -0
  154. data/lib/pymongo/results.py +217 -0
  155. data/lib/pymongo/saslprep.py +113 -0
  156. data/lib/pymongo/server.py +247 -0
  157. data/lib/pymongo/server_api.py +170 -0
  158. data/lib/pymongo/server_description.py +285 -0
  159. data/lib/pymongo/server_selectors.py +153 -0
  160. data/lib/pymongo/server_type.py +32 -0
  161. data/lib/pymongo/settings.py +159 -0
  162. data/lib/pymongo/socket_checker.py +104 -0
  163. data/lib/pymongo/srv_resolver.py +126 -0
  164. data/lib/pymongo/ssl_context.py +39 -0
  165. data/lib/pymongo/ssl_support.py +99 -0
  166. data/lib/pymongo/topology.py +890 -0
  167. data/lib/pymongo/topology_description.py +639 -0
  168. data/lib/pymongo/typings.py +39 -0
  169. data/lib/pymongo/uri_parser.py +624 -0
  170. data/lib/pymongo/write_concern.py +129 -0
  171. data/lib/pymongo-4.2.0.dist-info/INSTALLER +1 -0
  172. data/lib/pymongo-4.2.0.dist-info/LICENSE +201 -0
  173. data/lib/pymongo-4.2.0.dist-info/METADATA +250 -0
  174. data/lib/pymongo-4.2.0.dist-info/RECORD +167 -0
  175. data/lib/pymongo-4.2.0.dist-info/REQUESTED +0 -0
  176. data/lib/pymongo-4.2.0.dist-info/WHEEL +6 -0
  177. data/lib/pymongo-4.2.0.dist-info/top_level.txt +3 -0
  178. data/lib/sensu-plugins-mongodb/metrics.rb +391 -0
  179. data/lib/sensu-plugins-mongodb/version.rb +9 -0
  180. data/lib/sensu-plugins-mongodb.rb +1 -0
  181. metadata +407 -0
@@ -0,0 +1,884 @@
1
+ # Copyright 2019-present MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Support for explicit client-side field level encryption."""
16
+
17
+ import contextlib
18
+ import enum
19
+ import socket
20
+ import weakref
21
+ from typing import Any, Mapping, Optional, Sequence
22
+
23
+ try:
24
+ from pymongocrypt.auto_encrypter import AutoEncrypter
25
+ from pymongocrypt.errors import MongoCryptError # noqa: F401
26
+ from pymongocrypt.explicit_encrypter import ExplicitEncrypter
27
+ from pymongocrypt.mongocrypt import MongoCryptOptions
28
+ from pymongocrypt.state_machine import MongoCryptCallback
29
+
30
+ _HAVE_PYMONGOCRYPT = True
31
+ except ImportError:
32
+ _HAVE_PYMONGOCRYPT = False
33
+ MongoCryptCallback = object
34
+
35
+ from bson import _dict_to_bson, decode, encode
36
+ from bson.binary import STANDARD, UUID_SUBTYPE, Binary
37
+ from bson.codec_options import CodecOptions
38
+ from bson.errors import BSONError
39
+ from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument, _inflate_bson
40
+ from bson.son import SON
41
+ from pymongo import _csot
42
+ from pymongo.cursor import Cursor
43
+ from pymongo.daemon import _spawn_daemon
44
+ from pymongo.encryption_options import AutoEncryptionOpts
45
+ from pymongo.errors import (
46
+ ConfigurationError,
47
+ EncryptionError,
48
+ InvalidOperation,
49
+ ServerSelectionTimeoutError,
50
+ )
51
+ from pymongo.mongo_client import MongoClient
52
+ from pymongo.network import BLOCKING_IO_ERRORS
53
+ from pymongo.operations import UpdateOne
54
+ from pymongo.pool import PoolOptions, _configured_socket
55
+ from pymongo.read_concern import ReadConcern
56
+ from pymongo.results import BulkWriteResult, DeleteResult
57
+ from pymongo.ssl_support import get_ssl_context
58
+ from pymongo.uri_parser import parse_host
59
+ from pymongo.write_concern import WriteConcern
60
+
61
+ _HTTPS_PORT = 443
62
+ _KMS_CONNECT_TIMEOUT = 10 # TODO: CDRIVER-3262 will define this value.
63
+ _MONGOCRYPTD_TIMEOUT_MS = 10000
64
+
65
+
66
+ _DATA_KEY_OPTS: CodecOptions = CodecOptions(document_class=SON, uuid_representation=STANDARD)
67
+ # Use RawBSONDocument codec options to avoid needlessly decoding
68
+ # documents from the key vault.
69
+ _KEY_VAULT_OPTS = CodecOptions(document_class=RawBSONDocument)
70
+
71
+
72
+ @contextlib.contextmanager
73
+ def _wrap_encryption_errors():
74
+ """Context manager to wrap encryption related errors."""
75
+ try:
76
+ yield
77
+ except BSONError:
78
+ # BSON encoding/decoding errors are unrelated to encryption so
79
+ # we should propagate them unchanged.
80
+ raise
81
+ except Exception as exc:
82
+ raise EncryptionError(exc)
83
+
84
+
85
+ class _EncryptionIO(MongoCryptCallback): # type: ignore
86
+ def __init__(self, client, key_vault_coll, mongocryptd_client, opts):
87
+ """Internal class to perform I/O on behalf of pymongocrypt."""
88
+ self.client_ref: Any
89
+ # Use a weak ref to break reference cycle.
90
+ if client is not None:
91
+ self.client_ref = weakref.ref(client)
92
+ else:
93
+ self.client_ref = None
94
+ self.key_vault_coll = key_vault_coll.with_options(
95
+ codec_options=_KEY_VAULT_OPTS,
96
+ read_concern=ReadConcern(level="majority"),
97
+ write_concern=WriteConcern(w="majority"),
98
+ )
99
+ self.mongocryptd_client = mongocryptd_client
100
+ self.opts = opts
101
+ self._spawned = False
102
+
103
+ def kms_request(self, kms_context):
104
+ """Complete a KMS request.
105
+
106
+ :Parameters:
107
+ - `kms_context`: A :class:`MongoCryptKmsContext`.
108
+
109
+ :Returns:
110
+ None
111
+ """
112
+ endpoint = kms_context.endpoint
113
+ message = kms_context.message
114
+ provider = kms_context.kms_provider
115
+ ctx = self.opts._kms_ssl_contexts.get(provider)
116
+ if ctx is None:
117
+ # Enable strict certificate verification, OCSP, match hostname, and
118
+ # SNI using the system default CA certificates.
119
+ ctx = get_ssl_context(
120
+ None, # certfile
121
+ None, # passphrase
122
+ None, # ca_certs
123
+ None, # crlfile
124
+ False, # allow_invalid_certificates
125
+ False, # allow_invalid_hostnames
126
+ False,
127
+ ) # disable_ocsp_endpoint_check
128
+ # CSOT: set timeout for socket creation.
129
+ connect_timeout = max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0.001)
130
+ opts = PoolOptions(
131
+ connect_timeout=connect_timeout,
132
+ socket_timeout=connect_timeout,
133
+ ssl_context=ctx,
134
+ )
135
+ host, port = parse_host(endpoint, _HTTPS_PORT)
136
+ conn = _configured_socket((host, port), opts)
137
+ try:
138
+ conn.sendall(message)
139
+ while kms_context.bytes_needed > 0:
140
+ # CSOT: update timeout.
141
+ conn.settimeout(max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0))
142
+ data = conn.recv(kms_context.bytes_needed)
143
+ if not data:
144
+ raise OSError("KMS connection closed")
145
+ kms_context.feed(data)
146
+ except BLOCKING_IO_ERRORS:
147
+ raise socket.timeout("timed out")
148
+ finally:
149
+ conn.close()
150
+
151
+ def collection_info(self, database, filter):
152
+ """Get the collection info for a namespace.
153
+
154
+ The returned collection info is passed to libmongocrypt which reads
155
+ the JSON schema.
156
+
157
+ :Parameters:
158
+ - `database`: The database on which to run listCollections.
159
+ - `filter`: The filter to pass to listCollections.
160
+
161
+ :Returns:
162
+ The first document from the listCollections command response as BSON.
163
+ """
164
+ with self.client_ref()[database].list_collections(filter=RawBSONDocument(filter)) as cursor:
165
+ for doc in cursor:
166
+ return _dict_to_bson(doc, False, _DATA_KEY_OPTS)
167
+
168
+ def spawn(self):
169
+ """Spawn mongocryptd.
170
+
171
+ Note this method is thread safe; at most one mongocryptd will start
172
+ successfully.
173
+ """
174
+ self._spawned = True
175
+ args = [self.opts._mongocryptd_spawn_path or "mongocryptd"]
176
+ args.extend(self.opts._mongocryptd_spawn_args)
177
+ _spawn_daemon(args)
178
+
179
+ def mark_command(self, database, cmd):
180
+ """Mark a command for encryption.
181
+
182
+ :Parameters:
183
+ - `database`: The database on which to run this command.
184
+ - `cmd`: The BSON command to run.
185
+
186
+ :Returns:
187
+ The marked command response from mongocryptd.
188
+ """
189
+ if not self._spawned and not self.opts._mongocryptd_bypass_spawn:
190
+ self.spawn()
191
+ # Database.command only supports mutable mappings so we need to decode
192
+ # the raw BSON command first.
193
+ inflated_cmd = _inflate_bson(cmd, DEFAULT_RAW_BSON_OPTIONS)
194
+ try:
195
+ res = self.mongocryptd_client[database].command(
196
+ inflated_cmd, codec_options=DEFAULT_RAW_BSON_OPTIONS
197
+ )
198
+ except ServerSelectionTimeoutError:
199
+ if self.opts._mongocryptd_bypass_spawn:
200
+ raise
201
+ self.spawn()
202
+ res = self.mongocryptd_client[database].command(
203
+ inflated_cmd, codec_options=DEFAULT_RAW_BSON_OPTIONS
204
+ )
205
+ return res.raw
206
+
207
+ def fetch_keys(self, filter):
208
+ """Yields one or more keys from the key vault.
209
+
210
+ :Parameters:
211
+ - `filter`: The filter to pass to find.
212
+
213
+ :Returns:
214
+ A generator which yields the requested keys from the key vault.
215
+ """
216
+ with self.key_vault_coll.find(RawBSONDocument(filter)) as cursor:
217
+ for key in cursor:
218
+ yield key.raw
219
+
220
+ def insert_data_key(self, data_key):
221
+ """Insert a data key into the key vault.
222
+
223
+ :Parameters:
224
+ - `data_key`: The data key document to insert.
225
+
226
+ :Returns:
227
+ The _id of the inserted data key document.
228
+ """
229
+ raw_doc = RawBSONDocument(data_key, _KEY_VAULT_OPTS)
230
+ data_key_id = raw_doc.get("_id")
231
+ if not isinstance(data_key_id, Binary) or data_key_id.subtype != UUID_SUBTYPE:
232
+ raise TypeError("data_key _id must be Binary with a UUID subtype")
233
+
234
+ self.key_vault_coll.insert_one(raw_doc)
235
+ return data_key_id
236
+
237
+ def bson_encode(self, doc):
238
+ """Encode a document to BSON.
239
+
240
+ A document can be any mapping type (like :class:`dict`).
241
+
242
+ :Parameters:
243
+ - `doc`: mapping type representing a document
244
+
245
+ :Returns:
246
+ The encoded BSON bytes.
247
+ """
248
+ return encode(doc)
249
+
250
+ def close(self):
251
+ """Release resources.
252
+
253
+ Note it is not safe to call this method from __del__ or any GC hooks.
254
+ """
255
+ self.client_ref = None
256
+ self.key_vault_coll = None
257
+ if self.mongocryptd_client:
258
+ self.mongocryptd_client.close()
259
+ self.mongocryptd_client = None
260
+
261
+
262
+ class RewrapManyDataKeyResult(object):
263
+ """Result object returned by a :meth:`~ClientEncryption.rewrap_many_data_key` operation.
264
+
265
+ .. versionadded:: 4.2
266
+ """
267
+
268
+ def __init__(self, bulk_write_result: Optional[BulkWriteResult] = None) -> None:
269
+ self._bulk_write_result = bulk_write_result
270
+
271
+ @property
272
+ def bulk_write_result(self) -> Optional[BulkWriteResult]:
273
+ """The result of the bulk write operation used to update the key vault
274
+ collection with one or more rewrapped data keys. If
275
+ :meth:`~ClientEncryption.rewrap_many_data_key` does not find any matching keys to rewrap,
276
+ no bulk write operation will be executed and this field will be
277
+ ``None``.
278
+ """
279
+ return self._bulk_write_result
280
+
281
+
282
+ class _Encrypter(object):
283
+ """Encrypts and decrypts MongoDB commands.
284
+
285
+ This class is used to support automatic encryption and decryption of
286
+ MongoDB commands."""
287
+
288
+ def __init__(self, client, opts):
289
+ """Create a _Encrypter for a client.
290
+
291
+ :Parameters:
292
+ - `client`: The encrypted MongoClient.
293
+ - `opts`: The encrypted client's :class:`AutoEncryptionOpts`.
294
+ """
295
+ if opts._schema_map is None:
296
+ schema_map = None
297
+ else:
298
+ schema_map = _dict_to_bson(opts._schema_map, False, _DATA_KEY_OPTS)
299
+
300
+ if opts._encrypted_fields_map is None:
301
+ encrypted_fields_map = None
302
+ else:
303
+ encrypted_fields_map = _dict_to_bson(opts._encrypted_fields_map, False, _DATA_KEY_OPTS)
304
+ self._bypass_auto_encryption = opts._bypass_auto_encryption
305
+ self._internal_client = None
306
+
307
+ def _get_internal_client(encrypter, mongo_client):
308
+ if mongo_client.options.pool_options.max_pool_size is None:
309
+ # Unlimited pool size, use the same client.
310
+ return mongo_client
311
+ # Else - limited pool size, use an internal client.
312
+ if encrypter._internal_client is not None:
313
+ return encrypter._internal_client
314
+ internal_client = mongo_client._duplicate(minPoolSize=0, auto_encryption_opts=None)
315
+ encrypter._internal_client = internal_client
316
+ return internal_client
317
+
318
+ if opts._key_vault_client is not None:
319
+ key_vault_client = opts._key_vault_client
320
+ else:
321
+ key_vault_client = _get_internal_client(self, client)
322
+
323
+ if opts._bypass_auto_encryption:
324
+ metadata_client = None
325
+ else:
326
+ metadata_client = _get_internal_client(self, client)
327
+
328
+ db, coll = opts._key_vault_namespace.split(".", 1)
329
+ key_vault_coll = key_vault_client[db][coll]
330
+
331
+ mongocryptd_client: MongoClient = MongoClient(
332
+ opts._mongocryptd_uri, connect=False, serverSelectionTimeoutMS=_MONGOCRYPTD_TIMEOUT_MS
333
+ )
334
+
335
+ io_callbacks = _EncryptionIO(metadata_client, key_vault_coll, mongocryptd_client, opts)
336
+ self._auto_encrypter = AutoEncrypter(
337
+ io_callbacks,
338
+ MongoCryptOptions(
339
+ opts._kms_providers,
340
+ schema_map,
341
+ crypt_shared_lib_path=opts._crypt_shared_lib_path,
342
+ crypt_shared_lib_required=opts._crypt_shared_lib_required,
343
+ bypass_encryption=opts._bypass_auto_encryption,
344
+ encrypted_fields_map=encrypted_fields_map,
345
+ bypass_query_analysis=opts._bypass_query_analysis,
346
+ ),
347
+ )
348
+ self._closed = False
349
+
350
+ def encrypt(self, database, cmd, codec_options):
351
+ """Encrypt a MongoDB command.
352
+
353
+ :Parameters:
354
+ - `database`: The database for this command.
355
+ - `cmd`: A command document.
356
+ - `codec_options`: The CodecOptions to use while encoding `cmd`.
357
+
358
+ :Returns:
359
+ The encrypted command to execute.
360
+ """
361
+ self._check_closed()
362
+ encoded_cmd = _dict_to_bson(cmd, False, codec_options)
363
+ with _wrap_encryption_errors():
364
+ encrypted_cmd = self._auto_encrypter.encrypt(database, encoded_cmd)
365
+ # TODO: PYTHON-1922 avoid decoding the encrypted_cmd.
366
+ encrypt_cmd = _inflate_bson(encrypted_cmd, DEFAULT_RAW_BSON_OPTIONS)
367
+ return encrypt_cmd
368
+
369
+ def decrypt(self, response):
370
+ """Decrypt a MongoDB command response.
371
+
372
+ :Parameters:
373
+ - `response`: A MongoDB command response as BSON.
374
+
375
+ :Returns:
376
+ The decrypted command response.
377
+ """
378
+ self._check_closed()
379
+ with _wrap_encryption_errors():
380
+ return self._auto_encrypter.decrypt(response)
381
+
382
+ def _check_closed(self):
383
+ if self._closed:
384
+ raise InvalidOperation("Cannot use MongoClient after close")
385
+
386
+ def close(self):
387
+ """Cleanup resources."""
388
+ self._closed = True
389
+ self._auto_encrypter.close()
390
+ if self._internal_client:
391
+ self._internal_client.close()
392
+ self._internal_client = None
393
+
394
+
395
+ class Algorithm(str, enum.Enum):
396
+ """An enum that defines the supported encryption algorithms."""
397
+
398
+ AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
399
+ """AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic."""
400
+ AEAD_AES_256_CBC_HMAC_SHA_512_Random = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
401
+ """AEAD_AES_256_CBC_HMAC_SHA_512_Random."""
402
+ INDEXED = "Indexed"
403
+ """Indexed.
404
+
405
+ .. note:: Support for Queryable Encryption is in beta.
406
+ Backwards-breaking changes may be made before the final release.
407
+
408
+ .. versionadded:: 4.2
409
+ """
410
+ UNINDEXED = "Unindexed"
411
+ """Unindexed.
412
+
413
+ .. note:: Support for Queryable Encryption is in beta.
414
+ Backwards-breaking changes may be made before the final release.
415
+
416
+ .. versionadded:: 4.2
417
+ """
418
+
419
+
420
+ class QueryType(str, enum.Enum):
421
+ """**(BETA)** An enum that defines the supported values for explicit encryption query_type.
422
+
423
+ .. note:: Support for Queryable Encryption is in beta.
424
+ Backwards-breaking changes may be made before the final release.
425
+
426
+ .. versionadded:: 4.2
427
+ """
428
+
429
+ EQUALITY = "equality"
430
+ """Used to encrypt a value for an equality query."""
431
+
432
+
433
+ class ClientEncryption(object):
434
+ """Explicit client-side field level encryption."""
435
+
436
+ def __init__(
437
+ self,
438
+ kms_providers: Mapping[str, Any],
439
+ key_vault_namespace: str,
440
+ key_vault_client: MongoClient,
441
+ codec_options: CodecOptions,
442
+ kms_tls_options: Optional[Mapping[str, Any]] = None,
443
+ ) -> None:
444
+ """Explicit client-side field level encryption.
445
+
446
+ The ClientEncryption class encapsulates explicit operations on a key
447
+ vault collection that cannot be done directly on a MongoClient. Similar
448
+ to configuring auto encryption on a MongoClient, it is constructed with
449
+ a MongoClient (to a MongoDB cluster containing the key vault
450
+ collection), KMS provider configuration, and keyVaultNamespace. It
451
+ provides an API for explicitly encrypting and decrypting values, and
452
+ creating data keys. It does not provide an API to query keys from the
453
+ key vault collection, as this can be done directly on the MongoClient.
454
+
455
+ See :ref:`explicit-client-side-encryption` for an example.
456
+
457
+ :Parameters:
458
+ - `kms_providers`: Map of KMS provider options. The `kms_providers`
459
+ map values differ by provider:
460
+
461
+ - `aws`: Map with "accessKeyId" and "secretAccessKey" as strings.
462
+ These are the AWS access key ID and AWS secret access key used
463
+ to generate KMS messages. An optional "sessionToken" may be
464
+ included to support temporary AWS credentials.
465
+ - `azure`: Map with "tenantId", "clientId", and "clientSecret" as
466
+ strings. Additionally, "identityPlatformEndpoint" may also be
467
+ specified as a string (defaults to 'login.microsoftonline.com').
468
+ These are the Azure Active Directory credentials used to
469
+ generate Azure Key Vault messages.
470
+ - `gcp`: Map with "email" as a string and "privateKey"
471
+ as `bytes` or a base64 encoded string.
472
+ Additionally, "endpoint" may also be specified as a string
473
+ (defaults to 'oauth2.googleapis.com'). These are the
474
+ credentials used to generate Google Cloud KMS messages.
475
+ - `kmip`: Map with "endpoint" as a host with required port.
476
+ For example: ``{"endpoint": "example.com:443"}``.
477
+ - `local`: Map with "key" as `bytes` (96 bytes in length) or
478
+ a base64 encoded string which decodes
479
+ to 96 bytes. "key" is the master key used to encrypt/decrypt
480
+ data keys. This key should be generated and stored as securely
481
+ as possible.
482
+
483
+ - `key_vault_namespace`: The namespace for the key vault collection.
484
+ The key vault collection contains all data keys used for encryption
485
+ and decryption. Data keys are stored as documents in this MongoDB
486
+ collection. Data keys are protected with encryption by a KMS
487
+ provider.
488
+ - `key_vault_client`: A MongoClient connected to a MongoDB cluster
489
+ containing the `key_vault_namespace` collection.
490
+ - `codec_options`: An instance of
491
+ :class:`~bson.codec_options.CodecOptions` to use when encoding a
492
+ value for encryption and decoding the decrypted BSON value. This
493
+ should be the same CodecOptions instance configured on the
494
+ MongoClient, Database, or Collection used to access application
495
+ data.
496
+ - `kms_tls_options` (optional): A map of KMS provider names to TLS
497
+ options to use when creating secure connections to KMS providers.
498
+ Accepts the same TLS options as
499
+ :class:`pymongo.mongo_client.MongoClient`. For example, to
500
+ override the system default CA file::
501
+
502
+ kms_tls_options={'kmip': {'tlsCAFile': certifi.where()}}
503
+
504
+ Or to supply a client certificate::
505
+
506
+ kms_tls_options={'kmip': {'tlsCertificateKeyFile': 'client.pem'}}
507
+
508
+ .. versionchanged:: 4.0
509
+ Added the `kms_tls_options` parameter and the "kmip" KMS provider.
510
+
511
+ .. versionadded:: 3.9
512
+ """
513
+ if not _HAVE_PYMONGOCRYPT:
514
+ raise ConfigurationError(
515
+ "client-side field level encryption requires the pymongocrypt "
516
+ "library: install a compatible version with: "
517
+ "python -m pip install 'pymongo[encryption]'"
518
+ )
519
+
520
+ if not isinstance(codec_options, CodecOptions):
521
+ raise TypeError("codec_options must be an instance of bson.codec_options.CodecOptions")
522
+
523
+ self._kms_providers = kms_providers
524
+ self._key_vault_namespace = key_vault_namespace
525
+ self._key_vault_client = key_vault_client
526
+ self._codec_options = codec_options
527
+
528
+ db, coll = key_vault_namespace.split(".", 1)
529
+ key_vault_coll = key_vault_client[db][coll]
530
+
531
+ opts = AutoEncryptionOpts(
532
+ kms_providers, key_vault_namespace, kms_tls_options=kms_tls_options
533
+ )
534
+ self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO(
535
+ None, key_vault_coll, None, opts
536
+ )
537
+ self._encryption = ExplicitEncrypter(
538
+ self._io_callbacks, MongoCryptOptions(kms_providers, None)
539
+ )
540
+ # Use the same key vault collection as the callback.
541
+ self._key_vault_coll = self._io_callbacks.key_vault_coll
542
+
543
+ def create_data_key(
544
+ self,
545
+ kms_provider: str,
546
+ master_key: Optional[Mapping[str, Any]] = None,
547
+ key_alt_names: Optional[Sequence[str]] = None,
548
+ key_material: Optional[bytes] = None,
549
+ ) -> Binary:
550
+ """Create and insert a new data key into the key vault collection.
551
+
552
+ :Parameters:
553
+ - `kms_provider`: The KMS provider to use. Supported values are
554
+ "aws", "azure", "gcp", "kmip", and "local".
555
+ - `master_key`: Identifies a KMS-specific key used to encrypt the
556
+ new data key. If the kmsProvider is "local" the `master_key` is
557
+ not applicable and may be omitted.
558
+
559
+ If the `kms_provider` is "aws" it is required and has the
560
+ following fields::
561
+
562
+ - `region` (string): Required. The AWS region, e.g. "us-east-1".
563
+ - `key` (string): Required. The Amazon Resource Name (ARN) to
564
+ the AWS customer.
565
+ - `endpoint` (string): Optional. An alternate host to send KMS
566
+ requests to. May include port number, e.g.
567
+ "kms.us-east-1.amazonaws.com:443".
568
+
569
+ If the `kms_provider` is "azure" it is required and has the
570
+ following fields::
571
+
572
+ - `keyVaultEndpoint` (string): Required. Host with optional
573
+ port, e.g. "example.vault.azure.net".
574
+ - `keyName` (string): Required. Key name in the key vault.
575
+ - `keyVersion` (string): Optional. Version of the key to use.
576
+
577
+ If the `kms_provider` is "gcp" it is required and has the
578
+ following fields::
579
+
580
+ - `projectId` (string): Required. The Google cloud project ID.
581
+ - `location` (string): Required. The GCP location, e.g. "us-east1".
582
+ - `keyRing` (string): Required. Name of the key ring that contains
583
+ the key to use.
584
+ - `keyName` (string): Required. Name of the key to use.
585
+ - `keyVersion` (string): Optional. Version of the key to use.
586
+ - `endpoint` (string): Optional. Host with optional port.
587
+ Defaults to "cloudkms.googleapis.com".
588
+
589
+ If the `kms_provider` is "kmip" it is optional and has the
590
+ following fields::
591
+
592
+ - `keyId` (string): Optional. `keyId` is the KMIP Unique
593
+ Identifier to a 96 byte KMIP Secret Data managed object. If
594
+ keyId is omitted, the driver creates a random 96 byte KMIP
595
+ Secret Data managed object.
596
+ - `endpoint` (string): Optional. Host with optional
597
+ port, e.g. "example.vault.azure.net:".
598
+
599
+ - `key_alt_names` (optional): An optional list of string alternate
600
+ names used to reference a key. If a key is created with alternate
601
+ names, then encryption may refer to the key by the unique alternate
602
+ name instead of by ``key_id``. The following example shows creating
603
+ and referring to a data key by alternate name::
604
+
605
+ client_encryption.create_data_key("local", keyAltNames=["name1"])
606
+ # reference the key with the alternate name
607
+ client_encryption.encrypt("457-55-5462", keyAltName="name1",
608
+ algorithm=Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random)
609
+ - `key_material` (optional): Sets the custom key material to be used
610
+ by the data key for encryption and decryption.
611
+
612
+ :Returns:
613
+ The ``_id`` of the created data key document as a
614
+ :class:`~bson.binary.Binary` with subtype
615
+ :data:`~bson.binary.UUID_SUBTYPE`.
616
+
617
+ .. versionchanged:: 4.2
618
+ Added the `key_material` parameter.
619
+ """
620
+ self._check_closed()
621
+ with _wrap_encryption_errors():
622
+ return self._encryption.create_data_key(
623
+ kms_provider,
624
+ master_key=master_key,
625
+ key_alt_names=key_alt_names,
626
+ key_material=key_material,
627
+ )
628
+
629
+ def encrypt(
630
+ self,
631
+ value: Any,
632
+ algorithm: str,
633
+ key_id: Optional[Binary] = None,
634
+ key_alt_name: Optional[str] = None,
635
+ query_type: Optional[str] = None,
636
+ contention_factor: Optional[int] = None,
637
+ ) -> Binary:
638
+ """Encrypt a BSON value with a given key and algorithm.
639
+
640
+ Note that exactly one of ``key_id`` or ``key_alt_name`` must be
641
+ provided.
642
+
643
+ :Parameters:
644
+ - `value`: The BSON value to encrypt.
645
+ - `algorithm` (string): The encryption algorithm to use. See
646
+ :class:`Algorithm` for some valid options.
647
+ - `key_id`: Identifies a data key by ``_id`` which must be a
648
+ :class:`~bson.binary.Binary` with subtype 4 (
649
+ :attr:`~bson.binary.UUID_SUBTYPE`).
650
+ - `key_alt_name`: Identifies a key vault document by 'keyAltName'.
651
+ - `query_type` (str): **(BETA)** The query type to execute. See
652
+ :class:`QueryType` for valid options.
653
+ - `contention_factor` (int): **(BETA)** The contention factor to use
654
+ when the algorithm is :attr:`Algorithm.INDEXED`. An integer value
655
+ *must* be given when the :attr:`Algorithm.INDEXED` algorithm is
656
+ used.
657
+
658
+ .. note:: `query_type` and `contention_factor` are part of the
659
+ Queryable Encryption beta. Backwards-breaking changes may be made before the
660
+ final release.
661
+
662
+ :Returns:
663
+ The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.
664
+
665
+ .. versionchanged:: 4.2
666
+ Added the `query_type` and `contention_factor` parameters.
667
+
668
+ """
669
+ self._check_closed()
670
+ if key_id is not None and not (
671
+ isinstance(key_id, Binary) and key_id.subtype == UUID_SUBTYPE
672
+ ):
673
+ raise TypeError("key_id must be a bson.binary.Binary with subtype 4")
674
+
675
+ doc = encode({"v": value}, codec_options=self._codec_options)
676
+ with _wrap_encryption_errors():
677
+ encrypted_doc = self._encryption.encrypt(
678
+ doc,
679
+ algorithm,
680
+ key_id=key_id,
681
+ key_alt_name=key_alt_name,
682
+ query_type=query_type,
683
+ contention_factor=contention_factor,
684
+ )
685
+ return decode(encrypted_doc)["v"] # type: ignore[index]
686
+
687
+ def decrypt(self, value: Binary) -> Any:
688
+ """Decrypt an encrypted value.
689
+
690
+ :Parameters:
691
+ - `value` (Binary): The encrypted value, a
692
+ :class:`~bson.binary.Binary` with subtype 6.
693
+
694
+ :Returns:
695
+ The decrypted BSON value.
696
+ """
697
+ self._check_closed()
698
+ if not (isinstance(value, Binary) and value.subtype == 6):
699
+ raise TypeError("value to decrypt must be a bson.binary.Binary with subtype 6")
700
+
701
+ with _wrap_encryption_errors():
702
+ doc = encode({"v": value})
703
+ decrypted_doc = self._encryption.decrypt(doc)
704
+ return decode(decrypted_doc, codec_options=self._codec_options)["v"]
705
+
706
+ def get_key(self, id: Binary) -> Optional[RawBSONDocument]:
707
+ """Get a data key by id.
708
+
709
+ :Parameters:
710
+ - `id` (Binary): The UUID of a key a which must be a
711
+ :class:`~bson.binary.Binary` with subtype 4 (
712
+ :attr:`~bson.binary.UUID_SUBTYPE`).
713
+
714
+ :Returns:
715
+ The key document.
716
+
717
+ .. versionadded:: 4.2
718
+ """
719
+ self._check_closed()
720
+ return self._key_vault_coll.find_one({"_id": id})
721
+
722
+ def get_keys(self) -> Cursor[RawBSONDocument]:
723
+ """Get all of the data keys.
724
+
725
+ :Returns:
726
+ An instance of :class:`~pymongo.cursor.Cursor` over the data key
727
+ documents.
728
+
729
+ .. versionadded:: 4.2
730
+ """
731
+ self._check_closed()
732
+ return self._key_vault_coll.find({})
733
+
734
+ def delete_key(self, id: Binary) -> DeleteResult:
735
+ """Delete a key document in the key vault collection that has the given ``key_id``.
736
+
737
+ :Parameters:
738
+ - `id` (Binary): The UUID of a key a which must be a
739
+ :class:`~bson.binary.Binary` with subtype 4 (
740
+ :attr:`~bson.binary.UUID_SUBTYPE`).
741
+
742
+ :Returns:
743
+ The delete result.
744
+
745
+ .. versionadded:: 4.2
746
+ """
747
+ self._check_closed()
748
+ return self._key_vault_coll.delete_one({"_id": id})
749
+
750
+ def add_key_alt_name(self, id: Binary, key_alt_name: str) -> Any:
751
+ """Add ``key_alt_name`` to the set of alternate names in the key document with UUID ``key_id``.
752
+
753
+ :Parameters:
754
+ - ``id``: The UUID of a key a which must be a
755
+ :class:`~bson.binary.Binary` with subtype 4 (
756
+ :attr:`~bson.binary.UUID_SUBTYPE`).
757
+ - ``key_alt_name``: The key alternate name to add.
758
+
759
+ :Returns:
760
+ The previous version of the key document.
761
+
762
+ .. versionadded:: 4.2
763
+ """
764
+ self._check_closed()
765
+ update = {"$addToSet": {"keyAltNames": key_alt_name}}
766
+ return self._key_vault_coll.find_one_and_update({"_id": id}, update)
767
+
768
+ def get_key_by_alt_name(self, key_alt_name: str) -> Optional[RawBSONDocument]:
769
+ """Get a key document in the key vault collection that has the given ``key_alt_name``.
770
+
771
+ :Parameters:
772
+ - `key_alt_name`: (str): The key alternate name of the key to get.
773
+
774
+ :Returns:
775
+ The key document.
776
+
777
+ .. versionadded:: 4.2
778
+ """
779
+ self._check_closed()
780
+ return self._key_vault_coll.find_one({"keyAltNames": key_alt_name})
781
+
782
+ def remove_key_alt_name(self, id: Binary, key_alt_name: str) -> Optional[RawBSONDocument]:
783
+ """Remove ``key_alt_name`` from the set of keyAltNames in the key document with UUID ``id``.
784
+
785
+ Also removes the ``keyAltNames`` field from the key document if it would otherwise be empty.
786
+
787
+ :Parameters:
788
+ - ``id``: The UUID of a key a which must be a
789
+ :class:`~bson.binary.Binary` with subtype 4 (
790
+ :attr:`~bson.binary.UUID_SUBTYPE`).
791
+ - ``key_alt_name``: The key alternate name to remove.
792
+
793
+ :Returns:
794
+ Returns the previous version of the key document.
795
+
796
+ .. versionadded:: 4.2
797
+ """
798
+ self._check_closed()
799
+ pipeline = [
800
+ {
801
+ "$set": {
802
+ "keyAltNames": {
803
+ "$cond": [
804
+ {"$eq": ["$keyAltNames", [key_alt_name]]},
805
+ "$$REMOVE",
806
+ {
807
+ "$filter": {
808
+ "input": "$keyAltNames",
809
+ "cond": {"$ne": ["$$this", key_alt_name]},
810
+ }
811
+ },
812
+ ]
813
+ }
814
+ }
815
+ }
816
+ ]
817
+ return self._key_vault_coll.find_one_and_update({"_id": id}, pipeline)
818
+
819
+ def rewrap_many_data_key(
820
+ self,
821
+ filter: Mapping[str, Any],
822
+ provider: Optional[str] = None,
823
+ master_key: Optional[Mapping[str, Any]] = None,
824
+ ) -> RewrapManyDataKeyResult:
825
+ """Decrypts and encrypts all matching data keys in the key vault with a possibly new `master_key` value.
826
+
827
+ :Parameters:
828
+ - `filter`: A document used to filter the data keys.
829
+ - `provider`: The new KMS provider to use to encrypt the data keys,
830
+ or ``None`` to use the current KMS provider(s).
831
+ - ``master_key``: The master key fields corresponding to the new KMS
832
+ provider when ``provider`` is not ``None``.
833
+
834
+ :Returns:
835
+ A :class:`RewrapManyDataKeyResult`.
836
+
837
+ .. versionadded:: 4.2
838
+ """
839
+ self._check_closed()
840
+ with _wrap_encryption_errors():
841
+ raw_result = self._encryption.rewrap_many_data_key(filter, provider, master_key)
842
+ if raw_result is None:
843
+ return RewrapManyDataKeyResult()
844
+
845
+ raw_doc = RawBSONDocument(raw_result, DEFAULT_RAW_BSON_OPTIONS)
846
+ replacements = []
847
+ for key in raw_doc["v"]:
848
+ update_model = {
849
+ "$set": {"keyMaterial": key["keyMaterial"], "masterKey": key["masterKey"]},
850
+ "$currentDate": {"updateDate": True},
851
+ }
852
+ op = UpdateOne({"_id": key["_id"]}, update_model)
853
+ replacements.append(op)
854
+ if not replacements:
855
+ return RewrapManyDataKeyResult()
856
+ result = self._key_vault_coll.bulk_write(replacements)
857
+ return RewrapManyDataKeyResult(result)
858
+
859
+ def __enter__(self) -> "ClientEncryption":
860
+ return self
861
+
862
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
863
+ self.close()
864
+
865
+ def _check_closed(self):
866
+ if self._encryption is None:
867
+ raise InvalidOperation("Cannot use closed ClientEncryption")
868
+
869
+ def close(self) -> None:
870
+ """Release resources.
871
+
872
+ Note that using this class in a with-statement will automatically call
873
+ :meth:`close`::
874
+
875
+ with ClientEncryption(...) as client_encryption:
876
+ encrypted = client_encryption.encrypt(value, ...)
877
+ decrypted = client_encryption.decrypt(encrypted)
878
+
879
+ """
880
+ if self._io_callbacks:
881
+ self._io_callbacks.close()
882
+ self._encryption.close()
883
+ self._io_callbacks = None
884
+ self._encryption = None