pyinfra 3.1.1__py2.py3-none-any.whl → 3.3__py2.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 (104) hide show
  1. pyinfra/api/arguments.py +9 -2
  2. pyinfra/api/arguments_typed.py +4 -5
  3. pyinfra/api/command.py +22 -3
  4. pyinfra/api/config.py +5 -2
  5. pyinfra/api/deploy.py +4 -2
  6. pyinfra/api/facts.py +3 -0
  7. pyinfra/api/host.py +15 -7
  8. pyinfra/api/operation.py +2 -1
  9. pyinfra/api/state.py +1 -1
  10. pyinfra/connectors/base.py +34 -8
  11. pyinfra/connectors/chroot.py +7 -2
  12. pyinfra/connectors/docker.py +24 -8
  13. pyinfra/connectors/dockerssh.py +7 -2
  14. pyinfra/connectors/local.py +7 -2
  15. pyinfra/connectors/ssh.py +9 -2
  16. pyinfra/connectors/sshuserclient/client.py +42 -14
  17. pyinfra/connectors/sshuserclient/config.py +2 -0
  18. pyinfra/connectors/terraform.py +1 -1
  19. pyinfra/connectors/util.py +13 -9
  20. pyinfra/context.py +9 -2
  21. pyinfra/facts/apk.py +8 -1
  22. pyinfra/facts/apt.py +68 -0
  23. pyinfra/facts/brew.py +13 -0
  24. pyinfra/facts/bsdinit.py +3 -0
  25. pyinfra/facts/cargo.py +5 -0
  26. pyinfra/facts/choco.py +6 -0
  27. pyinfra/facts/crontab.py +195 -0
  28. pyinfra/facts/deb.py +10 -0
  29. pyinfra/facts/dnf.py +5 -0
  30. pyinfra/facts/docker.py +16 -0
  31. pyinfra/facts/efibootmgr.py +113 -0
  32. pyinfra/facts/files.py +112 -7
  33. pyinfra/facts/flatpak.py +7 -0
  34. pyinfra/facts/freebsd.py +75 -0
  35. pyinfra/facts/gem.py +5 -0
  36. pyinfra/facts/git.py +12 -2
  37. pyinfra/facts/gpg.py +7 -0
  38. pyinfra/facts/hardware.py +13 -0
  39. pyinfra/facts/iptables.py +9 -1
  40. pyinfra/facts/launchd.py +5 -0
  41. pyinfra/facts/lxd.py +5 -0
  42. pyinfra/facts/mysql.py +9 -2
  43. pyinfra/facts/npm.py +5 -0
  44. pyinfra/facts/openrc.py +8 -0
  45. pyinfra/facts/opkg.py +245 -0
  46. pyinfra/facts/pacman.py +9 -1
  47. pyinfra/facts/pip.py +5 -0
  48. pyinfra/facts/pipx.py +82 -0
  49. pyinfra/facts/pkg.py +4 -0
  50. pyinfra/facts/pkgin.py +5 -0
  51. pyinfra/facts/podman.py +54 -0
  52. pyinfra/facts/postgres.py +10 -2
  53. pyinfra/facts/rpm.py +11 -0
  54. pyinfra/facts/runit.py +7 -0
  55. pyinfra/facts/selinux.py +16 -0
  56. pyinfra/facts/server.py +87 -79
  57. pyinfra/facts/snap.py +7 -0
  58. pyinfra/facts/systemd.py +5 -0
  59. pyinfra/facts/sysvinit.py +4 -0
  60. pyinfra/facts/upstart.py +5 -0
  61. pyinfra/facts/util/__init__.py +4 -1
  62. pyinfra/facts/util/units.py +30 -0
  63. pyinfra/facts/vzctl.py +5 -0
  64. pyinfra/facts/xbps.py +6 -1
  65. pyinfra/facts/yum.py +5 -0
  66. pyinfra/facts/zfs.py +41 -21
  67. pyinfra/facts/zypper.py +5 -0
  68. pyinfra/local.py +3 -2
  69. pyinfra/operations/apt.py +36 -22
  70. pyinfra/operations/crontab.py +189 -0
  71. pyinfra/operations/docker.py +61 -56
  72. pyinfra/operations/files.py +65 -1
  73. pyinfra/operations/freebsd/__init__.py +12 -0
  74. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  75. pyinfra/operations/freebsd/pkg.py +219 -0
  76. pyinfra/operations/freebsd/service.py +116 -0
  77. pyinfra/operations/freebsd/sysrc.py +92 -0
  78. pyinfra/operations/git.py +23 -7
  79. pyinfra/operations/opkg.py +88 -0
  80. pyinfra/operations/pip.py +3 -2
  81. pyinfra/operations/pipx.py +90 -0
  82. pyinfra/operations/postgres.py +114 -27
  83. pyinfra/operations/runit.py +2 -0
  84. pyinfra/operations/server.py +9 -181
  85. pyinfra/operations/util/docker.py +44 -22
  86. pyinfra/operations/zfs.py +3 -3
  87. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/LICENSE.md +1 -1
  88. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/METADATA +25 -25
  89. pyinfra-3.3.dist-info/RECORD +187 -0
  90. pyinfra_cli/exceptions.py +5 -0
  91. pyinfra_cli/inventory.py +26 -9
  92. pyinfra_cli/log.py +3 -0
  93. pyinfra_cli/main.py +9 -8
  94. pyinfra_cli/prints.py +19 -4
  95. pyinfra_cli/util.py +3 -0
  96. pyinfra_cli/virtualenv.py +1 -1
  97. tests/test_cli/test_cli_deploy.py +15 -13
  98. tests/test_cli/test_cli_inventory.py +53 -0
  99. tests/test_connectors/test_ssh.py +302 -182
  100. tests/test_connectors/test_sshuserclient.py +68 -1
  101. pyinfra-3.1.1.dist-info/RECORD +0 -172
  102. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/WHEEL +0 -0
  103. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/entry_points.txt +0 -0
  104. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/top_level.txt +0 -0
