pyinfra 0.11.dev3__py3-none-any.whl → 3.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +19 -3
  4. pyinfra/api/arguments.py +413 -0
  5. pyinfra/api/arguments_typed.py +79 -0
  6. pyinfra/api/command.py +274 -0
  7. pyinfra/api/config.py +222 -28
  8. pyinfra/api/connect.py +33 -13
  9. pyinfra/api/connectors.py +27 -0
  10. pyinfra/api/deploy.py +65 -66
  11. pyinfra/api/exceptions.py +73 -18
  12. pyinfra/api/facts.py +267 -200
  13. pyinfra/api/host.py +416 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/metadata.py +69 -0
  16. pyinfra/api/operation.py +432 -262
  17. pyinfra/api/operations.py +273 -260
  18. pyinfra/api/state.py +302 -248
  19. pyinfra/api/util.py +309 -369
  20. pyinfra/connectors/base.py +173 -0
  21. pyinfra/connectors/chroot.py +212 -0
  22. pyinfra/connectors/docker.py +405 -0
  23. pyinfra/connectors/dockerssh.py +297 -0
  24. pyinfra/connectors/local.py +238 -0
  25. pyinfra/connectors/scp/__init__.py +1 -0
  26. pyinfra/connectors/scp/client.py +204 -0
  27. pyinfra/connectors/ssh.py +727 -0
  28. pyinfra/connectors/ssh_util.py +114 -0
  29. pyinfra/connectors/sshuserclient/client.py +309 -0
  30. pyinfra/connectors/sshuserclient/config.py +102 -0
  31. pyinfra/connectors/terraform.py +135 -0
  32. pyinfra/connectors/util.py +417 -0
  33. pyinfra/connectors/vagrant.py +183 -0
  34. pyinfra/context.py +145 -0
  35. pyinfra/facts/__init__.py +7 -6
  36. pyinfra/facts/apk.py +22 -7
  37. pyinfra/facts/apt.py +117 -60
  38. pyinfra/facts/brew.py +100 -15
  39. pyinfra/facts/bsdinit.py +23 -0
  40. pyinfra/facts/cargo.py +37 -0
  41. pyinfra/facts/choco.py +47 -0
  42. pyinfra/facts/crontab.py +195 -0
  43. pyinfra/facts/deb.py +94 -0
  44. pyinfra/facts/dnf.py +48 -0
  45. pyinfra/facts/docker.py +96 -23
  46. pyinfra/facts/efibootmgr.py +113 -0
  47. pyinfra/facts/files.py +629 -58
  48. pyinfra/facts/flatpak.py +77 -0
  49. pyinfra/facts/freebsd.py +70 -0
  50. pyinfra/facts/gem.py +19 -6
  51. pyinfra/facts/git.py +59 -14
  52. pyinfra/facts/gpg.py +150 -0
  53. pyinfra/facts/hardware.py +313 -167
  54. pyinfra/facts/iptables.py +72 -62
  55. pyinfra/facts/launchd.py +44 -0
  56. pyinfra/facts/lxd.py +17 -4
  57. pyinfra/facts/mysql.py +122 -86
  58. pyinfra/facts/npm.py +17 -9
  59. pyinfra/facts/openrc.py +71 -0
  60. pyinfra/facts/opkg.py +246 -0
  61. pyinfra/facts/pacman.py +50 -7
  62. pyinfra/facts/pip.py +24 -7
  63. pyinfra/facts/pipx.py +82 -0
  64. pyinfra/facts/pkg.py +15 -6
  65. pyinfra/facts/pkgin.py +35 -0
  66. pyinfra/facts/podman.py +54 -0
  67. pyinfra/facts/postgres.py +178 -0
  68. pyinfra/facts/postgresql.py +6 -147
  69. pyinfra/facts/rpm.py +105 -0
  70. pyinfra/facts/runit.py +77 -0
  71. pyinfra/facts/selinux.py +161 -0
  72. pyinfra/facts/server.py +762 -285
  73. pyinfra/facts/snap.py +88 -0
  74. pyinfra/facts/systemd.py +139 -0
  75. pyinfra/facts/sysvinit.py +59 -0
  76. pyinfra/facts/upstart.py +35 -0
  77. pyinfra/facts/util/__init__.py +17 -0
  78. pyinfra/facts/util/databases.py +4 -6
  79. pyinfra/facts/util/packaging.py +37 -6
  80. pyinfra/facts/util/units.py +30 -0
  81. pyinfra/facts/util/win_files.py +99 -0
  82. pyinfra/facts/vzctl.py +20 -13
  83. pyinfra/facts/xbps.py +35 -0
  84. pyinfra/facts/yum.py +34 -40
  85. pyinfra/facts/zfs.py +77 -0
  86. pyinfra/facts/zypper.py +42 -0
  87. pyinfra/local.py +45 -83
  88. pyinfra/operations/__init__.py +12 -0
  89. pyinfra/operations/apk.py +99 -0
  90. pyinfra/operations/apt.py +496 -0
  91. pyinfra/operations/brew.py +232 -0
  92. pyinfra/operations/bsdinit.py +59 -0
  93. pyinfra/operations/cargo.py +45 -0
  94. pyinfra/operations/choco.py +61 -0
  95. pyinfra/operations/crontab.py +194 -0
  96. pyinfra/operations/dnf.py +213 -0
  97. pyinfra/operations/docker.py +492 -0
  98. pyinfra/operations/files.py +2014 -0
  99. pyinfra/operations/flatpak.py +95 -0
  100. pyinfra/operations/freebsd/__init__.py +12 -0
  101. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  102. pyinfra/operations/freebsd/pkg.py +219 -0
  103. pyinfra/operations/freebsd/service.py +116 -0
  104. pyinfra/operations/freebsd/sysrc.py +92 -0
  105. pyinfra/operations/gem.py +48 -0
  106. pyinfra/operations/git.py +420 -0
  107. pyinfra/operations/iptables.py +312 -0
  108. pyinfra/operations/launchd.py +45 -0
  109. pyinfra/operations/lxd.py +69 -0
  110. pyinfra/operations/mysql.py +610 -0
  111. pyinfra/operations/npm.py +57 -0
  112. pyinfra/operations/openrc.py +63 -0
  113. pyinfra/operations/opkg.py +89 -0
  114. pyinfra/operations/pacman.py +82 -0
  115. pyinfra/operations/pip.py +206 -0
  116. pyinfra/operations/pipx.py +103 -0
  117. pyinfra/operations/pkg.py +71 -0
  118. pyinfra/operations/pkgin.py +92 -0
  119. pyinfra/operations/postgres.py +437 -0
  120. pyinfra/operations/postgresql.py +30 -0
  121. pyinfra/operations/puppet.py +41 -0
  122. pyinfra/operations/python.py +73 -0
  123. pyinfra/operations/runit.py +184 -0
  124. pyinfra/operations/selinux.py +190 -0
  125. pyinfra/operations/server.py +1100 -0
  126. pyinfra/operations/snap.py +118 -0
  127. pyinfra/operations/ssh.py +217 -0
  128. pyinfra/operations/systemd.py +150 -0
  129. pyinfra/operations/sysvinit.py +142 -0
  130. pyinfra/operations/upstart.py +68 -0
  131. pyinfra/operations/util/__init__.py +12 -0
  132. pyinfra/operations/util/docker.py +407 -0
  133. pyinfra/operations/util/files.py +247 -0
  134. pyinfra/operations/util/packaging.py +338 -0
  135. pyinfra/operations/util/service.py +46 -0
  136. pyinfra/operations/vzctl.py +137 -0
  137. pyinfra/operations/xbps.py +78 -0
  138. pyinfra/operations/yum.py +213 -0
  139. pyinfra/operations/zfs.py +176 -0
  140. pyinfra/operations/zypper.py +193 -0
  141. pyinfra/progress.py +44 -32
  142. pyinfra/py.typed +0 -0
  143. pyinfra/version.py +9 -1
  144. pyinfra-3.6.dist-info/METADATA +142 -0
  145. pyinfra-3.6.dist-info/RECORD +160 -0
  146. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
  147. pyinfra-3.6.dist-info/entry_points.txt +12 -0
  148. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
  149. pyinfra_cli/__init__.py +1 -0
  150. pyinfra_cli/cli.py +793 -0
  151. pyinfra_cli/commands.py +66 -0
  152. pyinfra_cli/exceptions.py +155 -65
  153. pyinfra_cli/inventory.py +233 -89
  154. pyinfra_cli/log.py +39 -43
  155. pyinfra_cli/main.py +26 -495
  156. pyinfra_cli/prints.py +215 -156
  157. pyinfra_cli/util.py +172 -105
  158. pyinfra_cli/virtualenv.py +25 -20
  159. pyinfra/api/connectors/__init__.py +0 -21
  160. pyinfra/api/connectors/ansible.py +0 -99
  161. pyinfra/api/connectors/docker.py +0 -178
  162. pyinfra/api/connectors/local.py +0 -169
  163. pyinfra/api/connectors/ssh.py +0 -402
  164. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  165. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  166. pyinfra/api/connectors/util.py +0 -63
  167. pyinfra/api/connectors/vagrant.py +0 -155
  168. pyinfra/facts/init.py +0 -176
  169. pyinfra/facts/util/files.py +0 -102
  170. pyinfra/hook.py +0 -41
  171. pyinfra/modules/__init__.py +0 -11
  172. pyinfra/modules/apk.py +0 -64
  173. pyinfra/modules/apt.py +0 -272
  174. pyinfra/modules/brew.py +0 -122
  175. pyinfra/modules/files.py +0 -711
  176. pyinfra/modules/gem.py +0 -30
  177. pyinfra/modules/git.py +0 -115
  178. pyinfra/modules/init.py +0 -344
  179. pyinfra/modules/iptables.py +0 -271
  180. pyinfra/modules/lxd.py +0 -45
  181. pyinfra/modules/mysql.py +0 -347
  182. pyinfra/modules/npm.py +0 -47
  183. pyinfra/modules/pacman.py +0 -60
  184. pyinfra/modules/pip.py +0 -99
  185. pyinfra/modules/pkg.py +0 -43
  186. pyinfra/modules/postgresql.py +0 -245
  187. pyinfra/modules/puppet.py +0 -20
  188. pyinfra/modules/python.py +0 -37
  189. pyinfra/modules/server.py +0 -524
  190. pyinfra/modules/ssh.py +0 -150
  191. pyinfra/modules/util/files.py +0 -52
  192. pyinfra/modules/util/packaging.py +0 -118
  193. pyinfra/modules/vzctl.py +0 -133
  194. pyinfra/modules/yum.py +0 -171
  195. pyinfra/pseudo_modules.py +0 -64
  196. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  197. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  198. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  199. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  200. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  201. pyinfra_cli/__main__.py +0 -40
  202. pyinfra_cli/config.py +0 -92
  203. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  204. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
