fix vlan id bug
add new examples add magick for png output
This commit is contained in:
@@ -1,3 +1,9 @@
|
|||||||
- в данный момент маска никуда не прходит
|
- CI для тестов
|
||||||
- обработка виртуальных интерфейсов в yaml
|
- CI для автоматической генерации topology
|
||||||
- добавление bridge и vlan сущностей
|
- CI для автоматической генерации документации (если хватит сил)
|
||||||
|
- Флаг для генерации "чистых" yaml файлов
|
||||||
|
- Добавление к examples yaml файлов (?)
|
||||||
|
- тогда надо и диаграммы, а тогда и сами могут прогнать, лишние файлы
|
||||||
|
- Актуализировать flake
|
||||||
|
- проверить реальность yaml
|
||||||
|
- валидация сейчас раскинута повсюду
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
Name,Role,Adapter,Interface,Master Interface,Network,VLAN,Network IP,Mask,Device IP,Default Gateway
|
Name,Role,Adapter,Interface,Parent Interface,Slave Interfaces,Network,VLAN,Network IP,Mask,Device IP,Default Gateway
|
||||||
PC1,Host,Adapter1,eth1,vlan7,A,,,,,
|
PC1,Host,Adapter1,eth1,,,A,,,,,
|
||||||
PC1,Host,,vlan7,,,7,10.10.10.0,/24,10.10.10.7,0.0.0.0
|
PC1,Host,,vlan7,eth1,,,7,10.10.10.0,/24,10.10.10.7,0.0.0.0
|
||||||
PC2,Host,Adapter1,eth1,vlan9,B,,,,,
|
PC2,Host,Adapter1,eth1,,,B,,,,,
|
||||||
PC2,Host,,vlan9,,,9,10.10.10.0,/24,10.10.10.9,0.0.0.0
|
PC2,Host,,vlan9,eth1,,,9,10.10.10.0,/24,10.10.10.9,0.0.0.0
|
||||||
com,Switch,Adapter1,eth1,vlan7,A,7,,,,
|
com,Switch,Adapter1,eth1,,,A,7,,,,
|
||||||
com,Switch,Adapter2,eth2,vlan9,B,9,,,,
|
com,Switch,Adapter2,eth2,,,B,9,,,,
|
||||||
com,Switch,,vlan7,bridge,,,,,,
|
com,Switch,,vlan7,eth1,,,,,,,
|
||||||
com,Switch,,vlan9,bridge,,,,,,
|
com,Switch,,vlan9,eth2,,,,,,,
|
||||||
com,Switch,,bridge,,,,,,,
|
com,Switch,,bridge,,"vlan7,vlan9",,,,,,wa
|
||||||
|
|
||||||
## REMAKE
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from typing import Any, Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
# ===========================
|
# TODO rewrite to dataclasses with validation
|
||||||
|
|
||||||
|
|
||||||
class Interface:
|
class Interface:
|
||||||
@@ -13,13 +14,13 @@ class Interface:
|
|||||||
network: Optional[str]
|
network: Optional[str]
|
||||||
subnet_mask: Optional[str]
|
subnet_mask: Optional[str]
|
||||||
default_gateway: Optional[str]
|
default_gateway: Optional[str]
|
||||||
|
vlan: Optional[str]
|
||||||
# ---
|
# ---
|
||||||
device: "Device" # set by Device.add_interface() when the interface is added to a device
|
device: "Device" # set by Device.add_interface() when the interface is added to a device
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
itype: Optional[str] = None,
|
|
||||||
adapter: Optional[str] = None,
|
adapter: Optional[str] = None,
|
||||||
slave_interfaces: Optional[List[str]] = None,
|
slave_interfaces: Optional[List[str]] = None,
|
||||||
parent_interface: Optional[str] = None,
|
parent_interface: Optional[str] = None,
|
||||||
@@ -27,11 +28,10 @@ class Interface:
|
|||||||
network: Optional[str] = None,
|
network: Optional[str] = None,
|
||||||
subnet_mask: Optional[str] = None,
|
subnet_mask: Optional[str] = None,
|
||||||
default_gateway: Optional[str] = None,
|
default_gateway: Optional[str] = None,
|
||||||
|
vlan: Optional[str] = None,
|
||||||
):
|
):
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise ValueError("Interface 'name' must be a string")
|
raise ValueError("Interface 'name' must be a string")
|
||||||
if itype is not None and not isinstance(itype, str):
|
|
||||||
raise ValueError("Interface 'itype' must be a string or None")
|
|
||||||
if ip_address is not None and not isinstance(ip_address, str):
|
if ip_address is not None and not isinstance(ip_address, str):
|
||||||
raise ValueError("Interface 'ip_address' must be a string or None")
|
raise ValueError("Interface 'ip_address' must be a string or None")
|
||||||
if network is not None and not isinstance(network, str):
|
if network is not None and not isinstance(network, str):
|
||||||
@@ -48,33 +48,43 @@ class Interface:
|
|||||||
)
|
)
|
||||||
if parent_interface is not None and not isinstance(parent_interface, str):
|
if parent_interface is not None and not isinstance(parent_interface, str):
|
||||||
raise ValueError("Interface 'parent_interface' must be a string or None")
|
raise ValueError("Interface 'parent_interface' must be a string or None")
|
||||||
|
if vlan is not None and not isinstance(vlan, str):
|
||||||
|
raise ValueError("Interface 'vlan' must be a string or None")
|
||||||
|
|
||||||
itype = None if itype == "" else itype
|
# edit this
|
||||||
adapter = None if adapter == "" else adapter
|
adapter = None if adapter == "" else adapter
|
||||||
|
slave_interfaces = None if slave_interfaces == [] else slave_interfaces
|
||||||
|
parent_interface = None if parent_interface == "" else parent_interface
|
||||||
|
vlan = None if vlan == "" else vlan
|
||||||
|
|
||||||
if itype is None and adapter is None:
|
logging.debug(
|
||||||
itype = "virtual"
|
f"Creating interface '{name}' with adapter='{adapter}', slave_interfaces='{slave_interfaces}', parent_interface='{parent_interface}'"
|
||||||
if adapter is not None:
|
)
|
||||||
if itype is not None and itype != "physical":
|
|
||||||
raise ValueError(
|
|
||||||
"Interface with an adapter must have 'itype' set to 'physical'"
|
|
||||||
)
|
|
||||||
itype = "physical"
|
|
||||||
|
|
||||||
if itype not in (None, "physical", "virtual", "bridge", "vlan"):
|
# looks like shit
|
||||||
|
if (
|
||||||
|
(adapter is not None)
|
||||||
|
+ (slave_interfaces is not None)
|
||||||
|
+ (parent_interface is not None)
|
||||||
|
) != 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Interface 'itype' must be one of 'physical', 'virtual', 'bridge', 'vlan', or None"
|
"Interface must have exactly one of 'adapter', 'slave_interfaces', or 'parent_interface' defined"
|
||||||
)
|
)
|
||||||
|
|
||||||
# print(
|
if slave_interfaces is not None:
|
||||||
# f"Creating interface '{name}' with itype='{itype}', adapter='{adapter}', slave_interfaces='{slave_interfaces}', parent_interface='{parent_interface}', ip_address='{ip_address}', network='{network}', subnet_mask='{subnet_mask}', default_gateway='{default_gateway}'"
|
itype = "bridge"
|
||||||
# )
|
network = None
|
||||||
|
elif parent_interface is not None:
|
||||||
if itype == "bridge" and not slave_interfaces:
|
if vlan is None:
|
||||||
raise ValueError("Bridge interfaces must have 'slave_interfaces' defined")
|
raise ValueError(
|
||||||
if itype == "vlan" and not parent_interface:
|
"Interface with 'parent_interface' must have 'vlan' defined"
|
||||||
raise ValueError("VLAN interfaces must have 'parent_interface' defined")
|
)
|
||||||
|
itype = "vlan"
|
||||||
|
network = None
|
||||||
|
else:
|
||||||
|
itype = "physical"
|
||||||
|
|
||||||
|
# sort all this values
|
||||||
self.name = name
|
self.name = name
|
||||||
self.itype = itype
|
self.itype = itype
|
||||||
self.ip_address = ip_address
|
self.ip_address = ip_address
|
||||||
@@ -84,9 +94,10 @@ class Interface:
|
|||||||
self.adapter = adapter
|
self.adapter = adapter
|
||||||
self.slave_interfaces = slave_interfaces
|
self.slave_interfaces = slave_interfaces
|
||||||
self.parent_interface = parent_interface
|
self.parent_interface = parent_interface
|
||||||
|
self.vlan = vlan
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Interface(name={self.name}, itype={self.itype}, ip_address={self.ip_address}, network={self.network}, default_gateway={self.default_gateway}, subnet_mask={self.subnet_mask}, adapter={self.adapter}, slave_interfaces={self.slave_interfaces})"
|
return f"Interface(name={self.name}, itype={self.itype}, ip_address={self.ip_address}, network={self.network}, default_gateway={self.default_gateway}, subnet_mask={self.subnet_mask}, adapter={self.adapter}, slave_interfaces={self.slave_interfaces}, vlan={self.vlan})"
|
||||||
|
|
||||||
|
|
||||||
# ===========================
|
# ===========================
|
||||||
@@ -113,7 +124,6 @@ class Device:
|
|||||||
def rm_interface(self, interface: Interface):
|
def rm_interface(self, interface: Interface):
|
||||||
if interface.name in self.interfaces:
|
if interface.name in self.interfaces:
|
||||||
del self.interfaces[interface.name]
|
del self.interfaces[interface.name]
|
||||||
# interface.device = None # clear the device attribute of the interface
|
|
||||||
del interface
|
del interface
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -142,8 +152,7 @@ class Switch(Device):
|
|||||||
class Network:
|
class Network:
|
||||||
name: str
|
name: str
|
||||||
interfaces: List[Interface]
|
interfaces: List[Interface]
|
||||||
vlan: Optional[str] # need a proper parse
|
network_ip: Optional[str] # а что он делает?
|
||||||
network_ip: Optional[str]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
+31
-32
@@ -10,42 +10,15 @@ from py_d2.connection import Direction
|
|||||||
# https://d2lang.com/tour/themes/
|
# https://d2lang.com/tour/themes/
|
||||||
THEME_NUMBER = 200
|
THEME_NUMBER = 200
|
||||||
|
|
||||||
"""
|
|
||||||
PC2
|
|
||||||
|
|
||||||
PC2
|
|
||||||
|
|
||||||
PC3
|
|
||||||
|
|
||||||
PC3.adapter1 : {
|
|
||||||
shape: parallelogram
|
|
||||||
}
|
|
||||||
|
|
||||||
PC3.adapter2 : {
|
|
||||||
shape: parallelogram
|
|
||||||
}
|
|
||||||
|
|
||||||
netw_A : {
|
|
||||||
shape: cloud
|
|
||||||
}
|
|
||||||
netw_B : {
|
|
||||||
shape: cloud
|
|
||||||
}
|
|
||||||
|
|
||||||
PC1.adapter1 -- netw_A
|
|
||||||
PC2.adapter1 -- netw_A
|
|
||||||
PC3.adapter1 -- netw_A
|
|
||||||
|
|
||||||
PC1.adapter2 -- netw_B
|
|
||||||
PC3.adapter2 -- netw_B
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _check_d2_installed() -> bool:
|
def _check_d2_installed() -> bool:
|
||||||
return shutil.which("d2") is not None
|
return shutil.which("d2") is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _check_magick_installed() -> bool:
|
||||||
|
return shutil.which("magick") is not None
|
||||||
|
|
||||||
|
|
||||||
def generate_d2_diagram(topology: Topology, output_path: Path) -> None:
|
def generate_d2_diagram(topology: Topology, output_path: Path) -> None:
|
||||||
shapes = []
|
shapes = []
|
||||||
connections = []
|
connections = []
|
||||||
@@ -80,7 +53,33 @@ def generate_d2_diagram(topology: Topology, output_path: Path) -> None:
|
|||||||
with open(output_path, "w", encoding="utf-8") as f:
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
f.write(str(diagram))
|
f.write(str(diagram))
|
||||||
|
|
||||||
_generate_picture(output_path, output_path.with_suffix(".png"))
|
_generate_picture(
|
||||||
|
output_path, output_path.with_suffix(".png")
|
||||||
|
) # output_path not used
|
||||||
|
|
||||||
|
_run_magick_command(
|
||||||
|
output_path.with_suffix(".svg"), output_path.with_suffix(".png")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_magick_command(input_path: Path, output_path: Path) -> None:
|
||||||
|
if not _check_magick_installed():
|
||||||
|
raise EnvironmentError(
|
||||||
|
"ImageMagick is not installed or 'magick' command is not found in PATH. Please install ImageMagick to use this feature."
|
||||||
|
)
|
||||||
|
|
||||||
|
res = subprocess.run(
|
||||||
|
["magick", "convert", str(input_path), str(output_path)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if res.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Image conversion failed (code {res.returncode})\n"
|
||||||
|
f"Stdout: {res.stdout.decode()}\n"
|
||||||
|
f"Stderr: {res.stderr.decode()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _generate_picture(diagram: Path, output_path: Path) -> None:
|
def _generate_picture(diagram: Path, output_path: Path) -> None:
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ def make_yaml(topology: Topology, output_path: Path) -> None:
|
|||||||
"name": interface.name,
|
"name": interface.name,
|
||||||
"members": interface.slave_interfaces,
|
"members": interface.slave_interfaces,
|
||||||
"ip": ip,
|
"ip": ip,
|
||||||
"network": interface.network if interface.network else None,
|
|
||||||
"gateway": (
|
"gateway": (
|
||||||
interface.default_gateway
|
interface.default_gateway
|
||||||
if interface.default_gateway
|
if interface.default_gateway
|
||||||
@@ -75,12 +74,12 @@ def make_yaml(topology: Topology, output_path: Path) -> None:
|
|||||||
"name": interface.name,
|
"name": interface.name,
|
||||||
"parent": interface.parent_interface,
|
"parent": interface.parent_interface,
|
||||||
"ip": ip,
|
"ip": ip,
|
||||||
"network": interface.network if interface.network else None,
|
|
||||||
"gateway": (
|
"gateway": (
|
||||||
interface.default_gateway
|
interface.default_gateway
|
||||||
if interface.default_gateway
|
if interface.default_gateway
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
|
"id": interface.vlan,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ from pathlib import Path
|
|||||||
import graphviz
|
import graphviz
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# - find ithers implementations of graphviz
|
||||||
|
# - actualize code
|
||||||
|
|
||||||
|
|
||||||
def _check_graphviz_installed() -> bool:
|
def _check_graphviz_installed() -> bool:
|
||||||
return shutil.which("dot") is not None
|
return shutil.which("dot") is not None
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ name_matching = {
|
|||||||
"DEVICE_TYPE": "Role",
|
"DEVICE_TYPE": "Role",
|
||||||
"DEVICE_NAME": "Name",
|
"DEVICE_NAME": "Name",
|
||||||
"ADAPTER": "Adapter",
|
"ADAPTER": "Adapter",
|
||||||
"INTERFACE TYPE": "Interface Type",
|
|
||||||
"MASTER": "Master Interface",
|
"MASTER": "Master Interface",
|
||||||
"SLAVES": "Slave Interfaces",
|
"SLAVES": "Slave Interfaces",
|
||||||
"PARENT": "Parent Interface",
|
"PARENT": "Parent Interface",
|
||||||
@@ -83,13 +82,13 @@ def add_interfaces(devices: list[Device], raw_devices: list[RawDevices]) -> None
|
|||||||
|
|
||||||
interface = Interface(
|
interface = Interface(
|
||||||
name=_get_from_field(raw_device.fields, "INTERFACE_NAME"),
|
name=_get_from_field(raw_device.fields, "INTERFACE_NAME"),
|
||||||
itype=_get_from_field(raw_device.fields, "INTERFACE TYPE"),
|
|
||||||
adapter=_get_from_field(raw_device.fields, "ADAPTER"),
|
adapter=_get_from_field(raw_device.fields, "ADAPTER"),
|
||||||
slave_interfaces=(
|
slave_interfaces=(
|
||||||
_get_from_field(raw_device.fields, "SLAVES").split(",")
|
_get_from_field(raw_device.fields, "SLAVES").split(",")
|
||||||
if _get_from_field(raw_device.fields, "SLAVES")
|
if _get_from_field(raw_device.fields, "SLAVES")
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
|
vlan=_get_from_field(raw_device.fields, "VLAN"),
|
||||||
parent_interface=_get_from_field(raw_device.fields, "PARENT"),
|
parent_interface=_get_from_field(raw_device.fields, "PARENT"),
|
||||||
ip_address=_get_from_field(raw_device.fields, "IP_ADDRESS"),
|
ip_address=_get_from_field(raw_device.fields, "IP_ADDRESS"),
|
||||||
network=_get_from_field(raw_device.fields, "NETWORK_NAME"),
|
network=_get_from_field(raw_device.fields, "NETWORK_NAME"),
|
||||||
@@ -109,21 +108,16 @@ def parse_networks(raw_devices: list[RawDevices]) -> list[Network]:
|
|||||||
if not network_name:
|
if not network_name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
vlan = (raw_device.fields.get(name_matching["VLAN"], "").strip(),)
|
|
||||||
network_ip = (raw_device.fields.get(name_matching["NETWORK_IP"], "").strip(),)
|
network_ip = (raw_device.fields.get(name_matching["NETWORK_IP"], "").strip(),)
|
||||||
|
|
||||||
if network_name in [n.name for n in networks]: # update if already exists
|
if network_name in [n.name for n in networks]: # update if already exists
|
||||||
network = next(n for n in networks if n.name == network_name)
|
network = next(n for n in networks if n.name == network_name)
|
||||||
|
|
||||||
if not network.vlan and vlan:
|
|
||||||
network.vlan = vlan
|
|
||||||
if not network.network_ip and network_ip:
|
if not network.network_ip and network_ip:
|
||||||
network.network_ip = network_ip
|
network.network_ip = network_ip
|
||||||
continue
|
continue
|
||||||
|
|
||||||
network = Network(
|
network = Network(
|
||||||
name=network_name,
|
name=network_name,
|
||||||
vlan=raw_device.fields.get(name_matching["VLAN"], "").strip(),
|
|
||||||
network_ip=raw_device.fields.get(name_matching["NETWORK_IP"], "").strip(),
|
network_ip=raw_device.fields.get(name_matching["NETWORK_IP"], "").strip(),
|
||||||
)
|
)
|
||||||
networks.append(network)
|
networks.append(network)
|
||||||
|
|||||||
Reference in New Issue
Block a user