2022-05-22 06:34:18 +02:00
|
|
|
import re
|
2022-05-27 11:29:07 +02:00
|
|
|
from typing import List, Optional
|
2022-05-07 18:42:14 +02:00
|
|
|
|
2022-05-27 13:56:30 +02:00
|
|
|
from pydantic import ValidationError
|
|
|
|
|
2022-05-27 11:29:07 +02:00
|
|
|
import compose_viz.spec.compose_spec as spec
|
2022-05-27 08:17:04 +02:00
|
|
|
from compose_viz.models.compose import Compose, Service
|
|
|
|
from compose_viz.models.extends import Extends
|
|
|
|
from compose_viz.models.port import Port, Protocol
|
|
|
|
from compose_viz.models.volume import Volume, VolumeType
|
2022-05-21 11:47:35 +02:00
|
|
|
|
2022-05-21 16:32:59 +02:00
|
|
|
|
2022-05-07 18:42:14 +02:00
|
|
|
class Parser:
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def parse(self, file_path: str) -> Compose:
|
2022-05-27 13:56:30 +02:00
|
|
|
compose_data: spec.ComposeSpecification
|
|
|
|
|
|
|
|
try:
|
|
|
|
compose_data = spec.ComposeSpecification.parse_file(file_path)
|
|
|
|
except ValidationError as e:
|
|
|
|
raise RuntimeError(f"Error parsing file '{file_path}': {e}")
|
|
|
|
|
2022-05-21 16:32:59 +02:00
|
|
|
services: List[Service] = []
|
2022-05-27 11:29:07 +02:00
|
|
|
|
|
|
|
assert compose_data.services is not None, "No services found, aborting."
|
|
|
|
|
|
|
|
for service_name, service_data in compose_data.services.items():
|
|
|
|
service_name = str(service_name)
|
2022-05-21 16:32:59 +02:00
|
|
|
|
2022-05-18 17:28:18 +02:00
|
|
|
service_image: Optional[str] = None
|
2022-05-27 11:29:07 +02:00
|
|
|
if service_data.build is not None:
|
|
|
|
if type(service_data.build) is str:
|
|
|
|
service_image = f"build from '{service_data.build}'"
|
|
|
|
elif type(service_data.build) is spec.BuildItem:
|
|
|
|
if service_data.build.context is not None and service_data.build.dockerfile is not None:
|
2022-05-25 11:47:23 +02:00
|
|
|
service_image = (
|
2022-05-27 11:29:07 +02:00
|
|
|
f"build from '{service_data.build.context}' using '{service_data.build.dockerfile}'"
|
2022-05-25 11:47:23 +02:00
|
|
|
)
|
2022-05-27 11:29:07 +02:00
|
|
|
elif service_data.build.context is not None:
|
|
|
|
service_image = f"build from '{service_data.build.context}'"
|
|
|
|
if service_data.image is not None:
|
|
|
|
if service_image is not None:
|
|
|
|
service_image += ", image: " + service_data.image
|
2022-05-25 11:47:23 +02:00
|
|
|
else:
|
2022-05-27 11:29:07 +02:00
|
|
|
service_image = service_data.image
|
2022-05-18 17:28:18 +02:00
|
|
|
|
|
|
|
service_networks: List[str] = []
|
2022-05-27 11:29:07 +02:00
|
|
|
if service_data.networks is not None:
|
|
|
|
if type(service_data.networks) is spec.ListOfStrings:
|
|
|
|
service_networks = service_data.networks.__root__
|
|
|
|
elif type(service_data.networks) is dict:
|
|
|
|
service_networks = list(service_data.networks.keys())
|
2022-05-21 08:32:07 +02:00
|
|
|
|
|
|
|
service_extends: Optional[Extends] = None
|
2022-05-27 11:29:07 +02:00
|
|
|
if service_data.extends is not None:
|
2022-05-27 14:27:59 +02:00
|
|
|
# https://github.com/compose-spec/compose-spec/blob/master/spec.md#extends
|
|
|
|
# The value of the extends key MUST be a dictionary.
|
|
|
|
assert type(service_data.extends) is spec.Extend
|
|
|
|
service_extends = Extends(
|
|
|
|
service_name=service_data.extends.service, from_file=service_data.extends.file
|
|
|
|
)
|
2022-05-18 17:28:18 +02:00
|
|
|
|
2022-05-21 19:57:21 +02:00
|
|
|
service_ports: List[Port] = []
|
2022-05-27 11:29:07 +02:00
|
|
|
if service_data.ports is not None:
|
|
|
|
for port_data in service_data.ports:
|
|
|
|
host_ip: Optional[str] = None
|
|
|
|
host_port: Optional[str] = None
|
|
|
|
container_port: Optional[str] = None
|
|
|
|
protocol: Optional[str] = None
|
|
|
|
|
2022-05-27 11:37:39 +02:00
|
|
|
if type(port_data) is float:
|
|
|
|
container_port = str(int(port_data))
|
|
|
|
host_port = f"0.0.0.0:{container_port}"
|
|
|
|
elif type(port_data) is str:
|
2022-05-27 11:29:07 +02:00
|
|
|
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 = match.group("host_ip")
|
|
|
|
host_port = match.group("host_port")
|
|
|
|
container_port = match.group("container_port")
|
|
|
|
protocol = match.group("protocol")
|
|
|
|
|
|
|
|
assert container_port, "Invalid port format, aborting."
|
|
|
|
|
2022-05-27 11:37:39 +02:00
|
|
|
if container_port is not None and host_port is None:
|
2022-05-22 06:34:18 +02:00
|
|
|
host_port = container_port
|
|
|
|
|
2022-05-27 11:37:39 +02:00
|
|
|
if host_ip is not None:
|
2022-05-27 11:29:07 +02:00
|
|
|
host_port = f"{host_ip}{host_port}"
|
2022-05-24 10:02:47 +02:00
|
|
|
else:
|
|
|
|
host_port = f"0.0.0.0:{host_port}"
|
2022-05-27 11:29:07 +02:00
|
|
|
elif type(port_data) is spec.Port:
|
|
|
|
assert port_data.target is not None, "Invalid port format, aborting."
|
2022-05-22 06:34:18 +02:00
|
|
|
|
2022-05-27 14:27:59 +02:00
|
|
|
# ruamel.yaml does not parse port as int
|
|
|
|
assert type(port_data.published) is not int
|
|
|
|
|
|
|
|
if type(port_data.published) is str:
|
2022-05-27 11:29:07 +02:00
|
|
|
host_port = port_data.published
|
2022-05-22 06:34:18 +02:00
|
|
|
|
2022-05-27 11:29:07 +02:00
|
|
|
if type(port_data.target) is int:
|
|
|
|
container_port = str(port_data.target)
|
2022-05-22 06:34:18 +02:00
|
|
|
|
2022-05-27 11:29:07 +02:00
|
|
|
host_ip = port_data.host_ip
|
|
|
|
protocol = port_data.protocol
|
2022-05-22 06:34:18 +02:00
|
|
|
|
2022-05-27 11:37:39 +02:00
|
|
|
if container_port is not None and host_port is None:
|
2022-05-27 11:29:07 +02:00
|
|
|
host_port = container_port
|
2022-05-18 17:28:18 +02:00
|
|
|
|
2022-05-27 11:37:39 +02:00
|
|
|
if host_ip is not None:
|
|
|
|
host_port = f"{host_ip}:{host_port}"
|
2022-05-27 11:29:07 +02:00
|
|
|
else:
|
|
|
|
host_port = f"0.0.0.0:{host_port}"
|
2022-05-14 15:30:18 +02:00
|
|
|
|
2022-05-27 11:29:07 +02:00
|
|
|
assert host_port is not None, "Error while parsing port, aborting."
|
|
|
|
assert container_port is not None, "Error while parsing port, aborting."
|
2022-05-22 06:34:18 +02:00
|
|
|
|
2022-05-27 11:29:07 +02:00
|
|
|
if protocol is None:
|
|
|
|
protocol = "any"
|
2022-05-22 06:34:18 +02:00
|
|
|
|
2022-05-27 11:29:07 +02:00
|
|
|
service_ports.append(
|
|
|
|
Port(
|
|
|
|
host_port=host_port,
|
|
|
|
container_port=container_port,
|
|
|
|
protocol=Protocol[protocol],
|
|
|
|
)
|
|
|
|
)
|
2022-05-22 06:34:18 +02:00
|
|
|
|
2022-05-27 11:29:07 +02:00
|
|
|
service_depends_on: List[str] = []
|
|
|
|
if service_data.depends_on is not None:
|
|
|
|
if type(service_data.depends_on) is spec.ListOfStrings:
|
|
|
|
service_depends_on = service_data.depends_on.__root__
|
|
|
|
elif type(service_data.depends_on) is dict:
|
|
|
|
for depends_on in service_data.depends_on.keys():
|
|
|
|
service_depends_on.append(str(depends_on))
|
|
|
|
|
|
|
|
service_volumes: List[Volume] = []
|
|
|
|
if service_data.volumes is not None:
|
|
|
|
for volume_data in service_data.volumes:
|
|
|
|
if type(volume_data) is str:
|
2022-05-22 06:34:18 +02:00
|
|
|
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(
|
2022-05-27 11:29:07 +02:00
|
|
|
Volume(
|
|
|
|
source=spilt_data[0],
|
|
|
|
target=spilt_data[1],
|
|
|
|
access_mode=spilt_data[2],
|
|
|
|
)
|
2022-05-22 06:34:18 +02:00
|
|
|
)
|
2022-05-27 11:29:07 +02:00
|
|
|
elif type(volume_data) is spec.ServiceVolume:
|
|
|
|
assert volume_data.target is not None, "Invalid volume input, aborting."
|
|
|
|
|
2022-05-27 14:27:59 +02:00
|
|
|
# https://github.com/compose-spec/compose-spec/blob/master/spec.md#long-syntax-4
|
|
|
|
# `volume_data.source` is not applicable for a tmpfs mount.
|
2022-05-27 11:29:07 +02:00
|
|
|
if volume_data.source is None:
|
|
|
|
volume_data.source = volume_data.target
|
|
|
|
|
|
|
|
assert volume_data.source is not None
|
|
|
|
|
|
|
|
service_volumes.append(
|
|
|
|
Volume(
|
|
|
|
source=volume_data.source,
|
|
|
|
target=volume_data.target,
|
|
|
|
type=VolumeType[volume_data.type],
|
|
|
|
)
|
|
|
|
)
|
2022-05-21 15:57:18 +02:00
|
|
|
|
2022-05-21 16:00:38 +02:00
|
|
|
service_links: List[str] = []
|
2022-05-27 11:29:07 +02:00
|
|
|
if service_data.links is not None:
|
|
|
|
service_links = service_data.links
|
2022-05-21 15:57:18 +02:00
|
|
|
|
2022-05-21 10:08:35 +02:00
|
|
|
services.append(
|
|
|
|
Service(
|
|
|
|
name=service_name,
|
|
|
|
image=service_image,
|
|
|
|
networks=service_networks,
|
|
|
|
extends=service_extends,
|
2022-05-21 19:50:07 +02:00
|
|
|
ports=service_ports,
|
2022-05-21 10:08:35 +02:00
|
|
|
depends_on=service_depends_on,
|
2022-05-21 15:57:18 +02:00
|
|
|
volumes=service_volumes,
|
2022-05-21 16:32:59 +02:00
|
|
|
links=service_links,
|
2022-05-21 10:08:35 +02:00
|
|
|
)
|
|
|
|
)
|
2022-05-27 11:29:07 +02:00
|
|
|
|
|
|
|
return Compose(services=services)
|