pyinfra/facts/hardware.py CHANGED
@@ -1,51 +1,64 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
 
5
+ from typing_extensions import override
6
+
3
7
  from pyinfra.api import FactBase, ShortFactBase
4
8
 
5
9
 
6
- class Cpus(FactBase):
7
- '''
10
+ class Cpus(FactBase[int]):
11
+ """
8
12
  Returns the number of CPUs on this server.
9
- '''
13
+ """
10
14
 
11
- command = 'getconf NPROCESSORS_ONLN || getconf _NPROCESSORS_ONLN'
15
+ @override
16
+ def command(self) -> str:
17
+ return "getconf NPROCESSORS_ONLN 2> /dev/null || getconf _NPROCESSORS_ONLN"
12
18
 
13
- @staticmethod
14
- def process(output):
19
+ @override
20
+ def process(self, output):
15
21
  try:
16
- return int(output[0])
22
+ return int(list(output)[0])
17
23
  except ValueError:
18
24
  pass
19
25
 
20
26
 
21
27
  class Memory(FactBase):
22
- '''
28
+ """
23
29
  Returns the memory installed in this server, in MB.
24
- '''
30
+ """
31
+
32
+ @override
33
+ def requires_command(self) -> str:
34
+ return "vmstat"
25
35
 
26
- command = 'vmstat -s'
36
+ @override
37
+ def command(self) -> str:
38
+ return "vmstat -s"
27
39
 
28
- @staticmethod
29
- def process(output):
40
+ @override
41
+ def process(self, output):
30
42
  data = {}
31
43
 
32
44
  for line in output:
33
- value, key = line.split(' ', 1)
45
+ line = line.strip()
46
+ value, key = line.split(" ", 1)
34
47
 
35
48
  try:
36
- value = int(value)
49
+ value = float(value)
37
50
  except ValueError:
38
51
  continue
39
52
 
40
53
  data[key.strip()] = value
41
54
 
42
55
  # Easy - Linux just gives us the number
43
- total_memory = data.get('K total memory', data.get('total memory'))
56
+ total_memory = data.get("K total memory", data.get("total memory"))
44
57
 
45
58
  # BSD - calculate the total from the # pages and the page size
46
59
  if not total_memory:
47
- bytes_per_page = data.get('bytes per page')
48
- pages_managed = data.get('pages managed')
60
+ bytes_per_page = data.get("bytes per page")
61
+ pages_managed = data.get("pages managed")
49
62
 
50
63
  if bytes_per_page and pages_managed:
51
64
  total_memory = (pages_managed * bytes_per_page) / 1024
@@ -55,225 +68,358 @@ class Memory(FactBase):
55
68
 
56
69
 
57
70
  class BlockDevices(FactBase):
58
- '''
71
+ """
59
72
  Returns a dict of (mounted) block devices:
60
73
 
61
74
  .. code:: python
62
75
 
63
- '/dev/sda1': {
64
- 'available': '39489508',
65
- 'used_percent': '3',
66
- 'mount': '/',
67
- 'used': '836392',
68
- 'blocks': '40325900'
69
- },
70
- ...
71
- '''
76
+ {
77
+ "/dev/sda1": {
78
+ "available": "39489508",
79
+ "used_percent": "3",
80
+ "mount": "/",
81
+ "used": "836392",
82
+ "blocks": "40325900"
83
+ },
84
+ }
85
+ """
72
86
 
