pyinfra 3.0.dev0__py2.py3-none-any.whl → 3.0.1__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 (148) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +115 -97
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +5 -3
  5. pyinfra/api/config.py +139 -39
  6. pyinfra/api/connectors.py +5 -2
  7. pyinfra/api/deploy.py +19 -19
  8. pyinfra/api/exceptions.py +35 -4
  9. pyinfra/api/facts.py +62 -86
  10. pyinfra/api/host.py +102 -15
  11. pyinfra/api/inventory.py +4 -0
  12. pyinfra/api/operation.py +184 -118
  13. pyinfra/api/operations.py +66 -113
  14. pyinfra/api/state.py +53 -34
  15. pyinfra/api/util.py +64 -33
  16. pyinfra/connectors/base.py +65 -20
  17. pyinfra/connectors/chroot.py +15 -13
  18. pyinfra/connectors/docker.py +62 -72
  19. pyinfra/connectors/dockerssh.py +20 -19
  20. pyinfra/connectors/local.py +32 -22
  21. pyinfra/connectors/ssh.py +162 -86
  22. pyinfra/connectors/sshuserclient/client.py +1 -1
  23. pyinfra/connectors/terraform.py +57 -39
  24. pyinfra/connectors/util.py +26 -27
  25. pyinfra/connectors/vagrant.py +27 -26
  26. pyinfra/context.py +1 -0
  27. pyinfra/facts/apk.py +7 -2
  28. pyinfra/facts/apt.py +15 -7
  29. pyinfra/facts/brew.py +28 -13
  30. pyinfra/facts/bsdinit.py +9 -6
  31. pyinfra/facts/cargo.py +6 -3
  32. pyinfra/facts/choco.py +8 -4
  33. pyinfra/facts/deb.py +21 -9
  34. pyinfra/facts/dnf.py +11 -6
  35. pyinfra/facts/docker.py +30 -5
  36. pyinfra/facts/files.py +49 -33
  37. pyinfra/facts/gem.py +7 -2
  38. pyinfra/facts/git.py +14 -21
  39. pyinfra/facts/gpg.py +4 -1
  40. pyinfra/facts/hardware.py +186 -138
  41. pyinfra/facts/launchd.py +7 -2
  42. pyinfra/facts/lxd.py +8 -2
  43. pyinfra/facts/mysql.py +19 -12
  44. pyinfra/facts/npm.py +3 -1
  45. pyinfra/facts/openrc.py +8 -2
  46. pyinfra/facts/pacman.py +13 -5
  47. pyinfra/facts/pip.py +2 -0
  48. pyinfra/facts/pkg.py +5 -1
  49. pyinfra/facts/pkgin.py +7 -2
  50. pyinfra/facts/postgres.py +170 -0
  51. pyinfra/facts/postgresql.py +5 -162
  52. pyinfra/facts/rpm.py +21 -15
  53. pyinfra/facts/runit.py +70 -0
  54. pyinfra/facts/selinux.py +12 -4
  55. pyinfra/facts/server.py +240 -82
  56. pyinfra/facts/snap.py +8 -2
  57. pyinfra/facts/systemd.py +37 -13
  58. pyinfra/facts/sysvinit.py +7 -4
  59. pyinfra/facts/upstart.py +7 -2
  60. pyinfra/facts/util/packaging.py +3 -2
  61. pyinfra/facts/vzctl.py +8 -4
  62. pyinfra/facts/xbps.py +7 -2
  63. pyinfra/facts/yum.py +10 -5
  64. pyinfra/facts/zypper.py +9 -4
  65. pyinfra/operations/apk.py +5 -3
  66. pyinfra/operations/apt.py +28 -25
  67. pyinfra/operations/brew.py +60 -29
  68. pyinfra/operations/bsdinit.py +6 -4
  69. pyinfra/operations/cargo.py +3 -1
  70. pyinfra/operations/choco.py +3 -1
  71. pyinfra/operations/dnf.py +16 -20
  72. pyinfra/operations/docker.py +339 -0
  73. pyinfra/operations/files.py +187 -168
  74. pyinfra/operations/gem.py +3 -1
  75. pyinfra/operations/git.py +23 -25
  76. pyinfra/operations/iptables.py +33 -25
  77. pyinfra/operations/launchd.py +5 -6
  78. pyinfra/operations/lxd.py +7 -4
  79. pyinfra/operations/mysql.py +59 -55
  80. pyinfra/operations/npm.py +8 -1
  81. pyinfra/operations/openrc.py +5 -3
  82. pyinfra/operations/pacman.py +6 -7
  83. pyinfra/operations/pip.py +19 -12
  84. pyinfra/operations/pkg.py +3 -1
  85. pyinfra/operations/pkgin.py +5 -3
  86. pyinfra/operations/postgres.py +349 -0
  87. pyinfra/operations/postgresql.py +18 -335
  88. pyinfra/operations/puppet.py +3 -1
  89. pyinfra/operations/python.py +8 -19
  90. pyinfra/operations/runit.py +182 -0
  91. pyinfra/operations/selinux.py +47 -29
  92. pyinfra/operations/server.py +138 -67
  93. pyinfra/operations/snap.py +3 -1
  94. pyinfra/operations/ssh.py +18 -16
  95. pyinfra/operations/systemd.py +18 -12
  96. pyinfra/operations/sysvinit.py +7 -5
  97. pyinfra/operations/upstart.py +7 -5
  98. pyinfra/operations/util/__init__.py +12 -0
  99. pyinfra/operations/util/docker.py +177 -0
  100. pyinfra/operations/util/files.py +24 -16
  101. pyinfra/operations/util/packaging.py +54 -38
  102. pyinfra/operations/util/service.py +39 -47
  103. pyinfra/operations/vzctl.py +12 -10
  104. pyinfra/operations/xbps.py +5 -3
  105. pyinfra/operations/yum.py +15 -19
  106. pyinfra/operations/zypper.py +9 -10
  107. pyinfra/version.py +5 -2
  108. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/METADATA +51 -58
  109. pyinfra-3.0.1.dist-info/RECORD +168 -0
  110. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/WHEEL +1 -1
  111. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/entry_points.txt +0 -3
  112. pyinfra_cli/__main__.py +4 -3
  113. pyinfra_cli/commands.py +3 -2
  114. pyinfra_cli/exceptions.py +75 -43
  115. pyinfra_cli/inventory.py +52 -31
  116. pyinfra_cli/log.py +10 -2
  117. pyinfra_cli/main.py +88 -65
  118. pyinfra_cli/prints.py +37 -109
  119. pyinfra_cli/util.py +15 -10
  120. tests/test_api/test_api.py +2 -0
  121. tests/test_api/test_api_arguments.py +9 -9
  122. tests/test_api/test_api_deploys.py +15 -19
  123. tests/test_api/test_api_facts.py +4 -5
  124. tests/test_api/test_api_operations.py +18 -20
  125. tests/test_api/test_api_util.py +41 -2
  126. tests/test_cli/test_cli.py +14 -50
  127. tests/test_cli/test_cli_deploy.py +10 -12
  128. tests/test_cli/test_cli_exceptions.py +50 -19
  129. tests/test_cli/test_cli_inventory.py +66 -0
  130. tests/test_cli/util.py +1 -1
  131. tests/test_connectors/test_dockerssh.py +11 -8
  132. tests/test_connectors/test_ssh.py +88 -23
  133. tests/test_connectors/test_sshuserclient.py +1 -1
  134. tests/test_connectors/test_terraform.py +11 -8
  135. tests/test_connectors/test_vagrant.py +6 -6
  136. pyinfra/connectors/ansible.py +0 -175
  137. pyinfra/connectors/mech.py +0 -189
  138. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  139. pyinfra/connectors/winrm.py +0 -312
  140. pyinfra/facts/windows.py +0 -366
  141. pyinfra/facts/windows_files.py +0 -90
  142. pyinfra/operations/windows.py +0 -59
  143. pyinfra/operations/windows_files.py +0 -538
  144. pyinfra-3.0.dev0.dist-info/RECORD +0 -170
  145. tests/test_connectors/test_ansible.py +0 -64
  146. tests/test_connectors/test_mech.py +0 -126
  147. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/LICENSE.md +0 -0
  148. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/top_level.txt +0 -0
