pyinfra 3.2__py2.py3-none-any.whl → 3.3.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 (89) hide show
  1. pyinfra/api/arguments_typed.py +4 -5
  2. pyinfra/api/command.py +22 -3
  3. pyinfra/api/config.py +5 -2
  4. pyinfra/api/facts.py +3 -0
  5. pyinfra/api/host.py +10 -4
  6. pyinfra/api/operation.py +2 -1
  7. pyinfra/api/state.py +1 -1
  8. pyinfra/connectors/base.py +34 -8
  9. pyinfra/connectors/chroot.py +7 -2
  10. pyinfra/connectors/docker.py +7 -2
  11. pyinfra/connectors/dockerssh.py +7 -2
  12. pyinfra/connectors/local.py +7 -2
  13. pyinfra/connectors/ssh.py +9 -2
  14. pyinfra/connectors/sshuserclient/client.py +18 -2
  15. pyinfra/connectors/sshuserclient/config.py +2 -0
  16. pyinfra/connectors/terraform.py +1 -1
  17. pyinfra/connectors/util.py +13 -9
  18. pyinfra/context.py +9 -2
  19. pyinfra/facts/apk.py +5 -0
  20. pyinfra/facts/apt.py +9 -1
  21. pyinfra/facts/brew.py +13 -0
  22. pyinfra/facts/bsdinit.py +3 -0
  23. pyinfra/facts/cargo.py +5 -0
  24. pyinfra/facts/choco.py +6 -0
  25. pyinfra/facts/crontab.py +6 -1
  26. pyinfra/facts/deb.py +10 -0
  27. pyinfra/facts/dnf.py +5 -0
  28. pyinfra/facts/docker.py +10 -0
  29. pyinfra/facts/efibootmgr.py +5 -0
  30. pyinfra/facts/files.py +19 -1
  31. pyinfra/facts/flatpak.py +7 -0
  32. pyinfra/facts/freebsd.py +75 -0
  33. pyinfra/facts/gem.py +5 -0
  34. pyinfra/facts/git.py +9 -0
  35. pyinfra/facts/gpg.py +7 -0
  36. pyinfra/facts/hardware.py +13 -0
  37. pyinfra/facts/iptables.py +9 -1
  38. pyinfra/facts/launchd.py +5 -0
  39. pyinfra/facts/lxd.py +5 -0
  40. pyinfra/facts/mysql.py +8 -0
  41. pyinfra/facts/npm.py +5 -0
  42. pyinfra/facts/openrc.py +8 -0
  43. pyinfra/facts/opkg.py +12 -0
  44. pyinfra/facts/pacman.py +9 -1
  45. pyinfra/facts/pip.py +5 -0
  46. pyinfra/facts/pipx.py +8 -0
  47. pyinfra/facts/pkg.py +4 -0
  48. pyinfra/facts/pkgin.py +5 -0
  49. pyinfra/facts/podman.py +7 -0
  50. pyinfra/facts/postgres.py +8 -2
  51. pyinfra/facts/rpm.py +11 -0
  52. pyinfra/facts/runit.py +7 -0
  53. pyinfra/facts/selinux.py +16 -0
  54. pyinfra/facts/server.py +49 -3
  55. pyinfra/facts/snap.py +7 -0
  56. pyinfra/facts/systemd.py +5 -0
  57. pyinfra/facts/sysvinit.py +4 -0
  58. pyinfra/facts/upstart.py +5 -0
  59. pyinfra/facts/util/__init__.py +4 -1
  60. pyinfra/facts/vzctl.py +5 -0
  61. pyinfra/facts/xbps.py +6 -1
  62. pyinfra/facts/yum.py +5 -0
  63. pyinfra/facts/zfs.py +19 -2
  64. pyinfra/facts/zypper.py +5 -0
  65. pyinfra/operations/apt.py +10 -3
  66. pyinfra/operations/docker.py +48 -44
  67. pyinfra/operations/files.py +47 -1
  68. pyinfra/operations/freebsd/__init__.py +12 -0
  69. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  70. pyinfra/operations/freebsd/pkg.py +219 -0
  71. pyinfra/operations/freebsd/service.py +116 -0
  72. pyinfra/operations/freebsd/sysrc.py +92 -0
  73. pyinfra/operations/opkg.py +5 -5
  74. pyinfra/operations/postgres.py +99 -16
  75. pyinfra/operations/server.py +6 -4
  76. pyinfra/operations/util/docker.py +44 -22
  77. {pyinfra-3.2.dist-info → pyinfra-3.3.1.dist-info}/LICENSE.md +1 -1
  78. {pyinfra-3.2.dist-info → pyinfra-3.3.1.dist-info}/METADATA +25 -24
  79. {pyinfra-3.2.dist-info → pyinfra-3.3.1.dist-info}/RECORD +89 -83
  80. pyinfra_cli/exceptions.py +5 -0
  81. pyinfra_cli/log.py +3 -0
  82. pyinfra_cli/main.py +9 -8
  83. pyinfra_cli/prints.py +1 -1
  84. pyinfra_cli/virtualenv.py +1 -1
  85. tests/test_connectors/test_ssh.py +302 -182
  86. tests/test_connectors/test_sshuserclient.py +10 -5
  87. {pyinfra-3.2.dist-info → pyinfra-3.3.1.dist-info}/WHEEL +0 -0
  88. {pyinfra-3.2.dist-info → pyinfra-3.3.1.dist-info}/entry_points.txt +0 -0
  89. {pyinfra-3.2.dist-info → pyinfra-3.3.1.dist-info}/top_level.txt +0 -0