73
- command = 'df'
74
- regex = r'([a-zA-Z0-9\/\-_]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]{1,3})%\s+([a-zA-Z\/0-9\-_]+)' # noqa: E501
87
+ regex = r"([a-zA-Z0-9\/\-_]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]{1,3})%\s+([a-zA-Z\/0-9\-_]+)" # noqa: E501
75
88
  default = dict
76
89
 
90
+ @override
91
+ def command(self) -> str:
92
+ return "df"
93
+
94
+ @override
77
95
  def process(self, output):
78
96
  devices = {}
79
97
 
80
98
  for line in output:
81
99
  matches = re.match(self.regex, line)
82
100
  if matches:
83
- if matches.group(1) == 'none':
101
+ if matches.group(1) == "none":
84
102
  continue
85
103
 
86
104
  devices[matches.group(1)] = {
87
- 'blocks': matches.group(2),
88
- 'used': matches.group(3),
89
- 'available': matches.group(4),
90
- 'used_percent': matches.group(5),
91
- 'mount': matches.group(6),
105
+ "blocks": matches.group(2),
106
+ "used": matches.group(3),
107
+ "available": matches.group(4),
108
+ "used_percent": matches.group(5),
109
+ "mount": matches.group(6),
92
110
  }
93
111
 
94
112
  return devices
