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,624 @@
1
+ # Copyright 2011-present MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
4
+ # may not use this file except in compliance with the License. You
5
+ # 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
12
+ # implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+
16
+ """Tools to parse and validate a MongoDB URI."""
17
+ import re
18
+ import sys
19
+ import warnings
20
+ from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Tuple, Union
21
+ from urllib.parse import unquote_plus
22
+
23
+ from pymongo.client_options import _parse_ssl_options
24
+ from pymongo.common import (
25
+ INTERNAL_URI_OPTION_NAME_MAP,
26
+ SRV_SERVICE_NAME,
27
+ URI_OPTIONS_DEPRECATION_MAP,
28
+ _CaseInsensitiveDictionary,
29
+ get_validated_options,
30
+ )
31
+ from pymongo.errors import ConfigurationError, InvalidURI
32
+ from pymongo.srv_resolver import _HAVE_DNSPYTHON, _SrvResolver
33
+ from pymongo.typings import _Address
34
+
35
+ SCHEME = "mongodb://"
36
+ SCHEME_LEN = len(SCHEME)
37
+ SRV_SCHEME = "mongodb+srv://"
38
+ SRV_SCHEME_LEN = len(SRV_SCHEME)
39
+ DEFAULT_PORT = 27017
40
+
41
+
42
+ def _unquoted_percent(s):
43
+ """Check for unescaped percent signs.
44
+
45
+ :Paramaters:
46
+ - `s`: A string. `s` can have things like '%25', '%2525',
47
+ and '%E2%85%A8' but cannot have unquoted percent like '%foo'.
48
+ """
49
+ for i in range(len(s)):
50
+ if s[i] == "%":
51
+ sub = s[i : i + 3]
52
+ # If unquoting yields the same string this means there was an
53
+ # unquoted %.
54
+ if unquote_plus(sub) == sub:
55
+ return True
56
+ return False
57
+
58
+
59
+ def parse_userinfo(userinfo: str) -> Tuple[str, str]:
60
+ """Validates the format of user information in a MongoDB URI.
61
+ Reserved characters that are gen-delimiters (":", "/", "?", "#", "[",
62
+ "]", "@") as per RFC 3986 must be escaped.
63
+
64
+ Returns a 2-tuple containing the unescaped username followed
65
+ by the unescaped password.
66
+
67
+ :Paramaters:
68
+ - `userinfo`: A string of the form <username>:<password>
69
+ """
70
+ if "@" in userinfo or userinfo.count(":") > 1 or _unquoted_percent(userinfo):
71
+ raise InvalidURI(
72
+ "Username and password must be escaped according to "
73
+ "RFC 3986, use urllib.parse.quote_plus"
74
+ )
75
+
76
+ user, _, passwd = userinfo.partition(":")
77
+ # No password is expected with GSSAPI authentication.
78
+ if not user:
79
+ raise InvalidURI("The empty string is not valid username.")
80
+
81
+ return unquote_plus(user), unquote_plus(passwd)
82
+
83
+
84
+ def parse_ipv6_literal_host(
85
+ entity: str, default_port: Optional[int]
86
+ ) -> Tuple[str, Optional[Union[str, int]]]:
87
+ """Validates an IPv6 literal host:port string.
88
+
89
+ Returns a 2-tuple of IPv6 literal followed by port where
90
+ port is default_port if it wasn't specified in entity.
91
+
92
+ :Parameters:
93
+ - `entity`: A string that represents an IPv6 literal enclosed
94
+ in braces (e.g. '[::1]' or '[::1]:27017').
95
+ - `default_port`: The port number to use when one wasn't
96
+ specified in entity.
97
+ """
98
+ if entity.find("]") == -1:
99
+ raise ValueError(
100
+ "an IPv6 address literal must be enclosed in '[' and ']' according to RFC 2732."
101
+ )
102
+ i = entity.find("]:")
103
+ if i == -1:
104
+ return entity[1:-1], default_port
105
+ return entity[1:i], entity[i + 2 :]
106
+
107
+
108
+ def parse_host(entity: str, default_port: Optional[int] = DEFAULT_PORT) -> _Address:
109
+ """Validates a host string
110
+
111
+ Returns a 2-tuple of host followed by port where port is default_port
112
+ if it wasn't specified in the string.
113
+
114
+ :Parameters:
115
+ - `entity`: A host or host:port string where host could be a
116
+ hostname or IP address.
117
+ - `default_port`: The port number to use when one wasn't
118
+ specified in entity.
119
+ """
120
+ host = entity
121
+ port: Optional[Union[str, int]] = default_port
122
+ if entity[0] == "[":
123
+ host, port = parse_ipv6_literal_host(entity, default_port)
124
+ elif entity.endswith(".sock"):
125
+ return entity, default_port
126
+ elif entity.find(":") != -1:
127
+ if entity.count(":") > 1:
128
+ raise ValueError(
129
+ "Reserved characters such as ':' must be "
130
+ "escaped according RFC 2396. An IPv6 "
131
+ "address literal must be enclosed in '[' "
132
+ "and ']' according to RFC 2732."
133
+ )
134
+ host, port = host.split(":", 1)
135
+ if isinstance(port, str):
136
+ if not port.isdigit() or int(port) > 65535 or int(port) <= 0:
137
+ raise ValueError("Port must be an integer between 0 and 65535: %r" % (port,))
138
+ port = int(port)
139
+
140
+ # Normalize hostname to lowercase, since DNS is case-insensitive:
141
+ # http://tools.ietf.org/html/rfc4343
142
+ # This prevents useless rediscovery if "foo.com" is in the seed list but
143
+ # "FOO.com" is in the hello response.
144
+ return host.lower(), port
145
+
146
+
147
+ # Options whose values are implicitly determined by tlsInsecure.
148
+ _IMPLICIT_TLSINSECURE_OPTS = {
149
+ "tlsallowinvalidcertificates",
150
+ "tlsallowinvalidhostnames",
151
+ "tlsdisableocspendpointcheck",
152
+ }
153
+
154
+
155
+ def _parse_options(opts, delim):
156
+ """Helper method for split_options which creates the options dict.
157
+ Also handles the creation of a list for the URI tag_sets/
158
+ readpreferencetags portion, and the use of a unicode options string."""
159
+ options = _CaseInsensitiveDictionary()
160
+ for uriopt in opts.split(delim):
161
+ key, value = uriopt.split("=")
162
+ if key.lower() == "readpreferencetags":
163
+ options.setdefault(key, []).append(value)
164
+ else:
165
+ if key in options:
166
+ warnings.warn("Duplicate URI option '%s'." % (key,))
167
+ if key.lower() == "authmechanismproperties":
168
+ val = value
169
+ else:
170
+ val = unquote_plus(value)
171
+ options[key] = val
172
+
173
+ return options
174
+
175
+
176
+ def _handle_security_options(options):
177
+ """Raise appropriate errors when conflicting TLS options are present in
178
+ the options dictionary.
179
+
180
+ :Parameters:
181
+ - `options`: Instance of _CaseInsensitiveDictionary containing
182
+ MongoDB URI options.
183
+ """
184
+ # Implicitly defined options must not be explicitly specified.
185
+ tlsinsecure = options.get("tlsinsecure")
186
+ if tlsinsecure is not None:
187
+ for opt in _IMPLICIT_TLSINSECURE_OPTS:
188
+ if opt in options:
189
+ err_msg = "URI options %s and %s cannot be specified simultaneously."
190
+ raise InvalidURI(
191
+ err_msg % (options.cased_key("tlsinsecure"), options.cased_key(opt))
192
+ )
193
+
194
+ # Handle co-occurence of OCSP & tlsAllowInvalidCertificates options.
195
+ tlsallowinvalidcerts = options.get("tlsallowinvalidcertificates")
196
+ if tlsallowinvalidcerts is not None:
197
+ if "tlsdisableocspendpointcheck" in options:
198
+ err_msg = "URI options %s and %s cannot be specified simultaneously."
199
+ raise InvalidURI(
200
+ err_msg
201
+ % ("tlsallowinvalidcertificates", options.cased_key("tlsdisableocspendpointcheck"))
202
+ )
203
+ if tlsallowinvalidcerts is True:
204
+ options["tlsdisableocspendpointcheck"] = True
205
+
206
+ # Handle co-occurence of CRL and OCSP-related options.
207
+ tlscrlfile = options.get("tlscrlfile")
208
+ if tlscrlfile is not None:
209
+ for opt in ("tlsinsecure", "tlsallowinvalidcertificates", "tlsdisableocspendpointcheck"):
210
+ if options.get(opt) is True:
211
+ err_msg = "URI option %s=True cannot be specified when CRL checking is enabled."
212
+ raise InvalidURI(err_msg % (opt,))
213
+
214
+ if "ssl" in options and "tls" in options:
215
+
216
+ def truth_value(val):
217
+ if val in ("true", "false"):
218
+ return val == "true"
219
+ if isinstance(val, bool):
220
+ return val
221
+ return val
222
+
223
+ if truth_value(options.get("ssl")) != truth_value(options.get("tls")):
224
+ err_msg = "Can not specify conflicting values for URI options %s and %s."
225
+ raise InvalidURI(err_msg % (options.cased_key("ssl"), options.cased_key("tls")))
226
+
227
+ return options
228
+
229
+
230
+ def _handle_option_deprecations(options):
231
+ """Issue appropriate warnings when deprecated options are present in the
232
+ options dictionary. Removes deprecated option key, value pairs if the
233
+ options dictionary is found to also have the renamed option.
234
+
235
+ :Parameters:
236
+ - `options`: Instance of _CaseInsensitiveDictionary containing
237
+ MongoDB URI options.
238
+ """
239
+ for optname in list(options):
240
+ if optname in URI_OPTIONS_DEPRECATION_MAP:
241
+ mode, message = URI_OPTIONS_DEPRECATION_MAP[optname]
242
+ if mode == "renamed":
243
+ newoptname = message
244
+ if newoptname in options:
245
+ warn_msg = "Deprecated option '%s' ignored in favor of '%s'."
246
+ warnings.warn(
247
+ warn_msg % (options.cased_key(optname), options.cased_key(newoptname)),
248
+ DeprecationWarning,
249
+ stacklevel=2,
250
+ )
251
+ options.pop(optname)
252
+ continue
253
+ warn_msg = "Option '%s' is deprecated, use '%s' instead."
254
+ warnings.warn(
255
+ warn_msg % (options.cased_key(optname), newoptname),
256
+ DeprecationWarning,
257
+ stacklevel=2,
258
+ )
259
+ elif mode == "removed":
260
+ warn_msg = "Option '%s' is deprecated. %s."
261
+ warnings.warn(
262
+ warn_msg % (options.cased_key(optname), message),
263
+ DeprecationWarning,
264
+ stacklevel=2,
265
+ )
266
+
267
+ return options
268
+
269
+
270
+ def _normalize_options(options):
271
+ """Normalizes option names in the options dictionary by converting them to
272
+ their internally-used names.
273
+
274
+ :Parameters:
275
+ - `options`: Instance of _CaseInsensitiveDictionary containing
276
+ MongoDB URI options.
277
+ """
278
+ # Expand the tlsInsecure option.
279
+ tlsinsecure = options.get("tlsinsecure")
280
+ if tlsinsecure is not None:
281
+ for opt in _IMPLICIT_TLSINSECURE_OPTS:
282
+ # Implicit options are logically the same as tlsInsecure.
283
+ options[opt] = tlsinsecure
284
+
285
+ for optname in list(options):
286
+ intname = INTERNAL_URI_OPTION_NAME_MAP.get(optname, None)
287
+ if intname is not None:
288
+ options[intname] = options.pop(optname)
289
+
290
+ return options
291
+
292
+
293
+ def validate_options(opts: Mapping[str, Any], warn: bool = False) -> MutableMapping[str, Any]:
294
+ """Validates and normalizes options passed in a MongoDB URI.
295
+
296
+ Returns a new dictionary of validated and normalized options. If warn is
297
+ False then errors will be thrown for invalid options, otherwise they will
298
+ be ignored and a warning will be issued.
299
+
300
+ :Parameters:
301
+ - `opts`: A dict of MongoDB URI options.
302
+ - `warn` (optional): If ``True`` then warnings will be logged and
303
+ invalid options will be ignored. Otherwise invalid options will
304
+ cause errors.
305
+ """
306
+ return get_validated_options(opts, warn)
307
+
308
+
309
+ def split_options(
310
+ opts: str, validate: bool = True, warn: bool = False, normalize: bool = True
311
+ ) -> MutableMapping[str, Any]:
312
+ """Takes the options portion of a MongoDB URI, validates each option
313
+ and returns the options in a dictionary.
314
+
315
+ :Parameters:
316
+ - `opt`: A string representing MongoDB URI options.
317
+ - `validate`: If ``True`` (the default), validate and normalize all
318
+ options.
319
+ - `warn`: If ``False`` (the default), suppress all warnings raised
320
+ during validation of options.
321
+ - `normalize`: If ``True`` (the default), renames all options to their
322
+ internally-used names.
323
+ """
324
+ and_idx = opts.find("&")
325
+ semi_idx = opts.find(";")
326
+ try:
327
+ if and_idx >= 0 and semi_idx >= 0:
328
+ raise InvalidURI("Can not mix '&' and ';' for option separators.")
329
+ elif and_idx >= 0:
330
+ options = _parse_options(opts, "&")
331
+ elif semi_idx >= 0:
332
+ options = _parse_options(opts, ";")
333
+ elif opts.find("=") != -1:
334
+ options = _parse_options(opts, None)
335
+ else:
336
+ raise ValueError
337
+ except ValueError:
338
+ raise InvalidURI("MongoDB URI options are key=value pairs.")
339
+
340
+ options = _handle_security_options(options)
341
+
342
+ options = _handle_option_deprecations(options)
343
+
344
+ if normalize:
345
+ options = _normalize_options(options)
346
+
347
+ if validate:
348
+ options = validate_options(options, warn)
349
+ if options.get("authsource") == "":
350
+ raise InvalidURI("the authSource database cannot be an empty string")
351
+
352
+ return options
353
+
354
+
355
+ def split_hosts(hosts: str, default_port: Optional[int] = DEFAULT_PORT) -> List[_Address]:
356
+ """Takes a string of the form host1[:port],host2[:port]... and
357
+ splits it into (host, port) tuples. If [:port] isn't present the
358
+ default_port is used.
359
+
360
+ Returns a set of 2-tuples containing the host name (or IP) followed by
361
+ port number.
362
+
363
+ :Parameters:
364
+ - `hosts`: A string of the form host1[:port],host2[:port],...
365
+ - `default_port`: The port number to use when one wasn't specified
366
+ for a host.
367
+ """
368
+ nodes = []
369
+ for entity in hosts.split(","):
370
+ if not entity:
371
+ raise ConfigurationError("Empty host (or extra comma in host list).")
372
+ port = default_port
373
+ # Unix socket entities don't have ports
374
+ if entity.endswith(".sock"):
375
+ port = None
376
+ nodes.append(parse_host(entity, port))
377
+ return nodes
378
+
379
+
380
+ # Prohibited characters in database name. DB names also can't have ".", but for
381
+ # backward-compat we allow "db.collection" in URI.
382
+ _BAD_DB_CHARS = re.compile("[" + re.escape(r'/ "$') + "]")
383
+
384
+ _ALLOWED_TXT_OPTS = frozenset(
385
+ ["authsource", "authSource", "replicaset", "replicaSet", "loadbalanced", "loadBalanced"]
386
+ )
387
+
388
+
389
+ def _check_options(nodes, options):
390
+ # Ensure directConnection was not True if there are multiple seeds.
391
+ if len(nodes) > 1 and options.get("directconnection"):
392
+ raise ConfigurationError("Cannot specify multiple hosts with directConnection=true")
393
+
394
+ if options.get("loadbalanced"):
395
+ if len(nodes) > 1:
396
+ raise ConfigurationError("Cannot specify multiple hosts with loadBalanced=true")
397
+ if options.get("directconnection"):
398
+ raise ConfigurationError("Cannot specify directConnection=true with loadBalanced=true")
399
+ if options.get("replicaset"):
400
+ raise ConfigurationError("Cannot specify replicaSet with loadBalanced=true")
401
+
402
+
403
+ def parse_uri(
404
+ uri: str,
405
+ default_port: Optional[int] = DEFAULT_PORT,
406
+ validate: bool = True,
407
+ warn: bool = False,
408
+ normalize: bool = True,
409
+ connect_timeout: Optional[float] = None,
410
+ srv_service_name: Optional[str] = None,
411
+ srv_max_hosts: Optional[int] = None,
412
+ ) -> Dict[str, Any]:
413
+ """Parse and validate a MongoDB URI.
414
+
415
+ Returns a dict of the form::
416
+
417
+ {
418
+ 'nodelist': <list of (host, port) tuples>,
419
+ 'username': <username> or None,
420
+ 'password': <password> or None,
421
+ 'database': <database name> or None,
422
+ 'collection': <collection name> or None,
423
+ 'options': <dict of MongoDB URI options>,
424
+ 'fqdn': <fqdn of the MongoDB+SRV URI> or None
425
+ }
426
+
427
+ If the URI scheme is "mongodb+srv://" DNS SRV and TXT lookups will be done
428
+ to build nodelist and options.
429
+
430
+ :Parameters:
431
+ - `uri`: The MongoDB URI to parse.
432
+ - `default_port`: The port number to use when one wasn't specified
433
+ for a host in the URI.
434
+ - `validate` (optional): If ``True`` (the default), validate and
435
+ normalize all options. Default: ``True``.
436
+ - `warn` (optional): When validating, if ``True`` then will warn
437
+ the user then ignore any invalid options or values. If ``False``,
438
+ validation will error when options are unsupported or values are
439
+ invalid. Default: ``False``.
440
+ - `normalize` (optional): If ``True``, convert names of URI options
441
+ to their internally-used names. Default: ``True``.
442
+ - `connect_timeout` (optional): The maximum time in milliseconds to
443
+ wait for a response from the DNS server.
444
+ - 'srv_service_name` (optional): A custom SRV service name
445
+
446
+ .. versionchanged:: 4.0
447
+ To better follow RFC 3986, unquoted percent signs ("%") are no longer
448
+ supported.
449
+
450
+ .. versionchanged:: 3.9
451
+ Added the ``normalize`` parameter.
452
+
453
+ .. versionchanged:: 3.6
454
+ Added support for mongodb+srv:// URIs.
455
+
456
+ .. versionchanged:: 3.5
457
+ Return the original value of the ``readPreference`` MongoDB URI option
458
+ instead of the validated read preference mode.
459
+
460
+ .. versionchanged:: 3.1
461
+ ``warn`` added so invalid options can be ignored.
462
+ """
463
+ if uri.startswith(SCHEME):
464
+ is_srv = False
465
+ scheme_free = uri[SCHEME_LEN:]
466
+ elif uri.startswith(SRV_SCHEME):
467
+ if not _HAVE_DNSPYTHON:
468
+ python_path = sys.executable or "python"
469
+ raise ConfigurationError(
470
+ 'The "dnspython" module must be '
471
+ "installed to use mongodb+srv:// URIs. "
472
+ "To fix this error install pymongo with the srv extra:\n "
473
+ '%s -m pip install "pymongo[srv]"' % (python_path)
474
+ )
475
+ is_srv = True
476
+ scheme_free = uri[SRV_SCHEME_LEN:]
477
+ else:
478
+ raise InvalidURI(
479
+ "Invalid URI scheme: URI must begin with '%s' or '%s'" % (SCHEME, SRV_SCHEME)
480
+ )
481
+
482
+ if not scheme_free:
483
+ raise InvalidURI("Must provide at least one hostname or IP.")
484
+
485
+ user = None
486
+ passwd = None
487
+ dbase = None
488
+ collection = None
489
+ options = _CaseInsensitiveDictionary()
490
+
491
+ host_part, _, path_part = scheme_free.partition("/")
492
+ if not host_part:
493
+ host_part = path_part
494
+ path_part = ""
495
+
496
+ if not path_part and "?" in host_part:
497
+ raise InvalidURI("A '/' is required between the host list and any options.")
498
+
499
+ if path_part:
500
+ dbase, _, opts = path_part.partition("?")
501
+ if dbase:
502
+ dbase = unquote_plus(dbase)
503
+ if "." in dbase:
504
+ dbase, collection = dbase.split(".", 1)
505
+ if _BAD_DB_CHARS.search(dbase):
506
+ raise InvalidURI('Bad database name "%s"' % dbase)
507
+ else:
508
+ dbase = None
509
+
510
+ if opts:
511
+ options.update(split_options(opts, validate, warn, normalize))
512
+ if srv_service_name is None:
513
+ srv_service_name = options.get("srvServiceName", SRV_SERVICE_NAME)
514
+ if "@" in host_part:
515
+ userinfo, _, hosts = host_part.rpartition("@")
516
+ user, passwd = parse_userinfo(userinfo)
517
+ else:
518
+ hosts = host_part
519
+
520
+ if "/" in hosts:
521
+ raise InvalidURI("Any '/' in a unix domain socket must be percent-encoded: %s" % host_part)
522
+
523
+ hosts = unquote_plus(hosts)
524
+ fqdn = None
525
+ srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts")
526
+ if is_srv:
527
+ if options.get("directConnection"):
528
+ raise ConfigurationError(
529
+ "Cannot specify directConnection=true with %s URIs" % (SRV_SCHEME,)
530
+ )
531
+ nodes = split_hosts(hosts, default_port=None)
532
+ if len(nodes) != 1:
533
+ raise InvalidURI("%s URIs must include one, and only one, hostname" % (SRV_SCHEME,))
534
+ fqdn, port = nodes[0]
535
+ if port is not None:
536
+ raise InvalidURI("%s URIs must not include a port number" % (SRV_SCHEME,))
537
+
538
+ # Use the connection timeout. connectTimeoutMS passed as a keyword
539
+ # argument overrides the same option passed in the connection string.
540
+ connect_timeout = connect_timeout or options.get("connectTimeoutMS")
541
+ dns_resolver = _SrvResolver(fqdn, connect_timeout, srv_service_name, srv_max_hosts)
542
+ nodes = dns_resolver.get_hosts()
543
+ dns_options = dns_resolver.get_options()
544
+ if dns_options:
545
+ parsed_dns_options = split_options(dns_options, validate, warn, normalize)
546
+ if set(parsed_dns_options) - _ALLOWED_TXT_OPTS:
547
+ raise ConfigurationError(
548
+ "Only authSource, replicaSet, and loadBalanced are supported from DNS"
549
+ )
550
+ for opt, val in parsed_dns_options.items():
551
+ if opt not in options:
552
+ options[opt] = val
553
+ if options.get("loadBalanced") and srv_max_hosts:
554
+ raise InvalidURI("You cannot specify loadBalanced with srvMaxHosts")
555
+ if options.get("replicaSet") and srv_max_hosts:
556
+ raise InvalidURI("You cannot specify replicaSet with srvMaxHosts")
557
+ if "tls" not in options and "ssl" not in options:
558
+ options["tls"] = True if validate else "true"
559
+ elif not is_srv and options.get("srvServiceName") is not None:
560
+ raise ConfigurationError(
561
+ "The srvServiceName option is only allowed with 'mongodb+srv://' URIs"
562
+ )
563
+ elif not is_srv and srv_max_hosts:
564
+ raise ConfigurationError(
565
+ "The srvMaxHosts option is only allowed with 'mongodb+srv://' URIs"
566
+ )
567
+ else:
568
+ nodes = split_hosts(hosts, default_port=default_port)
569
+
570
+ _check_options(nodes, options)
571
+
572
+ return {
573
+ "nodelist": nodes,
574
+ "username": user,
575
+ "password": passwd,
576
+ "database": dbase,
577
+ "collection": collection,
578
+ "options": options,
579
+ "fqdn": fqdn,
580
+ }
581
+
582
+
583
+ def _parse_kms_tls_options(kms_tls_options):
584
+ """Parse KMS TLS connection options."""
585
+ if not kms_tls_options:
586
+ return {}
587
+ if not isinstance(kms_tls_options, dict):
588
+ raise TypeError("kms_tls_options must be a dict")
589
+ contexts = {}
590
+ for provider, opts in kms_tls_options.items():
591
+ if not isinstance(opts, dict):
592
+ raise TypeError(f'kms_tls_options["{provider}"] must be a dict')
593
+ opts.setdefault("tls", True)
594
+ opts = _CaseInsensitiveDictionary(opts)
595
+ opts = _handle_security_options(opts)
596
+ opts = _normalize_options(opts)
597
+ opts = validate_options(opts)
598
+ ssl_context, allow_invalid_hostnames = _parse_ssl_options(opts)
599
+ if ssl_context is None:
600
+ raise ConfigurationError("TLS is required for KMS providers")
601
+ if allow_invalid_hostnames:
602
+ raise ConfigurationError("Insecure TLS options prohibited")
603
+
604
+ for n in [
605
+ "tlsInsecure",
606
+ "tlsAllowInvalidCertificates",
607
+ "tlsAllowInvalidHostnames",
608
+ "tlsDisableOCSPEndpointCheck",
609
+ "tlsDisableCertificateRevocationCheck",
610
+ ]:
611
+ if n in opts:
612
+ raise ConfigurationError(f"Insecure TLS options prohibited: {n}")
613
+ contexts[provider] = ssl_context
614
+ return contexts
615
+
616
+
617
+ if __name__ == "__main__":
618
+ import pprint
619
+
620
+ try:
621
+ pprint.pprint(parse_uri(sys.argv[1]))
622
+ except InvalidURI as exc:
623
+ print(exc)
624
+ sys.exit(0)