pyinfra/facts/selinux.py CHANGED
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import re
4
4
  from collections import defaultdict
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from pyinfra.api import FactBase
7
9
 
8
10
  FIELDS = ["user", "role", "type", "level"] # order is significant, do not change
@@ -14,14 +16,17 @@ class SEBoolean(FactBase):
14
16
  If ``boolean`` does not exist, ``SEBoolean`` returns the empty string.
15
17
  """
16
18
 
19
+ @override
17
20
  def requires_command(self, boolean) -> str:
18
21
  return "getsebool"
19
22
 
20
23
  default = str
21
24
 
25
+ @override
22
26
  def command(self, boolean):
23
27
  return "getsebool {0}".format(boolean)
24
28
 
29
+ @override
25
30
  def process(self, output):
26
31
  components = output[0].split(" --> ")
27
32
  return components[1]
@@ -42,9 +47,11 @@ class FileContext(FactBase):
42
47
  }
43
48
  """
44
49
 
50
+ @override
45
51
  def command(self, path):
46
52
  return "stat -c %C {0} || exit 0".format(path)
47
53
 
54
+ @override
48
55
  def process(self, output):
49
56
  context = {}
50
57
  components = output[0].split(":")
@@ -65,12 +72,15 @@ class FileContextMapping(FactBase):
65
72
 
66
73
  default = dict
67
74
 
75
+ @override
68
76
  def requires_command(self, target) -> str:
69
77
  return "semanage"
70
78
 
79
+ @override
71
80
  def command(self, target):
72
81
  return "set -o pipefail && semanage fcontext -n -l | (grep '^{0}' || true)".format(target)
73
82
 
83
+ @override
74
84
  def process(self, output):
75
85
  # example output: /etc all files system_u:object_r:etc_t:s0 # noqa: SC100
76
86
  # but lines at end that won't match: /etc/systemd/system = /usr/lib/systemd/system
@@ -97,12 +107,15 @@ class SEPorts(FactBase):
97
107
  # example output: amqp_port_t tcp 15672, 5671-5672 # noqa: SC100
98
108
  _regex = re.compile(r"^([\w_]+)\s+(\w+)\s+([\w\-,\s]+)$")
99
109
 
110
+ @override
100
111
  def requires_command(self) -> str:
101
112
  return "semanage"
102
113
 
114
+ @override
103
115
  def command(self):
104
116
  return "semanage port -ln"
105
117
 
118
+ @override
106
119
  def process(self, output):
107
120
  labels: dict[str, dict] = defaultdict(dict)
