pyinfra 3.0b1__py2.py3-none-any.whl → 3.0b3__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.
- pyinfra/api/arguments.py +9 -3
- pyinfra/api/arguments_typed.py +8 -5
- pyinfra/api/command.py +5 -3
- pyinfra/api/config.py +115 -13
- pyinfra/api/connectors.py +5 -2
- pyinfra/api/facts.py +33 -32
- pyinfra/api/host.py +5 -5
- pyinfra/api/inventory.py +4 -0
- pyinfra/api/operation.py +22 -14
- pyinfra/api/util.py +24 -16
- pyinfra/connectors/base.py +3 -6
- pyinfra/connectors/docker.py +2 -9
- pyinfra/connectors/local.py +2 -2
- pyinfra/connectors/ssh.py +2 -2
- pyinfra/connectors/util.py +6 -7
- pyinfra/connectors/vagrant.py +5 -5
- pyinfra/context.py +1 -0
- pyinfra/facts/apk.py +2 -0
- pyinfra/facts/apt.py +2 -0
- pyinfra/facts/brew.py +2 -0
- pyinfra/facts/bsdinit.py +2 -0
- pyinfra/facts/cargo.py +2 -0
- pyinfra/facts/choco.py +2 -0
- pyinfra/facts/deb.py +7 -2
- pyinfra/facts/dnf.py +2 -0
- pyinfra/facts/docker.py +18 -0
- pyinfra/facts/files.py +2 -0
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/gpg.py +2 -0
- pyinfra/facts/hardware.py +30 -22
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +12 -6
- pyinfra/facts/npm.py +1 -0
- pyinfra/facts/openrc.py +2 -0
- pyinfra/facts/pacman.py +6 -2
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +2 -0
- pyinfra/facts/pkgin.py +2 -0
- pyinfra/facts/postgres.py +6 -6
- pyinfra/facts/postgresql.py +2 -0
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/runit.py +68 -0
- pyinfra/facts/server.py +10 -13
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +2 -0
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +3 -2
- pyinfra/facts/vzctl.py +2 -0
- pyinfra/facts/xbps.py +2 -0
- pyinfra/facts/yum.py +2 -0
- pyinfra/facts/zypper.py +2 -0
- pyinfra/operations/apk.py +3 -1
- pyinfra/operations/apt.py +16 -18
- pyinfra/operations/brew.py +10 -8
- pyinfra/operations/bsdinit.py +5 -3
- pyinfra/operations/cargo.py +3 -1
- pyinfra/operations/choco.py +3 -1
- pyinfra/operations/dnf.py +15 -19
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +81 -66
- pyinfra/operations/gem.py +3 -1
- pyinfra/operations/git.py +18 -16
- pyinfra/operations/iptables.py +27 -25
- pyinfra/operations/launchd.py +5 -6
- pyinfra/operations/lxd.py +7 -4
- pyinfra/operations/mysql.py +57 -53
- pyinfra/operations/npm.py +8 -1
- pyinfra/operations/openrc.py +5 -3
- pyinfra/operations/pacman.py +4 -5
- pyinfra/operations/pip.py +11 -9
- pyinfra/operations/pkg.py +3 -1
- pyinfra/operations/pkgin.py +3 -1
- pyinfra/operations/postgres.py +39 -37
- pyinfra/operations/postgresql.py +2 -0
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +7 -3
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +42 -16
- pyinfra/operations/server.py +52 -43
- pyinfra/operations/snap.py +3 -1
- pyinfra/operations/ssh.py +12 -10
- pyinfra/operations/systemd.py +12 -8
- pyinfra/operations/sysvinit.py +6 -4
- pyinfra/operations/upstart.py +5 -3
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +53 -37
- pyinfra/operations/util/service.py +25 -18
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +3 -1
- pyinfra/operations/yum.py +14 -18
- pyinfra/operations/zypper.py +8 -9
- pyinfra/version.py +5 -2
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/METADATA +30 -28
- pyinfra-3.0b3.dist-info/RECORD +167 -0
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/WHEEL +1 -1
- pyinfra_cli/exceptions.py +0 -5
- pyinfra_cli/inventory.py +38 -19
- pyinfra_cli/prints.py +15 -11
- pyinfra_cli/util.py +3 -1
- tests/test_api/test_api_operations.py +1 -1
- tests/test_connectors/test_ssh.py +66 -13
- tests/test_connectors/test_vagrant.py +3 -3
- pyinfra-3.0b1.dist-info/RECORD +0 -163
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/entry_points.txt +0 -0
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/top_level.txt +0 -0
pyinfra/connectors/ssh.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import shlex
|
|
4
|
-
from distutils.spawn import find_executable
|
|
5
4
|
from random import uniform
|
|
5
|
+
from shutil import which
|
|
6
6
|
from socket import error as socket_error, gaierror
|
|
7
7
|
from time import sleep
|
|
8
8
|
from typing import TYPE_CHECKING, Any, Iterable, Optional, Tuple
|
|
@@ -539,7 +539,7 @@ class SSHConnector(BaseConnector):
|
|
|
539
539
|
if self.data["ssh_password"]:
|
|
540
540
|
raise NotImplementedError("Rsync does not currently work with SSH passwords.")
|
|
541
541
|
|
|
542
|
-
if not
|
|
542
|
+
if not which("rsync"):
|
|
543
543
|
raise NotImplementedError("The `rsync` binary is not available on this system.")
|
|
544
544
|
|
|
545
545
|
def rsync(
|
pyinfra/connectors/util.py
CHANGED
|
@@ -274,12 +274,11 @@ def make_unix_command_for_host(
|
|
|
274
274
|
# If no sudo, we've nothing to do here
|
|
275
275
|
return make_unix_command(command, **command_arguments)
|
|
276
276
|
|
|
277
|
-
#
|
|
278
|
-
# connector data value.
|
|
279
|
-
command_arguments["_sudo_password"]
|
|
280
|
-
"_sudo_password"
|
|
281
|
-
|
|
282
|
-
)
|
|
277
|
+
# If the sudo password is not set in the direct arguments,
|
|
278
|
+
# set it from the connector data value.
|
|
279
|
+
if "_sudo_password" not in command_arguments or not command_arguments["_sudo_password"]:
|
|
280
|
+
command_arguments["_sudo_password"] = host.connector_data.get("prompted_sudo_password")
|
|
281
|
+
|
|
283
282
|
if command_arguments["_sudo_password"]:
|
|
284
283
|
# Ensure the askpass path is correctly set and passed through
|
|
285
284
|
_ensure_sudo_askpass_set_for_host(host)
|
|
@@ -339,7 +338,7 @@ def make_unix_command(
|
|
|
339
338
|
[
|
|
340
339
|
"env",
|
|
341
340
|
"SUDO_ASKPASS={0}".format(_sudo_askpass_path),
|
|
342
|
-
MaskString("{0}={1}".format(SUDO_ASKPASS_ENV_VAR, _sudo_password)),
|
|
341
|
+
MaskString("{0}={1}".format(SUDO_ASKPASS_ENV_VAR, shlex.quote(_sudo_password))),
|
|
343
342
|
],
|
|
344
343
|
)
|
|
345
344
|
|
pyinfra/connectors/vagrant.py
CHANGED
|
@@ -93,13 +93,13 @@ def _make_name_data(host):
|
|
|
93
93
|
"ssh_hostname": host["HostName"],
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
for config_key, data_key in (
|
|
97
|
-
("Port", "ssh_port"),
|
|
98
|
-
("User", "ssh_user"),
|
|
99
|
-
("IdentityFile", "ssh_key"),
|
|
96
|
+
for config_key, data_key, data_cast in (
|
|
97
|
+
("Port", "ssh_port", int),
|
|
98
|
+
("User", "ssh_user", str),
|
|
99
|
+
("IdentityFile", "ssh_key", str),
|
|
100
100
|
):
|
|
101
101
|
if config_key in host:
|
|
102
|
-
data[data_key] = host[config_key]
|
|
102
|
+
data[data_key] = data_cast(host[config_key])
|
|
103
103
|
|
|
104
104
|
# Update any configured JSON data
|
|
105
105
|
if vagrant_host in vagrant_options.get("data", {}):
|
pyinfra/context.py
CHANGED
pyinfra/facts/apk.py
CHANGED
pyinfra/facts/apt.py
CHANGED
pyinfra/facts/brew.py
CHANGED
pyinfra/facts/bsdinit.py
CHANGED
pyinfra/facts/cargo.py
CHANGED
pyinfra/facts/choco.py
CHANGED
pyinfra/facts/deb.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
4
|
+
import shlex
|
|
2
5
|
|
|
3
6
|
from pyinfra.api import FactBase
|
|
4
7
|
|
|
@@ -54,8 +57,10 @@ class DebPackage(FactBase):
|
|
|
54
57
|
|
|
55
58
|
requires_command = "dpkg"
|
|
56
59
|
|
|
57
|
-
def command(self,
|
|
58
|
-
return "! test -e {0} && (dpkg -s {0} 2>/dev/null || true) || dpkg -I {0}".format(
|
|
60
|
+
def command(self, package):
|
|
61
|
+
return "! test -e {0} && (dpkg -s {0} 2>/dev/null || true) || dpkg -I {0}".format(
|
|
62
|
+
shlex.quote(package)
|
|
63
|
+
)
|
|
59
64
|
|
|
60
65
|
def process(self, output):
|
|
61
66
|
data = {}
|
pyinfra/facts/dnf.py
CHANGED
pyinfra/facts/docker.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
|
|
3
5
|
from pyinfra.api import FactBase
|
|
@@ -84,3 +86,19 @@ class DockerNetwork(DockerSingleMixin):
|
|
|
84
86
|
"""
|
|
85
87
|
|
|
86
88
|
docker_type = "network"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class DockerVolumes(DockerFactBase):
|
|
92
|
+
"""
|
|
93
|
+
Returns ``docker inspect`` output for all Docker volumes.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
command = "docker volume inspect `docker volume ls -q`"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DockerVolume(DockerSingleMixin):
|
|
100
|
+
"""
|
|
101
|
+
Returns ``docker inspect`` output for a single Docker container.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
docker_type = "volume"
|
pyinfra/facts/files.py
CHANGED
pyinfra/facts/gem.py
CHANGED
pyinfra/facts/gpg.py
CHANGED
pyinfra/facts/hardware.py
CHANGED
|
@@ -195,28 +195,36 @@ class NetworkDevices(FactBase):
|
|
|
195
195
|
output = "\n".join(map(str.strip, output))
|
|
196
196
|
|
|
197
197
|
# Splitting the output into sections per network device
|
|
198
|
-
device_sections = re.split(r"\n(?=\d+:
|
|
198
|
+
device_sections = re.split(r"\n(?=\d+: [^\s/:]|[^\s/:]+:.*mtu )", output)
|
|
199
199
|
|
|
200
200
|
# Dictionary to hold all device information
|
|
201
201
|
all_devices = {}
|
|
202
202
|
|
|
203
203
|
for section in device_sections:
|
|
204
204
|
# Extracting the device name
|
|
205
|
-
device_name_match = re.match(r"^(?:\d+: )?([
|
|
205
|
+
device_name_match = re.match(r"^(?:\d+: )?([^\s/:]+):", section)
|
|
206
206
|
if not device_name_match:
|
|
207
207
|
continue
|
|
208
208
|
device_name = device_name_match.group(1)
|
|
209
209
|
|
|
210
210
|
# Regular expressions to match different parts of the output
|
|
211
|
-
ether_re = re.compile(r"([0-9A-Fa-f:]{17})")
|
|
211
|
+
ether_re = re.compile(r"ether ([0-9A-Fa-f:]{17})")
|
|
212
212
|
mtu_re = re.compile(r"mtu (\d+)")
|
|
213
213
|
ipv4_re = (
|
|
214
|
+
# ip a
|
|
214
215
|
re.compile(
|
|
215
|
-
r"inet (
|
|
216
|
-
),
|
|
216
|
+
r"inet (?P<address>\d+\.\d+\.\d+\.\d+)/(?P<mask>\d+)(?: metric \d+)?(?: brd (?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
|
|
217
|
+
),
|
|
218
|
+
# ifconfig -a
|
|
217
219
|
re.compile(
|
|
218
|
-
r"inet (
|
|
219
|
-
),
|
|
220
|
+
r"inet (?P<address>\d+\.\d+\.\d+\.\d+)\s+netmask\s+(?P<mask>(?:\d+\.\d+\.\d+\.\d+)|(?:[0-9a-fA-FxX]+))(?:\s+broadcast\s+(?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
ipv6_re = (
|
|
224
|
+
# ip a
|
|
225
|
+
re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)/(?P<mask>\d+)"),
|
|
226
|
+
# ifconfig -a
|
|
227
|
+
re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)\s+prefixlen\s+(?P<mask>\d+)"),
|
|
220
228
|
)
|
|
221
229
|
|
|
222
230
|
# Parsing the output
|
|
@@ -235,18 +243,22 @@ class NetworkDevices(FactBase):
|
|
|
235
243
|
)
|
|
236
244
|
|
|
237
245
|
# IPv4 Addresses
|
|
246
|
+
ipv4_matches: list[re.Match[str]]
|
|
238
247
|
for ipv4_re_ in ipv4_re:
|
|
239
|
-
ipv4_matches = ipv4_re_.
|
|
240
|
-
if ipv4_matches:
|
|
248
|
+
ipv4_matches = list(ipv4_re_.finditer(section))
|
|
249
|
+
if len(ipv4_matches):
|
|
241
250
|
break
|
|
242
251
|
|
|
243
|
-
if ipv4_matches:
|
|
252
|
+
if len(ipv4_matches):
|
|
244
253
|
ipv4_info = []
|
|
245
254
|
for ipv4 in ipv4_matches:
|
|
246
|
-
address = ipv4
|
|
247
|
-
mask_value = ipv4
|
|
255
|
+
address = ipv4.group("address")
|
|
256
|
+
mask_value = ipv4.group("mask")
|
|
248
257
|
mask_bits, netmask = mask(mask_value)
|
|
249
|
-
|
|
258
|
+
try:
|
|
259
|
+
broadcast = ipv4.group("broadcast")
|
|
260
|
+
except IndexError:
|
|
261
|
+
broadcast = None
|
|
250
262
|
|
|
251
263
|
ipv4_info.append(
|
|
252
264
|
{
|
|
@@ -261,21 +273,17 @@ class NetworkDevices(FactBase):
|
|
|
261
273
|
device_info["ipv4"]["additional_ips"] = ipv4_info[1:] # type: ignore[index]
|
|
262
274
|
|
|
263
275
|
# IPv6 Addresses
|
|
264
|
-
|
|
265
|
-
re.compile(r"inet6\s+([0-9a-fA-F:]+)/(\d+)"),
|
|
266
|
-
re.compile(r"inet6\s+([0-9a-fA-F:]+)\s+prefixlen\s+(\d+)"),
|
|
267
|
-
)
|
|
268
|
-
|
|
276
|
+
ipv6_matches: list[re.Match[str]]
|
|
269
277
|
for ipv6_re_ in ipv6_re:
|
|
270
|
-
ipv6_matches = ipv6_re_.
|
|
278
|
+
ipv6_matches = list(ipv6_re_.finditer(section))
|
|
271
279
|
if ipv6_matches:
|
|
272
280
|
break
|
|
273
281
|
|
|
274
|
-
if ipv6_matches:
|
|
282
|
+
if len(ipv6_matches):
|
|
275
283
|
ipv6_info = []
|
|
276
284
|
for ipv6 in ipv6_matches:
|
|
277
|
-
address = ipv6
|
|
278
|
-
mask_bits = ipv6
|
|
285
|
+
address = ipv6.group("address")
|
|
286
|
+
mask_bits = ipv6.group("mask")
|
|
279
287
|
ipv6_info.append({"address": address, "mask_bits": int(mask_bits)})
|
|
280
288
|
device_info["ipv6"] = ipv6_info[0]
|
|
281
289
|
if len(ipv6_matches) > 1:
|
pyinfra/facts/launchd.py
CHANGED
pyinfra/facts/lxd.py
CHANGED
pyinfra/facts/mysql.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
from collections import defaultdict
|
|
3
5
|
|
|
@@ -8,11 +10,11 @@ from .util.databases import parse_columns_and_rows
|
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def make_mysql_command(
|
|
11
|
-
database=None,
|
|
12
|
-
user=None,
|
|
13
|
-
password=None,
|
|
14
|
-
host=None,
|
|
15
|
-
port=None,
|
|
13
|
+
database: str | None = None,
|
|
14
|
+
user: str | None = None,
|
|
15
|
+
password: str | None = None,
|
|
16
|
+
host: str | None = None,
|
|
17
|
+
port: int | None = None,
|
|
16
18
|
executable="mysql",
|
|
17
19
|
):
|
|
18
20
|
target_bits = [executable]
|
|
@@ -37,7 +39,11 @@ def make_mysql_command(
|
|
|
37
39
|
return StringCommand(*target_bits)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def make_execute_mysql_command(
|
|
42
|
+
def make_execute_mysql_command(
|
|
43
|
+
command: str | StringCommand,
|
|
44
|
+
ignore_errors=False,
|
|
45
|
+
**mysql_kwargs,
|
|
46
|
+
):
|
|
41
47
|
commands_bits = [
|
|
42
48
|
make_mysql_command(**mysql_kwargs),
|
|
43
49
|
"-Be",
|
pyinfra/facts/npm.py
CHANGED
pyinfra/facts/openrc.py
CHANGED
pyinfra/facts/pacman.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
|
|
1
5
|
from pyinfra.api import FactBase
|
|
2
6
|
|
|
3
7
|
from .util.packaging import parse_packages
|
|
@@ -21,9 +25,9 @@ class PacmanUnpackGroup(FactBase):
|
|
|
21
25
|
|
|
22
26
|
default = list
|
|
23
27
|
|
|
24
|
-
def command(self,
|
|
28
|
+
def command(self, package):
|
|
25
29
|
# Accept failure here (|| true) for invalid/unknown packages
|
|
26
|
-
return 'pacman -S --print-format "%n" {0} || true'.format(
|
|
30
|
+
return 'pacman -S --print-format "%n" {0} || true'.format(shlex.quote(package))
|
|
27
31
|
|
|
28
32
|
def process(self, output):
|
|
29
33
|
return output
|
pyinfra/facts/pip.py
CHANGED
pyinfra/facts/pkg.py
CHANGED
pyinfra/facts/pkgin.py
CHANGED
pyinfra/facts/postgres.py
CHANGED
|
@@ -7,13 +7,13 @@ from .util.databases import parse_columns_and_rows
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def make_psql_command(
|
|
10
|
-
database=None,
|
|
11
|
-
user=None,
|
|
12
|
-
password=None,
|
|
13
|
-
host=None,
|
|
14
|
-
port=None,
|
|
10
|
+
database: str | None = None,
|
|
11
|
+
user: str | None = None,
|
|
12
|
+
password: str | None = None,
|
|
13
|
+
host: str | None = None,
|
|
14
|
+
port: str | int | None = None,
|
|
15
15
|
executable="psql",
|
|
16
|
-
):
|
|
16
|
+
) -> StringCommand:
|
|
17
17
|
target_bits: list[str] = []
|
|
18
18
|
|
|
19
19
|
if password:
|
pyinfra/facts/postgresql.py
CHANGED
pyinfra/facts/rpm.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
4
|
+
import shlex
|
|
2
5
|
|
|
3
6
|
from pyinfra.api import FactBase
|
|
4
7
|
|
|
@@ -19,7 +22,7 @@ class RpmPackages(FactBase):
|
|
|
19
22
|
}
|
|
20
23
|
"""
|
|
21
24
|
|
|
22
|
-
command =
|
|
25
|
+
command = "rpm --queryformat {0} -qa".format(shlex.quote(rpm_query_format))
|
|
23
26
|
requires_command = "rpm"
|
|
24
27
|
|
|
25
28
|
default = dict
|
|
@@ -42,12 +45,12 @@ class RpmPackage(FactBase):
|
|
|
42
45
|
|
|
43
46
|
requires_command = "rpm"
|
|
44
47
|
|
|
45
|
-
def command(self,
|
|
48
|
+
def command(self, package):
|
|
46
49
|
return (
|
|
47
|
-
|
|
50
|
+
"rpm --queryformat {0} -q {1} || "
|
|
48
51
|
"! test -e {1} || "
|
|
49
|
-
|
|
50
|
-
).format(rpm_query_format,
|
|
52
|
+
"rpm --queryformat {0} -qp {1} 2> /dev/null"
|
|
53
|
+
).format(shlex.quote(rpm_query_format), shlex.quote(package))
|
|
51
54
|
|
|
52
55
|
def process(self, output):
|
|
53
56
|
for line in output:
|
|
@@ -69,11 +72,11 @@ class RpmPackageProvides(FactBase):
|
|
|
69
72
|
requires_command = "repoquery"
|
|
70
73
|
|
|
71
74
|
@staticmethod
|
|
72
|
-
def command(
|
|
75
|
+
def command(package):
|
|
73
76
|
# Accept failure here (|| true) for invalid/unknown packages
|
|
74
|
-
return
|
|
75
|
-
rpm_query_format,
|
|
76
|
-
|
|
77
|
+
return "repoquery --queryformat {0} --whatprovides {1} || true".format(
|
|
78
|
+
shlex.quote(rpm_query_format),
|
|
79
|
+
shlex.quote(package),
|
|
77
80
|
)
|
|
78
81
|
|
|
79
82
|
@staticmethod
|
pyinfra/facts/runit.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from pyinfra.api import FactBase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RunitStatus(FactBase):
|
|
5
|
+
"""
|
|
6
|
+
Returns a dict of name -> status for runit services.
|
|
7
|
+
|
|
8
|
+
+ service: optionally check only for a single service
|
|
9
|
+
+ svdir: alternative ``SVDIR``
|
|
10
|
+
|
|
11
|
+
.. code:: python
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
'agetty-tty1': True, # service is running
|
|
15
|
+
'dhcpcd': False, # service is down
|
|
16
|
+
'wpa_supplicant': None, # service is managed, but not running or down,
|
|
17
|
+
# possibly in a fail state
|
|
18
|
+
}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
requires_command = "sv"
|
|
22
|
+
default = dict
|
|
23
|
+
|
|
24
|
+
def command(self, service=None, svdir="/var/service"):
|
|
25
|
+
if service is None:
|
|
26
|
+
return (
|
|
27
|
+
'export SVDIR="{0}" && '
|
|
28
|
+
'cd "$SVDIR" && find * -maxdepth 0 -exec sv status {{}} + 2>/dev/null'
|
|
29
|
+
).format(svdir)
|
|
30
|
+
else:
|
|
31
|
+
return 'SVDIR="{0}" sv status "{1}"'.format(svdir, service)
|
|
32
|
+
|
|
33
|
+
def process(self, output):
|
|
34
|
+
services = {}
|
|
35
|
+
for line in output:
|
|
36
|
+
statusstr, service, _ = line.split(sep=": ", maxsplit=2)
|
|
37
|
+
status = None
|
|
38
|
+
|
|
39
|
+
if statusstr == "run":
|
|
40
|
+
status = True
|
|
41
|
+
elif statusstr == "down":
|
|
42
|
+
status = False
|
|
43
|
+
# another observable state is "fail"
|
|
44
|
+
# report as ``None`` for now
|
|
45
|
+
|
|
46
|
+
services[service] = status
|
|
47
|
+
|
|
48
|
+
return services
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RunitManaged(FactBase):
|
|
52
|
+
"""
|
|
53
|
+
Returns a set of all services managed by runit
|
|
54
|
+
|
|
55
|
+
+ service: optionally check only for a single service
|
|
56
|
+
+ svdir: alternative ``SVDIR``
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
default = set
|
|
60
|
+
|
|
61
|
+
def command(self, service=None, svdir="/var/service"):
|
|
62
|
+
if service is None:
|
|
63
|
+
return 'cd "{0}" && find -mindepth 1 -maxdepth 1 -type l -printf "%f\n"'.format(svdir)
|
|
64
|
+
else:
|
|
65
|
+
return 'cd "{0}" && test -h "{1}" && echo "{1}" || true'.format(svdir, service)
|
|
66
|
+
|
|
67
|
+
def process(self, output):
|
|
68
|
+
return set(output)
|
pyinfra/facts/server.py
CHANGED
|
@@ -5,7 +5,7 @@ import re
|
|
|
5
5
|
import shutil
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from tempfile import mkdtemp
|
|
8
|
-
from typing import Dict, List,
|
|
8
|
+
from typing import Dict, List, Optional, Union
|
|
9
9
|
|
|
10
10
|
from dateutil.parser import parse as parse_date
|
|
11
11
|
from distro import distro
|
|
@@ -25,12 +25,14 @@ class User(FactBase):
|
|
|
25
25
|
command = "echo $USER"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class Home(FactBase):
|
|
28
|
+
class Home(FactBase[Optional[str]]):
|
|
29
29
|
"""
|
|
30
|
-
Returns the home directory of the current user.
|
|
30
|
+
Returns the home directory of the given user, or the current user if no user is given.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
@staticmethod
|
|
34
|
+
def command(user=""):
|
|
35
|
+
return f"echo ~{user}"
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
class Path(FactBase):
|
|
@@ -348,8 +350,7 @@ class Groups(FactBase[List[str]]):
|
|
|
348
350
|
command = "cat /etc/group"
|
|
349
351
|
default = list
|
|
350
352
|
|
|
351
|
-
|
|
352
|
-
def process(output) -> list[str]:
|
|
353
|
+
def process(self, output) -> list[str]:
|
|
353
354
|
groups: list[str] = []
|
|
354
355
|
|
|
355
356
|
for line in output:
|
|
@@ -359,9 +360,6 @@ class Groups(FactBase[List[str]]):
|
|
|
359
360
|
return groups
|
|
360
361
|
|
|
361
362
|
|
|
362
|
-
CrontabCommand = NewType("CrontabCommand", int)
|
|
363
|
-
|
|
364
|
-
|
|
365
363
|
class CrontabDict(TypedDict):
|
|
366
364
|
minute: NotRequired[Union[int, str]]
|
|
367
365
|
hour: NotRequired[Union[int, str]]
|
|
@@ -372,7 +370,7 @@ class CrontabDict(TypedDict):
|
|
|
372
370
|
special_time: NotRequired[str]
|
|
373
371
|
|
|
374
372
|
|
|
375
|
-
class Crontab(FactBase[Dict[
|
|
373
|
+
class Crontab(FactBase[Dict[str, CrontabDict]]):
|
|
376
374
|
"""
|
|
377
375
|
Returns a dictionary of cron command -> execution time.
|
|
378
376
|
|
|
@@ -402,9 +400,8 @@ class Crontab(FactBase[Dict[CrontabCommand, CrontabDict]]):
|
|
|
402
400
|
return "crontab -l -u {0} || true".format(user)
|
|
403
401
|
return "crontab -l || true"
|
|
404
402
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
crons: dict[Command, CrontabDict] = {}
|
|
403
|
+
def process(self, output):
|
|
404
|
+
crons: dict[str, CrontabDict] = {}
|
|
408
405
|
current_comments = []
|
|
409
406
|
|
|
410
407
|
for line in output:
|
pyinfra/facts/snap.py
CHANGED
pyinfra/facts/systemd.py
CHANGED
pyinfra/facts/upstart.py
CHANGED
pyinfra/facts/util/packaging.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from typing import Iterable
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
def parse_packages(regex, output):
|
|
7
|
+
def parse_packages(regex: str, output: Iterable[str]) -> dict[str, set[str]]:
|
|
7
8
|
packages: dict[str, set[str]] = {}
|
|
8
9
|
|
|
9
10
|
for line in output:
|
|
@@ -34,7 +35,7 @@ def _parse_yum_or_zypper_repositories(output):
|
|
|
34
35
|
current_repo["name"] = line[1:-1]
|
|
35
36
|
|
|
36
37
|
if current_repo and "=" in line:
|
|
37
|
-
key, value =
|
|
38
|
+
key, value = re.split(r"\s*=\s*", line, maxsplit=1)
|
|
38
39
|
current_repo[key] = value
|
|
39
40
|
|
|
40
41
|
if current_repo:
|