pyinfra/facts/server.py CHANGED
@@ -1,11 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import re
3
5
  import shutil
4
6
  from datetime import datetime
5
7
  from tempfile import mkdtemp
8
+ from typing import Dict, List, Optional, Union
6
9
 
7
10
  from dateutil.parser import parse as parse_date
8
11
  from distro import distro
12
+ from typing_extensions import NotRequired, TypedDict
9
13
 
10
14
  from pyinfra.api import FactBase, ShortFactBase
11
15
  from pyinfra.api.util import try_int
@@ -18,15 +22,18 @@ class User(FactBase):
18
22
  Returns the name of the current user.
19
23
  """
20
24
 
21
- command = "echo $USER"
25
+ def command(self):
26
+ return "echo $USER"
22
27
 
23
28
 
24
- class Home(FactBase):
29
+ class Home(FactBase[Optional[str]]):
25
30
  """
26
- Returns the home directory of the current user.
31
+ Returns the home directory of the given user, or the current user if no user is given.
27
32
  """
28
33
 
29
- command = "echo $HOME"
34
+ @staticmethod
35
+ def command(user=""):
36
+ return f"echo ~{user}"
30
37
 
31
38
 
32
39
  class Path(FactBase):
@@ -34,7 +41,17 @@ class Path(FactBase):
34
41
  Returns the path environment variable of the current user.
35
42
  """