108
121
  for line in output:
@@ -132,12 +145,15 @@ class SEPort(FactBase):
132
145
 
133
146
  default = str
134
147
 
148
+ @override
135
149
  def requires_command(self, protocol, port) -> str:
136
150
  return "sepolicy"
137
151
 
152
+ @override
138
153
  def command(self, protocol, port):
139
154
  return "(sepolicy network -p {0} 2>/dev/null || true) | grep {1}".format(port, protocol)
140
155
 
156
+ @override
141
157
  def process(self, output):
142
158
  # if type set, first line is specific and second is generic type for port range
143
159
  # each rows in the format "22: tcp ssh_port_t 22"
pyinfra/facts/server.py CHANGED
@@ -5,14 +5,15 @@ import re
5
5
  import shutil
6
6
  from datetime import datetime
7
7
  from tempfile import mkdtemp
8
- from typing import Dict, List, Optional, Union
8
+ from typing import Dict, List, Optional
9
9
 
10
10
  from dateutil.parser import parse as parse_date
11
11
  from distro import distro
12
- from typing_extensions import NotRequired, TypedDict
12
+ from typing_extensions import TypedDict, override
13
13
 
14
14
  from pyinfra.api import FactBase, ShortFactBase
15
15
  from pyinfra.api.util import try_int
16
+ from pyinfra.facts import crontab
16
17
 
17
18
  ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
18
19
 
@@ -22,6 +23,7 @@ class User(FactBase):
22
23
  Returns the name of the current user.
23
24
  """
24
25
 
26
+ @override
25
27
  def command(self):
26
28
  return "echo $USER"
27
29
 
@@ -31,8 +33,8 @@ class Home(FactBase[Optional[str]]):
31
33
  Returns the home directory of the given user, or the current user if no user is given.
32
34
  """
33
35
 
34
- @staticmethod
35
- def command(user=""):
36
+ @override
37
+ def command(self, user=""):
36
38
  return f"echo ~{user}"
37
39
 
38
40
 
@@ -41,6 +43,7 @@ class Path(FactBase):
41
43
  Returns the path environment variable of the current user.
42
44
  """
43
45
 
46
+ @override
44
47
  def command(self):
45
48
  return "echo $PATH"
46
49
 
@@ -50,6 +53,7 @@ class TmpDir(FactBase):
50
53
  Returns the temporary directory of the current server, if configured.
51
54
  """
52
55
 
56
+ @override
53
57
  def command(self):
54
58
  return "echo $TMPDIR"
55
59
 
@@ -59,6 +63,7 @@ class Hostname(FactBase):
59
63
  Returns the current hostname of the server.
60
64
  """
61
65
 
66
+ @override
62
67
  def command(self):
63
68
  return "uname -n"
64
69
 
@@ -68,6 +73,7 @@ class Kernel(FactBase):
68
73
  Returns the kernel name according to ``uname``.
69
74
  """
70
75
 
76
+ @override
71
77
  def command(self):
72
78
  return "uname -s"
73
79
 
@@ -77,6 +83,7 @@ class KernelVersion(FactBase):
77
83
  Returns the kernel version according to ``uname``.
78
84
  """
79
85
 
86
+ @override
80
87
  def command(self):
81
88
  return "uname -r"
82
89
 
@@ -90,6 +97,7 @@ class Os(FactBase[str]):
90
97
  This fact is deprecated/renamed, please use the ``server.Kernel`` fact.
91
98
  """
92
99
 
100
+ @override
93
101
  def command(self):
94
102
  return "uname -s"
95
103
 
@@ -103,6 +111,7 @@ class OsVersion(FactBase[str]):
103
111
  This fact is deprecated/renamed, please use the ``server.KernelVersion`` fact.
104
112
  """
105
113
 
114
+ @override
106
115
  def command(self):
107
116
  return "uname -r"
108
117
 
@@ -114,6 +123,7 @@ class Arch(FactBase[str]):
114
123
 
115
124
  # ``uname -p`` is not portable and returns ``unknown`` on Debian.
116
125
  # ``uname -m`` works on most Linux and BSD systems.
126
+ @override
117
127
  def command(self):
118
128
  return "uname -m"
119
129
 
@@ -123,8 +133,8 @@ class Command(FactBase[str]):
123
133
  Returns the raw output lines of a given command.
124
134
  """