@@ -15,6 +15,7 @@ from paramiko import (
15
15
  )
16
16
  from paramiko.agent import AgentRequestHandler
17
17
  from paramiko.hostkeys import HostKeyEntry
18
+ from typing_extensions import override
18
19
 
19
20
  from pyinfra import logger
20
21
  from pyinfra.api.util import memoize
@@ -25,6 +26,7 @@ HOST_KEYS_LOCK = BoundedSemaphore()
25
26
 
26
27
 
27
28
  class StrictPolicy(MissingHostKeyPolicy):
29
+ @override
28
30
  def missing_host_key(self, client, hostname, key):
29
31
  logger.error("No host key for {0} found in known_hosts".format(hostname))
30
32
  raise SSHException(
@@ -55,6 +57,7 @@ def append_hostkey(client, hostname, key):
55
57
 
56
58
 
57
59
  class AcceptNewPolicy(MissingHostKeyPolicy):
60
+ @override
58
61
  def missing_host_key(self, client, hostname, key):
59
62
  logger.warning(
60
63
  (
@@ -68,6 +71,7 @@ class AcceptNewPolicy(MissingHostKeyPolicy):
68
71
 
69
72
 
70
73
  class AskPolicy(MissingHostKeyPolicy):
74
+ @override
71
75
  def missing_host_key(self, client, hostname, key):
72
76
  should_continue = input(
73
77
  "No host key for {0} found in known_hosts, do you want to continue [y/n] ".format(
@@ -84,6 +88,7 @@ class AskPolicy(MissingHostKeyPolicy):
84
88
 
85
89
 
86
90
  class WarningPolicy(MissingHostKeyPolicy):
91
+ @override
87
92
  def missing_host_key(self, client, hostname, key):
88
93
  logger.warning("No host key for {0} found in known_hosts".format(hostname))
89
94
 
@@ -136,6 +141,7 @@ class SSHClient(ParamikoClient):
136
141
  original idea at http://bitprophet.org/blog/2012/11/05/gateway-solutions/.
137
142
  """
138
143
 
144
+ @override
139
145
  def connect( # type: ignore[override]
140
146
  self,
141
147
  hostname,
@@ -152,6 +158,7 @@ class SSHClient(ParamikoClient):
152
158
  forward_agent,
153
159
  missing_host_key_policy,
154
160
  host_keys_file,
161
+ keep_alive,
155
162
  ) = self.parse_config(
156
163
  hostname,
157
164
  kwargs,
@@ -177,6 +184,11 @@ class SSHClient(ParamikoClient):
177
184
  if _pyinfra_ssh_forward_agent is not None:
178
185
  forward_agent = _pyinfra_ssh_forward_agent
179
186
 
187
+ if keep_alive:
188
+ transport = self.get_transport()
189
+ assert transport is not None, "No transport"
190
+ transport.set_keepalive(keep_alive)
191
+
180
192
  if forward_agent:
181
193
  transport = self.get_transport()
182
194
  assert transport is not None, "No transport"
@@ -202,13 +214,14 @@ class SSHClient(ParamikoClient):
202
214
  cfg: dict = {"port": 22}
203
215
  cfg.update(initial_cfg or {})
204
216
 
217
+ keep_alive = 0
205
218
  forward_agent = False
206
219
  missing_host_key_policy = get_missing_host_key_policy(strict_host_key_checking)
207
220
  host_keys_file = path.expanduser("~/.ssh/known_hosts") # OpenSSH default
208
221
 
209
222
  ssh_config = get_ssh_config(ssh_config_file)
210
223
  if not ssh_config:
211
- return hostname, cfg, forward_agent, missing_host_key_policy, host_keys_file
224
+ return hostname, cfg, forward_agent, missing_host_key_policy, host_keys_file, keep_alive
212
225
 
213
226
  host_config = ssh_config.lookup(hostname)
214
227
  forward_agent = host_config.get("forwardagent") == "yes"
@@ -234,6 +247,9 @@ class SSHClient(ParamikoClient):
234
247
  if "port" in host_config:
235
248
  cfg["port"] = int(host_config["port"])
236
249
 
250
+ if "serveraliveinterval" in host_config:
251
+ keep_alive = int(host_config["serveraliveinterval"])
252
+
237
253
  if "proxycommand" in host_config:
238
254
  cfg["sock"] = ProxyCommand(host_config["proxycommand"])
239
255
 
@@ -259,7 +275,7 @@ class SSHClient(ParamikoClient):
259
275
  sock = c.gateway(hostname, cfg["port"], target, target_config["port"])
260
276
  cfg["sock"] = sock
261
277
 
262
- return hostname, cfg, forward_agent, missing_host_key_policy, host_keys_file
278
+ return hostname, cfg, forward_agent, missing_host_key_policy, host_keys_file, keep_alive
263
279
 
264
280
  @staticmethod
265
281
  def derive_shorthand(ssh_config, host_string):
@@ -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,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
@@ -20,13 +22,16 @@ class ApkPackages(FactBase):
20
22
  }
21
23
  """
22
24
 
25
+ @override
23
26
  def command(self) -> str:
24
27
  return "apk list --installed"
25
28
 
29
+ @override
26
30
  def requires_command(self) -> str:
27
31
  return "apk"
28
32
 
29
33
  default = dict
30
34
 
35
+ @override
31
36
  def process(self, output):
32
37
  return parse_packages(APK_REGEX, output)
pyinfra/facts/apt.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
 
5
- from typing_extensions import TypedDict
5
+ from typing_extensions import TypedDict, override
6
6
 
7
7
  from pyinfra.api import FactBase
8
8
 
@@ -76,17 +76,20 @@ class AptSources(FactBase):
76
76
  ]
77
77
  """
78
78
 
79
+ @override
79
80
  def command(self) -> str:
80
81
  return make_cat_files_command(
81
82
  "/etc/apt/sources.list",
82
83
  "/etc/apt/sources.list.d/*.list",
83
84
  )
84
85
 
86
+ @override
85
87
  def requires_command(self) -> str:
86
88
  return "apt" # if apt installed, above should exist
87
89
 
88
90
  default = list
89
91
 
92
+ @override
90
93
  def process(self, output):
91
94
  repos = []
92
95
 
@@ -113,9 +116,11 @@ class AptKeys(GpgFactBase):
113
116
  """
114
117
 
115
118
  # This requires both apt-key *and* apt-key itself requires gpg
119
+ @override
116
120
  def command(self) -> str:
117
121
  return "! command -v gpg || apt-key list --with-colons"
118
122
 
123
+ @override
119
124
  def requires_command(self) -> str:
120
125
  return "apt-key"
121
126
 
@@ -132,13 +137,16 @@ class SimulateOperationWillChange(FactBase[AptSimulationDict]):
132
137
  Simulate an 'apt-get' operation and try to detect if any changes would be performed.
133
138
  """
134
139
 
140
+ @override
135
141
  def command(self, command: str) -> str:
136
142
  # LC_ALL=C: Ensure the output is in english, as we want to parse it
137
143
  return "LC_ALL=C " + noninteractive_apt(f"{command} --dry-run")
138
144
 
145
+ @override
139
146
  def requires_command(self, command: str) -> str:
140
147
  return "apt-get"
141
148
 
149
+ @override
142
150
  def process(self, output) -> AptSimulationDict:
143
151
  # We are looking for a line similar to
144
152
  # "3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded."
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", "")
pyinfra/facts/crontab.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, List, Optional, TypedDict, Union
3
3
 
4
- from typing_extensions import NotRequired
4
+ from typing_extensions import NotRequired, override
5
5
 
6
6
  from pyinfra.api import FactBase
7
7
  from pyinfra.api.util import try_int
@@ -70,6 +70,7 @@ class CrontabFile:
70
70
  def __getitem__(self, item) -> Optional[CrontabDict]:
71
71
  return self.get(item)
72
72
 
73
+ @override
73
74
  def __repr__(self):
74
75
  return f"CrontabResult({self.commands})"
75
76
 
@@ -91,6 +92,7 @@ class CrontabFile:
91
92
  )
92
93
  return "\n".join(lines)
93
94
 
95
+ @override
94
96
  def __str__(self):
95
97
  return "\n".join(self.format_item(item) for item in self.commands)
96
98
 
@@ -139,14 +141,17 @@ class Crontab(FactBase[CrontabFile]):
139
141
 
140
142
  default = CrontabFile
141
143
 
144
+ @override
142
145
  def requires_command(self, user=None) -> str:
143
146
  return "crontab"
144
147
 
148
+ @override
145
149
  def command(self, user=None):
146
150
  if user:
147
151
  return "crontab -l -u {0} || true".format(user)
148
152
  return "crontab -l || true"
149
153
 
154
+ @override
150
155
  def process(self, output):
151
156
  crons = CrontabFile()
152
157
  current_comments = []
pyinfra/facts/deb.py CHANGED
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import re
4
4
  import shlex
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from pyinfra.api import FactBase
7
9
 
8
10
  from .util.packaging import parse_packages
@@ -16,9 +18,11 @@ class DebArch(FactBase):
16
18
  Returns the architecture string used in apt repository sources, eg ``amd64``.
17
19
  """
18
20
 
21
+ @override
19
22
  def command(self) -> str:
20
23
  return "dpkg --print-architecture"
21
24
 
25
+ @override
22
26
  def requires_command(self) -> str:
23
27
  return "dpkg"
24
28
 
@@ -34,9 +38,11 @@ class DebPackages(FactBase):
34
38
  }
35
39
  """
36
40
 
41
+ @override
37
42
  def command(self) -> str:
38
43
  return "dpkg -l"
39
44
 
45
+ @override
40
46
  def requires_command(self) -> str:
41
47
  return "dpkg"
42
48
 
@@ -47,6 +53,7 @@ class DebPackages(FactBase):
47
53
  DEB_PACKAGE_VERSION_REGEX,
48
54
  )
49
55
 
56
+ @override
50
57
  def process(self, output):
51
58
  return parse_packages(self.regex, output)
52
59
 
@@ -61,14 +68,17 @@ class DebPackage(FactBase):
61
68
  "version": r"^Version:\s+({0})$".format(DEB_PACKAGE_VERSION_REGEX),
62
69
  }
63
70
 
71
+ @override
64
72
  def requires_command(self, package) -> str:
65
73
  return "dpkg"
66
74
 
75
+ @override
67
76
  def command(self, package):
68
77
  return "! test -e {0} && (dpkg -s {0} 2>/dev/null || true) || dpkg -I {0}".format(
69
78
  shlex.quote(package)
70
79
  )
71
80
 
81
+ @override
72
82
  def process(self, output):
73
83
  data = {}
74
84
 
pyinfra/facts/dnf.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 import make_cat_files_command
@@ -23,6 +25,7 @@ class DnfRepositories(FactBase):
23
25
  ]
24
26
  """
25
27
 
28
+ @override
26
29
  def command(self) -> str:
27
30
  return make_cat_files_command(
28
31
  "/etc/dnf.conf",
@@ -30,10 +33,12 @@ class DnfRepositories(FactBase):
30
33
  "/etc/yum.repos.d/*.repo",
31
34
  )
32
35
 
36
+ @override
33
37
  def requires_command(self) -> str:
34
38
  return "dnf"
35
39
 
36
40
  default = list
37
41
 
42
+ @override
38
43
  def process(self, output):
39
44
  return parse_yum_repositories(output)
pyinfra/facts/docker.py CHANGED
@@ -8,6 +8,8 @@ from __future__ import annotations
8
8
 
9
9
  import json
10
10
 
11
+ from typing_extensions import override
12
+
11
13
  from pyinfra.api import FactBase
12
14
 
13
15
 
@@ -16,9 +18,11 @@ class DockerFactBase(FactBase):
16
18
 
17
19
  docker_type: str
18
20
 
21
+ @override
19
22
  def requires_command(self, *args, **kwargs) -> str:
20
23
  return "docker"
21
24
 
25
+ @override
22
26
  def process(self, output):
23
27
  output = "".join(output)
24
28
  return json.loads(output)
@@ -29,6 +33,7 @@ class DockerSystemInfo(DockerFactBase):
29
33
  Returns ``docker system info`` output in JSON format.
30
34
  """
31
35
 
36
+ @override
32
37
  def command(self) -> str:
33
38
  return 'docker system info --format="{{json .}}"'
34
39
 
@@ -42,6 +47,7 @@ class DockerContainers(DockerFactBase):
42
47
  Returns ``docker inspect`` output for all Docker containers.
43
48
  """
44
49
 
50
+ @override
45
51
  def command(self) -> str:
46
52
  return "docker container inspect `docker ps -qa`"
47
53
 
@@ -51,6 +57,7 @@ class DockerImages(DockerFactBase):
51
57
  Returns ``docker inspect`` output for all Docker images.
52
58
  """
53
59
 
60
+ @override
54
61
  def command(self) -> str:
55
62
  return "docker image inspect `docker images -q`"
56
63
 
@@ -60,6 +67,7 @@ class DockerNetworks(DockerFactBase):
60
67
  Returns ``docker inspect`` output for all Docker networks.
61
68
  """
62
69
 
70
+ @override
63
71
  def command(self) -> str:
64
72
  return "docker network inspect `docker network ls -q`"
65
73
 
@@ -69,6 +77,7 @@ class DockerNetworks(DockerFactBase):
69
77
 
70
78
 
71
79
  class DockerSingleMixin(DockerFactBase):
80
+ @override
72
81
  def command(self, object_id):
73
82
  return "docker {0} inspect {1} 2>&- || true".format(
74
83
  self.docker_type,
@@ -105,6 +114,7 @@ class DockerVolumes(DockerFactBase):
105
114
  Returns ``docker inspect`` output for all Docker volumes.
106
115
  """
107
116
 
117
+ @override
108
118
  def command(self) -> str:
109
119
  return "docker volume inspect `docker volume ls -q`"
110
120
 
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, Dict, Iterable, List, Optional, Tuple, TypedDict
4
4
 
5
+ from typing_extensions import override
6
+
5
7
  from pyinfra.api import FactBase
6
8
 
7
9
  BootEntry = Tuple[bool, str]
@@ -37,14 +39,17 @@ class EFIBootMgr(FactBase[Optional[EFIBootMgrInfoDict]]):
37
39
  }
38
40
  """
39
41
 
42
+ @override
40
43
  def requires_command(self, *args: Any, **kwargs: Any) -> str:
41
44
  return "efibootmgr"
42
45
 
46
+ @override
43
47
  def command(self) -> str:
44
48
  # FIXME: Use '|| true' to properly handle the case where
45
49
  # 'efibootmgr' is run on a non-UEFI system
46
50
  return "efibootmgr || true"
47
51
 
52
+ @override
48
53
  def process(self, output: Iterable[str]) -> Optional[EFIBootMgrInfoDict]:
49
54
  # This parsing code closely follows the printing code of efibootmgr
50
55
  # at <https://github.com/rhboot/efibootmgr/blob/main/src/efibootmgr.c#L2020-L2048>