36
43
 
37
- command = "echo $PATH"
44
+ def command(self):
45
+ return "echo $PATH"
46
+
47
+
48
+ class TmpDir(FactBase):
49
+ """
50
+ Returns the temporary directory of the current server, if configured.
51
+ """
52
+
53
+ def command(self):
54
+ return "echo $TMPDIR"
38
55
 
39
56
 
40
57
  class Hostname(FactBase):
@@ -42,7 +59,8 @@ class Hostname(FactBase):
42
59
  Returns the current hostname of the server.
43
60
  """
44
61
 
45
- command = "uname -n"
62
+ def command(self):
63
+ return "uname -n"
46
64
 
47
65
 
48
66
  class Kernel(FactBase):
@@ -50,7 +68,8 @@ class Kernel(FactBase):
50
68
  Returns the kernel name according to ``uname``.
51
69
  """
52
70
 
53
- command = "uname -s"
71
+ def command(self):
72
+ return "uname -s"
54
73
 
55
74
 
56
75
  class KernelVersion(FactBase):
@@ -58,11 +77,12 @@ class KernelVersion(FactBase):
58
77
  Returns the kernel version according to ``uname``.
59
78
  """
60
79
 
61
- command = "uname -r"
80
+ def command(self):
81
+ return "uname -r"
62
82
 
63
83
 
64
84
  # Deprecated/renamed -> Kernel
65
- class Os(FactBase):
85
+ class Os(FactBase[str]):
66
86
  """
67
87
  Returns the OS name according to ``uname``.
68
88
 
@@ -70,11 +90,12 @@ class Os(FactBase):
70
90
  This fact is deprecated/renamed, please use the ``server.Kernel`` fact.
71
91
  """
72
92
 
73
- command = "uname -s"
93
+ def command(self):
94
+ return "uname -s"
74
95
 
75
96
 
76
97
  # Deprecated/renamed -> KernelVersion
77
- class OsVersion(FactBase):
98
+ class OsVersion(FactBase[str]):
78
99
  """
79
100
  Returns the OS version according to ``uname``.
80
101
 
@@ -82,20 +103,22 @@ class OsVersion(FactBase):
82
103
  This fact is deprecated/renamed, please use the ``server.KernelVersion`` fact.
83
104
  """
84
105
 
85
- command = "uname -r"
106
+ def command(self):
107
+ return "uname -r"
86
108
 
87
109
 
88
- class Arch(FactBase):
110
+ class Arch(FactBase[str]):
89
111
  """
90
112
  Returns the system architecture according to ``uname``.
91
113
  """
92
114
 
93
115
  # ``uname -p`` is not portable and returns ``unknown`` on Debian.
94
116
  # ``uname -m`` works on most Linux and BSD systems.
95
- command = "uname -m"
117
+ def command(self):
118
+ return "uname -m"
96
119
 
97
120
 
98
- class Command(FactBase):
121
+ class Command(FactBase[str]):
99
122
  """
