From af2c20fb9017d0f2f4111fc1e4ae54a9b9f4bb69 Mon Sep 17 00:00:00 2001 From: Ember 'n0emis' Keske Date: Tue, 10 Feb 2026 14:11:49 +0100 Subject: [PATCH 1/2] Add feature-flag to allow private IPs on default vrf loopbacks --- cosmo/features.py | 6 +++++- cosmo/routervisitor.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cosmo/features.py b/cosmo/features.py index 403542c..b28a045 100644 --- a/cosmo/features.py +++ b/cosmo/features.py @@ -97,5 +97,9 @@ def exe_with_feature(*args, **kwargs): features = FeatureToggle( - {"interface-auto-descriptions": True, "new-bgp-cpe-group-naming": False} + { + "interface-auto-descriptions": True, + "new-bgp-cpe-group-naming": False, + "allow-private-ips-default-vrf": False, + } ) diff --git a/cosmo/routervisitor.py b/cosmo/routervisitor.py index 0e43864..37fb867 100644 --- a/cosmo/routervisitor.py +++ b/cosmo/routervisitor.py @@ -19,6 +19,7 @@ from cosmo.vrfhelper import TVRFHelpers from cosmo.manufacturers import ManufacturerFactoryFromDevice from cosmo.routerbgpcpevisitor import RouterBgpCpeExporterVisitor +from cosmo.features import features from cosmo.routerl2vpnvisitor import ( RouterL2VPNValidatorVisitor, RouterL2VPNExporterVisitor, @@ -54,7 +55,7 @@ def __init__( self.l2vpn_validator = RouterL2VPNValidatorVisitor(loopbacks=loopbacks, asn=asn) self.bgpcpe_exporter = RouterBgpCpeExporterVisitor() self.loopbacks = loopbacks - self.allow_private_ips = False + self.allow_private_ips = features.featureIsEnabled("allow-private-ips-default-vrf") self.asn = asn def getASN(self) -> int: From cfcbbc96c9a6fda984550131129140e346d1cfbc Mon Sep 17 00:00:00 2001 From: Ember 'n0emis' Keske Date: Tue, 10 Feb 2026 14:18:14 +0100 Subject: [PATCH 2/2] add feature flag to run without loopback interface type in netbox --- cosmo/clients/netbox_v4.py | 3 +-- cosmo/features.py | 1 + cosmo/netbox_types.py | 15 ++++++++++----- cosmo/routervisitor.py | 4 +++- cosmo/tests/utils.py | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/cosmo/clients/netbox_v4.py b/cosmo/clients/netbox_v4.py index 2bc9450..a8486ed 100644 --- a/cosmo/clients/netbox_v4.py +++ b/cosmo/clients/netbox_v4.py @@ -126,8 +126,7 @@ def _fetch_data(self, kwargs, pool): """ query{ interface_list(filters: { - name: {starts_with: "lo"}, - type: {exact:"loopback"} + name: {starts_with: "lo"} }) { __typename name, diff --git a/cosmo/features.py b/cosmo/features.py index b28a045..2d53a03 100644 --- a/cosmo/features.py +++ b/cosmo/features.py @@ -101,5 +101,6 @@ def exe_with_feature(*args, **kwargs): "interface-auto-descriptions": True, "new-bgp-cpe-group-naming": False, "allow-private-ips-default-vrf": False, + "netbox-loopback-interface-type": False, } ) diff --git a/cosmo/netbox_types.py b/cosmo/netbox_types.py index 6d37980..86f1234 100644 --- a/cosmo/netbox_types.py +++ b/cosmo/netbox_types.py @@ -17,6 +17,7 @@ head, CosmoOutputType, ) +from .features import features from typing import ( Self, Iterator, @@ -675,11 +676,15 @@ def isLoopbackOrParentIsLoopback(self) -> bool: ) return parent_interface.isLoopbackOrParentIsLoopback() elif self.getName().startswith("lo"): - raise InterfaceSerializationError( - f"Interface {self.getName()} is named as a loopback but is not marked" - f"as such in the data source, please fix.", - on=self, - ) + if self.getAssociatedType() != "loopback" and features.featureIsEnabled( + "netbox-loopback-interface-type" + ): + raise InterfaceSerializationError( + f"Interface {self.getName()} is named as a loopback but is not marked" + f"as such in the data source, please fix.", + on=self, + ) + return True return False def getIPAddresses(self) -> list[IPAddressType]: diff --git a/cosmo/routervisitor.py b/cosmo/routervisitor.py index 37fb867..91897eb 100644 --- a/cosmo/routervisitor.py +++ b/cosmo/routervisitor.py @@ -55,7 +55,9 @@ def __init__( self.l2vpn_validator = RouterL2VPNValidatorVisitor(loopbacks=loopbacks, asn=asn) self.bgpcpe_exporter = RouterBgpCpeExporterVisitor() self.loopbacks = loopbacks - self.allow_private_ips = features.featureIsEnabled("allow-private-ips-default-vrf") + self.allow_private_ips = features.featureIsEnabled( + "allow-private-ips-default-vrf" + ) self.asn = asn def getASN(self) -> int: diff --git a/cosmo/tests/utils.py b/cosmo/tests/utils.py index 7eab04f..cc31ec7 100644 --- a/cosmo/tests/utils.py +++ b/cosmo/tests/utils.py @@ -76,7 +76,7 @@ def patchPostFunc(url, json, **kwargs): retVal["interface_list"] = patchKwArgs.get( "connected_devices_interface_list", [] ) - elif "loopback" in q: + elif 'starts_with: "lo"' in q: retVal["interface_list"] = patchKwArgs.get( "loopback_interface_list", [] )