Merge pull request #12 from compose-viz/dev-parser

feat: implement full parser
This commit is contained in:
Xyphuz 2022-05-23 02:14:53 +08:00 committed by GitHub
commit 8ba5b75da4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 145 additions and 73 deletions

View file

@ -1,11 +1,12 @@
import re
from typing import Dict, List, Optional from typing import Dict, List, Optional
from ruamel.yaml import YAML from ruamel.yaml import YAML
from compose_viz.compose import Compose, Service from compose_viz.compose import Compose, Service
from compose_viz.extends import Extends from compose_viz.extends import Extends
from compose_viz.port import Port from compose_viz.port import Port, Protocol
from compose_viz.volume import Volume, VolumeType from compose_viz.volume import AccessMode, Volume, VolumeType
class Parser: class Parser:
@ -48,16 +49,97 @@ class Parser:
if service.get("networks"): if service.get("networks"):
if type(service["networks"]) is list: if type(service["networks"]) is list:
service_networks = service["networks"] service_networks = service["networks"]
else: elif type(service["networks"]) is dict:
service_networks = list(service["networks"].keys()) service_networks = list(service["networks"].keys())
service_extends: Optional[Extends] = None service_extends: Optional[Extends] = None
if service.get("extends"): if service.get("extends"):
if service["extends"].get("service"):
service_extends = Extends(service_name=service["extends"]["service"]) service_extends = Extends(service_name=service["extends"]["service"])
service_ports: List[str] = [] service_ports: List[Port] = []
if service.get("ports"): if service.get("ports"):
service_ports = service["ports"] 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<host_ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:)?((?P<host_port>\d+(\-\d+)?):)?((?P<container_port>\d+(\-\d+)?))?(/(?P<protocol>\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] = [] service_depends_on: List[str] = []
if service.get("depends_on"): if service.get("depends_on"):
@ -67,15 +149,28 @@ class Parser:
if service.get("volumes"): if service.get("volumes"):
for volume_data in service["volumes"]: for volume_data in service["volumes"]:
if type(volume_data) is dict: if type(volume_data) is dict:
volume_source = volume_data["source"] assert volume_data["source"] and volume_data["target"], "Invalid volume input, aborting."
volume_target = volume_data["target"]
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"]] 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: elif type(volume_data) is str:
spilt_data = volume_data.split(":", 1) assert ":" in volume_data, "Invalid volume input, aborting."
volume_source = spilt_data[0]
volume_target = spilt_data[1] spilt_data = volume_data.split(":")
service_volumes.append(Volume(source=volume_source, target=volume_target)) 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] = [] service_links: List[str] = []
if service.get("links"): if service.get("links"):
@ -87,13 +182,7 @@ class Parser:
image=service_image, image=service_image,
networks=service_networks, networks=service_networks,
extends=service_extends, extends=service_extends,
ports=[ ports=service_ports,
Port(
# TODO: not implemented yet
host_port=service_ports[0],
container_port=service_ports[0],
)
],
depends_on=service_depends_on, depends_on=service_depends_on,
volumes=service_volumes, volumes=service_volumes,
links=service_links, links=service_links,

View file

@ -5,13 +5,24 @@ class VolumeType(str, Enum):
volume = "volume" volume = "volume"
bind = "bind" bind = "bind"
tmpfs = "tmpfs" tmpfs = "tmpfs"
npipe = "npipe"
class AccessMode(str, Enum):
rw = "rw"
ro = "ro"
z = "z"
Z = "Z"
class Volume: 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._source = source
self._target = target self._target = target
self._type = type self._type = type
self._access_mode = access_mode
@property @property
def source(self): def source(self):
@ -24,3 +35,7 @@ class Volume:
@property @property
def type(self): def type(self):
return self._type return self._type
@property
def access_mode(self):
return self._access_mode

View file

@ -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:

View file

@ -5,7 +5,7 @@ from compose_viz.extends import Extends
from compose_viz.parser import Parser from compose_viz.parser import Parser
from compose_viz.port import Port from compose_viz.port import Port
from compose_viz.service import Service from compose_viz.service import Service
from compose_viz.volume import Volume, VolumeType from compose_viz.volume import AccessMode, Volume, VolumeType
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -428,12 +428,14 @@ from compose_viz.volume import Volume, VolumeType
Service( Service(
name="common", name="common",
image="busybox", 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( Service(
name="cli", name="cli",
extends=Extends(service_name="common"), 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)],
), ),
] ]
), ),

View file

@ -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: def test_volume_init_normal() -> None:
@ -8,6 +8,7 @@ def test_volume_init_normal() -> None:
assert v.source == "./foo" assert v.source == "./foo"
assert v.target == "./bar" assert v.target == "./bar"
assert v.type == VolumeType.volume assert v.type == VolumeType.volume
assert v.access_mode == AccessMode.rw
except Exception as e: except Exception as e:
assert False, e assert False, e
@ -19,5 +20,18 @@ def test_volume_with_type() -> None:
assert v.source == "./foo" assert v.source == "./foo"
assert v.target == "./bar" assert v.target == "./bar"
assert v.type == VolumeType.bind 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: except Exception as e:
assert False, e assert False, e