Skip to content

Commit 0c11c91

Browse files
authored
feat: add option for exposing multiple ports
2 parents 9997d80 + 8d4da6c commit 0c11c91

2 files changed

Lines changed: 53 additions & 26 deletions

File tree

dotrun.py

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
import threading
88
import time
99
from importlib import metadata
10+
from typing import Mapping
1011

1112
# Packages
1213
import docker
14+
import docker.errors
15+
import docker.types
16+
import docker.models.containers
1317
import dockerpty
1418
from dotenv import dotenv_values
1519
from slugify import slugify
@@ -24,6 +28,8 @@ def __init__(self):
2428
self.cwd = os.getcwd()
2529
self.project_name = slugify(os.path.basename(self.cwd))
2630
self.project_port = dotenv_values(".env").get("PORT", 8080)
31+
if self.project_port is not None:
32+
self.project_port = int(self.project_port)
2733
self.container_home = "/home/ubuntu/"
2834
self.container_path = f"{self.container_home}{self.project_name}"
2935
# --network host is only supported on Linux
@@ -132,11 +138,11 @@ def _prepare_mounts(self, command):
132138

133139
additional_mounts = self._get_additional_mounts(command)
134140
if additional_mounts:
135-
for mount in additional_mounts:
141+
for host_path, container_mount in additional_mounts.items():
136142
mounts.append(
137143
docker.types.Mount(
138-
target=f"{self.container_path}/{mount[1]}",
139-
source=f"{mount[0]}",
144+
target=f"{self.container_home}/{container_mount}",
145+
source=f"{host_path}",
140146
type="bind",
141147
read_only=False,
142148
consistency="cached",
@@ -164,35 +170,56 @@ def _get_container_name(self, command=None):
164170
# Remove duplicated hyphens
165171
return re.sub(r"(-)+", r"\1", name)
166172

167-
def _get_additional_mounts(self, command):
168-
"""
169-
Return a list of additional mounts
170-
"""
171-
if "-m" not in command:
172-
return
173+
def _get_binding_attrs(self, option, command) -> Mapping[str, str]:
174+
if option not in command:
175+
return {}
173176

174-
def get_mount(command, mounts):
175-
mount_index = command.index("-m")
176-
mount_string = command[mount_index + 1]
177-
del command[mount_index]
178-
if ":" in mount_string:
179-
mount_parts = mount_string.split(":")
180-
mounts.append(mount_parts)
181-
del command[mount_index]
177+
def get_attributes(command, attributes):
178+
index: int = command.index(option)
179+
option_value: str = command[index + 1]
180+
del command[index]
181+
if ":" in option_value:
182+
binding_parts = option_value.split(":")
183+
attributes[binding_parts[0]] = binding_parts[1]
184+
del command[index]
182185

183-
if "-m" in command:
184-
mounts = get_mount(command, mounts)
186+
# check for extra options with the same value,
187+
# for example multiple mounts or ports
188+
if option in command:
189+
attributes = get_attributes(command, attributes)
185190

186-
return mounts
191+
return attributes
187192

188-
return get_mount(command, [])
193+
return get_attributes(command, {})
189194

190-
def create_container(self, command, image_name=None):
195+
def _get_additional_ports(self, command) -> Mapping[str, str]:
196+
"""
197+
Return a list of additional ports to expose in the container
198+
"""
199+
return self._get_binding_attrs("-p", command)
200+
201+
def _get_additional_mounts(self, command):
202+
"""
203+
Return a list of additional mounts
204+
"""
205+
return self._get_binding_attrs("-m", command)
206+
207+
def create_container(
208+
self, command, image_name=None
209+
) -> docker.models.containers.Container:
191210
if not image_name:
192211
image_name = self.BASE_IMAGE_NAME
193-
ports = {self.project_port: self.project_port}
212+
213+
# set up binding ports (container:host)
214+
ports = {}
215+
ports[str(self.project_port)] = self.project_port
216+
additional_ports = self._get_additional_ports(command)
217+
for container_port, host_port in additional_ports.items():
218+
ports[container_port] = int(host_port)
219+
194220
# Run on the same network mode as the host
195221
network_mode = None
222+
196223
if command[1:]:
197224
first_cmd = command[1:][0]
198225

@@ -204,6 +231,7 @@ def create_container(self, command, image_name=None):
204231
name = self._get_container_name(first_cmd)
205232
else:
206233
name = self._get_container_name()
234+
207235
if self.network_host_mode:
208236
# network_mode host is incompatible with ports option
209237
ports = None
@@ -221,7 +249,7 @@ def create_container(self, command, image_name=None):
221249
command=command,
222250
ports=ports,
223251
network_mode=network_mode,
224-
)
252+
) # type: ignore
225253

226254

227255
def _extract_cli_command_arg(pattern, command_list):

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = 'dotrun'
3-
version = '2.5.0'
3+
version = '2.6.0'
44
description = 'A tool for developing Node.js and Python projects'
55
authors = ['Canonical Web Team <webteam@canonical.com>']
66
license = 'LGPL-3.0'
@@ -16,7 +16,6 @@ python-slugify = '8.0.4'
1616
docker = '7.1.0'
1717
dockerpty = '0.4.1'
1818

19-
2019
[build-system]
2120
requires = ['poetry>=0.12']
2221
build-backend = 'poetry.masonry.api'

0 commit comments

Comments
 (0)