95
113
 
96
114
 
97
- nettools_1_regexes = [
98
- (
99
- r'^inet addr:([0-9\.]+).+Bcast:([0-9\.]+).+Mask:([0-9\.]+)$',
100
- ('ipv4', 'address', 'broadcast', 'netmask'),
101
- ),
102
- (
103
- r'^inet6 addr: ([0-9a-z:]+)\/([0-9]+) Scope:Global',
104
- ('ipv6', 'address', 'size'), # COMPAT
105
- # TODO: rename size -> mask_bits (pre-v0.11 compat)
106
- ),
107
- ]
108
-
109
- nettools_2_regexes = [
110
- (
111
- r'^inet ([0-9\.]+)\s+netmask ([0-9\.fx]+)(?:\s+broadcast ([0-9\.]+))?$',
112
- ('ipv4', 'address', 'netmask', 'broadcast'),
113
- ),
114
- (
115
- r'^inet6 ([0-9a-z:]+)\s+prefixlen ([0-9]+)',
116
- ('ipv6', 'address', 'size'), # COMPAT
117
- # TODO: rename size -> mask_bits (pre-v0.11 compat)
118
- ),
119
- ]
120
-
121
- iproute2_regexes = [
122
- (
123
- r'^inet ([0-9\.]+)\/([0-9]{1,2})(?:\s+brd ([0-9\.]+))?',
124
- ('ipv4', 'address', 'mask_bits', 'broadcast'),
125
- ),
126
- (
127
- r'^inet6 ([0-9a-z:]+)\/([0-9]{1,3})',
128
- ('ipv6', 'address', 'mask_bits'),
129
- ),
130
- ]
131
-
132
-
133
- def _parse_regexes(regexes, lines):
134
- data = {
135
- 'ipv4': {},
136
- 'ipv6': {},
137
- }
138
-
139
- for line in lines:
140
- for regex, groups in regexes:
141
- matches = re.match(regex, line)
142
- if matches:
143
- target_group = data[groups[0]]
144
-
145
- for i, group in enumerate(groups[1:]):
146
- target_group[group] = matches.group(i + 1)
147
-
148
- # COMPAT
149
- # TODO: remove (for forwards compatability, see above)
150
- if 'size' in target_group:
151
- target_group['mask_bits'] = target_group['size']
152
-
153
- if 'mask_bits' in target_group:
154
- target_group['mask_bits'] = int(target_group['mask_bits'])
155
-
156
- break
157
-
158
- return data
159
-
160
-
161
115
  class NetworkDevices(FactBase):
