From 4e598ae400caeced8adc5e3b0fe03a4b6f43fd2a Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 4 Feb 2026 18:23:04 +0100 Subject: [PATCH] fix(script): update populate script --- pyproject.toml | 1 + scripts/README.md | 54 ++++++++++++++--------------- scripts/provision_fabric.py | 68 ++++++++++++++++++++++++++++++------- uv.lock | 34 ++++++++++++++++++- 4 files changed, 117 insertions(+), 40 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ce4d94..af449fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,5 +34,6 @@ ignore = ["E501"] [dependency-groups] dev = [ + "basedpyright>=1.37.4", "ruff>=0.14.10", ] diff --git a/scripts/README.md b/scripts/README.md index 7b3034c..e0ff3b7 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -27,18 +27,18 @@ uv run python scripts/provision_fabric.py ### Custom Fields -| Object Type | Field | Description | -|-------------|-------|-------------| -| Device | `asn` | BGP ASN | -| Device | `mlag_domain_id` | MLAG domain identifier | -| Device | `mlag_peer_address` | MLAG peer IP | -| Device | `mlag_local_address` | MLAG local IP | -| Device | `mlag_virtual_mac` | Shared virtual MAC | -| Interface | `mlag_peer_link` | Marks peer-link interfaces | -| Interface | `mlag_id` | MLAG ID for host LAGs | -| VRF | `l3vni` | L3 VNI for EVPN | -| VRF | `vrf_vlan` | VLAN for L3 VNI SVI | -| IP Address | `virtual_ip` | Anycast/virtual IP flag | +| Object Type | Field | Description | +| ----------- | -------------------- | -------------------------- | +| Device | `asn` | BGP ASN | +| Device | `mlag_domain_id` | MLAG domain identifier | +| Device | `mlag_peer_address` | MLAG peer IP | +| Device | `mlag_local_address` | MLAG local IP | +| Device | `mlag_virtual_mac` | Shared virtual MAC | +| Interface | `mlag_peer_link` | Marks peer-link interfaces | +| Interface | `mlag_id` | MLAG ID for host LAGs | +| VRF | `l3vni` | L3 VNI for EVPN | +| VRF | `vrf_vlan` | VLAN for L3 VNI SVI | +| IP Address | `virtual_ip` | Anycast/virtual IP flag | ### Organization @@ -49,14 +49,14 @@ uv run python scripts/provision_fabric.py ### Devices -| Device | Role | ASN | MLAG Domain | -|--------|------|-----|-------------| -| spine1, spine2 | Spine | 65000 | - | -| leaf1, leaf2 | Leaf | 65001 | MLAG1 | -| leaf3, leaf4 | Leaf | 65002 | MLAG2 | -| leaf5, leaf6 | Leaf | 65003 | MLAG3 | -| leaf7, leaf8 | Leaf | 65004 | MLAG4 | -| host1-4 | Server | - | - | +| Device | Role | ASN | MLAG Domain | +| -------------- | ------ | ----- | ----------- | +| spine1, spine2 | Spine | 65000 | - | +| leaf1, leaf2 | Leaf | 65001 | MLAG1 | +| leaf3, leaf4 | Leaf | 65002 | MLAG2 | +| leaf5, leaf6 | Leaf | 65003 | MLAG3 | +| leaf7, leaf8 | Leaf | 65004 | MLAG4 | +| host1-4 | Server | - | - | ### Cabling @@ -66,14 +66,14 @@ uv run python scripts/provision_fabric.py ### IP Addressing -| Purpose | Prefix | -|---------|--------| -| Spine1-Leaf P2P | 10.0.1.0/24 | -| Spine2-Leaf P2P | 10.0.2.0/24 | -| MLAG iBGP P2P | 10.0.3.0/24 | -| MLAG Peer VLAN | 10.0.199.0/24 | +| Purpose | Prefix | +| --------------------- | ------------- | +| Spine1-Leaf P2P | 10.0.1.0/24 | +| Spine2-Leaf P2P | 10.0.2.0/24 | +| MLAG iBGP P2P | 10.0.3.0/24 | +| MLAG Peer VLAN | 10.0.199.0/24 | | Loopback0 (Router-ID) | 10.0.250.0/24 | -| Loopback1 (VTEP) | 10.0.255.0/24 | +| Loopback1 (VTEP) | 10.0.255.0/24 | ## Idempotency diff --git a/scripts/provision_fabric.py b/scripts/provision_fabric.py index 8662ede..1c9b229 100644 --- a/scripts/provision_fabric.py +++ b/scripts/provision_fabric.py @@ -521,6 +521,7 @@ def create_vlans(nb: pynetbox.api, site) -> dict: nb.ipam.vlans, {"vid": vlan["vid"], "group_id": vlan_group.id}, { + "vid": vlan["vid"], "name": vlan["name"], "description": vlan.get("description", ""), "group": vlan_group.id, # Create expects 'group', filter expects 'group_id' @@ -546,7 +547,7 @@ def create_vrfs(nb: pynetbox.api) -> dict: rt_obj, created = get_or_create( nb.ipam.route_targets, {"name": rt}, - {"description": f"Import RT for {vrf_def['name']}"}, + {"name": rt, "description": f"Import RT for {vrf_def['name']}"}, ) import_rts.append(rt_obj.id) log_result("RouteTarget", "RouteTarget", rt, created) @@ -561,6 +562,7 @@ def create_vrfs(nb: pynetbox.api) -> dict: nb.ipam.vrfs, {"name": vrf_def["name"]}, { + "name": vrf_def["name"], "rd": vrf_def.get("rd"), "import_targets": import_rts, "export_targets": export_rts, @@ -587,7 +589,10 @@ def create_prefixes(nb: pynetbox.api, vrfs: dict): if vrf_id: vrf_id = vrf_id.id - create_params = {"prefix": prefix_def["prefix"], "description": prefix_def.get("description", "")} + create_params = { + "prefix": prefix_def["prefix"], + "description": prefix_def.get("description", ""), + } if vrf_id: create_params["vrf"] = vrf_id @@ -610,6 +615,7 @@ def create_devices(nb: pynetbox.api, org: dict) -> dict: nb.dcim.devices, {"name": spine["name"]}, { + "name": spine["name"], "device_type": org["device_type"].id, "role": org["roles"]["spine"].id, "site": org["site"].id, @@ -635,6 +641,7 @@ def create_devices(nb: pynetbox.api, org: dict) -> dict: nb.dcim.devices, {"name": leaf["name"]}, { + "name": leaf["name"], "device_type": org["device_type"].id, "role": org["roles"]["leaf"].id, "site": org["site"].id, @@ -651,6 +658,7 @@ def create_devices(nb: pynetbox.api, org: dict) -> dict: nb.dcim.devices, {"name": host["name"]}, { + "name": host["name"], "device_type": org["server_type"].id, "role": org["roles"]["server"].id, "site": org["site"].id, @@ -717,11 +725,13 @@ def create_interfaces(nb: pynetbox.api, devices: dict) -> dict: intf = existing created = False else: - intf = nb.dcim.interfaces.create({ - "device": device.id, - "name": intf_name, - "type": intf_type, - }) + intf = nb.dcim.interfaces.create( + { + "device": device.id, + "name": intf_name, + "type": intf_type, + } + ) created = True # Set custom fields for MLAG peer-link @@ -755,11 +765,19 @@ def create_ip_addresses(nb: pynetbox.api, devices: dict, interfaces: dict, vrfs: }, { "address": spine["loopback0"], + "role": "loopback", "assigned_object_type": "dcim.interface", "assigned_object_id": intf.id, "description": f"{spine['name']} Router-ID", }, ) + if not created: + current_role = ip.role.value if hasattr(ip.role, "value") else ip.role + if current_role != "loopback": + ip.role = "loopback" + ip.save() + log_result("IP", "IP Address", f"{spine['loopback0']} (updated role)", True) + log_result("IP", "IP Address", spine["loopback0"], created) # Loopback addresses for leafs @@ -776,11 +794,19 @@ def create_ip_addresses(nb: pynetbox.api, devices: dict, interfaces: dict, vrfs: }, { "address": leaf["loopback0"], + "role": "loopback", "assigned_object_type": "dcim.interface", "assigned_object_id": intf.id, "description": f"{leaf['name']} Router-ID", }, ) + if not created: + current_role = ip.role.value if hasattr(ip.role, "value") else ip.role + if current_role != "loopback": + ip.role = "loopback" + ip.save() + log_result("IP", "IP Address", f"{leaf['loopback0']} (updated role)", True) + log_result("IP", "IP Address", leaf["loopback0"], created) # Loopback1 (VTEP) @@ -793,11 +819,19 @@ def create_ip_addresses(nb: pynetbox.api, devices: dict, interfaces: dict, vrfs: }, { "address": leaf["loopback1"], + "role": "anycast", "assigned_object_type": "dcim.interface", "assigned_object_id": intf.id, "description": f"{leaf['name']} VTEP", }, ) + if not created: + current_role = ip.role.value if hasattr(ip.role, "value") else ip.role + if current_role != "anycast": + ip.role = "anycast" + ip.save() + log_result("IP", "IP Address", f"{leaf['loopback1']} (updated role)", True) + log_result("IP", "IP Address", leaf["loopback1"], created) # P2P addresses for Spine1-Leaf links @@ -881,7 +915,9 @@ def create_cables(nb: pynetbox.api, interfaces: dict): b_intf = interfaces.get(leaf, {}).get(leaf_intf) if not a_intf or not b_intf: - print(f" [Skip] Cable: {spine}:{spine_intf} <-> {leaf}:{leaf_intf} (interface not found)") + print( + f" [Skip] Cable: {spine}:{spine_intf} <-> {leaf}:{leaf_intf} (interface not found)" + ) continue # Check if cable already exists @@ -943,8 +979,12 @@ def create_cables(nb: pynetbox.api, interfaces: dict): try: cable = nb.dcim.cables.create( { - "a_terminations": [{"object_type": "dcim.interface", "object_id": a_intf.id}], - "b_terminations": [{"object_type": "dcim.interface", "object_id": host_eth1.id}], + "a_terminations": [ + {"object_type": "dcim.interface", "object_id": a_intf.id} + ], + "b_terminations": [ + {"object_type": "dcim.interface", "object_id": host_eth1.id} + ], "status": "connected", "type": "cat6a", } @@ -965,8 +1005,12 @@ def create_cables(nb: pynetbox.api, interfaces: dict): try: cable = nb.dcim.cables.create( { - "a_terminations": [{"object_type": "dcim.interface", "object_id": b_intf.id}], - "b_terminations": [{"object_type": "dcim.interface", "object_id": host_eth2.id}], + "a_terminations": [ + {"object_type": "dcim.interface", "object_id": b_intf.id} + ], + "b_terminations": [ + {"object_type": "dcim.interface", "object_id": host_eth2.id} + ], "status": "connected", "type": "cat6a", } diff --git a/uv.lock b/uv.lock index bebdcdf..db95dcf 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,18 @@ version = 1 revision = 3 requires-python = ">=3.12" +[[package]] +name = "basedpyright" +version = "1.37.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodejs-wheel-binaries" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/15/8f335ed50b5fed4d7587e293c200bb498049f4a74d9913c58c26a42d3503/basedpyright-1.37.4.tar.gz", hash = "sha256:f818d8b56c1e7f639dfbdaf875aa6b0bd53eef08204389959027d3d7fb2017ed", size = 25238593, upload-time = "2026-02-04T02:57:15.893Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b6/f075ecdc60a3e389c32934a98171b7f5c6f12250fc0030e12efc1102a557/basedpyright-1.37.4-py3-none-any.whl", hash = "sha256:bcf61d7d8dbd4570f346008fa591585bd605ce47a0561509899c276f2e53a450", size = 12299643, upload-time = "2026-02-04T02:57:19.915Z" }, +] + [[package]] name = "certifi" version = "2026.1.4" @@ -224,6 +236,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "basedpyright" }, { name = "ruff" }, ] @@ -236,7 +249,10 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.14.10" }] +dev = [ + { name = "basedpyright", specifier = ">=1.37.4" }, + { name = "ruff", specifier = ">=0.14.10" }, +] [[package]] name = "grpcio" @@ -309,6 +325,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "nodejs-wheel-binaries" +version = "24.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/f1/73182280e2c05f49a7c2c8dbd46144efe3f74f03f798fb90da67b4a93bbf/nodejs_wheel_binaries-24.13.0.tar.gz", hash = "sha256:766aed076e900061b83d3e76ad48bfec32a035ef0d41bd09c55e832eb93ef7a4", size = 8056, upload-time = "2026-01-14T11:05:33.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/dc/4d7548aa74a5b446d093f03aff4fb236b570959d793f21c9c42ab6ad870a/nodejs_wheel_binaries-24.13.0-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:356654baa37bfd894e447e7e00268db403ea1d223863963459a0fbcaaa1d9d48", size = 55133268, upload-time = "2026-01-14T11:05:05.335Z" }, + { url = "https://files.pythonhosted.org/packages/24/8a/8a4454d28339487240dd2232f42f1090e4a58544c581792d427f6239798c/nodejs_wheel_binaries-24.13.0-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:92fdef7376120e575f8b397789bafcb13bbd22a1b4d21b060d200b14910f22a5", size = 55314800, upload-time = "2026-01-14T11:05:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/fb/46c600fcc748bd13bc536a735f11532a003b14f5c4dfd6865f5911672175/nodejs_wheel_binaries-24.13.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:3f619ac140e039ecd25f2f71d6e83ad1414017a24608531851b7c31dc140cdfd", size = 59666320, upload-time = "2026-01-14T11:05:12.369Z" }, + { url = "https://files.pythonhosted.org/packages/85/47/d48f11fc5d1541ace5d806c62a45738a1db9ce33e85a06fe4cd3d9ce83f6/nodejs_wheel_binaries-24.13.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:dfb31ebc2c129538192ddb5bedd3d63d6de5d271437cd39ea26bf3fe229ba430", size = 60162447, upload-time = "2026-01-14T11:05:16.003Z" }, + { url = "https://files.pythonhosted.org/packages/b1/74/d285c579ae8157c925b577dde429543963b845e69cd006549e062d1cf5b6/nodejs_wheel_binaries-24.13.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdd720d7b378d5bb9b2710457bbc880d4c4d1270a94f13fbe257198ac707f358", size = 61659994, upload-time = "2026-01-14T11:05:19.68Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/88b4254a2ff93ed2eaed725f77b7d3d2d8d7973bf134359ce786db894faf/nodejs_wheel_binaries-24.13.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ad6383613f3485a75b054647a09f1cd56d12380d7459184eebcf4a5d403f35c", size = 62244373, upload-time = "2026-01-14T11:05:23.987Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c3/0e13a3da78f08cb58650971a6957ac7bfef84164b405176e53ab1e3584e2/nodejs_wheel_binaries-24.13.0-py2.py3-none-win_amd64.whl", hash = "sha256:605be4763e3ef427a3385a55da5a1bcf0a659aa2716eebbf23f332926d7e5f23", size = 41345528, upload-time = "2026-01-14T11:05:27.67Z" }, + { url = "https://files.pythonhosted.org/packages/a3/f1/0578d65b4e3dc572967fd702221ea1f42e1e60accfb6b0dd8d8f15410139/nodejs_wheel_binaries-24.13.0-py2.py3-none-win_arm64.whl", hash = "sha256:2e3431d869d6b2dbeef1d469ad0090babbdcc8baaa72c01dd3cc2c6121c96af5", size = 39054688, upload-time = "2026-01-14T11:05:30.739Z" }, +] + [[package]] name = "packaging" version = "25.0"