100
123
  Returns the raw output lines of a given command.
101
124
  """
@@ -105,39 +128,49 @@ class Command(FactBase):
105
128
  return command
106
129
 
107
130
 
108
- class Which(FactBase):
131
+ class Which(FactBase[Optional[str]]):
109
132
  """
110
- Returns the path of a given command, if available.
133
+ Returns the path of a given command according to `command -v`, if available.
111
134
  """
112
135
 
113
136
  @staticmethod
114
137
  def command(command):
115
- return "which {0} || true".format(command)
138
+ return "command -v {0} || true".format(command)
116
139
 
117
140
 
118
- class Date(FactBase):
141
+ class Date(FactBase[datetime]):
119
142
  """
120
143
  Returns the current datetime on the server.
121
144
  """
122
145
 
123
- command = f"date +'{ISO_DATE_FORMAT}'"
124
146
  default = datetime.now
125
147
 
126
- @staticmethod
127
- def process(output):
128
- return datetime.strptime(output[0], ISO_DATE_FORMAT)
148
+ def command(self):
149
+ return f"date +'{ISO_DATE_FORMAT}'"
129
150
 
151
+ def process(self, output) -> datetime:
152
+ return datetime.strptime(list(output)[0], ISO_DATE_FORMAT)
130
153
 
131
- class MacosVersion(FactBase):
154
+
155
+ class MacosVersion(FactBase[str]):
132
156
  """
133
157
  Returns the installed MacOS version.
134
158
  """
135
159
 
136
- command = "sw_vers -productVersion"
137
- requires_command = "sw_vers"
160
+ def requires_command(self) -> str:
161
+ return "sw_vers"
162
+
163
+ def command(self):
164
+ return "sw_vers -productVersion"
138
165
 
139
166
 
140
- class Mounts(FactBase):
167
+ class MountsDict(TypedDict):
168
+ device: str
169
+ type: str
170
+ options: list[str]
171
+
172
+
173
+ class Mounts(FactBase[Dict[str, MountsDict]]):
141
174
  """
142
175
  Returns a dictionary of mounted filesystems and information.
143
176
 
@@ -155,12 +188,13 @@ class Mounts(FactBase):
155
188
  }
156
189
  """
157
190
 
158
- command = "mount"
159
191
  default = dict
160
192
 
161
- @staticmethod
162
- def process(output):
163
- devices = {}
193
+ def command(self):
194
+ return "mount"
195
+
196
+ def process(self, output) -> dict[str, MountsDict]:
197
+ devices: dict[str, MountsDict] = {}
164
198
 
165
199
  for line in output:
166
200
  is_map = False
@@ -204,11 +238,12 @@ class KernelModules(FactBase):
204
238
  }
205
239
  """
206
240
 
207
- command = "! test -f /proc/modules || cat /proc/modules"
241
+ def command(self):
242
+ return "! test -f /proc/modules || cat /proc/modules"
243
+
208
244
  default = dict
209
245
 
210
- @staticmethod
211
- def process(output):
246
+ def process(self, output):
212
247
  modules = {}
213
248
 
214
249
  for line in output:
@@ -244,11 +279,13 @@ class LsbRelease(FactBase):
244
279
  }
245
280
  """
246
281
 
247
- command = "lsb_release -ca"
248
- requires_command = "lsb_release"
282
+ def command(self):
283
+ return "lsb_release -ca"
249
284
 
250
- @staticmethod
251
- def process(output):
285
+ def requires_command(self):
286
+ return "lsb_release"
287
+
288
+ def process(self, output):
252
289
  items = {}
253
290
 
254
291
  for line in output:
@@ -285,11 +322,14 @@ class Sysctl(FactBase):
285
322
  }
286
323
  """
287
324
 
288
- command = "sysctl -a"
289
325
  default = dict
290
326
 
291
- @staticmethod
292
- def process(output):
327
+ def command(self, keys=None):
328
+ if keys is None:
329
+ return "sysctl -a"
330
+ return f"sysctl {' '.join(keys)}"
331
+
332
+ def process(self, output):
293
333
  sysctls = {}