125
135
 
126
- @staticmethod
127
- def command(command):
136
+ @override
137
+ def command(self, command):
128
138
  return command
129
139
 
130
140
 
@@ -133,8 +143,8 @@ class Which(FactBase[Optional[str]]):
133
143
  Returns the path of a given command according to `command -v`, if available.
134
144
  """
135
145
 
136
- @staticmethod
137
- def command(command):
146
+ @override
147
+ def command(self, command):
138
148
  return "command -v {0} || true".format(command)
139
149
 
140
150
 
@@ -145,9 +155,11 @@ class Date(FactBase[datetime]):
145
155
 
146
156
  default = datetime.now
147
157
 
158
+ @override
148
159
  def command(self):
149
160
  return f"date +'{ISO_DATE_FORMAT}'"
150
161
 
162
+ @override
151
163
  def process(self, output) -> datetime:
152
164
  return datetime.strptime(list(output)[0], ISO_DATE_FORMAT)
153
165
 
@@ -157,9 +169,11 @@ class MacosVersion(FactBase[str]):
157
169
  Returns the installed MacOS version.
158
170
  """
159
171
 
172
+ @override
160
173
  def requires_command(self) -> str:
161
174
  return "sw_vers"
162
175
 
176
+ @override
163
177
  def command(self):
164
178
  return "sw_vers -productVersion"
165
179
 
@@ -190,9 +204,11 @@ class Mounts(FactBase[Dict[str, MountsDict]]):
190
204
 
191
205
  default = dict
192
206
 
207
+ @override
193
208
  def command(self):
194
209
  return "mount"
195
210
 
211
+ @override
196
212
  def process(self, output) -> dict[str, MountsDict]:
197
213
  devices: dict[str, MountsDict] = {}
198
214
 
@@ -238,11 +254,13 @@ class KernelModules(FactBase):
238
254
  }
239
255
  """
240
256
 
257
+ @override
241
258
  def command(self):
242
259
  return "! test -f /proc/modules || cat /proc/modules"
243
260
 
244
261
  default = dict
245
262
 
263
+ @override
246
264
  def process(self, output):
247
265
  modules = {}
248
266
 
@@ -279,12 +297,15 @@ class LsbRelease(FactBase):
279
297
  }
280
298
  """
281
299
 
300
+ @override
282
301
  def command(self):
283
302
  return "lsb_release -ca"
284
303
 
304
+ @override
285
305
  def requires_command(self):
286
306
  return "lsb_release"
287
307
 
308
+ @override
288
309
  def process(self, output):
289
310
  items = {}
290
311
 
@@ -307,6 +328,38 @@ class LsbRelease(FactBase):
307
328
  return items
308
329
 
309
330
 
331
+ class OsRelease(FactBase):
332
+ """
333
+ Returns a dictionary of release information stored in ``/etc/os-release``.
334
+
335
+ .. code:: python
336
+
337
+ {
338
+ "name": "EndeavourOS",
339
+ "pretty_name": "EndeavourOS",
340
+ "id": "endeavouros",
341
+ "id_like": "arch",
342
+ "build_id": "2024.06.25",
343
+ ...
344
+ }
345
+ """
346
+
347
+ @override
348
+ def command(self):
349
+ return "cat /etc/os-release"
350
+
351
+ @override
352
+ def process(self, output):
353
+ items = {}
354
+
355
+ for line in output:
356
+ if "=" in line:
357
+ key, value = line.split("=", 1)
358
+ items[key.strip().lower()] = value.strip().strip('"')
359
+
360
+ return items
361
+
362
+
310
363
  class Sysctl(FactBase):
311
364
  """
312
365
  Returns a dictionary of sysctl settings and values.
@@ -324,11 +377,13 @@ class Sysctl(FactBase):
324
377
 
325
378
  default = dict
326
379
 
380
+ @override
327
381
  def command(self, keys=None):
328
382
  if keys is None:
329
383
  return "sysctl -a"
330
384
  return f"sysctl {' '.join(keys)}"
331
385
 
386
+ @override
332
387
  def process(self, output):
333
388
  sysctls = {}
334
389
 
@@ -362,11 +417,13 @@ class Groups(FactBase[List[str]]):
362
417
  Returns a list of groups on the system.
363
418
  """
