diff --git a/pyinfra/facts/hardware.py b/pyinfra/facts/hardware.py index f8c36794f..81f394d75 100644 --- a/pyinfra/facts/hardware.py +++ b/pyinfra/facts/hardware.py @@ -195,28 +195,36 @@ def mask(value): output = "\n".join(map(str.strip, output)) # Splitting the output into sections per network device - device_sections = re.split(r"\n(?=\d+: \w|\w+:.*mtu.*)", output) + device_sections = re.split(r"\n(?=\d+: [^\s/:]|[^\s/:]+:.*mtu )", output) # Dictionary to hold all device information all_devices = {} for section in device_sections: # Extracting the device name - device_name_match = re.match(r"^(?:\d+: )?([\w@]+):", section) + device_name_match = re.match(r"^(?:\d+: )?([^\s/:]+):", section) if not device_name_match: continue device_name = device_name_match.group(1) # Regular expressions to match different parts of the output - ether_re = re.compile(r"([0-9A-Fa-f:]{17})") + ether_re = re.compile(r"ether ([0-9A-Fa-f:]{17})") mtu_re = re.compile(r"mtu (\d+)") ipv4_re = ( + # ip a re.compile( - r"inet (\d+\.\d+\.\d+\.\d+)/(\d+)(?: brd (\d+\.\d+\.\d+\.\d+))" - ), # ip a output, + r"inet (?P
\d+\.\d+\.\d+\.\d+)/(?P\d+)(?: metric \d+)?(?: brd (?P\d+\.\d+\.\d+\.\d+))?" # noqa: E501 + ), + # ifconfig -a re.compile( - r"inet (\d+\.\d+\.\d+\.\d+)\s+netmask\s+((?:\d+\.\d+\.\d+\.\d+)|(?:[0-9a-fA-FxX]+))(?:\s+broadcast\s+(\d+\.\d+\.\d+\.\d+))" # noqa: E501 - ), # ifconfig -a output + r"inet (?P
\d+\.\d+\.\d+\.\d+)\s+netmask\s+(?P(?:\d+\.\d+\.\d+\.\d+)|(?:[0-9a-fA-FxX]+))(?:\s+broadcast\s+(?P\d+\.\d+\.\d+\.\d+))?" # noqa: E501 + ), + ) + ipv6_re = ( + # ip a + re.compile(r"inet6\s+(?P
[0-9a-fA-F:]+)/(?P\d+)"), + # ifconfig -a + re.compile(r"inet6\s+(?P
[0-9a-fA-F:]+)\s+prefixlen\s+(?P\d+)"), ) # Parsing the output @@ -235,18 +243,22 @@ def mask(value): ) # IPv4 Addresses + ipv4_matches: list[re.Match[str]] for ipv4_re_ in ipv4_re: - ipv4_matches = ipv4_re_.findall(section) - if ipv4_matches: + ipv4_matches = list(ipv4_re_.finditer(section)) + if len(ipv4_matches): break - if ipv4_matches: + if len(ipv4_matches): ipv4_info = [] for ipv4 in ipv4_matches: - address = ipv4[0] - mask_value = ipv4[1] + address = ipv4.group("address") + mask_value = ipv4.group("mask") mask_bits, netmask = mask(mask_value) - broadcast = ipv4[2] if len(ipv4) == 3 else None + try: + broadcast = ipv4.group("broadcast") + except IndexError: + broadcast = None ipv4_info.append( { @@ -261,21 +273,17 @@ def mask(value): device_info["ipv4"]["additional_ips"] = ipv4_info[1:] # type: ignore[index] # IPv6 Addresses - ipv6_re = ( - re.compile(r"inet6\s+([0-9a-fA-F:]+)/(\d+)"), - re.compile(r"inet6\s+([0-9a-fA-F:]+)\s+prefixlen\s+(\d+)"), - ) - + ipv6_matches: list[re.Match[str]] for ipv6_re_ in ipv6_re: - ipv6_matches = ipv6_re_.findall(section) + ipv6_matches = list(ipv6_re_.finditer(section)) if ipv6_matches: break - if ipv6_matches: + if len(ipv6_matches): ipv6_info = [] for ipv6 in ipv6_matches: - address = ipv6[0] - mask_bits = ipv6[1] or ipv6[2] + address = ipv6.group("address") + mask_bits = ipv6.group("mask") ipv6_info.append({"address": address, "mask_bits": int(mask_bits)}) device_info["ipv6"] = ipv6_info[0] if len(ipv6_matches) > 1: diff --git a/tests/facts/hardware.NetworkDevices/linux_ifconfig.json b/tests/facts/hardware.NetworkDevices/linux_ifconfig.json index 4638b614d..169d619bd 100644 --- a/tests/facts/hardware.NetworkDevices/linux_ifconfig.json +++ b/tests/facts/hardware.NetworkDevices/linux_ifconfig.json @@ -30,7 +30,7 @@ ], "fact": { "enp1s0": { - "ether": "2a01:e0a:5c2:7450", + "ether": "b0:41:6f:0a:cf:22", "mtu": 1500, "state": "UP", "ipv4": { @@ -51,7 +51,7 @@ } }, "incusbr0": { - "ether": "fe80::216:3eff:fe", + "ether": "00:16:3e:9c:82:00", "mtu": 1500, "state": "UP", "ipv4": { @@ -74,6 +74,12 @@ "lo": { "mtu": 65536, "state": "UP", + "ipv4": { + "address": "127.0.0.1", + "mask_bits": 8, + "netmask": "255.0.0.0", + "broadcast": null + }, "ipv6": { "address": "::1", "mask_bits": 128 diff --git a/tests/facts/hardware.NetworkDevices/linux_ifconfig_multiple.json b/tests/facts/hardware.NetworkDevices/linux_ifconfig_multiple.json new file mode 100644 index 000000000..acb2ee471 --- /dev/null +++ b/tests/facts/hardware.NetworkDevices/linux_ifconfig_multiple.json @@ -0,0 +1,197 @@ +{ + "command": "ip addr show 2> /dev/null || ifconfig -a", + "output": [ + "myvlan: flags=4163 mtu 1500", + " inet 10.42.13.147 netmask 255.255.255.0 broadcast 10.42.13.255", + " inet6 fe80::3cd2:56ff:fe4d:1af2 prefixlen 64 scopeid 0x20", + " ether 3e:d2:56:4d:1a:f2 txqueuelen 1000 (Ethernet)", + " RX packets 115016 bytes 28450084 (27.1 MiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 53310 bytes 9803632 (9.3 MiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "eno1: flags=4163 mtu 1500", + " inet 10.42.6.142 netmask 255.255.255.0 broadcast 10.42.6.255", + " inet6 fe80::3cd2:56ff:fe4d:1af1 prefixlen 64 scopeid 0x20", + " ether 3e:d2:56:4d:1a:f1 txqueuelen 1000 (Ethernet)", + " RX packets 5097072 bytes 4315873036 (4.0 GiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 3897172 bytes 1574206075 (1.4 GiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "lo: flags=73 mtu 65536", + " inet 127.0.0.1 netmask 255.0.0.0", + " inet6 ::1 prefixlen 128 scopeid 0x10", + " loop txqueuelen 1000 (Local Loopback)", + " RX packets 9470290 bytes 4905913998 (4.5 GiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 9470290 bytes 4905913998 (4.5 GiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "my-vpn: flags=4305 mtu 1300", + " inet 10.66.7.3 netmask 255.255.255.0 destination 10.66.7.3", + " inet6 fe80::c9fc:e8d5:12d8:cfb2 prefixlen 64 scopeid 0x20", + " unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC)", + " RX packets 59069 bytes 22687707 (21.6 MiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 55603 bytes 8340765 (7.9 MiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "some-br: flags=4163 mtu 1500", + " inet 192.168.101.1 netmask 255.255.255.0 broadcast 0.0.0.0", + " inet6 fe80::f884:69ff:fe0a:a073 prefixlen 64 scopeid 0x20", + " ether 8e:15:02:8a:7f:ee txqueuelen 1000 (Ethernet)", + " RX packets 1315769 bytes 616685980 (588.1 MiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 1434638 bytes 628356665 (599.2 MiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "some-tap: flags=4163 mtu 1500", + " inet6 fe80::f884:69ff:fe0a:a073 prefixlen 64 scopeid 0x20", + " ether fa:84:69:0a:a0:73 txqueuelen 1000 (Ethernet)", + " RX packets 1315769 bytes 635106746 (605.6 MiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 1450904 bytes 631656508 (602.3 MiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "vmbr0: flags=4099 mtu 1500", + " inet 172.20.0.2 netmask 255.255.255.0 broadcast 172.20.0.255", + " ether 7a:c8:eb:21:98:98 txqueuelen 1000 (Ethernet)", + " RX packets 0 bytes 0 (0.0 B)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 0 bytes 0 (0.0 B)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "vmbr0-veth: flags=4099 mtu 1500", + " ether 52:a5:03:cb:ee:31 txqueuelen 1000 (Ethernet)", + " RX packets 0 bytes 0 (0.0 B)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 0 bytes 0 (0.0 B)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "vmbr0-veth-end: flags=4098 mtu 1500", + " ether 96:1d:5c:3b:20:9e txqueuelen 1000 (Ethernet)", + " RX packets 0 bytes 0 (0.0 B)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 0 bytes 0 (0.0 B)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "", + "wlp6s0: flags=4098 mtu 1500", + " ether b8:4c:68:59:45:7d txqueuelen 1000 (Ethernet)", + " RX packets 0 bytes 0 (0.0 B)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 0 bytes 0 (0.0 B)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "" + ], + "fact": { + "lo": { + "mtu": 65536, + "state": "UP", + "ipv4": { + "address": "127.0.0.1", + "mask_bits": 8, + "netmask": "255.0.0.0", + "broadcast": null + }, + "ipv6": { + "address": "::1", + "mask_bits": 128 + } + }, + "eno1": { + "ether": "3e:d2:56:4d:1a:f1", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "10.42.6.142", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "10.42.6.255" + }, + "ipv6": { + "address": "fe80::3cd2:56ff:fe4d:1af1", + "mask_bits": 64 + } + }, + "wlp6s0": { + "ether": "b8:4c:68:59:45:7d", + "mtu": 1500, + "state": "UNKNOWN" + }, + "myvlan": { + "ether": "3e:d2:56:4d:1a:f2", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "10.42.13.147", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "10.42.13.255" + }, + "ipv6": { + "address": "fe80::3cd2:56ff:fe4d:1af2", + "mask_bits": 64 + } + }, + "vmbr0": { + "ether": "7a:c8:eb:21:98:98", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "172.20.0.2", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "172.20.0.255" + } + }, + "vmbr0-veth-end": { + "ether": "96:1d:5c:3b:20:9e", + "mtu": 1500, + "state": "UNKNOWN" + }, + "vmbr0-veth": { + "ether": "52:a5:03:cb:ee:31", + "mtu": 1500, + "state": "UP" + }, + "some-tap": { + "ether": "fa:84:69:0a:a0:73", + "mtu": 1500, + "state": "UP", + "ipv6": { + "address": "fe80::f884:69ff:fe0a:a073", + "mask_bits": 64 + } + }, + "some-br": { + "ether": "8e:15:02:8a:7f:ee", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "192.168.101.1", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "0.0.0.0" + }, + "ipv6": { + "address": "fe80::f884:69ff:fe0a:a073", + "mask_bits": 64 + } + }, + "my-vpn": { + "mtu": 1300, + "state": "UP", + "ipv4": { + "address": "10.66.7.3", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": null + }, + "ipv6": { + "address": "fe80::c9fc:e8d5:12d8:cfb2", + "mask_bits": 64 + } + } + } +} diff --git a/tests/facts/hardware.NetworkDevices/linux_ip_multiple.json b/tests/facts/hardware.NetworkDevices/linux_ip_multiple_addrs.json similarity index 100% rename from tests/facts/hardware.NetworkDevices/linux_ip_multiple.json rename to tests/facts/hardware.NetworkDevices/linux_ip_multiple_addrs.json diff --git a/tests/facts/hardware.NetworkDevices/linux_ip_multiple_ifaces.json b/tests/facts/hardware.NetworkDevices/linux_ip_multiple_ifaces.json new file mode 100644 index 000000000..af5d277a1 --- /dev/null +++ b/tests/facts/hardware.NetworkDevices/linux_ip_multiple_ifaces.json @@ -0,0 +1,160 @@ +{ + "command": "ip addr show 2> /dev/null || ifconfig -a", + "output": [ + "1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000", + " link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00", + " inet 127.0.0.1/8 scope host lo", + " valid_lft forever preferred_lft forever", + " inet6 ::1/128 scope host noprefixroute ", + " valid_lft forever preferred_lft forever", + "2: eno1: mtu 1500 qdisc fq_codel state UP group default qlen 1000", + " link/ether 3e:d2:56:4d:1a:f1 brd ff:ff:ff:ff:ff:ff", + " altname enp4s0", + " inet 10.42.6.142/24 brd 10.42.6.255 scope global dynamic noprefixroute eno1", + " valid_lft 53778sec preferred_lft 53778sec", + " inet6 fe80::3cd2:56ff:fe4d:1af1/64 scope link proto kernel_ll ", + " valid_lft forever preferred_lft forever", + "3: wlp6s0: mtu 1500 qdisc noop state DOWN group default qlen 1000", + " link/ether b8:4c:68:59:45:7d brd ff:ff:ff:ff:ff:ff permaddr 64:5d:24:b6:ad:5c", + "4: myvlan@eno1: mtu 1500 qdisc noqueue state UP group default qlen 1000", + " link/ether 3e:d2:56:4d:1a:f2 brd ff:ff:ff:ff:ff:ff", + " inet 10.42.13.147/24 brd 10.42.13.255 scope global dynamic noprefixroute myvlan", + " valid_lft 58557sec preferred_lft 58557sec", + " inet6 fe80::3cd2:56ff:fe4d:1af2/64 scope link proto kernel_ll ", + " valid_lft forever preferred_lft forever", + "5: vmbr0: mtu 1500 qdisc noqueue state DOWN group default qlen 1000", + " link/ether 7a:c8:eb:21:98:98 brd ff:ff:ff:ff:ff:ff", + " inet 172.20.0.2/24 brd 172.20.0.255 scope global noprefixroute vmbr0", + " valid_lft forever preferred_lft forever", + "6: vmbr0-veth-end@vmbr0-veth: mtu 1500 qdisc noop state DOWN group default qlen 1000", + " link/ether 96:1d:5c:3b:20:9e brd ff:ff:ff:ff:ff:ff", + "7: vmbr0-veth@vmbr0-veth-end: mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000", + " link/ether 52:a5:03:cb:ee:31 brd ff:ff:ff:ff:ff:ff", + "8: some-tap: mtu 1500 qdisc fq_codel master some-br state UNKNOWN group default qlen 1000", + " link/ether fa:84:69:0a:a0:73 brd ff:ff:ff:ff:ff:ff", + " inet6 fe80::f884:69ff:fe0a:a073/64 scope link proto kernel_ll ", + " valid_lft forever preferred_lft forever", + "9: some-br: mtu 1500 qdisc noqueue state UP group default qlen 1000", + " link/ether 8e:15:02:8a:7f:ee brd ff:ff:ff:ff:ff:ff", + " inet 192.168.101.1/24 scope global some-br", + " valid_lft forever preferred_lft forever", + " inet6 fe80::f884:69ff:fe0a:a073/64 scope link proto kernel_ll ", + " valid_lft forever preferred_lft forever", + "14: my-vpn: mtu 1300 qdisc fq_codel state UNKNOWN group default qlen 500", + " link/none ", + " inet 10.66.7.3/24 scope global my-vpn", + " valid_lft forever preferred_lft forever", + " inet6 fe80::c9fc:e8d5:12d8:cfb2/64 scope link stable-privacy proto kernel_ll ", + " valid_lft forever preferred_lft forever" + ], + "fact": { + "lo": { + "mtu": 65536, + "state": "UP", + "ipv4": { + "address": "127.0.0.1", + "mask_bits": 8, + "netmask": "255.0.0.0", + "broadcast": null + }, + "ipv6": { + "address": "::1", + "mask_bits": 128 + } + }, + "eno1": { + "ether": "3e:d2:56:4d:1a:f1", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "10.42.6.142", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "10.42.6.255" + }, + "ipv6": { + "address": "fe80::3cd2:56ff:fe4d:1af1", + "mask_bits": 64 + } + }, + "wlp6s0": { + "ether": "b8:4c:68:59:45:7d", + "mtu": 1500, + "state": "DOWN" + }, + "myvlan@eno1": { + "ether": "3e:d2:56:4d:1a:f2", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "10.42.13.147", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "10.42.13.255" + }, + "ipv6": { + "address": "fe80::3cd2:56ff:fe4d:1af2", + "mask_bits": 64 + } + }, + "vmbr0": { + "ether": "7a:c8:eb:21:98:98", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "172.20.0.2", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "172.20.0.255" + } + }, + "vmbr0-veth-end@vmbr0-veth": { + "ether": "96:1d:5c:3b:20:9e", + "mtu": 1500, + "state": "DOWN" + }, + "vmbr0-veth@vmbr0-veth-end": { + "ether": "52:a5:03:cb:ee:31", + "mtu": 1500, + "state": "UP" + }, + "some-tap": { + "ether": "fa:84:69:0a:a0:73", + "mtu": 1500, + "state": "UP", + "ipv6": { + "address": "fe80::f884:69ff:fe0a:a073", + "mask_bits": 64 + } + }, + "some-br": { + "ether": "8e:15:02:8a:7f:ee", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "192.168.101.1", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": null + }, + "ipv6": { + "address": "fe80::f884:69ff:fe0a:a073", + "mask_bits": 64 + } + }, + "my-vpn": { + "mtu": 1300, + "state": "UP", + "ipv4": { + "address": "10.66.7.3", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": null + }, + "ipv6": { + "address": "fe80::c9fc:e8d5:12d8:cfb2", + "mask_bits": 64 + } + } + } +}