162
- '''
116
+ """
163
117
  Gets & returns a dict of network devices. See the ``ipv4_addresses`` and
164
118
  ``ipv6_addresses`` facts for easier-to-use shortcuts to get device addresses.
165
119
 
166
120
  .. code:: python
167
121
 
168
- 'eth0': {
169
- 'ipv4': {
170
- 'address': '127.0.0.1',
171
- 'broadcast': '127.0.0.13',
172
- # Only one of these will exist:
173
- 'netmask': '255.255.255.255',
174
- 'mask_bits': '32'
122
+ "enp1s0": {
123
+ "ether": "12:34:56:78:9A:BC",
124
+ "mtu": 1500,
125
+ "state": "UP",
126
+ "ipv4": {
127
+ "address": "192.168.1.100",
128
+ "mask_bits": 24,
129
+ "netmask": "255.255.255.0"
175
130
  },
176
- 'ipv6': {
177
- 'address': 'fe80::a00:27ff:fec3:36f0',
178
- 'mask_bits': '64'
131
+ "ipv6": {
132
+ "address": "2001:db8:85a3::8a2e:370:7334",
133
+ "mask_bits": 64,
134
+ "additional_ips": [
135
+ {
136
+ "address": "fe80::1234:5678:9abc:def0",
137
+ "mask_bits": 64
138
+ }
139
+ ]
179
140
  }
180
141
  },
181
- ...
182
- '''
142
+ "incusbr0": {
143
+ "ether": "DE:AD:BE:EF:CA:FE",
144
+ "mtu": 1500,
145
+ "state": "UP",
146
+ "ipv4": {
147
+ "address": "10.0.0.1",
148
+ "mask_bits": 24,
149
+ "netmask": "255.255.255.0"
150
+ },
151
+ "ipv6": {
152
+ "address": "fe80::dead:beef:cafe:babe",
153
+ "mask_bits": 64,
154
+ "additional_ips": [
155
+ {
156
+ "address": "2001:db8:1234:5678::1",
157
+ "mask_bits": 64
158
+ }
159
+ ]
160
+ }
161
+ },
162
+ "lo": {
163
+ "mtu": 65536,
164
+ "state": "UP",
165
+ "ipv6": {
166
+ "address": "::1",
167
+ "mask_bits": 128
168
+ }
169
+ },
170
+ "veth98806fd6": {
171
+ "ether": "AA:BB:CC:DD:EE:FF",
172
+ "mtu": 1500,
173
+ "state": "UP"
174
+ },
175
+ "vethda29df81": {
176
+ "ether": "11:22:33:44:55:66",
177
+ "mtu": 1500,
178
+ "state": "UP"
179
+ },
180
+ "wlo1": {
181
+ "ether": "77:88:99:AA:BB:CC",
182
+ "mtu": 1500,
183
+ "state": "UNKNOWN"
184
+ }
185
+ """
183
186
 
184
- command = 'ip addr show || ifconfig'
185
187
  default = dict
186
188
 
189
+ @override
190
+ def command(self) -> str:
191
+ return "ip addr show 2> /dev/null || ifconfig -a"
192
+
187
193
  # Definition of valid interface names for Linux:
188
194
  # https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/core/dev.c?h=v5.1.3#n1020
189
- _start_regexes = [
190
- (
191
- r'^([^/: \s]+)\s+Link encap:',
192
- lambda lines: _parse_regexes(nettools_1_regexes, lines),
193
- ),
194
- (
195
- r'^([^/: \s]+): flags=',
196
- lambda lines: _parse_regexes(nettools_2_regexes, lines),
197
- ),
198
- (
199
- r'^[0-9]+: ([^/: \s]+): ',
200
- lambda lines: _parse_regexes(iproute2_regexes, lines),
201
- ),
202
- ]
203
-
195
+ @override
204
196
  def process(self, output):
205
- devices = {}
197
+ def mask(value):
198
+ try:
199
+ if value.startswith("0x"):
200
+ mask_bits = bin(int(value, 16)).count("1")
201
+ else:
202
+ mask_bits = int(value)
203
+ netmask = ".".join(
204
+ str((0xFFFFFFFF << (32 - b) >> mask_bits) & 0xFF) for b in (24, 16, 8, 0)
205
+ )
206
+ except ValueError:
207
+ mask_bits = sum(bin(int(x)).count("1") for x in value.split("."))
208
+ netmask = value
206
209
 
207
- # Store current matches (start lines), the handler and any lines
208
- matches = None
209
- handler = None
210
- line_buffer = []
210
+ return mask_bits, netmask
211
211
 
212
- for line in output:
213
- matched = False
212
+ # Strip lines and merge them as a block of text
213
+ output = "\n".join(map(str.strip, output))
214
214
 
215
- # Look for start lines
216
- for regex, new_handler in self._start_regexes:
217
- new_matches = re.match(regex, line)
215
+ # Splitting the output into sections per network device
216
+ device_sections = re.split(r"\n(?=\d+: [^\s/:]|[^\s/:]+:.*mtu )", output)
218
217
 
219
- # If we find a start line
220
- if new_matches:
221
- matched = True
218
+ # Dictionary to hold all device information
219
+ all_devices = {}
222
220
 
223
- # Assign any current matches with current handler, reset buffer
224
- if matches:
225
- devices[matches.group(1)] = handler(line_buffer)
226
- line_buffer = []
221
+ for section in device_sections:
222
+ # Extracting the device name
223
+ device_name_match = re.match(r"^(?:\d+: )?([^\s/:]+):", section)
224
+ if not device_name_match:
225
+ continue
226
+ device_name = device_name_match.group(1)
227
+
228
+ # Regular expressions to match different parts of the output
229
+ ether_re = re.compile(r"ether ([0-9A-Fa-f:]{17})")
230
+ mtu_re = re.compile(r"mtu (\d+)")
231
+ ipv4_re = (
232
+ # ip a
233
+ re.compile(
234
+ r"inet (?P<address>\d+\.\d+\.\d+\.\d+)/(?P<mask>\d+)(?: metric \d+)?(?: brd (?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
235
+ ),
236
+ # ifconfig -a
237
+ re.compile(
238
+ r"inet (?P<address>\d+\.\d+\.\d+\.\d+)\s+netmask\s+(?P<mask>(?:\d+\.\d+\.\d+\.\d+)|(?:[0-9a-fA-FxX]+))(?:\s+broadcast\s+(?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
239
+ ),
240
+ )
241
+ ipv6_re = (
242
+ # ip a
243
+ re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)/(?P<mask>\d+)"),
244
+ # ifconfig -a
245
+ re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)\s+prefixlen\s+(?P<mask>\d+)"),
246
+ )
247
+
248
+ # Parsing the output
249
+ ether = ether_re.search(section)
250
+ mtu = mtu_re.search(section)
251
+
252
+ # Building the result dictionary for the device
253
+ device_info = {}
254
+ if ether:
255
+ device_info["ether"] = ether.group(1)
256
+ if mtu:
257
+ device_info["mtu"] = int(mtu.group(1))
258
+
259
+ device_info["state"] = (
260
+ "UP" if "UP" in section else "DOWN" if "DOWN" in section else "UNKNOWN"
261
+ )
262
+
263
+ # IPv4 Addresses
264
+ ipv4_matches: list[re.Match[str]]
265
+ for ipv4_re_ in ipv4_re:
266
+ ipv4_matches = list(ipv4_re_.finditer(section))
267
+ if len(ipv4_matches):
268
+ break
227
269
 
228
- # Set new matches/handler
229
- matches = new_matches
230
- handler = new_handler
270
+ if len(ipv4_matches):
271
+ ipv4_info = []
272
+ for ipv4 in ipv4_matches:
273
+ address = ipv4.group("address")
274
+ mask_value = ipv4.group("mask")
275
+ mask_bits, netmask = mask(mask_value)
276
+ try:
277
+ broadcast = ipv4.group("broadcast")
278
+ except IndexError:
279
+ broadcast = None
280
+
281
+ ipv4_info.append(
282
+ {
283
+ "address": address,
284
+ "mask_bits": mask_bits,
285
+ "netmask": netmask,
286
+ "broadcast": broadcast,
287
+ },
288
+ )
289
+ device_info["ipv4"] = ipv4_info[0]
290
+ if len(ipv4_matches) > 1:
291
+ device_info["ipv4"]["additional_ips"] = ipv4_info[1:] # type: ignore[index]
292
+
293
+ # IPv6 Addresses
294
+ ipv6_matches: list[re.Match[str]]
295
+ for ipv6_re_ in ipv6_re:
296
+ ipv6_matches = list(ipv6_re_.finditer(section))
297
+ if ipv6_matches:
231
298
  break
232
299
 
233
- if not matched:
234
- line_buffer.append(line)
300
+ if len(ipv6_matches):
301
+ ipv6_info = []
302
+ for ipv6 in ipv6_matches:
303
+ address = ipv6.group("address")
304
+ mask_bits = ipv6.group("mask")
305
+ ipv6_info.append({"address": address, "mask_bits": int(mask_bits)})
306
+ device_info["ipv6"] = ipv6_info[0]
307
+ if len(ipv6_matches) > 1:
308
+ device_info["ipv6"]["additional_ips"] = ipv6_info[1:] # type: ignore[index]
235
309
 
236
- # Handle any left over matches
237
- if matches:
238
- devices[matches.group(1)] = handler(line_buffer)
310
+ all_devices[device_name] = device_info
239
311
 
240
- return devices
312
+ return all_devices
313
+
314
+
315
+ class Ipv4Addrs(ShortFactBase):
316
+ """
317
+ Gets & returns a dictionary of network interface -> list of IPv4 addresses.
318
+
319
+ .. code:: python
320
+
321
+ {
322
+ "eth0": ["127.0.0.1"],
323
+ }
324
+
325
+ .. note::
326
+ Network interfaces with no IPv4 will not be part of the dictionary.
327
+ """
328
+
329
+ fact = NetworkDevices
330
+ ip_type = "ipv4"
331
+
332
+ @override
333
+ def process_data(self, data):
334
+ host_to_ips = {}
335
+
336
+ for interface, details in data.items():
337
+ ips = []
338
+
339
+ ip_details = details.get(self.ip_type)
340
+ if not ip_details or not ip_details.get("address"):
341
+ continue
342
+
343
+ ips.append(ip_details["address"])
344
+ if "additional_ips" in ip_details:
345
+ ips.extend([ip["address"] for ip in ip_details["additional_ips"]])
346
+
347
+ host_to_ips[interface] = ips
348
+
349
+ return host_to_ips
350
+
351
+
352
+ class Ipv6Addrs(Ipv4Addrs):
353
+ """
354
+ Gets & returns a dictionary of network interface -> list of IPv6 addresses.
355
+
356
+ .. code:: python
357
+
358
+ {
359
+ "eth0": ["fe80::a00:27ff::2"],
360
+ }
361
+
362
+ .. note::
363
+ Network interfaces with no IPv6 will not be part of the dictionary.
364
+ """
365
+
366
+ ip_type = "ipv6"
367
+
368
+
369
+ # TODO: remove these in v3
370
+ # Legacy versions of the above that only support one IP per interface
371
+ #
241
372
 
242
373
 
243
374
  class Ipv4Addresses(ShortFactBase):
244
- '''
375
+ """
245
376
  Gets & returns a dictionary of network interface -> IPv4 address.
