pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__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 +261 -255
- pyinfra/api/arguments_typed.py +77 -0
- pyinfra/api/command.py +66 -53
- pyinfra/api/config.py +27 -22
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +2 -24
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +77 -113
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +17 -25
- pyinfra/api/operation.py +232 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +55 -70
- pyinfra/connectors/base.py +150 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +227 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +195 -207
- pyinfra/connectors/ssh.py +528 -615
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +212 -137
- pyinfra/connectors/vagrant.py +55 -48
- pyinfra/context.py +3 -2
- pyinfra/facts/docker.py +1 -0
- pyinfra/facts/files.py +45 -32
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +1 -1
- pyinfra/facts/hardware.py +4 -2
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/mysql.py +1 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +5 -161
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +77 -30
- pyinfra/facts/systemd.py +29 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/util/packaging.py +4 -2
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +3 -3
- pyinfra/operations/apt.py +25 -47
- pyinfra/operations/brew.py +7 -14
- pyinfra/operations/bsdinit.py +4 -4
- pyinfra/operations/cargo.py +1 -1
- pyinfra/operations/choco.py +1 -1
- pyinfra/operations/dnf.py +4 -4
- pyinfra/operations/files.py +108 -321
- pyinfra/operations/gem.py +1 -1
- pyinfra/operations/git.py +6 -37
- pyinfra/operations/iptables.py +2 -10
- pyinfra/operations/launchd.py +1 -1
- pyinfra/operations/lxd.py +1 -9
- pyinfra/operations/mysql.py +5 -28
- pyinfra/operations/npm.py +1 -1
- pyinfra/operations/openrc.py +1 -1
- pyinfra/operations/pacman.py +3 -3
- pyinfra/operations/pip.py +14 -15
- pyinfra/operations/pkg.py +1 -1
- pyinfra/operations/pkgin.py +3 -3
- pyinfra/operations/postgres.py +347 -0
- pyinfra/operations/postgresql.py +17 -380
- pyinfra/operations/python.py +2 -17
- pyinfra/operations/selinux.py +5 -28
- pyinfra/operations/server.py +59 -84
- pyinfra/operations/snap.py +1 -3
- pyinfra/operations/ssh.py +8 -23
- pyinfra/operations/systemd.py +7 -7
- pyinfra/operations/sysvinit.py +3 -12
- pyinfra/operations/upstart.py +4 -4
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/files.py +2 -2
- pyinfra/operations/util/packaging.py +6 -24
- pyinfra/operations/util/service.py +18 -37
- pyinfra/operations/vzctl.py +2 -2
- pyinfra/operations/xbps.py +3 -3
- pyinfra/operations/yum.py +4 -4
- pyinfra/operations/zypper.py +4 -4
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
- pyinfra-3.0b1.dist-info/RECORD +163 -0
- pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +2 -0
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +83 -42
- pyinfra_cli/inventory.py +19 -4
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +93 -129
- pyinfra_cli/util.py +60 -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 +100 -200
- 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 +66 -107
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +4 -4
- 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.2.dist-info/RECORD +0 -170
- pyinfra-2.9.2.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.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
pyinfra/facts/files.py
CHANGED
|
@@ -5,7 +5,9 @@ The files facts provide information about the filesystem and it's contents on th
|
|
|
5
5
|
import re
|
|
6
6
|
import stat
|
|
7
7
|
from datetime import datetime
|
|
8
|
-
from typing import List, Tuple
|
|
8
|
+
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
|
9
|
+
|
|
10
|
+
from typing_extensions import Literal, NotRequired, TypedDict
|
|
9
11
|
|
|
10
12
|
from pyinfra.api.command import QuoteString, make_formatted_string_command
|
|
11
13
|
from pyinfra.api.facts import FactBase
|
|
@@ -64,7 +66,25 @@ def _parse_mode(mode: str) -> int:
|
|
|
64
66
|
return int(oct(out)[2:])
|
|
65
67
|
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
def _parse_datetime(value: str) -> Optional[datetime]:
|
|
70
|
+
value = try_int(value)
|
|
71
|
+
if isinstance(value, int):
|
|
72
|
+
return datetime.utcfromtimestamp(value)
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class FileDict(TypedDict):
|
|
77
|
+
mode: int
|
|
78
|
+
size: Union[int, str]
|
|
79
|
+
atime: Optional[datetime]
|
|
80
|
+
mtime: Optional[datetime]
|
|
81
|
+
ctime: Optional[datetime]
|
|
82
|
+
user: str
|
|
83
|
+
group: str
|
|
84
|
+
link_target: NotRequired[str]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class File(FactBase[Union[FileDict, Literal[False], None]]):
|
|
68
88
|
"""
|
|
69
89
|
Returns information about a file on the remote system:
|
|
70
90
|
|
|
@@ -98,36 +118,23 @@ class File(FactBase):
|
|
|
98
118
|
bsd_stat_command=BSD_STAT_COMMAND,
|
|
99
119
|
)
|
|
100
120
|
|
|
101
|
-
def process(self, output):
|
|
121
|
+
def process(self, output) -> Union[FileDict, Literal[False], None]:
|
|
102
122
|
match = re.match(STAT_REGEX, output[0])
|
|
103
123
|
if not match:
|
|
104
124
|
return None
|
|
105
125
|
|
|
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
|
|
126
|
+
mode = match.group(3)
|
|
127
|
+
path_type = FLAG_TO_TYPE[mode[0]]
|
|
128
|
+
|
|
129
|
+
data: FileDict = {
|
|
130
|
+
"user": match.group(1),
|
|
131
|
+
"group": match.group(2),
|
|
132
|
+
"mode": _parse_mode(mode[1:]),
|
|
133
|
+
"atime": _parse_datetime(match.group(4)),
|
|
134
|
+
"mtime": _parse_datetime(match.group(5)),
|
|
135
|
+
"ctime": _parse_datetime(match.group(6)),
|
|
136
|
+
"size": try_int(match.group(7)),
|
|
137
|
+
}
|
|
131
138
|
|
|
132
139
|
if path_type != self.type:
|
|
133
140
|
return False
|
|
@@ -205,7 +212,13 @@ class Socket(File):
|
|
|
205
212
|
type = "socket"
|
|
206
213
|
|
|
207
214
|
|
|
208
|
-
|
|
215
|
+
if TYPE_CHECKING:
|
|
216
|
+
FactBaseOptionalStr = FactBase[Optional[str]]
|
|
217
|
+
else:
|
|
218
|
+
FactBaseOptionalStr = FactBase
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class HashFileFactBase(FactBaseOptionalStr):
|
|
209
222
|
_raw_cmd: str
|
|
210
223
|
_regexes: Tuple[str, str]
|
|
211
224
|
|
|
@@ -229,13 +242,14 @@ class HashFileFactBase(FactBase):
|
|
|
229
242
|
self.path = path
|
|
230
243
|
return make_formatted_string_command(self._raw_cmd, QuoteString(path))
|
|
231
244
|
|
|
232
|
-
def process(self, output):
|
|
245
|
+
def process(self, output) -> Optional[str]:
|
|
233
246
|
output = output[0]
|
|
234
247
|
escaped_path = re.escape(self.path)
|
|
235
248
|
for regex in self._regexes:
|
|
236
249
|
matches = re.match(regex % escaped_path, output)
|
|
237
250
|
if matches:
|
|
238
251
|
return matches.group(1)
|
|
252
|
+
return None
|
|
239
253
|
|
|
240
254
|
|
|
241
255
|
class Sha1File(HashFileFactBase, digits=40, cmds=["sha1sum", "shasum", "sha1"]):
|
|
@@ -294,6 +308,7 @@ class FindInFile(FactBase):
|
|
|
294
308
|
class FindFilesBase(FactBase):
|
|
295
309
|
abstract = True
|
|
296
310
|
default = list
|
|
311
|
+
type_flag: str
|
|
297
312
|
|
|
298
313
|
@staticmethod
|
|
299
314
|
def process(output):
|
|
@@ -345,7 +360,6 @@ class Flags(FactBase):
|
|
|
345
360
|
)
|
|
346
361
|
|
|
347
362
|
def process(self, output):
|
|
348
|
-
|
|
349
363
|
return [flag for flag in output[0].split(",") if len(flag) > 0] if len(output) == 1 else []
|
|
350
364
|
|
|
351
365
|
|
|
@@ -381,7 +395,6 @@ class Block(FactBase):
|
|
|
381
395
|
default = list
|
|
382
396
|
|
|
383
397
|
def command(self, path, marker=None, begin=None, end=None):
|
|
384
|
-
|
|
385
398
|
self.path = path
|
|
386
399
|
start = (marker or MARKER_DEFAULT).format(mark=begin or MARKER_BEGIN_DEFAULT)
|
|
387
400
|
end = (marker or MARKER_DEFAULT).format(mark=end or MARKER_END_DEFAULT)
|
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
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
|
|
@@ -256,7 +258,7 @@ class NetworkDevices(FactBase):
|
|
|
256
258
|
)
|
|
257
259
|
device_info["ipv4"] = ipv4_info[0]
|
|
258
260
|
if len(ipv4_matches) > 1:
|
|
259
|
-
device_info["ipv4"]["additional_ips"] = ipv4_info[1:]
|
|
261
|
+
device_info["ipv4"]["additional_ips"] = ipv4_info[1:] # type: ignore[index]
|
|
260
262
|
|
|
261
263
|
# IPv6 Addresses
|
|
262
264
|
ipv6_re = (
|
|
@@ -277,7 +279,7 @@ class NetworkDevices(FactBase):
|
|
|
277
279
|
ipv6_info.append({"address": address, "mask_bits": int(mask_bits)})
|
|
278
280
|
device_info["ipv6"] = ipv6_info[0]
|
|
279
281
|
if len(ipv6_matches) > 1:
|
|
280
|
-
device_info["ipv6"]["additional_ips"] = ipv6_info[1:]
|
|
282
|
+
device_info["ipv6"]["additional_ips"] = ipv6_info[1:] # type: ignore[index]
|
|
281
283
|
|
|
282
284
|
all_devices[device_name] = device_info
|
|
283
285
|
|
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/mysql.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=None,
|
|
11
|
+
user=None,
|
|
12
|
+
password=None,
|
|
13
|
+
host=None,
|
|
14
|
+
port=None,
|
|
15
|
+
executable="psql",
|
|
16
|
+
):
|
|
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
|
pyinfra/facts/postgresql.py
CHANGED
|
@@ -1,165 +1,9 @@
|
|
|
1
|
-
from
|
|
2
|
-
from pyinfra.api.util import try_int
|
|
1
|
+
from .postgres import PostgresDatabases, PostgresRoles
|
|
3
2
|
|
|
4
|
-
from .util.databases import parse_columns_and_rows
|
|
5
3
|
|
|
4
|
+
class PostgresqlRoles(PostgresRoles):
|
|
5
|
+
deprecated = True
|
|
6
6
|
|
|
7
|
-
def make_psql_command(
|
|
8
|
-
database=None,
|
|
9
|
-
user=None,
|
|
10
|
-
password=None,
|
|
11
|
-
host=None,
|
|
12
|
-
port=None,
|
|
13
|
-
executable="psql",
|
|
14
|
-
):
|
|
15
|
-
target_bits = []
|
|
16
7
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
target_bits.append(executable)
|
|
21
|
-
|
|
22
|
-
if database:
|
|
23
|
-
target_bits.append("-d {0}".format(database))
|
|
24
|
-
|
|
25
|
-
if user:
|
|
26
|
-
target_bits.append("-U {0}".format(user))
|
|
27
|
-
|
|
28
|
-
if host:
|
|
29
|
-
target_bits.append("-h {0}".format(host))
|
|
30
|
-
|
|
31
|
-
if port:
|
|
32
|
-
target_bits.append("-p {0}".format(port))
|
|
33
|
-
|
|
34
|
-
return StringCommand(*target_bits)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def make_execute_psql_command(command, **psql_kwargs):
|
|
38
|
-
return StringCommand(
|
|
39
|
-
make_psql_command(**psql_kwargs),
|
|
40
|
-
"-Ac",
|
|
41
|
-
QuoteString(command), # quote this whole item as a single shell argument
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class PostgresqlFactBase(FactBase):
|
|
46
|
-
abstract = True
|
|
47
|
-
|
|
48
|
-
requires_command = "psql"
|
|
49
|
-
|
|
50
|
-
def command(
|
|
51
|
-
self,
|
|
52
|
-
psql_user=None,
|
|
53
|
-
psql_password=None,
|
|
54
|
-
psql_host=None,
|
|
55
|
-
psql_port=None,
|
|
56
|
-
):
|
|
57
|
-
return make_execute_psql_command(
|
|
58
|
-
self.psql_command,
|
|
59
|
-
user=psql_user,
|
|
60
|
-
password=psql_password,
|
|
61
|
-
host=psql_host,
|
|
62
|
-
port=psql_port,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class PostgresqlRoles(PostgresqlFactBase):
|
|
67
|
-
"""
|
|
68
|
-
Returns a dict of PostgreSQL roles and data:
|
|
69
|
-
|
|
70
|
-
.. code:: python
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
"pyinfra": {
|
|
74
|
-
"super": true,
|
|
75
|
-
"createrole": false,
|
|
76
|
-
"createdb": false,
|
|
77
|
-
...
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
default = dict
|
|
83
|
-
psql_command = "SELECT * FROM pg_catalog.pg_roles"
|
|
84
|
-
|
|
85
|
-
def process(self, output):
|
|
86
|
-
# Remove the last line of the output (row count)
|
|
87
|
-
output = output[:-1]
|
|
88
|
-
rows = parse_columns_and_rows(
|
|
89
|
-
output,
|
|
90
|
-
"|",
|
|
91
|
-
# Remove the "rol" prefix on column names
|
|
92
|
-
remove_column_prefix="rol",
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
users = {}
|
|
96
|
-
|
|
97
|
-
for details in rows:
|
|
98
|
-
for key, value in list(details.items()):
|
|
99
|
-
if key in ("oid", "connlimit"):
|
|
100
|
-
details[key] = try_int(value)
|
|
101
|
-
|
|
102
|
-
if key in (
|
|
103
|
-
"super",
|
|
104
|
-
"inherit",
|
|
105
|
-
"createrole",
|
|
106
|
-
"createdb",
|
|
107
|
-
"canlogin",
|
|
108
|
-
"replication",
|
|
109
|
-
"bypassrls",
|
|
110
|
-
):
|
|
111
|
-
details[key] = value == "t"
|
|
112
|
-
|
|
113
|
-
users[details.pop("name")] = details
|
|
114
|
-
|
|
115
|
-
return users
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
class PostgresqlDatabases(PostgresqlFactBase):
|
|
119
|
-
"""
|
|
120
|
-
Returns a dict of PostgreSQL databases and metadata:
|
|
121
|
-
|
|
122
|
-
.. code:: python
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
"pyinfra_stuff": {
|
|
126
|
-
"encoding": "UTF8",
|
|
127
|
-
"collate": "en_US.UTF-8",
|
|
128
|
-
"ctype": "en_US.UTF-8",
|
|
129
|
-
...
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
default = dict
|
|
135
|
-
psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), * FROM pg_catalog.pg_database"
|
|
136
|
-
|
|
137
|
-
def process(self, output):
|
|
138
|
-
# Remove the last line of the output (row count)
|
|
139
|
-
output = output[:-1]
|
|
140
|
-
rows = parse_columns_and_rows(
|
|
141
|
-
output,
|
|
142
|
-
"|",
|
|
143
|
-
# Remove the "dat" prefix on column names
|
|
144
|
-
remove_column_prefix="dat",
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
databases = {}
|
|
148
|
-
|
|
149
|
-
for details in rows:
|
|
150
|
-
details["encoding"] = details.pop("pg_encoding_to_char")
|
|
151
|
-
|
|
152
|
-
for key, value in list(details.items()):
|
|
153
|
-
if key.endswith("id") or key in (
|
|
154
|
-
"dba",
|
|
155
|
-
"tablespace",
|
|
156
|
-
"connlimit",
|
|
157
|
-
):
|
|
158
|
-
details[key] = try_int(value)
|
|
159
|
-
|
|
160
|
-
if key in ("istemplate", "allowconn"):
|
|
161
|
-
details[key] = value == "t"
|
|
162
|
-
|
|
163
|
-
databases[details.pop("name")] = details
|
|
164
|
-
|
|
165
|
-
return databases
|
|
8
|
+
class PostgresqlDatabases(PostgresDatabases):
|
|
9
|
+
deprecated = True
|
pyinfra/facts/selinux.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
from collections import defaultdict
|
|
3
5
|
|
|
@@ -94,7 +96,7 @@ class SEPorts(FactBase):
|
|
|
94
96
|
return "semanage port -ln"
|
|
95
97
|
|
|
96
98
|
def process(self, output):
|
|
97
|
-
labels = defaultdict(dict)
|
|
99
|
+
labels: dict[str, dict] = defaultdict(dict)
|
|
98
100
|
for line in output:
|
|
99
101
|
m = SEPorts._regex.match(line)
|
|
100
102
|
if m is None: # something went wrong
|