From ea854f3d07318b09d22c60533739dbd573ca00ea Mon Sep 17 00:00:00 2001 From: Ashmit JaiSarita Gupta Date: Fri, 10 Jul 2026 22:37:23 +0000 Subject: [PATCH 1/5] chore: add type annotations to cloudinit.netinfo Enable mypy's check_untyped_defs for cloudinit.netinfo (removed from the override list in pyproject.toml) by threading the existing Interface TypedDict through the netdev parsers and fixing the netdev_info return type. --- cloudinit/netinfo.py | 40 +++++++++++++++++++++++++--------------- pyproject.toml | 1 - 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index 69cc56f8926..2665fdc8e07 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -13,7 +13,7 @@ import re from copy import copy, deepcopy from ipaddress import IPv4Network -from typing import Dict, List, TypedDict +from typing import Dict, Iterable, List, TypedDict from cloudinit import lifecycle, subp, util from cloudinit.net.network_state import net_prefix_to_ipv4_mask @@ -21,6 +21,7 @@ LOG = logging.getLogger(__name__) + # Example netdev format: # {'eth0': {'hwaddr': '00:16:3e:16:db:54', # 'ipv4': [{'bcast': '10.85.130.255', @@ -38,9 +39,6 @@ # 'scope': 'host'}], # 'ipv6': [{'ip': '::1/128', 'scope6': 'host'}], # 'up': True}} -DEFAULT_NETDEV_INFO = {"ipv4": [], "ipv6": [], "hwaddr": "", "up": False} - - class Interface(TypedDict): up: bool hwaddr: str @@ -48,6 +46,14 @@ class Interface(TypedDict): ipv6: List[dict] +DEFAULT_NETDEV_INFO: Interface = { + "ipv4": [], + "ipv6": [], + "hwaddr": "", + "up": False, +} + + def _netdev_info_iproute_json(ipaddr_json): """Get network device dicts from ip route and ip link info. @@ -59,12 +65,12 @@ def _netdev_info_iproute_json(ipaddr_json): Raises json.JSONDecodeError if json could not be decoded """ ipaddr_data = json.loads(ipaddr_json) - devs = {} + devs: Dict[str, Interface] = {} for dev in ipaddr_data: flags = dev["flags"] if "flags" in dev else [] address = dev["address"] if dev.get("link_type") == "ether" else "" - dev_info = { + dev_info: Interface = { "hwaddr": address, "up": bool("UP" in flags and "LOWER_UP" in flags), "ipv4": [], @@ -115,7 +121,7 @@ def _netdev_info_iproute(ipaddr_out): device configuration values. @raise: TypeError if ipaddr_out isn't a string. """ - devs = {} + devs: Dict[str, Interface] = {} dev_name = None for num, line in enumerate(ipaddr_out.splitlines()): m = re.match(r"^\d+:\s(?P[^:]+):\s+<(?P\S+)>\s+.*", line) @@ -128,6 +134,9 @@ def _netdev_info_iproute(ipaddr_out): "hwaddr": "", "up": bool("UP" in flags and "LOWER_UP" in flags), } + elif dev_name is None: + # Skip any address lines appearing before the first device header + continue elif "inet6" in line: m = re.match( r"\s+inet6\s(?P\S+)" @@ -187,7 +196,7 @@ def _netdev_info_iproute(ipaddr_out): def _netdev_info_ifconfig_netbsd(ifconfig_data): # fields that need to be returned in devs for each dev - devs = {} + devs: Dict[str, Interface] = {} for line in ifconfig_data.splitlines(): if not line: continue @@ -235,7 +244,7 @@ def _netdev_info_ifconfig_netbsd(ifconfig_data): def _netdev_info_ifconfig(ifconfig_data): # fields that need to be returned in devs for each dev - devs = {} + devs: Dict[str, Interface] = {} for line in ifconfig_data.splitlines(): if not line: continue @@ -292,7 +301,7 @@ def _netdev_info_ifconfig(ifconfig_data): def netdev_info( empty="", -) -> Dict[str, Dict[str, Interface]]: +) -> Dict[str, Interface]: """return the instance's interfaces and interface data includes, interface name, link state, hardware address, and lists of ipv4 @@ -329,7 +338,7 @@ def netdev_info( } """ - devs = {} + devs: Dict[str, Interface] = {} if util.is_NetBSD(): (ifcfg_out, _err) = subp.subp(["ifconfig", "-a"], rcs=[0, 1]) devs = _netdev_info_ifconfig_netbsd(ifcfg_out) @@ -359,6 +368,7 @@ def netdev_info( def fill(data, new_val="", empty_vals=("", b"")): """Recursively replace 'empty_vals' in data (dict, tuple, list) with new_val""" + myiter: Iterable if isinstance(data, dict): myiter = data.items() elif isinstance(data, (tuple, list)): @@ -387,7 +397,7 @@ def _netdev_route_info_iproute(iproute_data): gateway, flags, genmask and interface information. """ - routes = {} + routes: Dict[str, list] = {} routes["ipv4"] = [] routes["ipv6"] = [] entries = iproute_data.splitlines() @@ -465,7 +475,7 @@ def _netdev_route_info_iproute(iproute_data): def _netdev_route_info_netstat(route_data): - routes = {} + routes: Dict[str, list] = {} routes["ipv4"] = [] routes["ipv6"] = [] @@ -587,7 +597,7 @@ def netdev_pformat(): fields = ["Device", "Up", "Address", "Mask", "Scope", "Hw-Address"] tbl = SimpleTable(fields) for dev, data in sorted(netdev.items()): - ipv4_addrs = data.get("ipv4") + ipv4_addrs = data["ipv4"] for addr in ipv4_addrs: tbl.add_row( ( @@ -600,7 +610,7 @@ def netdev_pformat(): ) ) - ipv6_addrs = data.get("ipv6") + ipv6_addrs = data["ipv6"] for addr in ipv6_addrs: tbl.add_row( ( diff --git a/pyproject.toml b/pyproject.toml index 8f08187ca8d..6e8fb1c5d90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,6 @@ module = [ "cloudinit.net.network_manager", "cloudinit.net.network_state", "cloudinit.net.networkd", - "cloudinit.netinfo", "cloudinit.sources.DataSourceAzure", "cloudinit.sources.DataSourceBigstep", "cloudinit.sources.DataSourceCloudStack", From 5071f6bed73aeece83a8ddf43b1d600132e1f1a7 Mon Sep 17 00:00:00 2001 From: Ashmit JaiSarita Gupta Date: Thu, 16 Jul 2026 00:58:38 +0000 Subject: [PATCH 2/5] chore(netinfo): raise KeyError on address line before first device --- cloudinit/netinfo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index 2665fdc8e07..b65cccbbb5d 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -135,8 +135,8 @@ def _netdev_info_iproute(ipaddr_out): "up": bool("UP" in flags and "LOWER_UP" in flags), } elif dev_name is None: - # Skip any address lines appearing before the first device header - continue + # An address line appeared before any device header. + raise KeyError(dev_name) elif "inet6" in line: m = re.match( r"\s+inet6\s(?P\S+)" From ea1a765253f80903be8fcf4e829eadae037fb3fc Mon Sep 17 00:00:00 2001 From: Ashmit JaiSarita Gupta Date: Thu, 16 Jul 2026 01:56:25 +0000 Subject: [PATCH 3/5] chore(netinfo): keep DEFAULT_NETDEV_INFO in place via forward ref --- cloudinit/netinfo.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index b65cccbbb5d..c8d16cbd8e6 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -21,7 +21,6 @@ LOG = logging.getLogger(__name__) - # Example netdev format: # {'eth0': {'hwaddr': '00:16:3e:16:db:54', # 'ipv4': [{'bcast': '10.85.130.255', @@ -39,14 +38,7 @@ # 'scope': 'host'}], # 'ipv6': [{'ip': '::1/128', 'scope6': 'host'}], # 'up': True}} -class Interface(TypedDict): - up: bool - hwaddr: str - ipv4: List[dict] - ipv6: List[dict] - - -DEFAULT_NETDEV_INFO: Interface = { +DEFAULT_NETDEV_INFO: "Interface" = { "ipv4": [], "ipv6": [], "hwaddr": "", @@ -54,6 +46,13 @@ class Interface(TypedDict): } +class Interface(TypedDict): + up: bool + hwaddr: str + ipv4: List[dict] + ipv6: List[dict] + + def _netdev_info_iproute_json(ipaddr_json): """Get network device dicts from ip route and ip link info. From 5be9fd4f732f5255ead8f7eada8a11c22040775d Mon Sep 17 00:00:00 2001 From: Ashmit JaiSarita Gupta Date: Fri, 17 Jul 2026 01:52:00 +0000 Subject: [PATCH 4/5] chore(netinfo): default to empty list when ipv4/ipv6 key is missing --- cloudinit/netinfo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index c8d16cbd8e6..9248ed40d1b 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -596,7 +596,7 @@ def netdev_pformat(): fields = ["Device", "Up", "Address", "Mask", "Scope", "Hw-Address"] tbl = SimpleTable(fields) for dev, data in sorted(netdev.items()): - ipv4_addrs = data["ipv4"] + ipv4_addrs = data.get("ipv4", []) for addr in ipv4_addrs: tbl.add_row( ( @@ -609,7 +609,7 @@ def netdev_pformat(): ) ) - ipv6_addrs = data["ipv6"] + ipv6_addrs = data.get("ipv6", []) for addr in ipv6_addrs: tbl.add_row( ( From e8ebc0ff047ecd1255702de3a6d60d1d55ca1a73 Mon Sep 17 00:00:00 2001 From: Ashmit JaiSarita Gupta Date: Fri, 17 Jul 2026 16:09:21 +0000 Subject: [PATCH 5/5] chore(netinfo): move DEFAULT_NETDEV_INFO below Interface --- cloudinit/netinfo.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index 9248ed40d1b..2a73dbdbe52 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -21,6 +21,7 @@ LOG = logging.getLogger(__name__) + # Example netdev format: # {'eth0': {'hwaddr': '00:16:3e:16:db:54', # 'ipv4': [{'bcast': '10.85.130.255', @@ -38,14 +39,6 @@ # 'scope': 'host'}], # 'ipv6': [{'ip': '::1/128', 'scope6': 'host'}], # 'up': True}} -DEFAULT_NETDEV_INFO: "Interface" = { - "ipv4": [], - "ipv6": [], - "hwaddr": "", - "up": False, -} - - class Interface(TypedDict): up: bool hwaddr: str @@ -53,6 +46,14 @@ class Interface(TypedDict): ipv6: List[dict] +DEFAULT_NETDEV_INFO: Interface = { + "ipv4": [], + "ipv6": [], + "hwaddr": "", + "up": False, +} + + def _netdev_info_iproute_json(ipaddr_json): """Get network device dicts from ip route and ip link info.