pyinfra 2.9.1__py2.py3-none-any.whl → 3.0__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/__init__.py +3 -0
- pyinfra/api/arguments.py +265 -253
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +68 -53
- pyinfra/api/config.py +139 -32
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +7 -26
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +102 -137
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +21 -25
- pyinfra/api/operation.py +240 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +79 -86
- pyinfra/connectors/base.py +147 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +220 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +196 -208
- pyinfra/connectors/ssh.py +530 -613
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +211 -137
- pyinfra/connectors/vagrant.py +60 -53
- pyinfra/context.py +4 -2
- 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 +19 -0
- pyinfra/facts/files.py +47 -32
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +3 -1
- pyinfra/facts/hardware.py +34 -24
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +13 -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 +168 -0
- pyinfra/facts/postgresql.py +6 -160
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/runit.py +68 -0
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +80 -36
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +31 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +7 -4
- 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/local.py +4 -5
- pyinfra/operations/apk.py +6 -4
- pyinfra/operations/apt.py +46 -65
- pyinfra/operations/brew.py +17 -22
- pyinfra/operations/bsdinit.py +9 -7
- pyinfra/operations/cargo.py +4 -2
- pyinfra/operations/choco.py +4 -2
- pyinfra/operations/dnf.py +19 -23
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +188 -386
- pyinfra/operations/gem.py +4 -2
- pyinfra/operations/git.py +24 -53
- pyinfra/operations/iptables.py +29 -35
- pyinfra/operations/launchd.py +6 -7
- pyinfra/operations/lxd.py +8 -13
- pyinfra/operations/mysql.py +62 -81
- pyinfra/operations/npm.py +9 -2
- pyinfra/operations/openrc.py +6 -4
- pyinfra/operations/pacman.py +7 -8
- pyinfra/operations/pip.py +25 -24
- pyinfra/operations/pkg.py +4 -2
- pyinfra/operations/pkgin.py +6 -4
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -379
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -44
- pyinfra/operations/server.py +111 -127
- pyinfra/operations/snap.py +4 -4
- pyinfra/operations/ssh.py +20 -33
- pyinfra/operations/systemd.py +19 -15
- pyinfra/operations/sysvinit.py +9 -16
- pyinfra/operations/upstart.py +9 -7
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +55 -57
- pyinfra/operations/util/service.py +39 -51
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +6 -4
- pyinfra/operations/yum.py +18 -22
- pyinfra/operations/zypper.py +12 -13
- pyinfra/version.py +5 -2
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
- pyinfra-3.0.dist-info/RECORD +167 -0
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
- pyinfra-3.0.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +78 -42
- pyinfra_cli/inventory.py +40 -6
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +95 -127
- pyinfra_cli/util.py +62 -29
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +13 -13
- tests/test_api/test_api_deploys.py +28 -29
- tests/test_api/test_api_facts.py +60 -98
- tests/test_api/test_api_operations.py +101 -201
- tests/test_cli/test_cli.py +18 -49
- tests/test_cli/test_cli_deploy.py +11 -37
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_chroot.py +6 -6
- tests/test_connectors/test_docker.py +4 -4
- tests/test_connectors/test_dockerssh.py +38 -50
- tests/test_connectors/test_local.py +11 -12
- tests/test_connectors/test_ssh.py +105 -93
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +7 -7
- pyinfra/api/operation.pyi +0 -117
- pyinfra/connectors/ansible.py +0 -171
- pyinfra/connectors/mech.py +0 -186
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -320
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -551
- pyinfra-2.9.1.dist-info/RECORD +0 -170
- pyinfra-2.9.1.dist-info/entry_points.txt +0 -14
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- tests/test_connectors/test_winrm.py +0 -76
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
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
|
|
@@ -6,6 +8,7 @@ from pyinfra.api import FactBase
|
|
|
6
8
|
class DockerFactBase(FactBase):
|
|
7
9
|
abstract = True
|
|
8
10
|
|
|
11
|
+
docker_type: str
|
|
9
12
|
requires_command = "docker"
|
|
10
13
|
|
|
11
14
|
def process(self, output):
|
|
@@ -83,3 +86,19 @@ class DockerNetwork(DockerSingleMixin):
|
|
|
83
86
|
"""
|
|
84
87
|
|
|
85
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
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
The files facts provide information about the filesystem and it's contents on the target host.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import re
|
|
6
8
|
import stat
|
|
7
9
|
from datetime import datetime
|
|
8
|
-
from typing import List, Tuple
|
|
10
|
+
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
|
11
|
+
|
|
12
|
+
from typing_extensions import Literal, NotRequired, TypedDict
|
|
9
13
|
|
|
10
14
|
from pyinfra.api.command import QuoteString, make_formatted_string_command
|
|
11
15
|
from pyinfra.api.facts import FactBase
|
|
@@ -64,7 +68,25 @@ def _parse_mode(mode: str) -> int:
|
|
|
64
68
|
return int(oct(out)[2:])
|
|
65
69
|
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
def _parse_datetime(value: str) -> Optional[datetime]:
|
|
72
|
+
value = try_int(value)
|
|
73
|
+
if isinstance(value, int):
|
|
74
|
+
return datetime.utcfromtimestamp(value)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class FileDict(TypedDict):
|
|
79
|
+
mode: int
|
|
80
|
+
size: Union[int, str]
|
|
81
|
+
atime: Optional[datetime]
|
|
82
|
+
mtime: Optional[datetime]
|
|
83
|
+
ctime: Optional[datetime]
|
|
84
|
+
user: str
|
|
85
|
+
group: str
|
|
86
|
+
link_target: NotRequired[str]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class File(FactBase[Union[FileDict, Literal[False], None]]):
|
|
68
90
|
"""
|
|
69
91
|
Returns information about a file on the remote system:
|
|
70
92
|
|
|
@@ -98,36 +120,23 @@ class File(FactBase):
|
|
|
98
120
|
bsd_stat_command=BSD_STAT_COMMAND,
|
|
99
121
|
)
|
|
100
122
|
|
|
101
|
-
def process(self, output):
|
|
123
|
+
def process(self, output) -> Union[FileDict, Literal[False], None]:
|
|
102
124
|
match = re.match(STAT_REGEX, output[0])
|
|
103
125
|
if not match:
|
|
104
126
|
return None
|
|
105
127
|
|
|
106
|
-
|
|
107
|
-
path_type =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if key == "mode":
|
|
119
|
-
path_type = FLAG_TO_TYPE[value[0]]
|
|
120
|
-
value = _parse_mode(value[1:])
|
|
121
|
-
|
|
122
|
-
elif key == "size":
|
|
123
|
-
value = try_int(value)
|
|
124
|
-
|
|
125
|
-
elif key in ("atime", "mtime", "ctime"):
|
|
126
|
-
value = try_int(value)
|
|
127
|
-
if isinstance(value, int):
|
|
128
|
-
value = datetime.utcfromtimestamp(value)
|
|
129
|
-
|
|
130
|
-
data[key] = value
|
|
128
|
+
mode = match.group(3)
|
|
129
|
+
path_type = FLAG_TO_TYPE[mode[0]]
|
|
130
|
+
|
|
131
|
+
data: FileDict = {
|
|
132
|
+
"user": match.group(1),
|
|
133
|
+
"group": match.group(2),
|
|
134
|
+
"mode": _parse_mode(mode[1:]),
|
|
135
|
+
"atime": _parse_datetime(match.group(4)),
|
|
136
|
+
"mtime": _parse_datetime(match.group(5)),
|
|
137
|
+
"ctime": _parse_datetime(match.group(6)),
|
|
138
|
+
"size": try_int(match.group(7)),
|
|
139
|
+
}
|
|
131
140
|
|
|
132
141
|
if path_type != self.type:
|
|
133
142
|
return False
|
|
@@ -205,7 +214,13 @@ class Socket(File):
|
|
|
205
214
|
type = "socket"
|
|
206
215
|
|
|
207
216
|
|
|
208
|
-
|
|
217
|
+
if TYPE_CHECKING:
|
|
218
|
+
FactBaseOptionalStr = FactBase[Optional[str]]
|
|
219
|
+
else:
|
|
220
|
+
FactBaseOptionalStr = FactBase
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class HashFileFactBase(FactBaseOptionalStr):
|
|
209
224
|
_raw_cmd: str
|
|
210
225
|
_regexes: Tuple[str, str]
|
|
211
226
|
|
|
@@ -229,13 +244,14 @@ class HashFileFactBase(FactBase):
|
|
|
229
244
|
self.path = path
|
|
230
245
|
return make_formatted_string_command(self._raw_cmd, QuoteString(path))
|
|
231
246
|
|
|
232
|
-
def process(self, output):
|
|
247
|
+
def process(self, output) -> Optional[str]:
|
|
233
248
|
output = output[0]
|
|
234
249
|
escaped_path = re.escape(self.path)
|
|
235
250
|
for regex in self._regexes:
|
|
236
251
|
matches = re.match(regex % escaped_path, output)
|
|
237
252
|
if matches:
|
|
238
253
|
return matches.group(1)
|
|
254
|
+
return None
|
|
239
255
|
|
|
240
256
|
|
|
241
257
|
class Sha1File(HashFileFactBase, digits=40, cmds=["sha1sum", "shasum", "sha1"]):
|
|
@@ -294,6 +310,7 @@ class FindInFile(FactBase):
|
|
|
294
310
|
class FindFilesBase(FactBase):
|
|
295
311
|
abstract = True
|
|
296
312
|
default = list
|
|
313
|
+
type_flag: str
|
|
297
314
|
|
|
298
315
|
@staticmethod
|
|
299
316
|
def process(output):
|
|
@@ -345,7 +362,6 @@ class Flags(FactBase):
|
|
|
345
362
|
)
|
|
346
363
|
|
|
347
364
|
def process(self, output):
|
|
348
|
-
|
|
349
365
|
return [flag for flag in output[0].split(",") if len(flag) > 0] if len(output) == 1 else []
|
|
350
366
|
|
|
351
367
|
|
|
@@ -381,7 +397,6 @@ class Block(FactBase):
|
|
|
381
397
|
default = list
|
|
382
398
|
|
|
383
399
|
def command(self, path, marker=None, begin=None, end=None):
|
|
384
|
-
|
|
385
400
|
self.path = path
|
|
386
401
|
start = (marker or MARKER_DEFAULT).format(mark=begin or MARKER_BEGIN_DEFAULT)
|
|
387
402
|
end = (marker or MARKER_DEFAULT).format(mark=end or MARKER_END_DEFAULT)
|
pyinfra/facts/gem.py
CHANGED
pyinfra/facts/git.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
|
|
3
5
|
from pyinfra.api.facts import FactBase
|
|
@@ -29,7 +31,7 @@ class GitConfig(FactBase):
|
|
|
29
31
|
|
|
30
32
|
@staticmethod
|
|
31
33
|
def process(output):
|
|
32
|
-
items = {}
|
|
34
|
+
items: dict[str, list[str]] = {}
|
|
33
35
|
|
|
34
36
|
for line in output:
|
|
35
37
|
key, value = line.split("=", 1)
|
pyinfra/facts/gpg.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from urllib.parse import urlparse
|
|
2
4
|
|
|
3
5
|
from pyinfra.api import FactBase
|
|
@@ -48,7 +50,7 @@ class GpgFactBase(FactBase):
|
|
|
48
50
|
|
|
49
51
|
elif current_subkey or current_key:
|
|
50
52
|
target = current_subkey or current_key
|
|
51
|
-
|
|
53
|
+
assert target is not None
|
|
52
54
|
if bits[0] == "fpr":
|
|
53
55
|
target["fingerprint"] = bits[9] # fingerprint = field 10
|
|
54
56
|
elif bits[0] == "uid":
|
pyinfra/facts/hardware.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, ShortFactBase
|
|
@@ -193,28 +195,36 @@ class NetworkDevices(FactBase):
|
|
|
193
195
|
output = "\n".join(map(str.strip, output))
|
|
194
196
|
|
|
195
197
|
# Splitting the output into sections per network device
|
|
196
|
-
device_sections = re.split(r"\n(?=\d+:
|
|
198
|
+
device_sections = re.split(r"\n(?=\d+: [^\s/:]|[^\s/:]+:.*mtu )", output)
|
|
197
199
|
|
|
198
200
|
# Dictionary to hold all device information
|
|
199
201
|
all_devices = {}
|
|
200
202
|
|
|
201
203
|
for section in device_sections:
|
|
202
204
|
# Extracting the device name
|
|
203
|
-
device_name_match = re.match(r"^(?:\d+: )?([
|
|
205
|
+
device_name_match = re.match(r"^(?:\d+: )?([^\s/:]+):", section)
|
|
204
206
|
if not device_name_match:
|
|
205
207
|
continue
|
|
206
208
|
device_name = device_name_match.group(1)
|
|
207
209
|
|
|
208
210
|
# Regular expressions to match different parts of the output
|
|
209
|
-
ether_re = re.compile(r"([0-9A-Fa-f:]{17})")
|
|
211
|
+
ether_re = re.compile(r"ether ([0-9A-Fa-f:]{17})")
|
|
210
212
|
mtu_re = re.compile(r"mtu (\d+)")
|
|
211
213
|
ipv4_re = (
|
|
214
|
+
# ip a
|
|
212
215
|
re.compile(
|
|
213
|
-
r"inet (
|
|
214
|
-
),
|
|
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
|
|
215
219
|
re.compile(
|
|
216
|
-
r"inet (
|
|
217
|
-
),
|
|
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+)"),
|
|
218
228
|
)
|
|
219
229
|
|
|
220
230
|
# Parsing the output
|
|
@@ -233,18 +243,22 @@ class NetworkDevices(FactBase):
|
|
|
233
243
|
)
|
|
234
244
|
|
|
235
245
|
# IPv4 Addresses
|
|
246
|
+
ipv4_matches: list[re.Match[str]]
|
|
236
247
|
for ipv4_re_ in ipv4_re:
|
|
237
|
-
ipv4_matches = ipv4_re_.
|
|
238
|
-
if ipv4_matches:
|
|
248
|
+
ipv4_matches = list(ipv4_re_.finditer(section))
|
|
249
|
+
if len(ipv4_matches):
|
|
239
250
|
break
|
|
240
251
|
|
|
241
|
-
if ipv4_matches:
|
|
252
|
+
if len(ipv4_matches):
|
|
242
253
|
ipv4_info = []
|
|
243
254
|
for ipv4 in ipv4_matches:
|
|
244
|
-
address = ipv4
|
|
245
|
-
mask_value = ipv4
|
|
255
|
+
address = ipv4.group("address")
|
|
256
|
+
mask_value = ipv4.group("mask")
|
|
246
257
|
mask_bits, netmask = mask(mask_value)
|
|
247
|
-
|
|
258
|
+
try:
|
|
259
|
+
broadcast = ipv4.group("broadcast")
|
|
260
|
+
except IndexError:
|
|
261
|
+
broadcast = None
|
|
248
262
|
|
|
249
263
|
ipv4_info.append(
|
|
250
264
|
{
|
|
@@ -256,28 +270,24 @@ class NetworkDevices(FactBase):
|
|
|
256
270
|
)
|
|
257
271
|
device_info["ipv4"] = ipv4_info[0]
|
|
258
272
|
if len(ipv4_matches) > 1:
|
|
259
|
-
device_info["ipv4"]["additional_ips"] = ipv4_info[1:]
|
|
273
|
+
device_info["ipv4"]["additional_ips"] = ipv4_info[1:] # type: ignore[index]
|
|
260
274
|
|
|
261
275
|
# IPv6 Addresses
|
|
262
|
-
|
|
263
|
-
re.compile(r"inet6\s+([0-9a-fA-F:]+)/(\d+)"),
|
|
264
|
-
re.compile(r"inet6\s+([0-9a-fA-F:]+)\s+prefixlen\s+(\d+)"),
|
|
265
|
-
)
|
|
266
|
-
|
|
276
|
+
ipv6_matches: list[re.Match[str]]
|
|
267
277
|
for ipv6_re_ in ipv6_re:
|
|
268
|
-
ipv6_matches = ipv6_re_.
|
|
278
|
+
ipv6_matches = list(ipv6_re_.finditer(section))
|
|
269
279
|
if ipv6_matches:
|
|
270
280
|
break
|
|
271
281
|
|
|
272
|
-
if ipv6_matches:
|
|
282
|
+
if len(ipv6_matches):
|
|
273
283
|
ipv6_info = []
|
|
274
284
|
for ipv6 in ipv6_matches:
|
|
275
|
-
address = ipv6
|
|
276
|
-
mask_bits = ipv6
|
|
285
|
+
address = ipv6.group("address")
|
|
286
|
+
mask_bits = ipv6.group("mask")
|
|
277
287
|
ipv6_info.append({"address": address, "mask_bits": int(mask_bits)})
|
|
278
288
|
device_info["ipv6"] = ipv6_info[0]
|
|
279
289
|
if len(ipv6_matches) > 1:
|
|
280
|
-
device_info["ipv6"]["additional_ips"] = ipv6_info[1:]
|
|
290
|
+
device_info["ipv6"]["additional_ips"] = ipv6_info[1:] # type: ignore[index]
|
|
281
291
|
|
|
282
292
|
all_devices[device_name] = device_info
|
|
283
293
|
|
pyinfra/facts/iptables.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pyinfra.api import FactBase
|
|
2
4
|
|
|
3
5
|
# Mapping for iptables code arguments to variable names
|
|
@@ -27,16 +29,16 @@ def parse_iptables_rule(line):
|
|
|
27
29
|
|
|
28
30
|
bits = line.split()
|
|
29
31
|
|
|
30
|
-
definition = {}
|
|
32
|
+
definition: dict = {}
|
|
31
33
|
|
|
32
34
|
key = None
|
|
33
|
-
args = []
|
|
35
|
+
args: list[str] = []
|
|
34
36
|
not_arg = False
|
|
35
37
|
|
|
36
38
|
def add_args():
|
|
37
39
|
arg_string = " ".join(args)
|
|
38
40
|
|
|
39
|
-
if key in IPTABLES_ARGS:
|
|
41
|
+
if key and key in IPTABLES_ARGS:
|
|
40
42
|
definition_key = "not_{0}".format(IPTABLES_ARGS[key]) if not_arg else IPTABLES_ARGS[key]
|
|
41
43
|
definition[definition_key] = arg_string
|
|
42
44
|
else:
|
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",
|
|
@@ -53,6 +59,7 @@ def make_execute_mysql_command(command, ignore_errors=False, **mysql_kwargs):
|
|
|
53
59
|
class MysqlFactBase(FactBase):
|
|
54
60
|
abstract = True
|
|
55
61
|
|
|
62
|
+
mysql_command: str
|
|
56
63
|
requires_command = "mysql"
|
|
57
64
|
ignore_errors = False
|
|
58
65
|
|
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
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pyinfra.api import FactBase, MaskString, QuoteString, StringCommand
|
|
4
|
+
from pyinfra.api.util import try_int
|
|
5
|
+
|
|
6
|
+
from .util.databases import parse_columns_and_rows
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def make_psql_command(
|
|
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
|
+
executable="psql",
|
|
16
|
+
) -> StringCommand:
|
|
17
|
+
target_bits: list[str] = []
|
|
18
|
+
|
|
19
|
+
if password:
|
|
20
|
+
target_bits.append(MaskString('PGPASSWORD="{0}"'.format(password)))
|
|
21
|
+
|
|
22
|
+
target_bits.append(executable)
|
|
23
|
+
|
|
24
|
+
if database:
|
|
25
|
+
target_bits.append("-d {0}".format(database))
|
|
26
|
+
|
|
27
|
+
if user:
|
|
28
|
+
target_bits.append("-U {0}".format(user))
|
|
29
|
+
|
|
30
|
+
if host:
|
|
31
|
+
target_bits.append("-h {0}".format(host))
|
|
32
|
+
|
|
33
|
+
if port:
|
|
34
|
+
target_bits.append("-p {0}".format(port))
|
|
35
|
+
|
|
36
|
+
return StringCommand(*target_bits)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def make_execute_psql_command(command, **psql_kwargs):
|
|
40
|
+
return StringCommand(
|
|
41
|
+
make_psql_command(**psql_kwargs),
|
|
42
|
+
"-Ac",
|
|
43
|
+
QuoteString(command), # quote this whole item as a single shell argument
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PostgresFactBase(FactBase):
|
|
48
|
+
abstract = True
|
|
49
|
+
|
|
50
|
+
psql_command: str
|
|
51
|
+
requires_command = "psql"
|
|
52
|
+
|
|
53
|
+
def command(
|
|
54
|
+
self,
|
|
55
|
+
psql_user=None,
|
|
56
|
+
psql_password=None,
|
|
57
|
+
psql_host=None,
|
|
58
|
+
psql_port=None,
|
|
59
|
+
):
|
|
60
|
+
return make_execute_psql_command(
|
|
61
|
+
self.psql_command,
|
|
62
|
+
user=psql_user,
|
|
63
|
+
password=psql_password,
|
|
64
|
+
host=psql_host,
|
|
65
|
+
port=psql_port,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class PostgresRoles(PostgresFactBase):
|
|
70
|
+
"""
|
|
71
|
+
Returns a dict of PostgreSQL roles and data:
|
|
72
|
+
|
|
73
|
+
.. code:: python
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
"pyinfra": {
|
|
77
|
+
"super": true,
|
|
78
|
+
"createrole": false,
|
|
79
|
+
"createdb": false,
|
|
80
|
+
...
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
default = dict
|
|
86
|
+
psql_command = "SELECT * FROM pg_catalog.pg_roles"
|
|
87
|
+
|
|
88
|
+
def process(self, output):
|
|
89
|
+
# Remove the last line of the output (row count)
|
|
90
|
+
output = output[:-1]
|
|
91
|
+
rows = parse_columns_and_rows(
|
|
92
|
+
output,
|
|
93
|
+
"|",
|
|
94
|
+
# Remove the "rol" prefix on column names
|
|
95
|
+
remove_column_prefix="rol",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
users = {}
|
|
99
|
+
|
|
100
|
+
for details in rows:
|
|
101
|
+
for key, value in list(details.items()):
|
|
102
|
+
if key in ("oid", "connlimit"):
|
|
103
|
+
details[key] = try_int(value)
|
|
104
|
+
|
|
105
|
+
if key in (
|
|
106
|
+
"super",
|
|
107
|
+
"inherit",
|
|
108
|
+
"createrole",
|
|
109
|
+
"createdb",
|
|
110
|
+
"canlogin",
|
|
111
|
+
"replication",
|
|
112
|
+
"bypassrls",
|
|
113
|
+
):
|
|
114
|
+
details[key] = value == "t"
|
|
115
|
+
|
|
116
|
+
users[details.pop("name")] = details
|
|
117
|
+
|
|
118
|
+
return users
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class PostgresDatabases(PostgresFactBase):
|
|
122
|
+
"""
|
|
123
|
+
Returns a dict of PostgreSQL databases and metadata:
|
|
124
|
+
|
|
125
|
+
.. code:: python
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
"pyinfra_stuff": {
|
|
129
|
+
"encoding": "UTF8",
|
|
130
|
+
"collate": "en_US.UTF-8",
|
|
131
|
+
"ctype": "en_US.UTF-8",
|
|
132
|
+
...
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
default = dict
|
|
138
|
+
psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), * FROM pg_catalog.pg_database"
|
|
139
|
+
|
|
140
|
+
def process(self, output):
|
|
141
|
+
# Remove the last line of the output (row count)
|
|
142
|
+
output = output[:-1]
|
|
143
|
+
rows = parse_columns_and_rows(
|
|
144
|
+
output,
|
|
145
|
+
"|",
|
|
146
|
+
# Remove the "dat" prefix on column names
|
|
147
|
+
remove_column_prefix="dat",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
databases = {}
|
|
151
|
+
|
|
152
|
+
for details in rows:
|
|
153
|
+
details["encoding"] = details.pop("pg_encoding_to_char")
|
|
154
|
+
|
|
155
|
+
for key, value in list(details.items()):
|
|
156
|
+
if key.endswith("id") or key in (
|
|
157
|
+
"dba",
|
|
158
|
+
"tablespace",
|
|
159
|
+
"connlimit",
|
|
160
|
+
):
|
|
161
|
+
details[key] = try_int(value)
|
|
162
|
+
|
|
163
|
+
if key in ("istemplate", "allowconn"):
|
|
164
|
+
details[key] = value == "t"
|
|
165
|
+
|
|
166
|
+
databases[details.pop("name")] = details
|
|
167
|
+
|
|
168
|
+
return databases
|