364
419
 
420
+ @override
365
421
  def command(self):
366
422
  return "cat /etc/group"
367
423
 
368
424
  default = list
369
425
 
426
+ @override
370
427
  def process(self, output) -> list[str]:
371
428
  groups: list[str] = []
372
429
 
@@ -377,75 +434,9 @@ class Groups(FactBase[List[str]]):
377
434
  return groups
378
435
 
379
436
 
380
- class CrontabDict(TypedDict):
381
- minute: NotRequired[Union[int, str]]
382
- hour: NotRequired[Union[int, str]]
383
- month: NotRequired[Union[int, str]]
384
- day_of_month: NotRequired[Union[int, str]]
385
- day_of_week: NotRequired[Union[int, str]]
386
- comments: Optional[list[str]]
387
- special_time: NotRequired[str]
388
-
389
-
390
- class Crontab(FactBase[Dict[str, CrontabDict]]):
391
- """
392
- Returns a dictionary of cron command -> execution time.
393
-
394
- .. code:: python
395
-
396
- {
397
- "/path/to/command": {
398
- "minute": "*",
399
- "hour": "*",
400
- "month": "*",
401
- "day_of_month": "*",
402
- "day_of_week": "*",
403
- },
404
- "echo another command": {
405
- "special_time": "@daily",
406
- },
407
- }
408
- """
409
-
410
- default = dict
411
-
412
- def requires_command(self, user=None) -> str:
413
- return "crontab"
414
-
415
- def command(self, user=None):
416
- if user:
417
- return "crontab -l -u {0} || true".format(user)
418
- return "crontab -l || true"
419
-
420
- def process(self, output):
421
- crons: dict[str, CrontabDict] = {}
422
- current_comments = []
423
-
424
- for line in output:
425
- line = line.strip()
426
- if not line or line.startswith("#"):
427
- current_comments.append(line)
428
- continue
429
-
430
- if line.startswith("@"):
431
- special_time, command = line.split(None, 1)
432
- crons[command] = {
433
- "special_time": special_time,
434
- "comments": current_comments,
435
- }
436
- else:
437
- minute, hour, day_of_month, month, day_of_week, command = line.split(None, 5)
438
- crons[command] = {
439
- "minute": try_int(minute),
440
- "hour": try_int(hour),
441
- "month": try_int(month),
442
- "day_of_month": try_int(day_of_month),
443
- "day_of_week": try_int(day_of_week),
444
- "comments": current_comments,
445
- }
446
-
447
- current_comments = []
448
- return crons
437
+ # for compatibility
438
+ CrontabDict = crontab.CrontabDict
439
+ Crontab = crontab.Crontab
449
440
 
450
441
 
451
442
  class Users(FactBase):
@@ -472,13 +463,13 @@ class Users(FactBase):
472
463
  }
473
464
  """
474
465
 
466
+ @override
475
467
  def command(self):
476
468
  return """
477
469
 
478
470
  for i in `cat /etc/passwd | cut -d: -f1`; do
479
471
  ENTRY=`grep ^$i: /etc/passwd`;
480
- LASTLOG_RAW=`(lastlog -u $i 2> /dev/null || lastlogin $i 2> /dev/null)`;
481
- LASTLOG=`echo $LASTLOG_RAW | grep ^$i | tr -s ' '`;
472
+ LASTLOG=`(((lastlog -u $i || lastlogin $i) 2> /dev/null) | grep ^$i | tr -s ' ')`;
482
473
  PASSWORD=`grep ^$i: /etc/shadow | cut -d: -f2`;
483
474
  echo "$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG|$PASSWORD";
484
475
  done
@@ -486,6 +477,7 @@ class Users(FactBase):
486
477
 
487
478
  default = dict
488
479
 
480
+ @override
489
481
  def process(self, output):
490
482
  users = {}
491
483
  rex = r"[A-Z][a-z]{2} [A-Z][a-z]{2} {1,2}\d+ .+$"
@@ -557,6 +549,7 @@ class LinuxDistribution(FactBase[LinuxDistributionDict]):
557
549
  }
