Add tests for port mapping conversion
MODIFY
repo2docker/app.py
repo2docker/app.py
@@ -31,14 +31,10 @@ from traitlets.config import Application
from . import __version__
from .buildpacks import (
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
- CondaBuildPack, JuliaBuildPack, BaseImage,
- RBuildPack, NixBuildPack
+ CondaBuildPack, JuliaBuildPack, RBuildPack, NixBuildPack
)
from . import contentproviders
-from .utils import (
- ByteSpecification, is_valid_docker_image_name,
- validate_and_generate_port_mapping, chdir
-)
+from .utils import ByteSpecification, chdir
class Repo2Docker(Application):

MODIFY
repo2docker/utils.py
repo2docker/utils.py
@@ -68,24 +68,17 @@ def chdir(path):
os.chdir(old_dir)
-def validate_and_generate_port_mapping(port_mapping):
+def validate_and_generate_port_mapping(port_mappings):
"""
- Validate the port mapping list and return a list of validated tuples.
-
- Each entry in the passed port mapping list will be converted to a
- tuple with a containing a string with the format 'key:value' with the
- `key` being the container's port and the
- `value` being `None`, `host_port` or `['interface_ip','host_port']`
-
+ Validate a list of port mappings and return a dictionary of port mappings
Args:
- port_mapping (list): List of strings of format
+ port_mappings (list): List of strings of format
`'host_port:container_port'` with optional tcp udp values and host
network interface
Returns:
- List of validated tuples of form ('host_port:container_port') with
- optional tcp udp values and host network interface
+ Dictionary of port mappings
Raises:
Exception on invalid port mapping
@@ -94,55 +87,43 @@ def validate_and_generate_port_mapping(port_mapping):
One limitation of repo2docker is it cannot bind a
single container_port to multiple host_ports
(docker-py supports this but repo2docker does not)
+ """
+ def check_port_string(p):
+ parts = p.split('/')
+ if len(parts) == 2: # 134/tcp
+ port, protocol = parts
+ if protocol not in ('tcp', 'udp'):
+ raise ValueError('Port specification "{}" has '
+ 'an invalid protocol.'.format(mapping))
+ elif len(parts) == 1:
+ port = parts[0]
+ protocol = 'tcp'
- Examples:
- Valid port mappings are:
- - `127.0.0.1:90:900`
- - `:999` (match to any host port)
- - `999:999/tcp` (bind 999 host port to 999 tcp container port)
+ try:
+ int(port)
+ except ValueError as e:
+ raise ValueError('Port specification "{}" has '
+ 'an invalid port.'.format(mapping))
+
+ return '/'.join((port, protocol))
- Invalid port mapping:
- - `127.0.0.1::999` (even though docker accepts it)
- - other invalid ip address combinations
- """
- reg_regex = re.compile(r"""
- ^(
- ( # or capturing group
- (?: # start capturing ip address of network interface
- (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} # first three parts
- (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # last part of the ip address
- :(?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){1,4})
- )?
- | # host ip with port or only port
- (?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){0,4})
- )
- :
- (?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){0,4})
- (?:/udp|/tcp)?
- )$
- """, re.VERBOSE)
ports = {}
- if port_mapping is None:
+ if port_mappings is None:
return ports
- for p in port_mapping:
- if reg_regex.match(p) is None:
- raise Exception('Invalid port mapping ' + str(p))
- # Do a reverse split twice on the separator :
- port_host = str(p).rsplit(':', 2)
- host = None
- if len(port_host) == 3:
- # host, optional host_port and container port information given
- host = port_host[0]
- host_port = port_host[1]
- container_port = port_host[2]
- else:
- host_port = port_host[0] if len(port_host[0]) > 0 else None
- container_port = port_host[1]
- if host is None:
- ports[str(container_port)] = host_port
+ for mapping in port_mappings:
+ parts = mapping.split(':')
+
+ *host, container_port = parts
+ # just a port
+ if len(host) == 1:
+ host = host[0]
else:
- ports[str(container_port)] = (host, host_port)
+ host = tuple(host)
+
+ container_port = check_port_string(container_port)
+ ports[container_port] = host
+
return ports

MODIFY
tests/unit/test_utils.py
tests/unit/test_utils.py
@@ -3,7 +3,6 @@ Tests for repo2docker/utils.py
"""
import traitlets
import os
-from tempfile import TemporaryDirectory
from repo2docker import utils
import pytest
import subprocess
@@ -16,6 +15,7 @@ def test_capture_cmd_no_capture_success():
]):
pass
+
def test_capture_cmd_no_capture_fail():
with pytest.raises(subprocess.CalledProcessError):
for line in utils.execute_cmd([
@@ -64,3 +64,17 @@ def test_byte_spec_validation():
with pytest.raises(traitlets.TraitError):
bs.validate(None, '1m')
+
+
+@pytest.mark.parametrize("input,expected", [
+ (["8888:8888"], {'8888/tcp': '8888'}),
+ (["8888:4321"], {'4321/tcp': '8888'}),
+ (["8888:4321/udp"], {'4321/udp': '8888'}),
+ (["8888:4321/udp", "8888:4321/tcp"], {'4321/udp': '8888',
+ '4321/tcp': '8888'}),
+ (['127.0.0.1:80:8000'], {'8000/tcp': ('127.0.0.1', '80')}),
+ (["8888:4321", "1234:12345"], {'4321/tcp': '8888', '12345/tcp': '1234'}),
+])
+def test_valid_port_mapping(input, expected):
+ actual = utils.validate_and_generate_port_mapping(input)
+ assert actual == expected

Add tests for port mapping conversion