sensu-plugins-mongodb-mrtrotl 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +22 -0
- data/README.md +27 -0
- data/bin/check-mongodb-metric.rb +144 -0
- data/bin/check-mongodb-query-count.rb +267 -0
- data/bin/check-mongodb.py +1644 -0
- data/bin/check-mongodb.rb +5 -0
- data/bin/metrics-mongodb-replication.rb +254 -0
- data/bin/metrics-mongodb.rb +133 -0
- data/lib/bson/__init__.py +1347 -0
- data/lib/bson/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/_helpers.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/binary.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/code.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/codec_options.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/dbref.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/decimal128.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/int64.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/json_util.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/max_key.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/min_key.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/objectid.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/raw_bson.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/regex.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/son.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/timestamp.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/tz_util.cpython-310.pyc +0 -0
- data/lib/bson/_cbson.cpython-310-x86_64-linux-gnu.so +0 -0
- data/lib/bson/_helpers.py +41 -0
- data/lib/bson/binary.py +364 -0
- data/lib/bson/code.py +101 -0
- data/lib/bson/codec_options.py +414 -0
- data/lib/bson/codec_options.pyi +100 -0
- data/lib/bson/dbref.py +133 -0
- data/lib/bson/decimal128.py +314 -0
- data/lib/bson/errors.py +35 -0
- data/lib/bson/int64.py +39 -0
- data/lib/bson/json_util.py +874 -0
- data/lib/bson/max_key.py +55 -0
- data/lib/bson/min_key.py +55 -0
- data/lib/bson/objectid.py +286 -0
- data/lib/bson/py.typed +2 -0
- data/lib/bson/raw_bson.py +175 -0
- data/lib/bson/regex.py +135 -0
- data/lib/bson/son.py +208 -0
- data/lib/bson/timestamp.py +124 -0
- data/lib/bson/tz_util.py +52 -0
- data/lib/gridfs/__init__.py +1015 -0
- data/lib/gridfs/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/gridfs/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/gridfs/__pycache__/grid_file.cpython-310.pyc +0 -0
- data/lib/gridfs/errors.py +33 -0
- data/lib/gridfs/grid_file.py +907 -0
- data/lib/gridfs/py.typed +2 -0
- data/lib/pymongo/__init__.py +185 -0
- data/lib/pymongo/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/_csot.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/aggregation.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/auth.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/auth_aws.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/bulk.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/change_stream.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/client_options.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/client_session.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/collation.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/collection.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/command_cursor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/common.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/compression_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/cursor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/daemon.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/database.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/driver_info.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/encryption.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/encryption_options.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/event_loggers.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/hello.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/helpers.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/max_staleness_selectors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/message.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/mongo_client.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/monitor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/monitoring.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/network.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ocsp_cache.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ocsp_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/operations.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/periodic_executor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/pool.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/pyopenssl_context.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/read_concern.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/read_preferences.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/response.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/results.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/saslprep.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_api.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_description.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_selectors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_type.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/settings.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/socket_checker.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/srv_resolver.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ssl_context.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ssl_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/topology.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/topology_description.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/typings.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/uri_parser.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/write_concern.cpython-310.pyc +0 -0
- data/lib/pymongo/_cmessage.cpython-310-x86_64-linux-gnu.so +0 -0
- data/lib/pymongo/_csot.py +118 -0
- data/lib/pymongo/aggregation.py +229 -0
- data/lib/pymongo/auth.py +549 -0
- data/lib/pymongo/auth_aws.py +94 -0
- data/lib/pymongo/bulk.py +513 -0
- data/lib/pymongo/change_stream.py +457 -0
- data/lib/pymongo/client_options.py +302 -0
- data/lib/pymongo/client_session.py +1112 -0
- data/lib/pymongo/collation.py +224 -0
- data/lib/pymongo/collection.py +3204 -0
- data/lib/pymongo/command_cursor.py +353 -0
- data/lib/pymongo/common.py +984 -0
- data/lib/pymongo/compression_support.py +149 -0
- data/lib/pymongo/cursor.py +1345 -0
- data/lib/pymongo/daemon.py +141 -0
- data/lib/pymongo/database.py +1202 -0
- data/lib/pymongo/driver_info.py +42 -0
- data/lib/pymongo/encryption.py +884 -0
- data/lib/pymongo/encryption_options.py +221 -0
- data/lib/pymongo/errors.py +365 -0
- data/lib/pymongo/event_loggers.py +221 -0
- data/lib/pymongo/hello.py +219 -0
- data/lib/pymongo/helpers.py +259 -0
- data/lib/pymongo/max_staleness_selectors.py +114 -0
- data/lib/pymongo/message.py +1440 -0
- data/lib/pymongo/mongo_client.py +2144 -0
- data/lib/pymongo/monitor.py +440 -0
- data/lib/pymongo/monitoring.py +1801 -0
- data/lib/pymongo/network.py +311 -0
- data/lib/pymongo/ocsp_cache.py +87 -0
- data/lib/pymongo/ocsp_support.py +372 -0
- data/lib/pymongo/operations.py +507 -0
- data/lib/pymongo/periodic_executor.py +183 -0
- data/lib/pymongo/pool.py +1660 -0
- data/lib/pymongo/py.typed +2 -0
- data/lib/pymongo/pyopenssl_context.py +383 -0
- data/lib/pymongo/read_concern.py +75 -0
- data/lib/pymongo/read_preferences.py +609 -0
- data/lib/pymongo/response.py +109 -0
- data/lib/pymongo/results.py +217 -0
- data/lib/pymongo/saslprep.py +113 -0
- data/lib/pymongo/server.py +247 -0
- data/lib/pymongo/server_api.py +170 -0
- data/lib/pymongo/server_description.py +285 -0
- data/lib/pymongo/server_selectors.py +153 -0
- data/lib/pymongo/server_type.py +32 -0
- data/lib/pymongo/settings.py +159 -0
- data/lib/pymongo/socket_checker.py +104 -0
- data/lib/pymongo/srv_resolver.py +126 -0
- data/lib/pymongo/ssl_context.py +39 -0
- data/lib/pymongo/ssl_support.py +99 -0
- data/lib/pymongo/topology.py +890 -0
- data/lib/pymongo/topology_description.py +639 -0
- data/lib/pymongo/typings.py +39 -0
- data/lib/pymongo/uri_parser.py +624 -0
- data/lib/pymongo/write_concern.py +129 -0
- data/lib/pymongo-4.2.0.dist-info/INSTALLER +1 -0
- data/lib/pymongo-4.2.0.dist-info/LICENSE +201 -0
- data/lib/pymongo-4.2.0.dist-info/METADATA +250 -0
- data/lib/pymongo-4.2.0.dist-info/RECORD +167 -0
- data/lib/pymongo-4.2.0.dist-info/REQUESTED +0 -0
- data/lib/pymongo-4.2.0.dist-info/WHEEL +6 -0
- data/lib/pymongo-4.2.0.dist-info/top_level.txt +3 -0
- data/lib/sensu-plugins-mongodb/metrics.rb +391 -0
- data/lib/sensu-plugins-mongodb/version.rb +9 -0
- data/lib/sensu-plugins-mongodb.rb +1 -0
- 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)
|