294
334
 
295
335
  for line in output:
@@ -317,17 +357,18 @@ class Sysctl(FactBase):
317
357
  return sysctls
318
358
 
319
359
 
320
- class Groups(FactBase):
360
+ class Groups(FactBase[List[str]]):
321
361
  """
322
362
  Returns a list of groups on the system.
323
363
  """
324
364
 
325
- command = "cat /etc/group"
365
+ def command(self):
366
+ return "cat /etc/group"
367
+
326
368
  default = list
327
369
 
328
- @staticmethod
329
- def process(output):
330
- groups = []
370
+ def process(self, output) -> list[str]:
371
+ groups: list[str] = []
331
372
 
332
373
  for line in output:
333
374
  if ":" in line:
@@ -336,7 +377,17 @@ class Groups(FactBase):
336
377
  return groups
337
378
 
338
379
 
339
- class Crontab(FactBase):
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]]):
340
391
  """
341
392
  Returns a dictionary of cron command -> execution time.
342
393
 
@@ -358,17 +409,16 @@ class Crontab(FactBase):
358
409
 
359
410
  default = dict
360
411
 
361
- requires_command = "crontab"
412
+ def requires_command(self, user=None) -> str:
413
+ return "crontab"
362
414
 
363
- @staticmethod
364
- def command(user=None):
415
+ def command(self, user=None):
365
416
  if user:
366
417
  return "crontab -l -u {0} || true".format(user)
367
418
  return "crontab -l || true"
368
419
 
369
- @staticmethod
370
- def process(output):
371
- crons = {}
420
+ def process(self, output):
421
+ crons: dict[str, CrontabDict] = {}
372
422
  current_comments = []
373
423
 
374
424
  for line in output:
@@ -417,16 +467,20 @@ class Users(FactBase):
417
467
  "uid": user_id,
418
468
  "gid": main_user_group_id,
419
469
  "lastlog": last_login_time,
470
+ "password": encrypted_password,
420
471
  },
421
472
  }
422
473
  """
423
474
 
424
- command = """
475
+ def command(self):
476
+ return """
477
+
425
478
  for i in `cat /etc/passwd | cut -d: -f1`; do
426
479
  ENTRY=`grep ^$i: /etc/passwd`;
427
480
  LASTLOG_RAW=`(lastlog -u $i 2> /dev/null || lastlogin $i 2> /dev/null)`;
428
481
  LASTLOG=`echo $LASTLOG_RAW | grep ^$i | tr -s ' '`;
429
- echo "$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG";
482
+ PASSWORD=`grep ^$i: /etc/shadow | cut -d: -f2`;
483
+ echo "$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG|$PASSWORD";
430
484
  done
431
485
  """.strip()
432
486
 
@@ -437,7 +491,7 @@ class Users(FactBase):
437
491
  rex = r"[A-Z][a-z]{2} [A-Z][a-z]{2} {1,2}\d+ .+$"
438
492
 
439
493
  for line in output:
440
- entry, group, user_groups, lastlog = line.split("|")
494
+ entry, group, user_groups, lastlog, password = line.rsplit("|", 4)
441
495
 
442
496
  if entry:
443
497
  # Parse out the comment/home/shell
@@ -470,12 +524,20 @@ class Users(FactBase):
470
524
  "gid": int(entries[3]),
471
525
  "lastlog": raw_login_time,
472
526
  "login_time": login_time,
527
+ "password": password,
473
528
  }
474
529
 
475
530
  return users
476
531
 
477
532
 
478
- class LinuxDistribution(FactBase):
533
+ class LinuxDistributionDict(TypedDict):
534
+ name: Optional[str]
535
+ major: Optional[int]
536
+ minor: Optional[int]
537
+ release_meta: Dict
538
+
539
+
540
+ class LinuxDistribution(FactBase[LinuxDistributionDict]):
479
541
  """
480
542
  Returns a dict of the Linux distribution version. Ubuntu, Debian, CentOS,
481
543
  Fedora & Gentoo currently. Also contains any key/value items located in
@@ -495,11 +557,12 @@ class LinuxDistribution(FactBase):
495
557
  }
