pyinfra 3.1.1__py2.py3-none-any.whl → 3.3__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 -2
- pyinfra/api/arguments_typed.py +4 -5
- pyinfra/api/command.py +22 -3
- pyinfra/api/config.py +5 -2
- pyinfra/api/deploy.py +4 -2
- pyinfra/api/facts.py +3 -0
- pyinfra/api/host.py +15 -7
- pyinfra/api/operation.py +2 -1
- pyinfra/api/state.py +1 -1
- pyinfra/connectors/base.py +34 -8
- pyinfra/connectors/chroot.py +7 -2
- pyinfra/connectors/docker.py +24 -8
- pyinfra/connectors/dockerssh.py +7 -2
- pyinfra/connectors/local.py +7 -2
- pyinfra/connectors/ssh.py +9 -2
- pyinfra/connectors/sshuserclient/client.py +42 -14
- pyinfra/connectors/sshuserclient/config.py +2 -0
- pyinfra/connectors/terraform.py +1 -1
- pyinfra/connectors/util.py +13 -9
- pyinfra/context.py +9 -2
- pyinfra/facts/apk.py +8 -1
- pyinfra/facts/apt.py +68 -0
- pyinfra/facts/brew.py +13 -0
- pyinfra/facts/bsdinit.py +3 -0
- pyinfra/facts/cargo.py +5 -0
- pyinfra/facts/choco.py +6 -0
- pyinfra/facts/crontab.py +195 -0
- pyinfra/facts/deb.py +10 -0
- pyinfra/facts/dnf.py +5 -0
- pyinfra/facts/docker.py +16 -0
- pyinfra/facts/efibootmgr.py +113 -0
- pyinfra/facts/files.py +112 -7
- pyinfra/facts/flatpak.py +7 -0
- pyinfra/facts/freebsd.py +75 -0
- pyinfra/facts/gem.py +5 -0
- pyinfra/facts/git.py +12 -2
- pyinfra/facts/gpg.py +7 -0
- pyinfra/facts/hardware.py +13 -0
- pyinfra/facts/iptables.py +9 -1
- pyinfra/facts/launchd.py +5 -0
- pyinfra/facts/lxd.py +5 -0
- pyinfra/facts/mysql.py +9 -2
- pyinfra/facts/npm.py +5 -0
- pyinfra/facts/openrc.py +8 -0
- pyinfra/facts/opkg.py +245 -0
- pyinfra/facts/pacman.py +9 -1
- pyinfra/facts/pip.py +5 -0
- pyinfra/facts/pipx.py +82 -0
- pyinfra/facts/pkg.py +4 -0
- pyinfra/facts/pkgin.py +5 -0
- pyinfra/facts/podman.py +54 -0
- pyinfra/facts/postgres.py +10 -2
- pyinfra/facts/rpm.py +11 -0
- pyinfra/facts/runit.py +7 -0
- pyinfra/facts/selinux.py +16 -0
- pyinfra/facts/server.py +87 -79
- pyinfra/facts/snap.py +7 -0
- pyinfra/facts/systemd.py +5 -0
- pyinfra/facts/sysvinit.py +4 -0
- pyinfra/facts/upstart.py +5 -0
- pyinfra/facts/util/__init__.py +4 -1
- pyinfra/facts/util/units.py +30 -0
- pyinfra/facts/vzctl.py +5 -0
- pyinfra/facts/xbps.py +6 -1
- pyinfra/facts/yum.py +5 -0
- pyinfra/facts/zfs.py +41 -21
- pyinfra/facts/zypper.py +5 -0
- pyinfra/local.py +3 -2
- pyinfra/operations/apt.py +36 -22
- pyinfra/operations/crontab.py +189 -0
- pyinfra/operations/docker.py +61 -56
- pyinfra/operations/files.py +65 -1
- 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/git.py +23 -7
- pyinfra/operations/opkg.py +88 -0
- pyinfra/operations/pip.py +3 -2
- pyinfra/operations/pipx.py +90 -0
- pyinfra/operations/postgres.py +114 -27
- pyinfra/operations/runit.py +2 -0
- pyinfra/operations/server.py +9 -181
- pyinfra/operations/util/docker.py +44 -22
- pyinfra/operations/zfs.py +3 -3
- {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/LICENSE.md +1 -1
- {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/METADATA +25 -25
- pyinfra-3.3.dist-info/RECORD +187 -0
- pyinfra_cli/exceptions.py +5 -0
- pyinfra_cli/inventory.py +26 -9
- pyinfra_cli/log.py +3 -0
- pyinfra_cli/main.py +9 -8
- pyinfra_cli/prints.py +19 -4
- pyinfra_cli/util.py +3 -0
- pyinfra_cli/virtualenv.py +1 -1
- tests/test_cli/test_cli_deploy.py +15 -13
- tests/test_cli/test_cli_inventory.py +53 -0
- tests/test_connectors/test_ssh.py +302 -182
- tests/test_connectors/test_sshuserclient.py +68 -1
- pyinfra-3.1.1.dist-info/RECORD +0 -172
- {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/WHEEL +0 -0
- {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/entry_points.txt +0 -0
- {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/top_level.txt +0 -0
pyinfra/facts/selinux.py
CHANGED
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
6
8
|
from pyinfra.api import FactBase
|
|
7
9
|
|
|
8
10
|
FIELDS = ["user", "role", "type", "level"] # order is significant, do not change
|
|
@@ -14,14 +16,17 @@ class SEBoolean(FactBase):
|
|
|
14
16
|
If ``boolean`` does not exist, ``SEBoolean`` returns the empty string.
|
|
15
17
|
"""
|
|
16
18
|
|
|
19
|
+
@override
|
|
17
20
|
def requires_command(self, boolean) -> str:
|
|
18
21
|
return "getsebool"
|
|
19
22
|
|
|
20
23
|
default = str
|
|
21
24
|
|
|
25
|
+
@override
|
|
22
26
|
def command(self, boolean):
|
|
23
27
|
return "getsebool {0}".format(boolean)
|
|
24
28
|
|
|
29
|
+
@override
|
|
25
30
|
def process(self, output):
|
|
26
31
|
components = output[0].split(" --> ")
|
|
27
32
|
return components[1]
|
|
@@ -42,9 +47,11 @@ class FileContext(FactBase):
|
|
|
42
47
|
}
|
|
43
48
|
"""
|
|
44
49
|
|
|
50
|
+
@override
|
|
45
51
|
def command(self, path):
|
|
46
52
|
return "stat -c %C {0} || exit 0".format(path)
|
|
47
53
|
|
|
54
|
+
@override
|
|
48
55
|
def process(self, output):
|
|
49
56
|
context = {}
|
|
50
57
|
components = output[0].split(":")
|
|
@@ -65,12 +72,15 @@ class FileContextMapping(FactBase):
|
|
|
65
72
|
|
|
66
73
|
default = dict
|
|
67
74
|
|
|
75
|
+
@override
|
|
68
76
|
def requires_command(self, target) -> str:
|
|
69
77
|
return "semanage"
|
|
70
78
|
|
|
79
|
+
@override
|
|
71
80
|
def command(self, target):
|
|
72
81
|
return "set -o pipefail && semanage fcontext -n -l | (grep '^{0}' || true)".format(target)
|
|
73
82
|
|
|
83
|
+
@override
|
|
74
84
|
def process(self, output):
|
|
75
85
|
# example output: /etc all files system_u:object_r:etc_t:s0 # noqa: SC100
|
|
76
86
|
# but lines at end that won't match: /etc/systemd/system = /usr/lib/systemd/system
|
|
@@ -97,12 +107,15 @@ class SEPorts(FactBase):
|
|
|
97
107
|
# example output: amqp_port_t tcp 15672, 5671-5672 # noqa: SC100
|
|
98
108
|
_regex = re.compile(r"^([\w_]+)\s+(\w+)\s+([\w\-,\s]+)$")
|
|
99
109
|
|
|
110
|
+
@override
|
|
100
111
|
def requires_command(self) -> str:
|
|
101
112
|
return "semanage"
|
|
102
113
|
|
|
114
|
+
@override
|
|
103
115
|
def command(self):
|
|
104
116
|
return "semanage port -ln"
|
|
105
117
|
|
|
118
|
+
@override
|
|
106
119
|
def process(self, output):
|
|
107
120
|
labels: dict[str, dict] = defaultdict(dict)
|
|
108
121
|
for line in output:
|
|
@@ -132,12 +145,15 @@ class SEPort(FactBase):
|
|
|
132
145
|
|
|
133
146
|
default = str
|
|
134
147
|
|
|
148
|
+
@override
|
|
135
149
|
def requires_command(self, protocol, port) -> str:
|
|
136
150
|
return "sepolicy"
|
|
137
151
|
|
|
152
|
+
@override
|
|
138
153
|
def command(self, protocol, port):
|
|
139
154
|
return "(sepolicy network -p {0} 2>/dev/null || true) | grep {1}".format(port, protocol)
|
|
140
155
|
|
|
156
|
+
@override
|
|
141
157
|
def process(self, output):
|
|
142
158
|
# if type set, first line is specific and second is generic type for port range
|
|
143
159
|
# each rows in the format "22: tcp ssh_port_t 22"
|
pyinfra/facts/server.py
CHANGED
|
@@ -5,14 +5,15 @@ import re
|
|
|
5
5
|
import shutil
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from tempfile import mkdtemp
|
|
8
|
-
from typing import Dict, List, Optional
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
9
|
|
|
10
10
|
from dateutil.parser import parse as parse_date
|
|
11
11
|
from distro import distro
|
|
12
|
-
from typing_extensions import
|
|
12
|
+
from typing_extensions import TypedDict, override
|
|
13
13
|
|
|
14
14
|
from pyinfra.api import FactBase, ShortFactBase
|
|
15
15
|
from pyinfra.api.util import try_int
|
|
16
|
+
from pyinfra.facts import crontab
|
|
16
17
|
|
|
17
18
|
ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
|
|
18
19
|
|
|
@@ -22,6 +23,7 @@ class User(FactBase):
|
|
|
22
23
|
Returns the name of the current user.
|
|
23
24
|
"""
|
|
24
25
|
|
|
26
|
+
@override
|
|
25
27
|
def command(self):
|
|
26
28
|
return "echo $USER"
|
|
27
29
|
|
|
@@ -31,8 +33,8 @@ class Home(FactBase[Optional[str]]):
|
|
|
31
33
|
Returns the home directory of the given user, or the current user if no user is given.
|
|
32
34
|
"""
|
|
33
35
|
|
|
34
|
-
@
|
|
35
|
-
def command(user=""):
|
|
36
|
+
@override
|
|
37
|
+
def command(self, user=""):
|
|
36
38
|
return f"echo ~{user}"
|
|
37
39
|
|
|
38
40
|
|
|
@@ -41,6 +43,7 @@ class Path(FactBase):
|
|
|
41
43
|
Returns the path environment variable of the current user.
|
|
42
44
|
"""
|
|
43
45
|
|
|
46
|
+
@override
|
|
44
47
|
def command(self):
|
|
45
48
|
return "echo $PATH"
|
|
46
49
|
|
|
@@ -50,6 +53,7 @@ class TmpDir(FactBase):
|
|
|
50
53
|
Returns the temporary directory of the current server, if configured.
|
|
51
54
|
"""
|
|
52
55
|
|
|
56
|
+
@override
|
|
53
57
|
def command(self):
|
|
54
58
|
return "echo $TMPDIR"
|
|
55
59
|
|
|
@@ -59,6 +63,7 @@ class Hostname(FactBase):
|
|
|
59
63
|
Returns the current hostname of the server.
|
|
60
64
|
"""
|
|
61
65
|
|
|
66
|
+
@override
|
|
62
67
|
def command(self):
|
|
63
68
|
return "uname -n"
|
|
64
69
|
|
|
@@ -68,6 +73,7 @@ class Kernel(FactBase):
|
|
|
68
73
|
Returns the kernel name according to ``uname``.
|
|
69
74
|
"""
|
|
70
75
|
|
|
76
|
+
@override
|
|
71
77
|
def command(self):
|
|
72
78
|
return "uname -s"
|
|
73
79
|
|
|
@@ -77,6 +83,7 @@ class KernelVersion(FactBase):
|
|
|
77
83
|
Returns the kernel version according to ``uname``.
|
|
78
84
|
"""
|
|
79
85
|
|
|
86
|
+
@override
|
|
80
87
|
def command(self):
|
|
81
88
|
return "uname -r"
|
|
82
89
|
|
|
@@ -90,6 +97,7 @@ class Os(FactBase[str]):
|
|
|
90
97
|
This fact is deprecated/renamed, please use the ``server.Kernel`` fact.
|
|
91
98
|
"""
|
|
92
99
|
|
|
100
|
+
@override
|
|
93
101
|
def command(self):
|
|
94
102
|
return "uname -s"
|
|
95
103
|
|
|
@@ -103,6 +111,7 @@ class OsVersion(FactBase[str]):
|
|
|
103
111
|
This fact is deprecated/renamed, please use the ``server.KernelVersion`` fact.
|
|
104
112
|
"""
|
|
105
113
|
|
|
114
|
+
@override
|
|
106
115
|
def command(self):
|
|
107
116
|
return "uname -r"
|
|
108
117
|
|
|
@@ -114,6 +123,7 @@ class Arch(FactBase[str]):
|
|
|
114
123
|
|
|
115
124
|
# ``uname -p`` is not portable and returns ``unknown`` on Debian.
|
|
116
125
|
# ``uname -m`` works on most Linux and BSD systems.
|
|
126
|
+
@override
|
|
117
127
|
def command(self):
|
|
118
128
|
return "uname -m"
|
|
119
129
|
|
|
@@ -123,8 +133,8 @@ class Command(FactBase[str]):
|
|
|
123
133
|
Returns the raw output lines of a given command.
|
|
124
134
|
"""
|
|
125
135
|
|
|
126
|
-
@
|
|
127
|
-
def command(command):
|
|
136
|
+
@override
|
|
137
|
+
def command(self, command):
|
|
128
138
|
return command
|
|
129
139
|
|
|
130
140
|
|
|
@@ -133,8 +143,8 @@ class Which(FactBase[Optional[str]]):
|
|
|
133
143
|
Returns the path of a given command according to `command -v`, if available.
|
|
134
144
|
"""
|
|
135
145
|
|
|
136
|
-
@
|
|
137
|
-
def command(command):
|
|
146
|
+
@override
|
|
147
|
+
def command(self, command):
|
|
138
148
|
return "command -v {0} || true".format(command)
|
|
139
149
|
|
|
140
150
|
|
|
@@ -145,9 +155,11 @@ class Date(FactBase[datetime]):
|
|
|
145
155
|
|
|
146
156
|
default = datetime.now
|
|
147
157
|
|
|
158
|
+
@override
|
|
148
159
|
def command(self):
|
|
149
160
|
return f"date +'{ISO_DATE_FORMAT}'"
|
|
150
161
|
|
|
162
|
+
@override
|
|
151
163
|
def process(self, output) -> datetime:
|
|
152
164
|
return datetime.strptime(list(output)[0], ISO_DATE_FORMAT)
|
|
153
165
|
|
|
@@ -157,9 +169,11 @@ class MacosVersion(FactBase[str]):
|
|
|
157
169
|
Returns the installed MacOS version.
|
|
158
170
|
"""
|
|
159
171
|
|
|
172
|
+
@override
|
|
160
173
|
def requires_command(self) -> str:
|
|
161
174
|
return "sw_vers"
|
|
162
175
|
|
|
176
|
+
@override
|
|
163
177
|
def command(self):
|
|
164
178
|
return "sw_vers -productVersion"
|
|
165
179
|
|
|
@@ -190,9 +204,11 @@ class Mounts(FactBase[Dict[str, MountsDict]]):
|
|
|
190
204
|
|
|
191
205
|
default = dict
|
|
192
206
|
|
|
207
|
+
@override
|
|
193
208
|
def command(self):
|
|
194
209
|
return "mount"
|
|
195
210
|
|
|
211
|
+
@override
|
|
196
212
|
def process(self, output) -> dict[str, MountsDict]:
|
|
197
213
|
devices: dict[str, MountsDict] = {}
|
|
198
214
|
|
|
@@ -238,11 +254,13 @@ class KernelModules(FactBase):
|
|
|
238
254
|
}
|
|
239
255
|
"""
|
|
240
256
|
|
|
257
|
+
@override
|
|
241
258
|
def command(self):
|
|
242
259
|
return "! test -f /proc/modules || cat /proc/modules"
|
|
243
260
|
|
|
244
261
|
default = dict
|
|
245
262
|
|
|
263
|
+
@override
|
|
246
264
|
def process(self, output):
|
|
247
265
|
modules = {}
|
|
248
266
|
|
|
@@ -279,12 +297,15 @@ class LsbRelease(FactBase):
|
|
|
279
297
|
}
|
|
280
298
|
"""
|
|
281
299
|
|
|
300
|
+
@override
|
|
282
301
|
def command(self):
|
|
283
302
|
return "lsb_release -ca"
|
|
284
303
|
|
|
304
|
+
@override
|
|
285
305
|
def requires_command(self):
|
|
286
306
|
return "lsb_release"
|
|
287
307
|
|
|
308
|
+
@override
|
|
288
309
|
def process(self, output):
|
|
289
310
|
items = {}
|
|
290
311
|
|
|
@@ -307,6 +328,38 @@ class LsbRelease(FactBase):
|
|
|
307
328
|
return items
|
|
308
329
|
|
|
309
330
|
|
|
331
|
+
class OsRelease(FactBase):
|
|
332
|
+
"""
|
|
333
|
+
Returns a dictionary of release information stored in ``/etc/os-release``.
|
|
334
|
+
|
|
335
|
+
.. code:: python
|
|
336
|
+
|
|
337
|
+
{
|
|
338
|
+
"name": "EndeavourOS",
|
|
339
|
+
"pretty_name": "EndeavourOS",
|
|
340
|
+
"id": "endeavouros",
|
|
341
|
+
"id_like": "arch",
|
|
342
|
+
"build_id": "2024.06.25",
|
|
343
|
+
...
|
|
344
|
+
}
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
@override
|
|
348
|
+
def command(self):
|
|
349
|
+
return "cat /etc/os-release"
|
|
350
|
+
|
|
351
|
+
@override
|
|
352
|
+
def process(self, output):
|
|
353
|
+
items = {}
|
|
354
|
+
|
|
355
|
+
for line in output:
|
|
356
|
+
if "=" in line:
|
|
357
|
+
key, value = line.split("=", 1)
|
|
358
|
+
items[key.strip().lower()] = value.strip().strip('"')
|
|
359
|
+
|
|
360
|
+
return items
|
|
361
|
+
|
|
362
|
+
|
|
310
363
|
class Sysctl(FactBase):
|
|
311
364
|
"""
|
|
312
365
|
Returns a dictionary of sysctl settings and values.
|
|
@@ -324,11 +377,13 @@ class Sysctl(FactBase):
|
|
|
324
377
|
|
|
325
378
|
default = dict
|
|
326
379
|
|
|
380
|
+
@override
|
|
327
381
|
def command(self, keys=None):
|
|
328
382
|
if keys is None:
|
|
329
383
|
return "sysctl -a"
|
|
330
384
|
return f"sysctl {' '.join(keys)}"
|
|
331
385
|
|
|
386
|
+
@override
|
|
332
387
|
def process(self, output):
|
|
333
388
|
sysctls = {}
|
|
334
389
|
|
|
@@ -362,11 +417,13 @@ class Groups(FactBase[List[str]]):
|
|
|
362
417
|
Returns a list of groups on the system.
|
|
363
418
|
"""
|
|
364
419
|
|
|
420
|
+
@override
|
|
365
421
|
def command(self):
|
|
366
422
|
return "cat /etc/group"
|
|
367
423
|
|
|
368
424
|
default = list
|
|
369
425
|
|
|
426
|
+
@override
|
|
370
427
|
def process(self, output) -> list[str]:
|
|
371
428
|
groups: list[str] = []
|
|
372
429
|
|
|
@@ -377,75 +434,9 @@ class Groups(FactBase[List[str]]):
|
|
|
377
434
|
return groups
|
|
378
435
|
|
|
379
436
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
month: NotRequired[Union[int, str]]
|
|
384
|
-
day_of_month: NotRequired[Union[int, str]]
|
|
385
|
-
day_of_week: NotRequired[Union[int, str]]
|
|
386
|
-
comments: Optional[list[str]]
|
|
387
|
-
special_time: NotRequired[str]
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
class Crontab(FactBase[Dict[str, CrontabDict]]):
|
|
391
|
-
"""
|
|
392
|
-
Returns a dictionary of cron command -> execution time.
|
|
393
|
-
|
|
394
|
-
.. code:: python
|
|
395
|
-
|
|
396
|
-
{
|
|
397
|
-
"/path/to/command": {
|
|
398
|
-
"minute": "*",
|
|
399
|
-
"hour": "*",
|
|
400
|
-
"month": "*",
|
|
401
|
-
"day_of_month": "*",
|
|
402
|
-
"day_of_week": "*",
|
|
403
|
-
},
|
|
404
|
-
"echo another command": {
|
|
405
|
-
"special_time": "@daily",
|
|
406
|
-
},
|
|
407
|
-
}
|
|
408
|
-
"""
|
|
409
|
-
|
|
410
|
-
default = dict
|
|
411
|
-
|
|
412
|
-
def requires_command(self, user=None) -> str:
|
|
413
|
-
return "crontab"
|
|
414
|
-
|
|
415
|
-
def command(self, user=None):
|
|
416
|
-
if user:
|
|
417
|
-
return "crontab -l -u {0} || true".format(user)
|
|
418
|
-
return "crontab -l || true"
|
|
419
|
-
|
|
420
|
-
def process(self, output):
|
|
421
|
-
crons: dict[str, CrontabDict] = {}
|
|
422
|
-
current_comments = []
|
|
423
|
-
|
|
424
|
-
for line in output:
|
|
425
|
-
line = line.strip()
|
|
426
|
-
if not line or line.startswith("#"):
|
|
427
|
-
current_comments.append(line)
|
|
428
|
-
continue
|
|
429
|
-
|
|
430
|
-
if line.startswith("@"):
|
|
431
|
-
special_time, command = line.split(None, 1)
|
|
432
|
-
crons[command] = {
|
|
433
|
-
"special_time": special_time,
|
|
434
|
-
"comments": current_comments,
|
|
435
|
-
}
|
|
436
|
-
else:
|
|
437
|
-
minute, hour, day_of_month, month, day_of_week, command = line.split(None, 5)
|
|
438
|
-
crons[command] = {
|
|
439
|
-
"minute": try_int(minute),
|
|
440
|
-
"hour": try_int(hour),
|
|
441
|
-
"month": try_int(month),
|
|
442
|
-
"day_of_month": try_int(day_of_month),
|
|
443
|
-
"day_of_week": try_int(day_of_week),
|
|
444
|
-
"comments": current_comments,
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
current_comments = []
|
|
448
|
-
return crons
|
|
437
|
+
# for compatibility
|
|
438
|
+
CrontabDict = crontab.CrontabDict
|
|
439
|
+
Crontab = crontab.Crontab
|
|
449
440
|
|
|
450
441
|
|
|
451
442
|
class Users(FactBase):
|
|
@@ -472,13 +463,13 @@ class Users(FactBase):
|
|
|
472
463
|
}
|
|
473
464
|
"""
|
|
474
465
|
|
|
466
|
+
@override
|
|
475
467
|
def command(self):
|
|
476
468
|
return """
|
|
477
469
|
|
|
478
470
|
for i in `cat /etc/passwd | cut -d: -f1`; do
|
|
479
471
|
ENTRY=`grep ^$i: /etc/passwd`;
|
|
480
|
-
|
|
481
|
-
LASTLOG=`echo $LASTLOG_RAW | grep ^$i | tr -s ' '`;
|
|
472
|
+
LASTLOG=`(((lastlog -u $i || lastlogin $i) 2> /dev/null) | grep ^$i | tr -s ' ')`;
|
|
482
473
|
PASSWORD=`grep ^$i: /etc/shadow | cut -d: -f2`;
|
|
483
474
|
echo "$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG|$PASSWORD";
|
|
484
475
|
done
|
|
@@ -486,6 +477,7 @@ class Users(FactBase):
|
|
|
486
477
|
|
|
487
478
|
default = dict
|
|
488
479
|
|
|
480
|
+
@override
|
|
489
481
|
def process(self, output):
|
|
490
482
|
users = {}
|
|
491
483
|
rex = r"[A-Z][a-z]{2} [A-Z][a-z]{2} {1,2}\d+ .+$"
|
|
@@ -557,6 +549,7 @@ class LinuxDistribution(FactBase[LinuxDistributionDict]):
|
|
|
557
549
|
}
|
|
558
550
|
"""
|
|
559
551
|
|
|
552
|
+
@override
|
|
560
553
|
def command(self) -> str:
|
|
561
554
|
return (
|
|
562
555
|
"cd /etc/ && for file in $(ls -pdL *-release | grep -v /); "
|
|
@@ -575,6 +568,7 @@ class LinuxDistribution(FactBase[LinuxDistributionDict]):
|
|
|
575
568
|
"debian": "Debian",
|
|
576
569
|
}
|
|
577
570
|
|
|
571
|
+
@override
|
|
578
572
|
@staticmethod
|
|
579
573
|
def default() -> LinuxDistributionDict:
|
|
580
574
|
return {
|
|
@@ -584,6 +578,7 @@ class LinuxDistribution(FactBase[LinuxDistributionDict]):
|
|
|
584
578
|
"release_meta": {},
|
|
585
579
|
}
|
|
586
580
|
|
|
581
|
+
@override
|
|
587
582
|
def process(self, output) -> LinuxDistributionDict:
|
|
588
583
|
parts = {}
|
|
589
584
|
for part in "\n".join(output).strip().split("---"):
|
|
@@ -646,6 +641,7 @@ class LinuxName(ShortFactBase[str]):
|
|
|
646
641
|
|
|
647
642
|
fact = LinuxDistribution
|
|
648
643
|
|
|
644
|
+
@override
|
|
649
645
|
def process_data(self, data) -> str:
|
|
650
646
|
return data["name"]
|
|
651
647
|
|
|
@@ -665,18 +661,22 @@ class Selinux(FactBase[SelinuxDict]):
|
|
|
665
661
|
}
|
|
666
662
|
"""
|
|
667
663
|
|
|
664
|
+
@override
|
|
668
665
|
def command(self):
|
|
669
666
|
return "sestatus"
|
|
670
667
|
|
|
668
|
+
@override
|
|
671
669
|
def requires_command(self) -> str:
|
|
672
670
|
return "sestatus"
|
|
673
671
|
|
|
672
|
+
@override
|
|
674
673
|
@staticmethod
|
|
675
674
|
def default() -> SelinuxDict:
|
|
676
675
|
return {
|
|
677
676
|
"mode": None,
|
|
678
677
|
}
|
|
679
678
|
|
|
679
|
+
@override
|
|
680
680
|
def process(self, output) -> SelinuxDict:
|
|
681
681
|
selinux_info = self.default()
|
|
682
682
|
|
|
@@ -695,6 +695,7 @@ class LinuxGui(FactBase[List[str]]):
|
|
|
695
695
|
Returns a list of available Linux GUIs.
|
|
696
696
|
"""
|
|
697
697
|
|
|
698
|
+
@override
|
|
698
699
|
def command(self):
|
|
699
700
|
return "ls /usr/bin/*session || true"
|
|
700
701
|
|
|
@@ -708,6 +709,7 @@ class LinuxGui(FactBase[List[str]]):
|
|
|
708
709
|
"/usr/bin/xfce4-session": "XFCE 4",
|
|
709
710
|
}
|
|
710
711
|
|
|
712
|
+
@override
|
|
711
713
|
def process(self, output) -> list[str]:
|
|
712
714
|
gui_names = []
|
|
713
715
|
|
|
@@ -726,6 +728,7 @@ class HasGui(ShortFactBase[bool]):
|
|
|
726
728
|
|
|
727
729
|
fact = LinuxGui
|
|
728
730
|
|
|
731
|
+
@override
|
|
729
732
|
def process_data(self, data) -> bool:
|
|
730
733
|
return len(data) > 0
|
|
731
734
|
|
|
@@ -739,14 +742,17 @@ class Locales(FactBase[List[str]]):
|
|
|
739
742
|
["C.UTF-8", "en_US.UTF-8"]
|
|
740
743
|
"""
|
|
741
744
|
|
|
745
|
+
@override
|
|
742
746
|
def command(self) -> str:
|
|
743
747
|
return "locale -a"
|
|
744
748
|
|
|
749
|
+
@override
|
|
745
750
|
def requires_command(self) -> str:
|
|
746
751
|
return "locale"
|
|
747
752
|
|
|
748
753
|
default = list
|
|
749
754
|
|
|
755
|
+
@override
|
|
750
756
|
def process(self, output) -> list[str]:
|
|
751
757
|
# replace utf8 with UTF-8 to match names in /etc/locale.gen
|
|
752
758
|
# return a list of enabled locales
|
|
@@ -811,11 +817,13 @@ class SecurityLimits(FactBase):
|
|
|
811
817
|
]
|
|
812
818
|
"""
|
|
813
819
|
|
|
820
|
+
@override
|
|
814
821
|
def command(self):
|
|
815
822
|
return "cat /etc/security/limits.conf"
|
|
816
823
|
|
|
817
824
|
default = list
|
|
818
825
|
|
|
826
|
+
@override
|
|
819
827
|
def process(self, output):
|
|
820
828
|
limits = []
|
|
821
829
|
|
pyinfra/facts/snap.py
CHANGED
|
@@ -2,12 +2,15 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
5
7
|
from pyinfra.api import FactBase
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class SnapBaseFact(FactBase):
|
|
9
11
|
abstract = True
|
|
10
12
|
|
|
13
|
+
@override
|
|
11
14
|
def requires_command(self, *args, **kwargs) -> str:
|
|
12
15
|
return "snap"
|
|
13
16
|
|
|
@@ -36,9 +39,11 @@ class SnapPackage(SnapBaseFact):
|
|
|
36
39
|
"version": r"^installed:[ ]+([\w\d.-]+).*$",
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
@override
|
|
39
43
|
def command(self, package):
|
|
40
44
|
return f"snap info {package}"
|
|
41
45
|
|
|
46
|
+
@override
|
|
42
47
|
def process(self, output):
|
|
43
48
|
data = {}
|
|
44
49
|
for line in output:
|
|
@@ -67,9 +72,11 @@ class SnapPackages(SnapBaseFact):
|
|
|
67
72
|
|
|
68
73
|
default = list
|
|
69
74
|
|
|
75
|
+
@override
|
|
70
76
|
def command(self) -> str:
|
|
71
77
|
return "snap list"
|
|
72
78
|
|
|
79
|
+
@override
|
|
73
80
|
def process(self, output):
|
|
74
81
|
# Discard header output line from snap list command
|
|
75
82
|
# 'snap list' command example output lines:
|
pyinfra/facts/systemd.py
CHANGED
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from typing import Dict, Iterable
|
|
5
5
|
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
6
8
|
from pyinfra.api import FactBase, QuoteString, StringCommand
|
|
7
9
|
|
|
8
10
|
# Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
|
|
@@ -55,6 +57,7 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
|
|
|
55
57
|
}
|
|
56
58
|
"""
|
|
57
59
|
|
|
60
|
+
@override
|
|
58
61
|
def requires_command(self, *args, **kwargs) -> str:
|
|
59
62
|
return "systemctl"
|
|
60
63
|
|
|
@@ -63,6 +66,7 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
|
|
|
63
66
|
state_key = "SubState"
|
|
64
67
|
state_values = ["running", "waiting", "exited", "listening"]
|
|
65
68
|
|
|
69
|
+
@override
|
|
66
70
|
def command(
|
|
67
71
|
self,
|
|
68
72
|
user_mode: bool = False,
|
|
@@ -94,6 +98,7 @@ class SystemdStatus(FactBase[Dict[str, bool]]):
|
|
|
94
98
|
*service_strs,
|
|
95
99
|
)
|
|
96
100
|
|
|
101
|
+
@override
|
|
97
102
|
def process(self, output) -> Dict[str, bool]:
|
|
98
103
|
services: Dict[str, bool] = {}
|
|
99
104
|
|
pyinfra/facts/sysvinit.py
CHANGED
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
6
8
|
from pyinfra.api import FactBase
|
|
7
9
|
|
|
8
10
|
|
|
@@ -18,6 +20,7 @@ class InitdStatus(FactBase):
|
|
|
18
20
|
http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
|
|
19
21
|
"""
|
|
20
22
|
|
|
23
|
+
@override
|
|
21
24
|
def command(self) -> str:
|
|
22
25
|
return """
|
|
23
26
|
for SERVICE in `ls /etc/init.d/`; do
|
|
@@ -33,6 +36,7 @@ class InitdStatus(FactBase):
|
|
|
33
36
|
regex = r"([a-zA-Z0-9\-]+)=([0-9]+)"
|
|
34
37
|
default = dict
|
|
35
38
|
|
|
39
|
+
@override
|
|
36
40
|
def process(self, output) -> dict[str, Optional[bool]]:
|
|
37
41
|
services: dict[str, Optional[bool]] = {}
|
|
38
42
|
|
pyinfra/facts/upstart.py
CHANGED
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
5
7
|
from pyinfra.api import FactBase
|
|
6
8
|
|
|
7
9
|
|
|
@@ -10,15 +12,18 @@ class UpstartStatus(FactBase):
|
|
|
10
12
|
Returns a dict of name -> status for upstart managed services.
|
|
11
13
|
"""
|
|
12
14
|
|
|
15
|
+
@override
|
|
13
16
|
def requires_command(self) -> str:
|
|
14
17
|
return "initctl"
|
|
15
18
|
|
|
16
19
|
regex = r"^([a-z\-]+) [a-z]+\/([a-z]+)"
|
|
17
20
|
default = dict
|
|
18
21
|
|
|
22
|
+
@override
|
|
19
23
|
def command(self):
|
|
20
24
|
return "initctl list"
|
|
21
25
|
|
|
26
|
+
@override
|
|
22
27
|
def process(self, output):
|
|
23
28
|
services = {}
|
|
24
29
|
|
pyinfra/facts/util/__init__.py
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# from https://stackoverflow.com/a/60708339, but with a few modifications
|
|
2
|
+
from __future__ import annotations # for | in type hints
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
units = {
|
|
7
|
+
"B": 1,
|
|
8
|
+
"KB": 10**3,
|
|
9
|
+
"MB": 10**6,
|
|
10
|
+
"GB": 10**9,
|
|
11
|
+
"TB": 10**12,
|
|
12
|
+
"KIB": 2**10,
|
|
13
|
+
"MIB": 2**20,
|
|
14
|
+
"GIB": 2**30,
|
|
15
|
+
"TIB": 2**40,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_human_readable_size(size: str) -> int:
|
|
20
|
+
size = size.upper()
|
|
21
|
+
if not re.match(r" ", size):
|
|
22
|
+
size = re.sub(r"([KMGT]?I?[B])", r" \1", size)
|
|
23
|
+
number, unit = [string.strip() for string in size.split()]
|
|
24
|
+
return int(float(number) * units[unit])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_size(size: str | int) -> int:
|
|
28
|
+
if isinstance(size, int):
|
|
29
|
+
return size
|
|
30
|
+
return parse_human_readable_size(size)
|