pyinfra 0.11.dev3__py3-none-any.whl → 3.6__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/__init__.py +9 -12
- pyinfra/__main__.py +4 -0
- pyinfra/api/__init__.py +19 -3
- pyinfra/api/arguments.py +413 -0
- pyinfra/api/arguments_typed.py +79 -0
- pyinfra/api/command.py +274 -0
- pyinfra/api/config.py +222 -28
- pyinfra/api/connect.py +33 -13
- pyinfra/api/connectors.py +27 -0
- pyinfra/api/deploy.py +65 -66
- pyinfra/api/exceptions.py +73 -18
- pyinfra/api/facts.py +267 -200
- pyinfra/api/host.py +416 -50
- pyinfra/api/inventory.py +121 -160
- pyinfra/api/metadata.py +69 -0
- pyinfra/api/operation.py +432 -262
- pyinfra/api/operations.py +273 -260
- pyinfra/api/state.py +302 -248
- pyinfra/api/util.py +309 -369
- pyinfra/connectors/base.py +173 -0
- pyinfra/connectors/chroot.py +212 -0
- pyinfra/connectors/docker.py +405 -0
- pyinfra/connectors/dockerssh.py +297 -0
- pyinfra/connectors/local.py +238 -0
- pyinfra/connectors/scp/__init__.py +1 -0
- pyinfra/connectors/scp/client.py +204 -0
- pyinfra/connectors/ssh.py +727 -0
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +309 -0
- pyinfra/connectors/sshuserclient/config.py +102 -0
- pyinfra/connectors/terraform.py +135 -0
- pyinfra/connectors/util.py +417 -0
- pyinfra/connectors/vagrant.py +183 -0
- pyinfra/context.py +145 -0
- pyinfra/facts/__init__.py +7 -6
- pyinfra/facts/apk.py +22 -7
- pyinfra/facts/apt.py +117 -60
- pyinfra/facts/brew.py +100 -15
- pyinfra/facts/bsdinit.py +23 -0
- pyinfra/facts/cargo.py +37 -0
- pyinfra/facts/choco.py +47 -0
- pyinfra/facts/crontab.py +195 -0
- pyinfra/facts/deb.py +94 -0
- pyinfra/facts/dnf.py +48 -0
- pyinfra/facts/docker.py +96 -23
- pyinfra/facts/efibootmgr.py +113 -0
- pyinfra/facts/files.py +629 -58
- pyinfra/facts/flatpak.py +77 -0
- pyinfra/facts/freebsd.py +70 -0
- pyinfra/facts/gem.py +19 -6
- pyinfra/facts/git.py +59 -14
- pyinfra/facts/gpg.py +150 -0
- pyinfra/facts/hardware.py +313 -167
- pyinfra/facts/iptables.py +72 -62
- pyinfra/facts/launchd.py +44 -0
- pyinfra/facts/lxd.py +17 -4
- pyinfra/facts/mysql.py +122 -86
- pyinfra/facts/npm.py +17 -9
- pyinfra/facts/openrc.py +71 -0
- pyinfra/facts/opkg.py +246 -0
- pyinfra/facts/pacman.py +50 -7
- pyinfra/facts/pip.py +24 -7
- pyinfra/facts/pipx.py +82 -0
- pyinfra/facts/pkg.py +15 -6
- pyinfra/facts/pkgin.py +35 -0
- pyinfra/facts/podman.py +54 -0
- pyinfra/facts/postgres.py +178 -0
- pyinfra/facts/postgresql.py +6 -147
- pyinfra/facts/rpm.py +105 -0
- pyinfra/facts/runit.py +77 -0
- pyinfra/facts/selinux.py +161 -0
- pyinfra/facts/server.py +762 -285
- pyinfra/facts/snap.py +88 -0
- pyinfra/facts/systemd.py +139 -0
- pyinfra/facts/sysvinit.py +59 -0
- pyinfra/facts/upstart.py +35 -0
- pyinfra/facts/util/__init__.py +17 -0
- pyinfra/facts/util/databases.py +4 -6
- pyinfra/facts/util/packaging.py +37 -6
- pyinfra/facts/util/units.py +30 -0
- pyinfra/facts/util/win_files.py +99 -0
- pyinfra/facts/vzctl.py +20 -13
- pyinfra/facts/xbps.py +35 -0
- pyinfra/facts/yum.py +34 -40
- pyinfra/facts/zfs.py +77 -0
- pyinfra/facts/zypper.py +42 -0
- pyinfra/local.py +45 -83
- pyinfra/operations/__init__.py +12 -0
- pyinfra/operations/apk.py +99 -0
- pyinfra/operations/apt.py +496 -0
- pyinfra/operations/brew.py +232 -0
- pyinfra/operations/bsdinit.py +59 -0
- pyinfra/operations/cargo.py +45 -0
- pyinfra/operations/choco.py +61 -0
- pyinfra/operations/crontab.py +194 -0
- pyinfra/operations/dnf.py +213 -0
- pyinfra/operations/docker.py +492 -0
- pyinfra/operations/files.py +2014 -0
- pyinfra/operations/flatpak.py +95 -0
- pyinfra/operations/freebsd/__init__.py +12 -0
- pyinfra/operations/freebsd/freebsd_update.py +70 -0
- pyinfra/operations/freebsd/pkg.py +219 -0
- pyinfra/operations/freebsd/service.py +116 -0
- pyinfra/operations/freebsd/sysrc.py +92 -0
- pyinfra/operations/gem.py +48 -0
- pyinfra/operations/git.py +420 -0
- pyinfra/operations/iptables.py +312 -0
- pyinfra/operations/launchd.py +45 -0
- pyinfra/operations/lxd.py +69 -0
- pyinfra/operations/mysql.py +610 -0
- pyinfra/operations/npm.py +57 -0
- pyinfra/operations/openrc.py +63 -0
- pyinfra/operations/opkg.py +89 -0
- pyinfra/operations/pacman.py +82 -0
- pyinfra/operations/pip.py +206 -0
- pyinfra/operations/pipx.py +103 -0
- pyinfra/operations/pkg.py +71 -0
- pyinfra/operations/pkgin.py +92 -0
- pyinfra/operations/postgres.py +437 -0
- pyinfra/operations/postgresql.py +30 -0
- pyinfra/operations/puppet.py +41 -0
- pyinfra/operations/python.py +73 -0
- pyinfra/operations/runit.py +184 -0
- pyinfra/operations/selinux.py +190 -0
- pyinfra/operations/server.py +1100 -0
- pyinfra/operations/snap.py +118 -0
- pyinfra/operations/ssh.py +217 -0
- pyinfra/operations/systemd.py +150 -0
- pyinfra/operations/sysvinit.py +142 -0
- pyinfra/operations/upstart.py +68 -0
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +407 -0
- pyinfra/operations/util/files.py +247 -0
- pyinfra/operations/util/packaging.py +338 -0
- pyinfra/operations/util/service.py +46 -0
- pyinfra/operations/vzctl.py +137 -0
- pyinfra/operations/xbps.py +78 -0
- pyinfra/operations/yum.py +213 -0
- pyinfra/operations/zfs.py +176 -0
- pyinfra/operations/zypper.py +193 -0
- pyinfra/progress.py +44 -32
- pyinfra/py.typed +0 -0
- pyinfra/version.py +9 -1
- pyinfra-3.6.dist-info/METADATA +142 -0
- pyinfra-3.6.dist-info/RECORD +160 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
- pyinfra-3.6.dist-info/entry_points.txt +12 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
- pyinfra_cli/__init__.py +1 -0
- pyinfra_cli/cli.py +793 -0
- pyinfra_cli/commands.py +66 -0
- pyinfra_cli/exceptions.py +155 -65
- pyinfra_cli/inventory.py +233 -89
- pyinfra_cli/log.py +39 -43
- pyinfra_cli/main.py +26 -495
- pyinfra_cli/prints.py +215 -156
- pyinfra_cli/util.py +172 -105
- pyinfra_cli/virtualenv.py +25 -20
- pyinfra/api/connectors/__init__.py +0 -21
- pyinfra/api/connectors/ansible.py +0 -99
- pyinfra/api/connectors/docker.py +0 -178
- pyinfra/api/connectors/local.py +0 -169
- pyinfra/api/connectors/ssh.py +0 -402
- pyinfra/api/connectors/sshuserclient/client.py +0 -105
- pyinfra/api/connectors/sshuserclient/config.py +0 -90
- pyinfra/api/connectors/util.py +0 -63
- pyinfra/api/connectors/vagrant.py +0 -155
- pyinfra/facts/init.py +0 -176
- pyinfra/facts/util/files.py +0 -102
- pyinfra/hook.py +0 -41
- pyinfra/modules/__init__.py +0 -11
- pyinfra/modules/apk.py +0 -64
- pyinfra/modules/apt.py +0 -272
- pyinfra/modules/brew.py +0 -122
- pyinfra/modules/files.py +0 -711
- pyinfra/modules/gem.py +0 -30
- pyinfra/modules/git.py +0 -115
- pyinfra/modules/init.py +0 -344
- pyinfra/modules/iptables.py +0 -271
- pyinfra/modules/lxd.py +0 -45
- pyinfra/modules/mysql.py +0 -347
- pyinfra/modules/npm.py +0 -47
- pyinfra/modules/pacman.py +0 -60
- pyinfra/modules/pip.py +0 -99
- pyinfra/modules/pkg.py +0 -43
- pyinfra/modules/postgresql.py +0 -245
- pyinfra/modules/puppet.py +0 -20
- pyinfra/modules/python.py +0 -37
- pyinfra/modules/server.py +0 -524
- pyinfra/modules/ssh.py +0 -150
- pyinfra/modules/util/files.py +0 -52
- pyinfra/modules/util/packaging.py +0 -118
- pyinfra/modules/vzctl.py +0 -133
- pyinfra/modules/yum.py +0 -171
- pyinfra/pseudo_modules.py +0 -64
- pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
- pyinfra-0.11.dev3.dist-info/METADATA +0 -135
- pyinfra-0.11.dev3.dist-info/RECORD +0 -95
- pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
- pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
- pyinfra_cli/__main__.py +0 -40
- pyinfra_cli/config.py +0 -92
- /pyinfra/{modules/util → connectors}/__init__.py +0 -0
- /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
5
|
+
from pyinfra.api import FactBase, MaskString, QuoteString, StringCommand
|
|
6
|
+
from pyinfra.api.util import try_int
|
|
7
|
+
|
|
8
|
+
from .util.databases import parse_columns_and_rows
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_psql_command(
|
|
12
|
+
database: str | None = None,
|
|
13
|
+
user: str | None = None,
|
|
14
|
+
password: str | None = None,
|
|
15
|
+
host: str | None = None,
|
|
16
|
+
port: str | int | None = None,
|
|
17
|
+
executable="psql",
|
|
18
|
+
) -> StringCommand:
|
|
19
|
+
target_bits: list[str] = []
|
|
20
|
+
|
|
21
|
+
if password:
|
|
22
|
+
target_bits.append(MaskString('PGPASSWORD="{0}"'.format(password)))
|
|
23
|
+
|
|
24
|
+
target_bits.append(executable)
|
|
25
|
+
|
|
26
|
+
if database:
|
|
27
|
+
target_bits.append("-d {0}".format(database))
|
|
28
|
+
|
|
29
|
+
if user:
|
|
30
|
+
target_bits.append("-U {0}".format(user))
|
|
31
|
+
|
|
32
|
+
if host:
|
|
33
|
+
target_bits.append("-h {0}".format(host))
|
|
34
|
+
|
|
35
|
+
if port:
|
|
36
|
+
target_bits.append("-p {0}".format(port))
|
|
37
|
+
|
|
38
|
+
return StringCommand(*target_bits)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def make_execute_psql_command(command, **psql_kwargs):
|
|
42
|
+
return StringCommand(
|
|
43
|
+
make_psql_command(**psql_kwargs),
|
|
44
|
+
"-Ac",
|
|
45
|
+
QuoteString(command), # quote this whole item as a single shell argument
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PostgresFactBase(FactBase):
|
|
50
|
+
abstract = True
|
|
51
|
+
|
|
52
|
+
psql_command: str
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def requires_command(self, *args, **kwargs):
|
|
56
|
+
return "psql"
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
def command(
|
|
60
|
+
self,
|
|
61
|
+
psql_user=None,
|
|
62
|
+
psql_password=None,
|
|
63
|
+
psql_host=None,
|
|
64
|
+
psql_port=None,
|
|
65
|
+
psql_database=None,
|
|
66
|
+
):
|
|
67
|
+
return make_execute_psql_command(
|
|
68
|
+
self.psql_command,
|
|
69
|
+
user=psql_user,
|
|
70
|
+
password=psql_password,
|
|
71
|
+
host=psql_host,
|
|
72
|
+
port=psql_port,
|
|
73
|
+
database=psql_database,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class PostgresRoles(PostgresFactBase):
|
|
78
|
+
"""
|
|
79
|
+
Returns a dict of PostgreSQL roles and data:
|
|
80
|
+
|
|
81
|
+
.. code:: python
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
"pyinfra": {
|
|
85
|
+
"super": true,
|
|
86
|
+
"createrole": false,
|
|
87
|
+
"createdb": false,
|
|
88
|
+
...
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
default = dict
|
|
94
|
+
psql_command = "SELECT * FROM pg_catalog.pg_roles"
|
|
95
|
+
|
|
96
|
+
@override
|
|
97
|
+
def process(self, output):
|
|
98
|
+
# Remove the last line of the output (row count)
|
|
99
|
+
output = output[:-1]
|
|
100
|
+
rows = parse_columns_and_rows(
|
|
101
|
+
output,
|
|
102
|
+
"|",
|
|
103
|
+
# Remove the "rol" prefix on column names
|
|
104
|
+
remove_column_prefix="rol",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
users = {}
|
|
108
|
+
|
|
109
|
+
for details in rows:
|
|
110
|
+
for key, value in list(details.items()):
|
|
111
|
+
if key in ("oid", "connlimit"):
|
|
112
|
+
details[key] = try_int(value)
|
|
113
|
+
|
|
114
|
+
if key in (
|
|
115
|
+
"super",
|
|
116
|
+
"inherit",
|
|
117
|
+
"createrole",
|
|
118
|
+
"createdb",
|
|
119
|
+
"canlogin",
|
|
120
|
+
"replication",
|
|
121
|
+
"bypassrls",
|
|
122
|
+
):
|
|
123
|
+
details[key] = value == "t"
|
|
124
|
+
|
|
125
|
+
users[details.pop("name")] = details
|
|
126
|
+
|
|
127
|
+
return users
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class PostgresDatabases(PostgresFactBase):
|
|
131
|
+
"""
|
|
132
|
+
Returns a dict of PostgreSQL databases and metadata:
|
|
133
|
+
|
|
134
|
+
.. code:: python
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
"pyinfra_stuff": {
|
|
138
|
+
"encoding": "UTF8",
|
|
139
|
+
"collate": "en_US.UTF-8",
|
|
140
|
+
"ctype": "en_US.UTF-8",
|
|
141
|
+
...
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
default = dict
|
|
147
|
+
psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), *, pg_catalog.pg_get_userbyid(datdba) AS owner FROM pg_catalog.pg_database" # noqa: E501
|
|
148
|
+
|
|
149
|
+
@override
|
|
150
|
+
def process(self, output):
|
|
151
|
+
# Remove the last line of the output (row count)
|
|
152
|
+
output = output[:-1]
|
|
153
|
+
rows = parse_columns_and_rows(
|
|
154
|
+
output,
|
|
155
|
+
"|",
|
|
156
|
+
# Remove the "dat" prefix on column names
|
|
157
|
+
remove_column_prefix="dat",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
databases = {}
|
|
161
|
+
|
|
162
|
+
for details in rows:
|
|
163
|
+
details["encoding"] = details.pop("pg_encoding_to_char")
|
|
164
|
+
details["owner"] = details.pop("owner")
|
|
165
|
+
for key, value in list(details.items()):
|
|
166
|
+
if key.endswith("id") or key in (
|
|
167
|
+
"dba",
|
|
168
|
+
"tablespace",
|
|
169
|
+
"connlimit",
|
|
170
|
+
):
|
|
171
|
+
details[key] = try_int(value)
|
|
172
|
+
|
|
173
|
+
if key in ("istemplate", "allowconn"):
|
|
174
|
+
details[key] = value == "t"
|
|
175
|
+
|
|
176
|
+
databases[details.pop("name")] = details
|
|
177
|
+
|
|
178
|
+
return databases
|
pyinfra/facts/postgresql.py
CHANGED
|
@@ -1,152 +1,11 @@
|
|
|
1
|
-
from
|
|
2
|
-
from pyinfra.api.util import try_int
|
|
1
|
+
from __future__ import annotations
|
|
3
2
|
|
|
4
|
-
from .
|
|
3
|
+
from .postgres import PostgresDatabases, PostgresRoles
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
user=None,
|
|
10
|
-
password=None,
|
|
11
|
-
host=None,
|
|
12
|
-
port=None,
|
|
13
|
-
executable='psql',
|
|
14
|
-
):
|
|
15
|
-
target_bits = []
|
|
6
|
+
class PostgresqlRoles(PostgresRoles):
|
|
7
|
+
deprecated = True
|
|
16
8
|
|
|
17
|
-
if password:
|
|
18
|
-
target_bits.append('PGPASSWORD="{0}"'.format(password))
|
|
19
9
|
|
|
20
|
-
|
|
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 ' '.join(target_bits)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def make_execute_psql_command(command, **postgresql_kwargs):
|
|
38
|
-
return '{0} -Ac "{1}"'.format(
|
|
39
|
-
make_psql_command(**postgresql_kwargs),
|
|
40
|
-
command.replace('"', '\\"'),
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class PostgresqlFactBase(FactBase):
|
|
45
|
-
abstract = True
|
|
46
|
-
|
|
47
|
-
def command(
|
|
48
|
-
self,
|
|
49
|
-
postgresql_user=None, postgresql_password=None,
|
|
50
|
-
postgresql_host=None, postgresql_port=None,
|
|
51
|
-
):
|
|
52
|
-
return make_execute_psql_command(
|
|
53
|
-
self.postgresql_command,
|
|
54
|
-
user=postgresql_user,
|
|
55
|
-
password=postgresql_password,
|
|
56
|
-
host=postgresql_host,
|
|
57
|
-
port=postgresql_port,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class PostgresqlRoles(PostgresqlFactBase):
|
|
62
|
-
'''
|
|
63
|
-
Returns a dict of PostgreSQL roles and data:
|
|
64
|
-
|
|
65
|
-
.. code:: python
|
|
66
|
-
|
|
67
|
-
'pyinfra': {
|
|
68
|
-
'super': true,
|
|
69
|
-
'createrole': false,
|
|
70
|
-
'createdb': false,
|
|
71
|
-
...
|
|
72
|
-
},
|
|
73
|
-
...
|
|
74
|
-
'''
|
|
75
|
-
|
|
76
|
-
default = dict
|
|
77
|
-
postgresql_command = 'SELECT * FROM pg_catalog.pg_roles'
|
|
78
|
-
|
|
79
|
-
def process(self, output):
|
|
80
|
-
# Remove the last line of the output (row count)
|
|
81
|
-
output = output[:-1]
|
|
82
|
-
rows = parse_columns_and_rows(
|
|
83
|
-
output, '|',
|
|
84
|
-
# Remove the "rol" prefix on column names
|
|
85
|
-
remove_column_prefix='rol',
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
users = {}
|
|
89
|
-
|
|
90
|
-
for details in rows:
|
|
91
|
-
for key, value in list(details.items()):
|
|
92
|
-
if key in ('oid', 'connlimit'):
|
|
93
|
-
details[key] = try_int(value)
|
|
94
|
-
|
|
95
|
-
if key in (
|
|
96
|
-
'super', 'inherit', 'createrole', 'createdb',
|
|
97
|
-
'canlogin', 'replication', 'bypassrls',
|
|
98
|
-
):
|
|
99
|
-
details[key] = value == 't'
|
|
100
|
-
|
|
101
|
-
users[details.pop('name')] = details
|
|
102
|
-
|
|
103
|
-
return users
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
class PostgresqlDatabases(PostgresqlFactBase):
|
|
107
|
-
'''
|
|
108
|
-
Returns a dict of PostgreSQL databases and metadata:
|
|
109
|
-
|
|
110
|
-
.. code:: python
|
|
111
|
-
|
|
112
|
-
"pyinfra_stuff": {
|
|
113
|
-
"encoding": "UTF8",
|
|
114
|
-
"collate": "en_US.UTF-8",
|
|
115
|
-
"ctype": "en_US.UTF-8",
|
|
116
|
-
...
|
|
117
|
-
},
|
|
118
|
-
...
|
|
119
|
-
'''
|
|
120
|
-
|
|
121
|
-
default = dict
|
|
122
|
-
postgresql_command = '''
|
|
123
|
-
SELECT pg_catalog.pg_encoding_to_char(encoding), *
|
|
124
|
-
FROM pg_catalog.pg_database
|
|
125
|
-
'''
|
|
126
|
-
|
|
127
|
-
def process(self, output):
|
|
128
|
-
# Remove the last line of the output (row count)
|
|
129
|
-
output = output[:-1]
|
|
130
|
-
rows = parse_columns_and_rows(
|
|
131
|
-
output, '|',
|
|
132
|
-
# Remove the "dat" prefix on column names
|
|
133
|
-
remove_column_prefix='dat',
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
databases = {}
|
|
137
|
-
|
|
138
|
-
for details in rows:
|
|
139
|
-
details['encoding'] = details.pop('pg_encoding_to_char')
|
|
140
|
-
|
|
141
|
-
for key, value in list(details.items()):
|
|
142
|
-
if key.endswith('id') or key in (
|
|
143
|
-
'dba', 'tablespace', 'connlimit',
|
|
144
|
-
):
|
|
145
|
-
details[key] = try_int(value)
|
|
146
|
-
|
|
147
|
-
if key in ('istemplate', 'allowconn'):
|
|
148
|
-
details[key] = value == 't'
|
|
149
|
-
|
|
150
|
-
databases[details.pop('name')] = details
|
|
151
|
-
|
|
152
|
-
return databases
|
|
10
|
+
class PostgresqlDatabases(PostgresDatabases):
|
|
11
|
+
deprecated = True
|
pyinfra/facts/rpm.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shlex
|
|
5
|
+
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
8
|
+
from pyinfra.api import FactBase
|
|
9
|
+
|
|
10
|
+
from .util.packaging import parse_packages
|
|
11
|
+
|
|
12
|
+
rpm_regex = r"^(\S+)\ (\S+)$"
|
|
13
|
+
rpm_query_format = "%{NAME} %{VERSION}-%{RELEASE}\\n"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RpmPackages(FactBase):
|
|
17
|
+
"""
|
|
18
|
+
Returns a dict of installed rpm packages:
|
|
19
|
+
|
|
20
|
+
.. code:: python
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
"package_name": ["version"],
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
def command(self) -> str:
|
|
29
|
+
return "rpm --queryformat {0} -qa".format(shlex.quote(rpm_query_format))
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
def requires_command(self) -> str:
|
|
33
|
+
return "rpm"
|
|
34
|
+
|
|
35
|
+
default = dict
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def process(self, output):
|
|
39
|
+
return parse_packages(rpm_regex, output)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RpmPackage(FactBase):
|
|
43
|
+
"""
|
|
44
|
+
Returns information on a .rpm file:
|
|
45
|
+
|
|
46
|
+
.. code:: python
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
"name": "my_package",
|
|
50
|
+
"version": "1.0.0",
|
|
51
|
+
}
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def requires_command(self, package) -> str:
|
|
56
|
+
return "rpm"
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
def command(self, package) -> str:
|
|
60
|
+
return (
|
|
61
|
+
"rpm --queryformat {0} -q {1} || "
|
|
62
|
+
"! test -e {1} || "
|
|
63
|
+
"rpm --queryformat {0} -qp {1} 2> /dev/null"
|
|
64
|
+
).format(shlex.quote(rpm_query_format), shlex.quote(package))
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def process(self, output):
|
|
68
|
+
for line in output:
|
|
69
|
+
matches = re.match(rpm_regex, line)
|
|
70
|
+
if matches:
|
|
71
|
+
return {
|
|
72
|
+
"name": matches.group(1),
|
|
73
|
+
"version": matches.group(2),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RpmPackageProvides(FactBase):
|
|
78
|
+
"""
|
|
79
|
+
Returns a list of packages that provide the specified capability (command, file, etc).
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
default = list
|
|
83
|
+
|
|
84
|
+
@override
|
|
85
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
86
|
+
return "repoquery"
|
|
87
|
+
|
|
88
|
+
@override
|
|
89
|
+
def command(self, package):
|
|
90
|
+
# Accept failure here (|| true) for invalid/unknown packages
|
|
91
|
+
return "repoquery --queryformat {0} --whatprovides {1} || true".format(
|
|
92
|
+
shlex.quote(rpm_query_format),
|
|
93
|
+
shlex.quote(package),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@override
|
|
97
|
+
def process(self, output):
|
|
98
|
+
packages = []
|
|
99
|
+
|
|
100
|
+
for line in output:
|
|
101
|
+
matches = re.match(rpm_regex, line)
|
|
102
|
+
if matches:
|
|
103
|
+
packages.append(list(matches.groups()))
|
|
104
|
+
|
|
105
|
+
return packages
|
pyinfra/facts/runit.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing_extensions import override
|
|
2
|
+
|
|
3
|
+
from pyinfra.api import FactBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RunitStatus(FactBase):
|
|
7
|
+
"""
|
|
8
|
+
Returns a dict of name -> status for runit services.
|
|
9
|
+
|
|
10
|
+
+ service: optionally check only for a single service
|
|
11
|
+
+ svdir: alternative ``SVDIR``
|
|
12
|
+
|
|
13
|
+
.. code:: python
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
'agetty-tty1': True, # service is running
|
|
17
|
+
'dhcpcd': False, # service is down
|
|
18
|
+
'wpa_supplicant': None, # service is managed, but not running or down,
|
|
19
|
+
# possibly in a fail state
|
|
20
|
+
}
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
default = dict
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
27
|
+
return "sv"
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def command(self, service=None, svdir="/var/service") -> str:
|
|
31
|
+
if service is None:
|
|
32
|
+
return (
|
|
33
|
+
'export SVDIR="{0}" && '
|
|
34
|
+
'cd "$SVDIR" && find * -maxdepth 0 -exec sv status {{}} + 2>/dev/null'
|
|
35
|
+
).format(svdir)
|
|
36
|
+
else:
|
|
37
|
+
return 'SVDIR="{0}" sv status "{1}"'.format(svdir, service)
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def process(self, output):
|
|
41
|
+
services = {}
|
|
42
|
+
for line in output:
|
|
43
|
+
statusstr, service, _ = line.split(sep=": ", maxsplit=2)
|
|
44
|
+
status = None
|
|
45
|
+
|
|
46
|
+
if statusstr == "run":
|
|
47
|
+
status = True
|
|
48
|
+
elif statusstr == "down":
|
|
49
|
+
status = False
|
|
50
|
+
# another observable state is "fail"
|
|
51
|
+
# report as ``None`` for now
|
|
52
|
+
|
|
53
|
+
services[service] = status
|
|
54
|
+
|
|
55
|
+
return services
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RunitManaged(FactBase):
|
|
59
|
+
"""
|
|
60
|
+
Returns a set of all services managed by runit
|
|
61
|
+
|
|
62
|
+
+ service: optionally check only for a single service
|
|
63
|
+
+ svdir: alternative ``SVDIR``
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
default = set
|
|
67
|
+
|
|
68
|
+
@override
|
|
69
|
+
def command(self, service=None, svdir="/var/service"):
|
|
70
|
+
if service is None:
|
|
71
|
+
return 'cd "{0}" && find -mindepth 1 -maxdepth 1 -type l -printf "%f\n"'.format(svdir)
|
|
72
|
+
else:
|
|
73
|
+
return 'cd "{0}" && test -h "{1}" && echo "{1}" || true'.format(svdir, service)
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
def process(self, output):
|
|
77
|
+
return set(output)
|
pyinfra/facts/selinux.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
8
|
+
from pyinfra.api import FactBase
|
|
9
|
+
|
|
10
|
+
FIELDS = ["user", "role", "type", "level"] # order is significant, do not change
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SEBoolean(FactBase):
|
|
14
|
+
"""
|
|
15
|
+
Returns the status of a SELinux Boolean as a string (``on`` or ``off``).
|
|
16
|
+
If ``boolean`` does not exist, ``SEBoolean`` returns the empty string.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def requires_command(self, boolean) -> str:
|
|
21
|
+
return "getsebool"
|
|
22
|
+
|
|
23
|
+
default = str
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def command(self, boolean):
|
|
27
|
+
return "getsebool {0}".format(boolean)
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def process(self, output):
|
|
31
|
+
components = output[0].split(" --> ")
|
|
32
|
+
return components[1]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FileContext(FactBase):
|
|
36
|
+
"""
|
|
37
|
+
Returns structured SELinux file context data for a specified file
|
|
38
|
+
or ``None`` if the file does not exist.
|
|
39
|
+
|
|
40
|
+
.. code:: python
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
"user": "system_u",
|
|
44
|
+
"role": "object_r",
|
|
45
|
+
"type": "default_t",
|
|
46
|
+
"level": "s0",
|
|
47
|
+
}
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
def command(self, path):
|
|
52
|
+
return "stat -c %C {0} || exit 0".format(path)
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def process(self, output):
|
|
56
|
+
context = {}
|
|
57
|
+
components = output[0].split(":")
|
|
58
|
+
context["user"] = components[0]
|
|
59
|
+
context["role"] = components[1]
|
|
60
|
+
context["type"] = components[2]
|
|
61
|
+
context["level"] = components[3]
|
|
62
|
+
return context
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class FileContextMapping(FactBase):
|
|
66
|
+
"""
|
|
67
|
+
Returns structured SELinux file context data for the specified target path prefix
|
|
68
|
+
using the same format as :ref:`facts:selinux.FileContext`.
|
|
69
|
+
If there is no mapping, it returns ``{}``
|
|
70
|
+
Note: This fact requires root privileges.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
default = dict
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
def requires_command(self, target) -> str:
|
|
77
|
+
return "semanage"
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
def command(self, target):
|
|
81
|
+
return "set -o pipefail && semanage fcontext -n -l | (grep '^{0}' || true)".format(target)
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
def process(self, output):
|
|
85
|
+
# example output: /etc all files system_u:object_r:etc_t:s0
|
|
86
|
+
# but lines at end that won't match: /etc/systemd/system = /usr/lib/systemd/system
|
|
87
|
+
if len(output) != 1:
|
|
88
|
+
return self.default()
|
|
89
|
+
m = re.match(r"^.*\s+(\w+):(\w+):(\w+):(\w+)", output[0])
|
|
90
|
+
return {k: m.group(i) for i, k in enumerate(FIELDS, 1)} if m is not None else self.default()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class SEPorts(FactBase):
|
|
94
|
+
"""
|
|
95
|
+
Returns the SELinux 'type' definitions for ``(tcp|udp|dccp|sctp)`` ports.
|
|
96
|
+
Note: This fact requires root privileges.
|
|
97
|
+
|
|
98
|
+
.. code:: python
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
"tcp": { 22: "ssh_port_t", ...},
|
|
102
|
+
"udp": { ...}
|
|
103
|
+
}
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
default = dict
|
|
107
|
+
# example output: amqp_port_t tcp 15672, 5671-5672
|
|
108
|
+
_regex = re.compile(r"^([\w_]+)\s+(\w+)\s+([\w\-,\s]+)$")
|
|
109
|
+
|
|
110
|
+
@override
|
|
111
|
+
def requires_command(self) -> str:
|
|
112
|
+
return "semanage"
|
|
113
|
+
|
|
114
|
+
@override
|
|
115
|
+
def command(self):
|
|
116
|
+
return "semanage port -ln"
|
|
117
|
+
|
|
118
|
+
@override
|
|
119
|
+
def process(self, output):
|
|
120
|
+
labels: dict[str, dict] = defaultdict(dict)
|
|
121
|
+
for line in output:
|
|
122
|
+
m = SEPorts._regex.match(line)
|
|
123
|
+
if m is None: # something went wrong
|
|
124
|
+
continue
|
|
125
|
+
if m.group(1) == "unreserved_port_t": # these cover the entire space
|
|
126
|
+
continue
|
|
127
|
+
for item in m.group(3).split(","):
|
|
128
|
+
item = item.strip()
|
|
129
|
+
if "-" in item:
|
|
130
|
+
pieces = item.split("-")
|
|
131
|
+
start, stop = int(pieces[0]), int(pieces[1])
|
|
132
|
+
else:
|
|
133
|
+
start = stop = int(item)
|
|
134
|
+
labels[m.group(2)].update({port: m.group(1) for port in range(start, stop + 1)})
|
|
135
|
+
|
|
136
|
+
return labels
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SEPort(FactBase):
|
|
140
|
+
"""
|
|
141
|
+
Returns the SELinux 'type' for the specified protocol ``(tcp|udp|dccp|sctp)`` and port number.
|
|
142
|
+
If no type has been set, ``SEPort`` returns the empty string.
|
|
143
|
+
Note: ``policycoreutils-dev`` must be installed for this to work.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
default = str
|
|
147
|
+
|
|
148
|
+
@override
|
|
149
|
+
def requires_command(self, protocol, port) -> str:
|
|
150
|
+
return "sepolicy"
|
|
151
|
+
|
|
152
|
+
@override
|
|
153
|
+
def command(self, protocol, port):
|
|
154
|
+
return "(sepolicy network -p {0} 2>/dev/null || true) | grep {1}".format(port, protocol)
|
|
155
|
+
|
|
156
|
+
@override
|
|
157
|
+
def process(self, output):
|
|
158
|
+
# if type set, first line is specific and second is generic type for port range
|
|
159
|
+
# each rows in the format "22: tcp ssh_port_t 22"
|
|
160
|
+
|
|
161
|
+
return output[0].split(" ")[2] if len(output) > 1 else self.default()
|