496
558
  """
497
559
 
498
- command = (
499
- "cd /etc/ && for file in $(ls -pdL *-release | grep -v /); "
500
- 'do echo "/etc/${file}"; cat "/etc/${file}"; echo ---; '
501
- "done"
502
- )
560
+ def command(self) -> str:
561
+ return (
562
+ "cd /etc/ && for file in $(ls -pdL *-release | grep -v /); "
563
+ 'do echo "/etc/${file}"; cat "/etc/${file}"; echo ---; '
564
+ "done"
565
+ )
503
566
 
504
567
  name_to_pretty_name = {
505
568
  "alpine": "Alpine",
@@ -513,7 +576,7 @@ class LinuxDistribution(FactBase):
513
576
  }
514
577
 
515
578
  @staticmethod
516
- def default():
579
+ def default() -> LinuxDistributionDict:
517
580
  return {
518
581
  "name": None,
519
582
  "major": None,
@@ -521,7 +584,7 @@ class LinuxDistribution(FactBase):
521
584
  "release_meta": {},
522
585
  }
523
586
 
524
- def process(self, output):
587
+ def process(self, output) -> LinuxDistributionDict:
525
588
  parts = {}
526
589
  for part in "\n".join(output).strip().split("---"):
527
590
  if not part.strip():
@@ -575,7 +638,7 @@ class LinuxDistribution(FactBase):
575
638
  return release_info
576
639
 
577
640
 
578
- class LinuxName(ShortFactBase):
641
+ class LinuxName(ShortFactBase[str]):
579
642
  """
580
643
  Returns the name of the Linux distribution. Shortcut for
581
644
  ``host.get_fact(LinuxDistribution)['name']``.
@@ -583,12 +646,15 @@ class LinuxName(ShortFactBase):
583
646
 
584
647
  fact = LinuxDistribution
585
648
 
586
- @staticmethod
587
- def process_data(data):
649
+ def process_data(self, data) -> str:
588
650
  return data["name"]
589
651
 
590
652
 
591
- class Selinux(FactBase):
653
+ class SelinuxDict(TypedDict):
654
+ mode: Optional[str]
655
+
656
+
657
+ class Selinux(FactBase[SelinuxDict]):
592
658
  """
593
659
  Discovers the SELinux related facts on the target host.
594
660
 
@@ -599,16 +665,19 @@ class Selinux(FactBase):
599
665
  }
600
666
  """
601
667
 
602
- command = "sestatus"
603
- requires_command = "sestatus"
668
+ def command(self):
669
+ return "sestatus"
670
+
671
+ def requires_command(self) -> str:
672
+ return "sestatus"
604
673
 
605
674
  @staticmethod
606
- def default():
675
+ def default() -> SelinuxDict:
607
676
  return {
608
677
  "mode": None,
609
678
  }
610
679
 
611
- def process(self, output):
680
+ def process(self, output) -> SelinuxDict:
612
681
  selinux_info = self.default()
613
682
 
614
683
  match = re.match(r"^SELinux status:\s+(\S+)", "\n".join(output))
@@ -621,12 +690,14 @@ class Selinux(FactBase):
621
690
  return selinux_info
622
691
 
623
692
 
624
- class LinuxGui(FactBase):
693
+ class LinuxGui(FactBase[List[str]]):
625
694
  """
626
695
  Returns a list of available Linux GUIs.
627
696
  """
628
697
 
629
- command = "ls /usr/bin/*session || true"
698
+ def command(self):
699
+ return "ls /usr/bin/*session || true"
700
+
630
701
  default = list
631
702
 
632
703
  known_gui_binaries = {
@@ -637,7 +708,7 @@ class LinuxGui(FactBase):
637
708
  "/usr/bin/xfce4-session": "XFCE 4",
638
709
  }
639
710
 
640
- def process(self, output):
711
+ def process(self, output) -> list[str]:
641
712
  gui_names = []
642
713
 
643
714
  for line in output:
@@ -648,19 +719,18 @@ class LinuxGui(FactBase):
648
719
  return gui_names
649
720
 
650
721
 
651
- class HasGui(ShortFactBase):
722
+ class HasGui(ShortFactBase[bool]):
652
723
  """
653
724
  Returns a boolean indicating the remote side has GUI capabilities. Linux only.
654
725
  """
655
726
 
656
727
  fact = LinuxGui
657
728
 
658
- @staticmethod
659
- def process_data(data):
729
+ def process_data(self, data) -> bool:
660
730
  return len(data) > 0
661
731
 
662
732
 
663
- class Locales(FactBase):
733
+ class Locales(FactBase[List[str]]):
664
734
  """
665
735
  Returns installed locales on the target host.
666
736
 
@@ -669,11 +739,99 @@ class Locales(FactBase):
669
739
  ["C.UTF-8", "en_US.UTF-8"]
670
740
  """
671
741
 
672
- command = "locale -a"
673
- requires_command = "locale"
742
+ def command(self) -> str:
743
+ return "locale -a"
744
+
745
+ def requires_command(self) -> str:
746
+ return "locale"
747
+
674
748
  default = list
675
749
 
676
- def process(self, output):
750
+ def process(self, output) -> list[str]:
677
751
  # replace utf8 with UTF-8 to match names in /etc/locale.gen
678
752
  # return a list of enabled locales
679
753
  return [line.replace("utf8", "UTF-8") for line in output]
754
+
755
+
756
+ class SecurityLimits(FactBase):
757
+ """
758
+ Returns a list of security limits on the target host.
759
+
760
+ .. code:: python
761
+
762
+ [
763
+ {
764
+ "domain": "*",
765
+ "limit_type": "soft",
766
+ "item": "nofile",
767
+ "value": "1048576"
768
+ },
769
+ {
770
+ "domain": "*",
771
+ "limit_type": "hard",
772
+ "item": "nofile",
773
+ "value": "1048576"
774
+ },
775
+ {
776
+ "domain": "root",
777
+ "limit_type": "soft",
778
+ "item": "nofile",
779
+ "value": "1048576"
780
+ },
781
+ {
782
+ "domain": "root",
783
+ "limit_type": "hard",
784
+ "item": "nofile",
785
+ "value": "1048576"
786
+ },
787
+ {
788
+ "domain": "*",
789
+ "limit_type": "soft",
790
+ "item": "memlock",
791
+ "value": "unlimited"
792
+ },
793
+ {
794
+ "domain": "*",
795
+ "limit_type": "hard",
796
+ "item": "memlock",
797
+ "value": "unlimited"
798
+ },
799
+ {
800
+ "domain": "root",
801
+ "limit_type": "soft",
802
+ "item": "memlock",
803
+ "value": "unlimited"
804
+ },
805
+ {
806
+ "domain": "root",
807
+ "limit_type": "hard",
808
+ "item": "memlock",
809
+ "value": "unlimited"
810
+ }
811
+ ]
812
+ """
813
+
814
+ def command(self):
815
+ return "cat /etc/security/limits.conf"
816
+
817
+ default = list
818
+
819
+ def process(self, output):
820
+ limits = []
821
+
822
+ for line in output:
823
+ if line.startswith("#") or not len(line.strip()):
824
+ continue
825
+
826
+ domain, limit_type, item, value = line.split()
827
+
828
+ limits.append(
829
+ {
830
+ "domain": domain,
831
+ "limit_type": limit_type,
832
+ "item": item,
833
+ "value": value,
834
+ },
835
+ )
836
+
837
+ return limits
pyinfra/facts/snap.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
 
3
5
  from pyinfra.api import FactBase
@@ -5,7 +7,9 @@ from pyinfra.api import FactBase
5
7
 
6
8
  class SnapBaseFact(FactBase):
7
9
  abstract = True
8
- requires_command = "snap"
10
+
11
+ def requires_command(self, *args, **kwargs) -> str:
12
+ return "snap"
9
13
 
10
14
 
11
15
  class SnapPackage(SnapBaseFact):
@@ -62,7 +66,9 @@ class SnapPackages(SnapBaseFact):
62
66
  """
63
67
 
64
68
  default = list
65
- command = "snap list"
69
+
70
+ def command(self) -> str:
71
+ return "snap list"
66
72
 
67
73
  def process(self, output):
68
74
  # Discard header output line from snap list command