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/connectors/ssh.py CHANGED
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any, Iterable, Optional, Tuple
9
9
 
10
10
  import click
11
11
  from paramiko import AuthenticationException, BadHostKeyException, SFTPClient, SSHException
12
- from typing_extensions import TypedDict, Unpack
12
+ from typing_extensions import TypedDict, Unpack, override
13
13
 
14
14
  from pyinfra import logger
15
15
  from pyinfra.api.command import QuoteString, StringCommand
@@ -191,6 +191,7 @@ class SSHConnector(BaseConnector):
191
191
 
192
192
  return kwargs
193
193
 
194
+ @override
194
195
  def connect(self) -> None:
195
196
  retries = self.data["ssh_connect_retries"]
196
197
 
@@ -264,9 +265,11 @@ class SSHConnector(BaseConnector):
264
265
  f"Host key for {e.hostname} does not match.",
265
266
  )
266
267
 
268
+ @override
267
269
  def disconnect(self) -> None:
268
270
  self.get_sftp_connection.cache.clear()
269
271
 
272
+ @override
270
273
  def run_shell_command(
271
274
  self,
272
275
  command: StringCommand,
@@ -368,6 +371,7 @@ class SSHConnector(BaseConnector):
368
371
  sftp = self.get_sftp_connection()
369
372
  sftp.getfo(remote_filename, file_io)
370
373
 
374
+ @override
371
375
  def get_file(
372
376
  self,
373
377
  remote_filename: str,
@@ -454,6 +458,7 @@ class SSHConnector(BaseConnector):
454
458
  if last_e is not None:
455
459
  raise last_e
456
460
 
461
+ @override
457
462
  def put_file(
458
463
  self,
459
464
  filename_or_io,
@@ -537,7 +542,8 @@ class SSHConnector(BaseConnector):
537
542
 
538
543
  return True
539
544
 
540
- def check_can_rsync(self):
545
+ @override
546
+ def check_can_rsync(self) -> None:
541
547
  if self.data["ssh_key_password"]:
542
548
  raise NotImplementedError(
543
549
  "Rsync does not currently work with SSH keys needing passwords."
@@ -549,6 +555,7 @@ class SSHConnector(BaseConnector):
549
555
  if not which("rsync"):
550
556
  raise NotImplementedError("The `rsync` binary is not available on this system.")
551
557
 
558
+ @override
552
559
  def rsync(
553
560
  self,
554
561
  src: str,
@@ -14,6 +14,8 @@ from paramiko import (
14
14
  SSHException,
15
15
  )
16
16
  from paramiko.agent import AgentRequestHandler
17
+ from paramiko.hostkeys import HostKeyEntry
18
+ from typing_extensions import override
17
19
 
18
20
  from pyinfra import logger
19
21
  from pyinfra.api.util import memoize
@@ -24,6 +26,7 @@ HOST_KEYS_LOCK = BoundedSemaphore()
24
26
 
25
27
 
26
28
  class StrictPolicy(MissingHostKeyPolicy):
29
+ @override
27
30
  def missing_host_key(self, client, hostname, key):
28
31
  logger.error("No host key for {0} found in known_hosts".format(hostname))
29
32
  raise SSHException(
@@ -31,7 +34,30 @@ class StrictPolicy(MissingHostKeyPolicy):
31
34
  )
32
35
 
33
36
 
37
+ def append_hostkey(client, hostname, key):
38
+ """Append hostname to the clients host_keys_file"""
39
+
40
+ with HOST_KEYS_LOCK:
41
+ # The paramiko client saves host keys incorrectly whereas the host keys object does
42
+ # this correctly, so use that with the client filename variable.
43
+ # See: https://github.com/paramiko/paramiko/pull/1989
44
+ host_key_entry = HostKeyEntry([hostname], key)
45
+ if host_key_entry is None:
46
+ raise SSHException(
47
+ "Append Hostkey: Failed to parse host {0}, could not append to hostfile".format(
48
+ hostname
49
+ ),
50
+ )
51
+ with open(client._host_keys_filename, "a") as host_keys_file:
52
+ hk_entry = host_key_entry.to_line()
53
+ if hk_entry is None:
54
+ raise SSHException(f"Append Hostkey: Failed to append hostkey ({host_key_entry})")
55
+
56
+ host_keys_file.write(hk_entry)
57
+
58
+
34
59
  class AcceptNewPolicy(MissingHostKeyPolicy):
60
+ @override
35
61
  def missing_host_key(self, client, hostname, key):
36
62
  logger.warning(
37
63
  (
@@ -40,16 +66,12 @@ class AcceptNewPolicy(MissingHostKeyPolicy):
40
66
  ),
41
67
  )
42
68
 
43
- with HOST_KEYS_LOCK:
44
- host_keys = client.get_host_keys()
45
- host_keys.add(hostname, key.get_name(), key)
46
- # The paramiko client saves host keys incorrectly whereas the host keys object does
47
- # this correctly, so use that with the client filename variable.
48
- # See: https://github.com/paramiko/paramiko/pull/1989
49
- host_keys.save(client._host_keys_filename)
69
+ append_hostkey(client, hostname, key)
70
+ logger.warning("Added host key for {0} to known_hosts".format(hostname))
50
71
 
51
72
 
52
73
  class AskPolicy(MissingHostKeyPolicy):
74
+ @override
53
75
  def missing_host_key(self, client, hostname, key):
54
76
  should_continue = input(
55
77
  "No host key for {0} found in known_hosts, do you want to continue [y/n] ".format(
@@ -60,18 +82,13 @@ class AskPolicy(MissingHostKeyPolicy):
60
82
  raise SSHException(
61
83
  "AskPolicy: No host key for {0} found in known_hosts".format(hostname),
62
84
  )
63
- with HOST_KEYS_LOCK:
64
- host_keys = client.get_host_keys()
65
- host_keys.add(hostname, key.get_name(), key)
66
- # The paramiko client saves host keys incorrectly whereas the host keys object does
67
- # this correctly, so use that with the client filename variable.
68
- # See: https://github.com/paramiko/paramiko/pull/1989
69
- host_keys.save(client._host_keys_filename)
85
+ append_hostkey(client, hostname, key)
70
86
  logger.warning("Added host key for {0} to known_hosts".format(hostname))
71
87
  return
72
88
 
73
89
 
74
90
  class WarningPolicy(MissingHostKeyPolicy):
91
+ @override
75
92
  def missing_host_key(self, client, hostname, key):
76
93
  logger.warning("No host key for {0} found in known_hosts".format(hostname))
77
94
 
@@ -124,6 +141,7 @@ class SSHClient(ParamikoClient):
124
141
  original idea at http://bitprophet.org/blog/2012/11/05/gateway-solutions/.
125
142
  """
126
143
 
144
+ @override
127
145
  def connect( # type: ignore[override]
128
146
  self,
129
147
  hostname,
@@ -165,6 +183,13 @@ class SSHClient(ParamikoClient):
165
183
  if _pyinfra_ssh_forward_agent is not None:
166
184
  forward_agent = _pyinfra_ssh_forward_agent
167
185
 
186
+ keep_alive = config.get("keep_alive")
187
+
188
+ if keep_alive:
189
+ transport = self.get_transport()
190
+ assert transport is not None, "No transport"
191
+ transport.set_keepalive(keep_alive)
192
+
168
193
  if forward_agent:
169
194
  transport = self.get_transport()
170
195
  assert transport is not None, "No transport"
@@ -222,6 +247,9 @@ class SSHClient(ParamikoClient):
222
247
  if "port" in host_config:
223
248
  cfg["port"] = int(host_config["port"])
224
249
 
250
+ if "serveraliveinterval" in host_config:
251
+ cfg["keep_alive"] = int(host_config["serveraliveinterval"])
252
+
225
253
  if "proxycommand" in host_config:
226
254
  cfg["sock"] = ProxyCommand(host_config["proxycommand"])
227
255
 
@@ -10,6 +10,7 @@ from os import environ, path
10
10
  import paramiko.config
11
11
  from gevent.subprocess import CalledProcessError, check_call
12
12
  from paramiko import SSHConfig as ParamikoSSHConfig
13
+ from typing_extensions import override
13
14
 
14
15
  from pyinfra import logger
15
16
 
@@ -95,6 +96,7 @@ class SSHConfig(ParamikoSSHConfig):
95
96
  https://github.com/paramiko/paramiko/pull/1194
96
97
  """
97
98
 
99
+ @override
98
100
  def parse(self, file_obj):
99
101
  file_obj = _expand_include_statements(file_obj)
100
102
  return super().parse(file_obj)
@@ -9,7 +9,7 @@ from .base import BaseConnector
9
9
 
10
10
 
11
11
  @memoize
12
- def show_warning():
12
+ def show_warning() -> None:
13
13
  logger.warning("The @terraform connector is in beta!")
14
14
 
15
15
 
@@ -198,14 +198,18 @@ def execute_command_with_sudo_retry(
198
198
  ) -> tuple[int, CommandOutput]:
199
199
  return_code, output = execute_command()
200
200
 
201
+ # If we failed look for a sudo password prompt line and re-submit using the sudo password. Look
202
+ # at all lines here in case anything else gets printed, eg in:
203
+ # https://github.com/pyinfra-dev/pyinfra/issues/1292
201
204
  if return_code != 0 and output and output.combined_lines:
202
- last_line = output.combined_lines[-1].line
203
- if last_line.strip() == "sudo: a password is required":
204
- # If we need a password, ask the user for it and attach to the host
205
- # internal connector data for use when executing future commands.
206
- sudo_password = getpass("{0}sudo password: ".format(host.print_prefix))
207
- host.connector_data["prompted_sudo_password"] = sudo_password
208
- return_code, output = execute_command()
205
+ for line in reversed(output.combined_lines):
206
+ if line.line.strip() == "sudo: a password is required":
207
+ # If we need a password, ask the user for it and attach to the host
208
+ # internal connector data for use when executing future commands.
209
+ sudo_password = getpass("{0}sudo password: ".format(host.print_prefix))
210
+ host.connector_data["prompted_sudo_password"] = sudo_password
211
+ return_code, output = execute_command()
212
+ break
209
213
 
210
214
  return return_code, output
211
215
 
@@ -232,7 +236,7 @@ def remove_any_sudo_askpass_file(host) -> None:
232
236
 
233
237
 
234
238
  @memoize
235
- def _show_use_su_login_warning():
239
+ def _show_use_su_login_warning() -> None:
236
240
  logger.warning(
237
241
  (
238
242
  "Using `use_su_login` may not work: "
@@ -304,7 +308,7 @@ def make_unix_command(
304
308
  _sudo=False,
305
309
  _sudo_user=None,
306
310
  _use_sudo_login=False,
307
- _sudo_password=False,
311
+ _sudo_password="",
308
312
  _sudo_askpass_path=None,
309
313
  _preserve_sudo_env=False,
310
314
  # Doas config
pyinfra/context.py CHANGED
@@ -10,6 +10,7 @@ from types import ModuleType
10
10
  from typing import TYPE_CHECKING
11
11
 
12
12
  from gevent.local import local
13
+ from typing_extensions import override
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from pyinfra.api.config import Config
@@ -26,22 +27,25 @@ class ContextObject:
26
27
  _container_cls = container
27
28
  _base_cls: ModuleType
28
29
 
29
- def __init__(self):
30
+ def __init__(self) -> None:
30
31
  self._container = self._container_cls()
31
32
  self._container.module = None
32
33
 
33
34
  def _get_module(self):
34
35
  return self._container.module
35
36
 
37
+ @override
36
38
  def __repr__(self):
37
39
  return "ContextObject({0}):{1}".format(
38
40
  self._base_cls.__name__,
39
41
  repr(self._get_module()),
40
42
  )
41
43
 
44
+ @override
42
45
  def __str__(self):
43
46
  return str(self._get_module())
44
47
 
48
+ @override
45
49
  def __dir__(self):
46
50
  return dir(self._base_cls)
47
51
 
@@ -50,6 +54,7 @@ class ContextObject:
50
54
  return getattr(self._base_cls, key)
51
55
  return getattr(self._get_module(), key)
52
56
 
57
+ @override
53
58
  def __setattr__(self, key, value):
54
59
  if key in ("_container", "_base_cls"):
55
60
  return super().__setattr__(key, value)
@@ -65,9 +70,11 @@ class ContextObject:
65
70
  def __len__(self):
66
71
  return len(self._get_module())
67
72
 
73
+ @override
68
74
  def __eq__(self, other):
69
75
  return self._get_module() == other
70
76
 
77
+ @override
71
78
  def __hash__(self):
72
79
  return hash(self._get_module())
73
80
 
@@ -89,7 +96,7 @@ class ContextManager:
89
96
  def set_base(self, module):
90
97
  self.context._base_cls = module
91
98
 
92
- def reset(self):
99
+ def reset(self) -> None:
93
100
  self.context._container.module = None
94
101
 
95
102
  def isset(self):
pyinfra/facts/apk.py CHANGED
@@ -1,10 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from pyinfra.api import FactBase
4
6
 
5
7
  from .util.packaging import parse_packages
6
8
 
7
- APK_REGEX = r"^([a-zA-Z0-9\-_]+)-([0-9\.]+\-?[a-z0-9]*)\s"
9
+ # Source: https://superuser.com/a/1472405
10
+ # Modified to return version and release inside a single group and removed extra capturing groups
11
+ APK_REGEX = r"(.+)-([^-]+-r[^-]+) \S+ \{\S+\} \(.+?\)"
8
12
 
9
13
 
10
14
  class ApkPackages(FactBase):
@@ -18,13 +22,16 @@ class ApkPackages(FactBase):
18
22
  }
19
23
  """
20
24
 
25
+ @override
21
26
  def command(self) -> str:
22
27
  return "apk list --installed"
23
28
 
29
+ @override
24
30
  def requires_command(self) -> str:
25
31
  return "apk"
26
32
 
27
33
  default = dict
28
34
 
35
+ @override
29
36
  def process(self, output):
30
37
  return parse_packages(APK_REGEX, output)
pyinfra/facts/apt.py CHANGED
@@ -2,12 +2,36 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
 
5
+ from typing_extensions import TypedDict, override
6
+
5
7
  from pyinfra.api import FactBase
6
8
 
7
9
  from .gpg import GpgFactBase
8
10
  from .util import make_cat_files_command
9
11
 
10
12
 
13
+ def noninteractive_apt(command: str, force=False):
14
+ args = ["DEBIAN_FRONTEND=noninteractive apt-get -y"]
15
+
16
+ if force:
17
+ args.append("--force-yes")
18
+
19
+ args.extend(
20
+ (
21
+ '-o Dpkg::Options::="--force-confdef"',
22
+ '-o Dpkg::Options::="--force-confold"',
23
+ command,
24
+ ),
25
+ )
26
+
27
+ return " ".join(args)
28
+
29
+
30
+ APT_CHANGES_RE = re.compile(
31
+ r"^(\d+) upgraded, (\d+) newly installed, (\d+) to remove and (\d+) not upgraded.$"
32
+ )
33
+
34
+
11
35
  def parse_apt_repo(name):
12
36
  regex = r"^(deb(?:-src)?)(?:\s+\[([^\]]+)\])?\s+([^\s]+)\s+([^\s]+)\s+([a-z-\s\d]*)$"
13
37
 
@@ -52,17 +76,20 @@ class AptSources(FactBase):
52
76
  ]
53
77
  """
54
78
 
79
+ @override
55
80
  def command(self) -> str:
56
81
  return make_cat_files_command(
57
82
  "/etc/apt/sources.list",
58
83
  "/etc/apt/sources.list.d/*.list",
59
84
  )
60
85
 
86
+ @override
61
87
  def requires_command(self) -> str:
62
88
  return "apt" # if apt installed, above should exist
63
89
 
64
90
  default = list
65
91
 
92
+ @override
66
93
  def process(self, output):
67
94
  repos = []
68
95
 
@@ -89,8 +116,49 @@ class AptKeys(GpgFactBase):
89
116
  """
90
117
 
91
118
  # This requires both apt-key *and* apt-key itself requires gpg
119
+ @override
92
120
  def command(self) -> str:
93
121
  return "! command -v gpg || apt-key list --with-colons"
94
122
 
123
+ @override
95
124
  def requires_command(self) -> str:
96
125
  return "apt-key"
126
+
127
+
128
+ class AptSimulationDict(TypedDict):
129
+ upgraded: int
130
+ newly_installed: int
131
+ removed: int
132
+ not_upgraded: int
133
+
134
+
135
+ class SimulateOperationWillChange(FactBase[AptSimulationDict]):
136
+ """
137
+ Simulate an 'apt-get' operation and try to detect if any changes would be performed.
138
+ """
139
+
140
+ @override
141
+ def command(self, command: str) -> str:
142
+ # LC_ALL=C: Ensure the output is in english, as we want to parse it
143
+ return "LC_ALL=C " + noninteractive_apt(f"{command} --dry-run")
144
+
145
+ @override
146
+ def requires_command(self, command: str) -> str:
147
+ return "apt-get"
148
+
149
+ @override
150
+ def process(self, output) -> AptSimulationDict:
151
+ # We are looking for a line similar to
152
+ # "3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded."
153
+ for line in output:
154
+ result = APT_CHANGES_RE.match(line)
155
+ if result is not None:
156
+ return {
157
+ "upgraded": int(result[1]),
158
+ "newly_installed": int(result[2]),
159
+ "removed": int(result[3]),
160
+ "not_upgraded": int(result[4]),
161
+ }
162
+
163
+ # We did not find the line we expected:
164
+ raise Exception("Did not find proposed changes in output")
pyinfra/facts/brew.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 import logger
6
8
  from pyinfra.api import FactBase
7
9
 
@@ -37,9 +39,11 @@ class BrewVersion(FactBase):
37
39
 
38
40
  """
39
41
 
42
+ @override
40
43
  def command(self) -> str:
41
44
  return "brew --version"
42
45
 
46
+ @override
43
47
  def requires_command(self) -> str:
44
48
  return "brew"
45
49
 
@@ -47,6 +51,7 @@ class BrewVersion(FactBase):
47
51
  def default():
48
52
  return [0, 0, 0]
49
53
 
54
+ @override
50
55
  def process(self, output):
51
56
  out = list(output)[0]
52
57
  m = VERSION_MATCHER.match(out)
@@ -67,14 +72,17 @@ class BrewPackages(FactBase):
67
72
  }
68
73
  """
69
74
 
75
+ @override
70
76
  def command(self) -> str:
71
77
  return "brew list --versions"
72
78
 
79
+ @override
73
80
  def requires_command(self) -> str:
74
81
  return "brew"
75
82
 
76
83
  default = dict
77
84
 
85
+ @override
78
86
  def process(self, output):
79
87
  return parse_packages(BREW_REGEX, output)
80
88
 
@@ -90,12 +98,14 @@ class BrewCasks(BrewPackages):
90
98
  }
91
99
  """
92
100
 
101
+ @override
93
102
  def command(self) -> str:
94
103
  return (
95
104
  r'if brew --version | grep -q -e "Homebrew\ +(1\.|2\.[0-5]).*" 1>/dev/null;'
96
105
  r"then brew cask list --versions; else brew list --cask --versions; fi"
97
106
  )
98
107
 
108
+ @override
99
109
  def requires_command(self) -> str:
100
110
  return "brew"
101
111
 
@@ -105,13 +115,16 @@ class BrewTaps(FactBase):
105
115
  Returns a list of brew taps.
106
116
  """
107
117
 
118
+ @override
108
119
  def command(self) -> str:
109
120
  return "brew tap"
110
121
 
122
+ @override
111
123
  def requires_command(self) -> str:
112
124
  return "brew"
113
125
 
114
126
  default = list
115
127
 
128
+ @override
116
129
  def process(self, output):
117
130
  return output
pyinfra/facts/bsdinit.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from .sysvinit import InitdStatus
4
6
 
5
7
 
@@ -9,6 +11,7 @@ class RcdStatus(InitdStatus):
9
11
  BSD init scripts are well behaved and as such their output can be trusted.
10
12
  """
11
13
 
14
+ @override
12
15
  def command(self) -> str:
13
16
  return """
14
17
  for SERVICE in `find /etc/rc.d /usr/local/etc/rc.d -type f`; do
pyinfra/facts/cargo.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from typing_extensions import override
6
+
5
7
  from pyinfra.api import FactBase
6
8
 
7
9
  from .util.packaging import parse_packages
@@ -22,11 +24,14 @@ class CargoPackages(FactBase):
22
24
 
23
25
  default = dict
24
26
 
27
+ @override
25
28
  def command(self) -> str:
26
29
  return "cargo install --list"
27
30
 
31
+ @override
28
32
  def requires_command(self) -> str:
29
33
  return "cargo"
30
34
 
35
+ @override
31
36
  def process(self, output):
32
37
  return parse_packages(CARGO_REGEX, output)
pyinfra/facts/choco.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from pyinfra.api import FactBase
4
6
 
5
7
  from .util.packaging import parse_packages
@@ -18,6 +20,7 @@ class ChocoPackages(FactBase):
18
20
  }
19
21
  """
20
22
 
23
+ @override
21
24
  def command(self) -> str:
22
25
  return "choco list"
23
26
 
@@ -25,6 +28,7 @@ class ChocoPackages(FactBase):
25
28
 
26
29
  default = dict
27
30
 
31
+ @override
28
32
  def process(self, output):
29
33
  return parse_packages(CHOCO_REGEX, output)
30
34
 
@@ -34,8 +38,10 @@ class ChocoVersion(FactBase):
34
38
  Returns the choco (Chocolatey) version.
35
39
  """
36
40
 
41
+ @override
37
42
  def command(self) -> str:
38
43
  return "choco --version"
39
44
 
45
+ @override
40
46
  def process(self, output):
41
47
  return "".join(output).replace("\n", "")