From 1c96d6f9f21c35c195bccbd13b49308007ecae9b Mon Sep 17 00:00:00 2001 From: uccu Date: Sun, 22 May 2022 01:50:07 +0800 Subject: [PATCH 1/5] chore: implement parser port parse --- compose_viz/parser.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compose_viz/parser.py b/compose_viz/parser.py index 3d94b31..649e437 100644 --- a/compose_viz/parser.py +++ b/compose_viz/parser.py @@ -57,7 +57,13 @@ class Parser: service_ports: List[str] = [] if service.get("ports"): - service_ports = service["ports"] + if type(service["ports"]) is list: + for port_data in service["ports"]: + if not port_data.contains(":"): + raise RuntimeError("Invalid ports input, aborting.") + spilt_data = port_data.split(":", 1) + service_ports.append(Port(host_port=spilt_data[0], + container_port=spilt_data[1])) service_depends_on: List[str] = [] if service.get("depends_on"): @@ -87,13 +93,7 @@ class Parser: image=service_image, networks=service_networks, extends=service_extends, - ports=[ - Port( - # TODO: not implemented yet - host_port=service_ports[0], - container_port=service_ports[0], - ) - ], + ports=service_ports, depends_on=service_depends_on, volumes=service_volumes, links=service_links, From 473033d3f5e7c034aa7659be12abfd6779e346e2 Mon Sep 17 00:00:00 2001 From: uccu Date: Sun, 22 May 2022 01:54:41 +0800 Subject: [PATCH 2/5] :fix fix parser port parse error --- compose_viz/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose_viz/parser.py b/compose_viz/parser.py index 649e437..0b327e6 100644 --- a/compose_viz/parser.py +++ b/compose_viz/parser.py @@ -59,7 +59,7 @@ class Parser: if service.get("ports"): if type(service["ports"]) is list: for port_data in service["ports"]: - if not port_data.contains(":"): + if ':' not in port_data: raise RuntimeError("Invalid ports input, aborting.") spilt_data = port_data.split(":", 1) service_ports.append(Port(host_port=spilt_data[0], From cd36de0a5f70ab515b9c943d2720ec6bfd6ca101 Mon Sep 17 00:00:00 2001 From: uccu Date: Sun, 22 May 2022 01:57:21 +0800 Subject: [PATCH 3/5] chore: parser add some value checking --- compose_viz/parser.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/compose_viz/parser.py b/compose_viz/parser.py index 0b327e6..345600b 100644 --- a/compose_viz/parser.py +++ b/compose_viz/parser.py @@ -48,14 +48,15 @@ class Parser: if service.get("networks"): if type(service["networks"]) is list: service_networks = service["networks"] - else: + elif type(service["networks"]) is dict: service_networks = list(service["networks"].keys()) service_extends: Optional[Extends] = None if service.get("extends"): - service_extends = Extends(service_name=service["extends"]["service"]) + if service["extends"].get("service"): + service_extends = Extends(service_name=service["extends"]["service"]) - service_ports: List[str] = [] + service_ports: List[Port] = [] if service.get("ports"): if type(service["ports"]) is list: for port_data in service["ports"]: @@ -73,15 +74,23 @@ class Parser: if service.get("volumes"): for volume_data in service["volumes"]: if type(volume_data) is dict: - volume_source = volume_data["source"] - volume_target = volume_data["target"] - volume_type = VolumeType[volume_data["type"]] - service_volumes.append(Volume(source=volume_source, target=volume_target, type=volume_type)) + volume_source: str = None + volume_target: str = None + volume_type: VolumeType.volume = None + if volume_data.get("source"): + volume_source = volume_data["source"] + if volume_data.get("target"): + volume_target = volume_data["target"] + if volume_data.get("type"): + volume_type = VolumeType[volume_data["type"]] + service_volumes.append(Volume(source=volume_source, + target=volume_target, + type=volume_type)) elif type(volume_data) is str: + if ':' not in volume_data: + raise RuntimeError("Invalid volume input, aborting.") spilt_data = volume_data.split(":", 1) - volume_source = spilt_data[0] - volume_target = spilt_data[1] - service_volumes.append(Volume(source=volume_source, target=volume_target)) + service_volumes.append(Volume(source=spilt_data[0], target=spilt_data[1])) service_links: List[str] = [] if service.get("links"): From 31839b7aa6c7e974d7efe0a259fb484a18d784bd Mon Sep 17 00:00:00 2001 From: Xyphuz Date: Sun, 22 May 2022 12:34:18 +0800 Subject: [PATCH 4/5] feat: re-implement parser with compose spec --- compose_viz/parser.py | 126 ++++++++++++++++++++++++++++++++------- compose_viz/volume.py | 17 +++++- tests/test_parse_file.py | 8 ++- tests/test_volume.py | 16 ++++- 4 files changed, 139 insertions(+), 28 deletions(-) diff --git a/compose_viz/parser.py b/compose_viz/parser.py index 345600b..608ef85 100644 --- a/compose_viz/parser.py +++ b/compose_viz/parser.py @@ -1,11 +1,12 @@ +import re from typing import Dict, List, Optional from ruamel.yaml import YAML from compose_viz.compose import Compose, Service from compose_viz.extends import Extends -from compose_viz.port import Port -from compose_viz.volume import Volume, VolumeType +from compose_viz.port import Port, Protocol +from compose_viz.volume import AccessMode, Volume, VolumeType class Parser: @@ -58,13 +59,87 @@ class Parser: service_ports: List[Port] = [] if service.get("ports"): - if type(service["ports"]) is list: - for port_data in service["ports"]: - if ':' not in port_data: - raise RuntimeError("Invalid ports input, aborting.") - spilt_data = port_data.split(":", 1) - service_ports.append(Port(host_port=spilt_data[0], - container_port=spilt_data[1])) + assert type(service["ports"]) is list + for port_data in service["ports"]: + if type(port_data) is dict: + # define a nested function to limit variable scope + def long_syntax(): + assert port_data["target"] + + container_port: str = port_data["target"] + host_port: str = "" + protocol: Protocol = Protocol.tcp + + if port_data.get("host_port"): + host_port = port_data["host_port"] + else: + host_port = container_port + + if port_data.get("host_ip"): + host_ip = str(port_data["host_ip"]) + host_port = f"{host_ip}:{host_port}" + + if port_data.get("protocol"): + protocol = Protocol(port_data["protocol"]) + + assert host_port, "Error parsing port, aborting." + + service_ports.append( + Port( + host_port=host_port, + container_port=container_port, + protocol=protocol, + ) + ) + + long_syntax() + elif type(port_data) is str: + # ports that needs to parse using regex: + # - "3000" + # - "3000-3005" + # - "8000:8000" + # - "9090-9091:8080-8081" + # - "49100:22" + # - "127.0.0.1:8001:8001" + # - "127.0.0.1:5000-5010:5000-5010" + # - "6060:6060/udp" + + def short_syntax(): + regex = r"(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:)?((?P\d+(\-\d+)?):)?((?P\d+(\-\d+)?))?(/(?P\w+))?" # noqa: E501 + match = re.match(regex, port_data) + if match: + host_ip: Optional[str] = match.group("host_ip") + host_port: Optional[str] = match.group("host_port") + container_port: Optional[str] = match.group("container_port") + protocol: Optional[str] = match.group("protocol") + + assert container_port, "Invalid port format, aborting." + + if container_port and not host_port: + host_port = container_port + + if host_ip: + host_port = f"{host_ip}{host_port}" + + assert host_port, "Error while parsing port, aborting." + + if protocol: + service_ports.append( + Port( + host_port=host_port, + container_port=container_port, + protocol=Protocol[protocol], + ) + ) + else: + service_ports.append( + Port( + host_port=host_port, + container_port=container_port, + ) + ) + + short_syntax() service_depends_on: List[str] = [] if service.get("depends_on"): @@ -74,23 +149,28 @@ class Parser: if service.get("volumes"): for volume_data in service["volumes"]: if type(volume_data) is dict: - volume_source: str = None - volume_target: str = None - volume_type: VolumeType.volume = None - if volume_data.get("source"): - volume_source = volume_data["source"] - if volume_data.get("target"): - volume_target = volume_data["target"] + assert volume_data["source"] and volume_data["target"], "Invalid volume input, aborting." + + volume_source: str = volume_data["source"] + volume_target: str = volume_data["target"] + volume_type: VolumeType = VolumeType.volume + if volume_data.get("type"): volume_type = VolumeType[volume_data["type"]] - service_volumes.append(Volume(source=volume_source, - target=volume_target, - type=volume_type)) + + service_volumes.append(Volume(source=volume_source, target=volume_target, type=volume_type)) elif type(volume_data) is str: - if ':' not in volume_data: - raise RuntimeError("Invalid volume input, aborting.") - spilt_data = volume_data.split(":", 1) - service_volumes.append(Volume(source=spilt_data[0], target=spilt_data[1])) + assert ":" in volume_data, "Invalid volume input, aborting." + + spilt_data = volume_data.split(":") + if len(spilt_data) == 2: + service_volumes.append(Volume(source=spilt_data[0], target=spilt_data[1])) + elif len(spilt_data) == 3: + service_volumes.append( + Volume( + source=spilt_data[0], target=spilt_data[1], access_mode=AccessMode[spilt_data[2]] + ) + ) service_links: List[str] = [] if service.get("links"): diff --git a/compose_viz/volume.py b/compose_viz/volume.py index d90faa9..f86d203 100644 --- a/compose_viz/volume.py +++ b/compose_viz/volume.py @@ -5,13 +5,24 @@ class VolumeType(str, Enum): volume = "volume" bind = "bind" tmpfs = "tmpfs" + npipe = "npipe" + + +class AccessMode(str, Enum): + rw = "rw" + ro = "ro" + z = "z" + Z = "Z" class Volume: - def __init__(self, source: str, target: str, type: VolumeType = VolumeType.volume): + def __init__( + self, source: str, target: str, type: VolumeType = VolumeType.volume, access_mode: AccessMode = AccessMode.rw + ): self._source = source self._target = target self._type = type + self._access_mode = access_mode @property def source(self): @@ -24,3 +35,7 @@ class Volume: @property def type(self): return self._type + + @property + def access_mode(self): + return self._access_mode diff --git a/tests/test_parse_file.py b/tests/test_parse_file.py index 7dfe071..8dc3dbf 100644 --- a/tests/test_parse_file.py +++ b/tests/test_parse_file.py @@ -5,7 +5,7 @@ from compose_viz.extends import Extends from compose_viz.parser import Parser from compose_viz.port import Port from compose_viz.service import Service -from compose_viz.volume import Volume, VolumeType +from compose_viz.volume import AccessMode, Volume, VolumeType @pytest.mark.parametrize( @@ -428,12 +428,14 @@ from compose_viz.volume import Volume, VolumeType Service( name="common", image="busybox", - volumes=[Volume(source="common-volume", target="/var/lib/backup/data:rw")], + volumes=[ + Volume(source="common-volume", target="/var/lib/backup/data", access_mode=AccessMode.rw) + ], ), Service( name="cli", extends=Extends(service_name="common"), - volumes=[Volume(source="cli-volume", target="/var/lib/backup/data:ro")], + volumes=[Volume(source="cli-volume", target="/var/lib/backup/data", access_mode=AccessMode.ro)], ), ] ), diff --git a/tests/test_volume.py b/tests/test_volume.py index 8081ae3..d1d4cac 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -1,4 +1,4 @@ -from compose_viz.volume import Volume, VolumeType +from compose_viz.volume import AccessMode, Volume, VolumeType def test_volume_init_normal() -> None: @@ -8,6 +8,7 @@ def test_volume_init_normal() -> None: assert v.source == "./foo" assert v.target == "./bar" assert v.type == VolumeType.volume + assert v.access_mode == AccessMode.rw except Exception as e: assert False, e @@ -19,5 +20,18 @@ def test_volume_with_type() -> None: assert v.source == "./foo" assert v.target == "./bar" assert v.type == VolumeType.bind + assert v.access_mode == AccessMode.rw + except Exception as e: + assert False, e + + +def test_volume_with_access_mode() -> None: + try: + v = Volume(source="./foo", target="./bar", access_mode=AccessMode.ro) + + assert v.source == "./foo" + assert v.target == "./bar" + assert v.type == VolumeType.volume + assert v.access_mode == AccessMode.ro except Exception as e: assert False, e From 914827e928848fda68ceb4b6d1d12b273255c3f2 Mon Sep 17 00:00:00 2001 From: Xyphuz Date: Mon, 23 May 2022 02:13:57 +0800 Subject: [PATCH 5/5] chore: delete unused docker-compose.yaml --- tests/in/docker-compose.yaml | 48 ------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 tests/in/docker-compose.yaml diff --git a/tests/in/docker-compose.yaml b/tests/in/docker-compose.yaml deleted file mode 100644 index 68e98a8..0000000 --- a/tests/in/docker-compose.yaml +++ /dev/null @@ -1,48 +0,0 @@ -services: - frontend: - image: awesome/webapp - networks: - - front-tier - - back-tier - - monitoring: - image: awesome/monitoring - networks: - - admin - - - backend: - image: awesome/backend - networks: - back-tier: - aliases: - - database - admin: - aliases: - - mysql - volumes: - - type: volume - source: db-data - target: /data - volume: - nocopy: true - - type: bind - source: /var/run/postgres/postgres.sock - target: /var/run/postgres/postgres.sock - depends_on: - - monitoring - extends: - service: frontend - ports: - - "8000:5010" - links: - - "db:database" - db: - image: postgres - -networks: - front-tier: - back-tier: - admin: -volumes: - db-data: