diff --git a/repo2docker/__main__.py b/repo2docker/__main__.py index 2f9887ff..1b52b1ae 100644 --- a/repo2docker/__main__.py +++ b/repo2docker/__main__.py @@ -354,7 +354,7 @@ def make_r2d(argv=None): ) sys.exit(1) - if args.ports and not r2d.run_cmd: + if args.ports and len(args.ports) > 1 and not r2d.run_cmd: print( "To publish user defined port mapping, user must specify " "the command to run in the container" diff --git a/repo2docker/app.py b/repo2docker/app.py index d9a114a4..e11f36b6 100755 --- a/repo2docker/app.py +++ b/repo2docker/app.py @@ -595,8 +595,24 @@ class Repo2Docker(Application): self.hostname = host_name if not self.run_cmd: - port = str(self._get_free_port()) - self.port = port + if len(self.ports) == 1: + # single port mapping specified + # retrieve container and host port from dict + # {'8888/tcp': ('hostname', 'port')} + # or + # {'8888/tcp': 'port'} + container_port_proto, host_port = next(iter(self.ports.items())) + if isinstance(host_port, tuple): + # (hostname, port) tuple or string port + host_name, host_port = host_port + self.hostname = host_name + host_port = int(host_port) + container_port = int(container_port_proto.split("/", 1)[0]) + else: + # no port specified, pick a random one + container_port = host_port = str(self._get_free_port()) + self.ports = {f"{container_port}/tcp": host_port} + self.port = host_port # To use the option --NotebookApp.custom_display_url # make sure the base-notebook image is updated: # docker pull jupyter/base-notebook @@ -606,20 +622,13 @@ class Repo2Docker(Application): "--ip", "0.0.0.0", "--port", - port, - "--NotebookApp.custom_display_url=http://{}:{}".format(host_name, port), + container_port, + f"--NotebookApp.custom_display_url=http://{host_name}:{host_port}" "--NotebookApp.default_url=/lab", ] - ports = {"%s/tcp" % port: port} else: # run_cmd given by user, if port is also given then pass it on run_cmd = self.run_cmd - if self.ports: - ports = self.ports - else: - ports = {} - # store ports on self so they can be retrieved in tests - self.ports = ports container_volumes = {} if self.volumes: @@ -634,7 +643,7 @@ class Repo2Docker(Application): run_kwargs = dict( publish_all_ports=self.all_ports, - ports=ports, + ports=self.ports, command=run_cmd, volumes=container_volumes, environment=self.environment, diff --git a/repo2docker/utils.py b/repo2docker/utils.py index ef61814b..e8833bd8 100644 --- a/repo2docker/utils.py +++ b/repo2docker/utils.py @@ -139,10 +139,10 @@ def validate_and_generate_port_mapping(port_mappings): raise ValueError( 'Port specification "{}" has ' "an invalid port.".format(mapping) ) - if p > 65535: + if not 0 < p <= 65535: raise ValueError( 'Port specification "{}" specifies ' - "a port above 65535.".format(mapping) + "a port outside 1-65535.".format(mapping) ) return port @@ -168,7 +168,12 @@ def validate_and_generate_port_mapping(port_mappings): return ports for mapping in port_mappings: - parts = mapping.split(":") + if ":" in mapping: + parts = mapping.split(":") + else: + # single port '8888' specified, + # treat as '8888:8888' + parts = [mapping, mapping] *host, container_port = parts # just a port diff --git a/tests/unit/test_args.py b/tests/unit/test_args.py index 5e8c2484..e5fd2c1a 100644 --- a/tests/unit/test_args.py +++ b/tests/unit/test_args.py @@ -3,7 +3,6 @@ Test argument parsing and r2d construction """ import os import pytest -import logging from repo2docker.__main__ import make_r2d from repo2docker import __version__ @@ -71,10 +70,6 @@ def test_run_required(): with pytest.raises(SystemExit): make_r2d(["--no-run", "-p", "8000:8000", "."]) - # Can't publish any ports while running if we don't specify a command explicitly - with pytest.raises(SystemExit): - make_r2d(["-p", "8000:8000", "."]) - def test_clean(): """ diff --git a/tests/unit/test_ports.py b/tests/unit/test_ports.py index edb71424..09944e87 100644 --- a/tests/unit/test_ports.py +++ b/tests/unit/test_ports.py @@ -13,6 +13,7 @@ import docker import pytest from repo2docker.app import Repo2Docker +from repo2docker.__main__ import make_r2d def read_port_mapping_response( @@ -95,7 +96,7 @@ def read_port_mapping_response( else: break else: - pytest.fail("Never succeded in talking to %s" % url) + pytest.fail(f"Never succeeded in talking to {url}") assert "Directory listing" in r.text @@ -113,3 +114,16 @@ def test_port_mapping(request, tmpdir, host, protocol): """Test a port mapping""" port = str(random.randint(50000, 51000)) read_port_mapping_response(request, tmpdir, host=host, port=port, protocol=protocol) + + +@pytest.mark.parametrize( + "port_str, port_dict", + [ + ("8000", {"8000/tcp": "8000"}), + ("8000:9000", {"9000/tcp": "8000"}), + ("127.0.0.1:8000:9000", {"9000/tcp": ("127.0.0.1", "8000")}), + ], +) +def test_port_args(port_str, port_dict): + app = make_r2d(["-p", port_str, "."]) + assert app.ports == port_dict