From 278e801b8985d5ee293820b05be0fa1952a4b2d7 Mon Sep 17 00:00:00 2001 From: Yuval Kohavi Date: Fri, 3 Apr 2015 17:23:50 +0300 Subject: [PATCH 1/5] Adding port utils to split port ranges Signed-off-by: Yuval Kohavi --- docker/utils/ports/__init__.py | 6 +++ docker/utils/ports/ports.py | 84 ++++++++++++++++++++++++++++++ tests/utils_test.py | 95 ++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 docker/utils/ports/__init__.py create mode 100644 docker/utils/ports/ports.py diff --git a/docker/utils/ports/__init__.py b/docker/utils/ports/__init__.py new file mode 100644 index 0000000000..ed891f6ee6 --- /dev/null +++ b/docker/utils/ports/__init__.py @@ -0,0 +1,6 @@ +from .ports import ( + to_port_range, + split_port, + add_port_mapping, + build_port_bindings +) # flake8: noqa diff --git a/docker/utils/ports/ports.py b/docker/utils/ports/ports.py new file mode 100644 index 0000000000..6a0a862a20 --- /dev/null +++ b/docker/utils/ports/ports.py @@ -0,0 +1,84 @@ + + +def add_port_mapping(port_bindings, internal_port, external): + if internal_port in port_bindings: + port_bindings[internal_port].append(external) + else: + port_bindings[internal_port] = [external] + + +def add_port(port_bindings, internal_port_range, external_range): + if external_range is None: + for internal_port in internal_port_range: + add_port_mapping(port_bindings, internal_port, None) + else: + ports = zip(internal_port_range, external_range) + for internal_port, external_port in ports: + add_port_mapping(port_bindings, internal_port, external_port) + + +def build_port_bindings(ports): + port_bindings = {} + for port in ports: + internal_port_range, external_range = split_port(port) + add_port(port_bindings, internal_port_range, external_range) + return port_bindings + + +def to_port_range(port): + if not port: + return None + + protocol = "" + if "/" in port: + parts = port.split("/") + if len(parts) != 2: + raise ValueError('Invalid port "%s", should be ' + '[[remote_ip:]remote_port[-remote_port]:]' + 'port[/protocol]' % port) + port, protocol = parts + protocol = "/" + protocol + + parts = str(port).split('-') + + if len(parts) == 1: + return ["%s%s" % (port, protocol)] + + if len(parts) == 2: + full_port_range = range(int(parts[0]), int(parts[1]) + 1) + return ["%s%s" % (p, protocol) for p in full_port_range] + + raise ValueError('Invalid port range "%s", should be ' + 'port or startport-endport' % port) + + +def split_port(port): + parts = str(port).split(':') + if not 1 <= len(parts) <= 3: + raise ValueError('Invalid port "%s", should be ' + '[[remote_ip:]remote_port:]port[/protocol]' % port) + + if len(parts) == 1: + internal_port, = parts + return to_port_range(internal_port), None + if len(parts) == 2: + external_port, internal_port = parts + + internal_range = to_port_range(internal_port) + external_range = to_port_range(external_port) + if len(internal_range) != len(external_range): + raise ValueError('Port ranges don\'t match in length') + + return internal_range, external_range + + external_ip, external_port, internal_port = parts + internal_range = to_port_range(internal_port) + external_range = to_port_range(external_port) + if not external_range: + external_range = [None] * len(internal_range) + + if len(internal_range) != len(external_range): + raise ValueError('Port ranges don\'t match in length') + + return internal_range, [(external_ip, ex_port or None) + for ex_port in external_range] diff --git a/tests/utils_test.py b/tests/utils_test.py index 75de915a40..1b89a61fbb 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -8,6 +8,7 @@ parse_repository_tag, parse_host, convert_filters, kwargs_from_env, create_host_config ) +from docker.utils.ports import build_port_bindings, split_port from docker.auth import resolve_authconfig @@ -165,6 +166,100 @@ def test_resolve_authconfig(self): resolve_authconfig(auth_config, 'does.not.exist') is None ) + def test_split_port_with_host_ip(self): + internal_port, external_port = split_port("127.0.0.1:1000:2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, [("127.0.0.1", "1000")]) + + def test_split_port_with_protocol(self): + internal_port, external_port = split_port("127.0.0.1:1000:2000/udp") + self.assertEqual(internal_port, ["2000/udp"]) + self.assertEqual(external_port, [("127.0.0.1", "1000")]) + + def test_split_port_with_host_ip_no_port(self): + internal_port, external_port = split_port("127.0.0.1::2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, [("127.0.0.1", None)]) + + def test_split_port_range_with_host_ip_no_port(self): + internal_port, external_port = split_port("127.0.0.1::2000-2001") + self.assertEqual(internal_port, ["2000", "2001"]) + self.assertEqual(external_port, + [("127.0.0.1", None), ("127.0.0.1", None)]) + + def test_split_port_with_host_port(self): + internal_port, external_port = split_port("1000:2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, ["1000"]) + + def test_split_port_range_with_host_port(self): + internal_port, external_port = split_port("1000-1001:2000-2001") + self.assertEqual(internal_port, ["2000", "2001"]) + self.assertEqual(external_port, ["1000", "1001"]) + + def test_split_port_no_host_port(self): + internal_port, external_port = split_port("2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, None) + + def test_split_port_range_no_host_port(self): + internal_port, external_port = split_port("2000-2001") + self.assertEqual(internal_port, ["2000", "2001"]) + self.assertEqual(external_port, None) + + def test_split_port_range_with_protocol(self): + internal_port, external_port = split_port( + "127.0.0.1:1000-1001:2000-2001/udp") + self.assertEqual(internal_port, ["2000/udp", "2001/udp"]) + self.assertEqual(external_port, + [("127.0.0.1", "1000"), ("127.0.0.1", "1001")]) + + def test_split_port_invalid(self): + with self.assertRaises(ValueError): + split_port("0.0.0.0:1000:2000:tcp") + + def test_non_matching_length_port_ranges(self): + with self.assertRaises(ValueError): + split_port("0.0.0.0:1000-1010:2000-2002/tcp") + + def test_port_and_range_invalid(self): + with self.assertRaises(ValueError): + split_port("0.0.0.0:1000:2000-2002/tcp") + + def test_build_port_bindings_with_one_port(self): + port_bindings = build_port_bindings(["127.0.0.1:1000:1000"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + + def test_build_port_bindings_with_matching_internal_ports(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:1000"]) + self.assertEqual(port_bindings["1000"], + [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) + + def test_build_port_bindings_with_nonmatching_internal_ports(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) + + def test_build_port_bindings_with_port_range(self): + port_bindings = build_port_bindings(["127.0.0.1:1000-1001:1000-1001"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + self.assertEqual(port_bindings["1001"], [("127.0.0.1", "1001")]) + + def test_build_port_bindings_with_matching_internal_port_ranges(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000-1001:1000-1001", "127.0.0.1:2000-2001:1000-1001"]) + self.assertEqual(port_bindings["1000"], + [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) + self.assertEqual(port_bindings["1001"], + [("127.0.0.1", "1001"), ("127.0.0.1", "2001")]) + + def test_build_port_bindings_with_nonmatching_internal_port_ranges(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) if __name__ == '__main__': unittest.main() From 8028c988ce363345988bc23444f08e27394a7fb5 Mon Sep 17 00:00:00 2001 From: Yuval Kohavi Date: Wed, 8 Apr 2015 11:00:25 +0300 Subject: [PATCH 2/5] added ports to setup.py Signed-off-by: Yuval Kohavi --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cdf2ec4bae..b39d283e58 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ name="docker-py", version=version, description="Python client for Docker.", - packages=['docker', 'docker.auth', 'docker.unixconn', 'docker.utils', + packages=['docker', 'docker.auth', 'docker.unixconn', 'docker.utils', 'docker.utils.ports', 'docker.ssladapter'], install_requires=requirements, tests_require=test_requirements, From b33052b39320da92c49660ce7bdda1cae67b8208 Mon Sep 17 00:00:00 2001 From: Yuval Kohavi Date: Sat, 11 Apr 2015 07:17:28 -0500 Subject: [PATCH 3/5] python 2.6 compatible Signed-off-by: Yuval Kohavi --- tests/utils_test.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/utils_test.py b/tests/utils_test.py index 1b89a61fbb..77a1ca15ab 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -215,16 +215,13 @@ def test_split_port_range_with_protocol(self): [("127.0.0.1", "1000"), ("127.0.0.1", "1001")]) def test_split_port_invalid(self): - with self.assertRaises(ValueError): - split_port("0.0.0.0:1000:2000:tcp") + self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000:2000:tcp")) def test_non_matching_length_port_ranges(self): - with self.assertRaises(ValueError): - split_port("0.0.0.0:1000-1010:2000-2002/tcp") + self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000-1010:2000-2002/tcp")) def test_port_and_range_invalid(self): - with self.assertRaises(ValueError): - split_port("0.0.0.0:1000:2000-2002/tcp") + self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000:2000-2002/tcp")) def test_build_port_bindings_with_one_port(self): port_bindings = build_port_bindings(["127.0.0.1:1000:1000"]) From 605fc7e04f3b6142b4e656fd964c76fe82716188 Mon Sep 17 00:00:00 2001 From: Yuval Kohavi Date: Sat, 11 Apr 2015 07:20:43 -0500 Subject: [PATCH 4/5] line length Signed-off-by: Yuval Kohavi --- tests/utils_test.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/utils_test.py b/tests/utils_test.py index 77a1ca15ab..852c4caefd 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -215,13 +215,18 @@ def test_split_port_range_with_protocol(self): [("127.0.0.1", "1000"), ("127.0.0.1", "1001")]) def test_split_port_invalid(self): - self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000:2000:tcp")) + self.assertRaises(ValueError, + lambda: split_port("0.0.0.0:1000:2000:tcp")) def test_non_matching_length_port_ranges(self): - self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000-1010:2000-2002/tcp")) + self.assertRaises( + ValueError, + lambda: split_port("0.0.0.0:1000-1010:2000-2002/tcp") + ) def test_port_and_range_invalid(self): - self.assertRaises(ValueError, lambda: split_port("0.0.0.0:1000:2000-2002/tcp")) + self.assertRaises(ValueError, + lambda: split_port("0.0.0.0:1000:2000-2002/tcp")) def test_build_port_bindings_with_one_port(self): port_bindings = build_port_bindings(["127.0.0.1:1000:1000"]) From baadad05ad722078eafa988123315a4f9ad79bad Mon Sep 17 00:00:00 2001 From: Yuval Kohavi Date: Fri, 17 Apr 2015 17:09:45 -0400 Subject: [PATCH 5/5] removed unneeded imports Signed-off-by: Yuval Kohavi --- docker/utils/ports/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/utils/ports/__init__.py b/docker/utils/ports/__init__.py index ed891f6ee6..1dbfa3a709 100644 --- a/docker/utils/ports/__init__.py +++ b/docker/utils/ports/__init__.py @@ -1,6 +1,4 @@ from .ports import ( - to_port_range, split_port, - add_port_mapping, build_port_bindings ) # flake8: noqa