558
550
  """
559
551
 
552
+ @override
560
553
  def command(self) -> str:
561
554
  return (
562
555
  "cd /etc/ && for file in $(ls -pdL *-release | grep -v /); "
@@ -575,6 +568,7 @@ class LinuxDistribution(FactBase[LinuxDistributionDict]):
575
568
  "debian": "Debian",
576
569
  }
577
570
 
571
+ @override
578
572
  @staticmethod
579
573
  def default() -> LinuxDistributionDict:
580
574
  return {
@@ -584,6 +578,7 @@ class LinuxDistribution(FactBase[LinuxDistributionDict]):
584
578
  "release_meta": {},
585
579
  }
586
580
 
581
+ @override
587
582
  def process(self, output) -> LinuxDistributionDict:
588
583
  parts = {}
589
584
  for part in "\n".join(output).strip().split("---"):
@@ -646,6 +641,7 @@ class LinuxName(ShortFactBase[str]):
646
641
 
647
642
  fact = LinuxDistribution
648
643
 
644
+ @override
649
645
  def process_data(self, data) -> str:
650
646
  return data["name"]
651
647
 
@@ -665,18 +661,22 @@ class Selinux(FactBase[SelinuxDict]):
665
661
  }
666
662
  """
667
663
 
664
+ @override
668
665
  def command(self):
669
666
  return "sestatus"
670
667
 
668
+ @override
671
669
  def requires_command(self) -> str:
672
670
  return "sestatus"
673
671
 
672
+ @override
674
673
  @staticmethod
675
674
  def default() -> SelinuxDict:
676
675
  return {
677
676
  "mode": None,
678
677
  }
679
678
 
679
+ @override
680
680
  def process(self, output) -> SelinuxDict:
681
681
  selinux_info = self.default()
682
682
 
@@ -695,6 +695,7 @@ class LinuxGui(FactBase[List[str]]):
695
695
  Returns a list of available Linux GUIs.
696
696
  """
697
697
 
698
+ @override
698
699
  def command(self):
699
700
  return "ls /usr/bin/*session || true"
700
701
 
@@ -708,6 +709,7 @@ class LinuxGui(FactBase[List[str]]):
708
709
  "/usr/bin/xfce4-session": "XFCE 4",
709
710
  }
710
711
 
712
+ @override
711
713
  def process(self, output) -> list[str]:
712
714
  gui_names = []
713
715
 
@@ -726,6 +728,7 @@ class HasGui(ShortFactBase[bool]):
726
728
 
727
729
  fact = LinuxGui
728
730
 
731
+ @override
729
732
  def process_data(self, data) -> bool:
730
733
  return len(data) > 0
731
734
 
@@ -739,14 +742,17 @@ class Locales(FactBase[List[str]]):
739
742
  ["C.UTF-8", "en_US.UTF-8"]
740
743
  """
741
744
 
745
+ @override
742
746
  def command(self) -> str:
743
747
  return "locale -a"
744
748
 
749
+ @override
745
750
  def requires_command(self) -> str:
746
751
  return "locale"
747
752
 
748
753
  default = list
749
754
 
755
+ @override
750
756
  def process(self, output) -> list[str]:
751
757
  # replace utf8 with UTF-8 to match names in /etc/locale.gen
752
758
  # return a list of enabled locales
@@ -811,11 +817,13 @@ class SecurityLimits(FactBase):
811
817
  ]
812
818
  """
813
819
 
820
+ @override
814
821
  def command(self):
815
822
  return "cat /etc/security/limits.conf"
816
823
 
817
824
  default = list
818
825
 
826
+ @override
819
827
  def process(self, output):
820
828
  limits = []
821
829
 
pyinfra/facts/snap.py CHANGED
@@ -2,12 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
 
5
+ from typing_extensions import override
6
+
5
7
  from pyinfra.api import FactBase
6
8
 
7
9
 
8
10
  class SnapBaseFact(FactBase):
9
11
  abstract = True
10
12
 
13
+ @override
11
14
  def requires_command(self, *args, **kwargs) -> str:
12
15
  return "snap"
13
16
 
@@ -36,9 +39,11 @@ class SnapPackage(SnapBaseFact):
36
39
  "version": r"^installed:[ ]+([\w\d.-]+).*$",
37
40
  }
38
41
 
42
+ @override
39
43
  def command(self, package):
40
44
  return f"snap info {package}"
41
45
 
46
+ @override
42
47
  def process(self, output):
43
48
  data = {}
44
49
  for line in output:
@@ -67,9 +72,11 @@ class SnapPackages(SnapBaseFact):
67
72
 
68
73
  default = list
69
74
 
75
+ @override
70
76
  def command(self) -> str:
71
77
  return "snap list"
72
78
 
79
+ @override
73
80
  def process(self, output):
74
81
  # Discard header output line from snap list command
75
82
  # 'snap list' command example output lines:
pyinfra/facts/systemd.py CHANGED
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import re
4
4
  from typing import Dict, Iterable
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from pyinfra.api import FactBase, QuoteString, StringCommand
7
9
 
8
10
  # Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
@@ -55,6 +57,7 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
55
57
  }
56
58
  """