246
377
 
247
378
  .. code:: python
248
379
 
249
- 'eth0': '127.0.0.1',
250
- ...
251
- '''
380
+ {
381
+ "eth0": "127.0.0.1",
382
+ }
383
+
384
+ .. warning::
385
+ This fact is deprecated, please use the ``hardware.Ipv4Addrs`` fact.
386
+
387
+ .. note::
388
+ Network interfaces with no IPv4 will not be part of the dictionary.
389
+ """
252
390
 
253
391
  fact = NetworkDevices
254
- ip_type = 'ipv4'
392
+ ip_type = "ipv4"
255
393
 
394
+ @override
256
395
  def process_data(self, data):
257
396
  addresses = {}
258
397
 
259
398
  for interface, details in data.items():
260
399
  ip_details = details.get(self.ip_type)
261
- if not ip_details:
262
- continue # pramga: no cover
400
+ if not ip_details or not ip_details.get("address"):
401
+ continue # pragma: no cover
263
402
 
264
- addresses[interface] = ip_details['address']
403
+ addresses[interface] = ip_details["address"]
265
404
 
266
405
  return addresses
267
406
 
268
407
 
269
408
  class Ipv6Addresses(Ipv4Addresses):
270
- '''
409
+ """
271
410
  Gets & returns a dictionary of network interface -> IPv6 address.
272
411
 
273
412
  .. code:: python
274
413
 
275
- 'eth0': 'fe80::a00:27ff::2',
276
- ...
277
- '''
414
+ {
415
+ "eth0": "fe80::a00:27ff::2",
416
+ }
417
+
418
+ .. warning::
419
+ This fact is deprecated, please use the ``hardware.Ipv6Addrs`` fact.
420
+
421
+ .. note::
422
+ Network interfaces with no IPv6 will not be part of the dictionary.
423
+ """
278
424
 
279
- ip_type = 'ipv6'
425
+ ip_type = "ipv6"