From 52cd77735c119fadea1e2ac7213dd0845013c13b Mon Sep 17 00:00:00 2001 From: Krosh Date: Mon, 1 Dec 2025 02:19:22 +0300 Subject: [PATCH] some terror with structures --- host-host.csv | 6 ++++ output.yaml | 16 +++++++++ schema.yaml | 21 ++++++++++++ src/check_correct.py | 4 ++- src/make_res.py | 81 ++++++++++++++++++++++++++++++++++++++++++++ src/parse.py | 11 +++--- src/structures.py | 44 ++++++++++++++++++++---- 7 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 host-host.csv create mode 100644 output.yaml create mode 100644 schema.yaml mode change 100644 => 100755 src/check_correct.py create mode 100644 src/make_res.py diff --git a/host-host.csv b/host-host.csv new file mode 100644 index 0000000..94a6413 --- /dev/null +++ b/host-host.csv @@ -0,0 +1,6 @@ +name,"type","interfaces","ip","endpoints" +PC1,"host","int1","","" +PC2,"host","int2","","" +int1,"interface","","10.0.12.1/24","" +int2,"interface","","10.0.12.2/24","" +link,"wire","","","int1,int2" \ No newline at end of file diff --git a/output.yaml b/output.yaml new file mode 100644 index 0000000..220ed9a --- /dev/null +++ b/output.yaml @@ -0,0 +1,16 @@ +links: +- endpoints: + - PC1 + - PC2 +meta: + id: /home/krosh/Documents/Github/network-diagrams-tool/host-host.csv + name: /home/krosh/Documents/Github/network-diagrams-tool/host-host.csv +nodes: +- interfaces: + - ip: 10.0.12.1/24 + name: PC1 + role: host +- interfaces: + - ip: 10.0.12.2/24 + name: PC2 + role: host diff --git a/schema.yaml b/schema.yaml new file mode 100644 index 0000000..e47ba5b --- /dev/null +++ b/schema.yaml @@ -0,0 +1,21 @@ +meta: + id: "lab-02-basic-utils" + name: "Basic Network Utilities & Traffic Monitoring" + description: "Two hosts connected directly to each other." + +links: + - endpoints: ["PC1", "PC2"] + +nodes: + - name: PC1 + role: host + interfaces: + - ip: "10.0.12.1/24" + + - name: PC2 + role: host + interfaces: + - ip: "10.0.12.2/24" + + # Optional: + # - "ip link set dev eth1 down" diff --git a/src/check_correct.py b/src/check_correct.py old mode 100644 new mode 100755 index ba0359e..f417406 --- a/src/check_correct.py +++ b/src/check_correct.py @@ -1,4 +1,6 @@ from parse import parse_csv_to_structure +import sys +from structures import Host, Wire def check_correct(data): @@ -14,7 +16,7 @@ def check_correct(data): if __name__ == "__main__": - data = parse_csv_to_structure("input.csv") + data = parse_csv_to_structure(sys.argv[1]) if check_correct(data): print("yay!") print(data) diff --git a/src/make_res.py b/src/make_res.py new file mode 100644 index 0000000..ee4f06d --- /dev/null +++ b/src/make_res.py @@ -0,0 +1,81 @@ +from check_correct import check_correct +from parse import parse_csv_to_structure +import sys +from structures import Host, Wire, Interface +import yaml + + +""" +meta: + id: "lab-02-basic-utils" + name: "Basic Network Utilities & Traffic Monitoring" + description: "Two hosts connected directly to each other." + +links: + - endpoints: ["PC1", "PC2"] + +nodes: + - name: PC1 + role: host + interfaces: + - ip: "10.0.12.1/24" + + - name: PC2 + role: host + interfaces: + - ip: "10.0.12.2/24" +""" + + +def make_res(data, name): + res = { + "meta": { + "id": name, + "name": name, + }, + "links": [], + "nodes": [], + } + + host_int = {} + + for key, value in data.items(): + if value.get("type") == "host": + value["name"] = key + host = Host(value) + node_entry = { + "name": host.name, + "role": "host", + "interfaces": [ + {"ip": Interface(data[iface]).ip_address} + for iface in host.interfaces + ], + } + res["nodes"].append(node_entry) + + host_int[host.interfaces[0]] = host.name + + for key, value in data.items(): + if value.get("type") == "wire": + wire = Wire(value) + link_entry = { + "endpoints": [ + host_int[wire.endpoints[0]], + host_int[wire.endpoints[1]], + ] + } + res["links"].append(link_entry) + + return res + + +if __name__ == "__main__": + data = parse_csv_to_structure(sys.argv[1]) + print(data) + if check_correct(data): + print("Data is correct.") + res = make_res(data, sys.argv[1]) + print(res) + + with open("output.yaml", "w") as f: + yaml.dump(res, f) diff --git a/src/parse.py b/src/parse.py index ed0a1ef..ea579a7 100644 --- a/src/parse.py +++ b/src/parse.py @@ -1,24 +1,25 @@ import csv + def parse_csv_to_structure(path): result = {} - with open(path, newline='') as f: + with open(path, newline="") as f: reader = csv.reader(f) rows = list(reader) headers = rows[0][1:] - + for row in rows[1:]: obj = row[0].strip() - + if not obj: continue - + result[obj] = {} for header, value in zip(headers, row[1:]): value = value.strip() - + if value: result[obj][header] = value diff --git a/src/structures.py b/src/structures.py index 5c6e0b6..159c74a 100644 --- a/src/structures.py +++ b/src/structures.py @@ -6,6 +6,7 @@ from typing import List, Optional, Dict # Interface Classes # =========================== + @dataclass class Interface: name: str @@ -14,6 +15,13 @@ class Interface: speed: Optional[str] = None duplex: Optional[str] = None + def __init__(self, fields): + self.name = fields.get("name") + self.ip_address = fields.get("ip") + self.mac_address = fields.get("mac") + self.speed = fields.get("speed") + self.duplex = fields.get("duplex") + def __post_init__(self): if not isinstance(self.name, str) or not self.name: raise ValueError("Interface 'name' must be a non-empty string") @@ -27,15 +35,20 @@ class VirtualInterface(Interface): def __post_init__(self): super().__post_init__() if not isinstance(self.vlan_id, int) or self.vlan_id < 0: - raise ValueError("VirtualInterface 'vlan_id' must be a non-negative integer") + raise ValueError( + "VirtualInterface 'vlan_id' must be a non-negative integer" + ) if not isinstance(self.parent_physical, Interface): - raise TypeError("VirtualInterface 'parent_physical' must reference an Interface") + raise TypeError( + "VirtualInterface 'parent_physical' must reference an Interface" + ) # =========================== # Network / Subnet # =========================== + @dataclass class Network: cidr: str @@ -53,12 +66,18 @@ class Network: # Device Base Class (Abstract) # =========================== + @dataclass class Device: name: str mgmt_ip: Optional[str] = None interfaces: List[Interface] = field(default_factory=list) + def __init__(self, fields): + self.name = fields.get("name") + self.mgmt_ip = fields.get("mgmtIP") + self.interfaces = [f for f in fields.get("interfaces").split(",") if f] + def __post_init__(self): if not isinstance(self.name, str) or not self.name: raise ValueError("Device 'name' must be a non-empty string") @@ -70,10 +89,15 @@ class Device: # Host / Router / Switch # =========================== + @dataclass class Host(Device): operating_system: Optional[str] = None + def __init__(self, fields): + super().__init__(fields) + self.operating_system = fields.get("operatingSystem") + def __post_init__(self): super().__post_init__() if not isinstance(self.operating_system, str): @@ -102,16 +126,24 @@ class Switch(Device): # Physical Link (Wire) # =========================== + @dataclass class Wire: - type: str + id: str endpoints: List[Interface] # Must contain exactly 2 interfaces bandwidth: Optional[str] = None + def __init__(self, fields): + self.id = fields.get("name") + self.endpoints = [ep for ep in fields.get("endpoints").split(",") if ep] + self.bandwidth = fields.get("bandwidth") + def __post_init__(self): - if not isinstance(self.type, str) or not self.type: - raise ValueError("Wire 'type' must be a non-empty string") + if not isinstance(self.id, str) or not self.id: + raise ValueError("Wire 'id' must be a non-empty string") if len(self.endpoints) != 2: - raise ValueError("Wire 'endpoints' must contain exactly 2 Interface objects") + raise ValueError( + "Wire 'endpoints' must contain exactly 2 Interface objects" + ) if not all(isinstance(i, Interface) for i in self.endpoints): raise TypeError("Wire 'endpoints' must be Interface objects")