57
59
 
60
+ @override
58
61
  def requires_command(self, *args, **kwargs) -> str:
59
62
  return "systemctl"
60
63
 
@@ -63,6 +66,7 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
63
66
  state_key = "SubState"
64
67
  state_values = ["running", "waiting", "exited", "listening"]
65
68
 
69
+ @override
66
70
  def command(
67
71
  self,
68
72
  user_mode: bool = False,
@@ -94,6 +98,7 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
94
98
  *service_strs,
95
99
  )
96
100
 
101
+ @override
97
102
  def process(self, output) -> Dict[str, bool]:
98
103
  services: Dict[str, bool] = {}
99
104
 
pyinfra/facts/sysvinit.py CHANGED
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import re
4
4
  from typing import Optional
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from pyinfra.api import FactBase
7
9
 
8
10
 
@@ -18,6 +20,7 @@ class InitdStatus(FactBase):
18
20
  http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
19
21
  """
20
22
 
23
+ @override
21
24
  def command(self) -> str:
22
25
  return """
23
26
  for SERVICE in `ls /etc/init.d/`; do
@@ -33,6 +36,7 @@ class InitdStatus(FactBase):
33
36
  regex = r"([a-zA-Z0-9\-]+)=([0-9]+)"
34
37
  default = dict
35
38
 
39
+ @override
36
40
  def process(self, output) -> dict[str, Optional[bool]]:
37
41
  services: dict[str, Optional[bool]] = {}
38
42
 
pyinfra/facts/upstart.py CHANGED
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
 
5
+ from typing_extensions import override
6
+
5
7
  from pyinfra.api import FactBase
6
8
 
7
9
 
@@ -10,15 +12,18 @@ class UpstartStatus(FactBase):
10
12
  Returns a dict of name -> status for upstart managed services.
11
13
  """
12
14
 
15
+ @override
13
16
  def requires_command(self) -> str:
14
17
  return "initctl"
15
18
 
16
19
  regex = r"^([a-z\-]+) [a-z]+\/([a-z]+)"
17
20
  default = dict
18
21
 
22
+ @override
19
23
  def command(self):
20
24
  return "initctl list"
21
25
 
26
+ @override
22
27
  def process(self, output):
23
28
  services = {}
24
29
 
@@ -1,4 +1,7 @@
1
- def make_cat_files_command(*filenames):
1
+ from typing import Iterable
2
+
3
+
4
+ def make_cat_files_command(*filenames: Iterable[str]) -> str:
2
5
  commands = []
3
6
 
4
7
  for filename in filenames:
@@ -0,0 +1,30 @@
1
+ # from https://stackoverflow.com/a/60708339, but with a few modifications
2
+ from __future__ import annotations # for | in type hints
3
+
4
+ import re
5
+
6
+ units = {
7
+ "B": 1,
8
+ "KB": 10**3,
9
+ "MB": 10**6,
10
+ "GB": 10**9,
11
+ "TB": 10**12,
12
+ "KIB": 2**10,
13
+ "MIB": 2**20,
14
+ "GIB": 2**30,
15
+ "TIB": 2**40,
16
+ }
17
+
18
+
19
+ def parse_human_readable_size(size: str) -> int:
20
+ size = size.upper()
21
+ if not re.match(r" ", size):
22
+ size = re.sub(r"([KMGT]?I?[B])", r" \1", size)
23
+ number, unit = [string.strip() for string in size.split()]
24
+ return int(float(number) * units[unit])
25
+
26
+
27
+ def parse_size(size: str | int) -> int:
28
+ if isinstance(size, int):
29
+ return size
30
+ return parse_human_readable_size(size)