pyinfra 2.9.2__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.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
- pyinfra-3.0.dist-info/RECORD +167 -0
- {pyinfra-2.9.2.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.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.0.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
pyinfra/operations/server.py
CHANGED
|
@@ -3,11 +3,14 @@ The server module takes care of os-level state. Targets POSIX compatibility, tes
|
|
|
3
3
|
Linux/BSD.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
6
8
|
import shlex
|
|
7
9
|
from io import StringIO
|
|
8
10
|
from itertools import filterfalse, tee
|
|
9
11
|
from os import path
|
|
10
12
|
from time import sleep
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
11
14
|
|
|
12
15
|
from pyinfra import host, logger, state
|
|
13
16
|
from pyinfra.api import FunctionCommand, OperationError, StringCommand, operation
|
|
@@ -17,6 +20,7 @@ from pyinfra.facts.files import Directory, FindInFile, Link
|
|
|
17
20
|
from pyinfra.facts.server import (
|
|
18
21
|
Crontab,
|
|
19
22
|
Groups,
|
|
23
|
+
Home,
|
|
20
24
|
Hostname,
|
|
21
25
|
KernelModules,
|
|
22
26
|
Locales,
|
|
@@ -37,6 +41,7 @@ from . import (
|
|
|
37
41
|
openrc,
|
|
38
42
|
pacman,
|
|
39
43
|
pkg,
|
|
44
|
+
runit,
|
|
40
45
|
systemd,
|
|
41
46
|
sysvinit,
|
|
42
47
|
upstart,
|
|
@@ -46,6 +51,9 @@ from . import (
|
|
|
46
51
|
)
|
|
47
52
|
from .util.files import chmod, sed_replace
|
|
48
53
|
|
|
54
|
+
if TYPE_CHECKING:
|
|
55
|
+
from pyinfra.api.arguments_typed import PyinfraOperation
|
|
56
|
+
|
|
49
57
|
|
|
50
58
|
@operation(is_idempotent=False)
|
|
51
59
|
def reboot(delay=10, interval=1, reboot_timeout=300):
|
|
@@ -75,7 +83,7 @@ def reboot(delay=10, interval=1, reboot_timeout=300):
|
|
|
75
83
|
|
|
76
84
|
yield FunctionCommand(remove_any_askpass_file, (), {})
|
|
77
85
|
|
|
78
|
-
yield StringCommand("reboot",
|
|
86
|
+
yield StringCommand("reboot", _success_exit_codes=[0, -1]) # -1 being error/disconnected
|
|
79
87
|
|
|
80
88
|
def wait_and_reconnect(state, host): # pragma: no cover
|
|
81
89
|
sleep(delay)
|
|
@@ -135,7 +143,7 @@ def wait(port: int):
|
|
|
135
143
|
|
|
136
144
|
|
|
137
145
|
@operation(is_idempotent=False)
|
|
138
|
-
def shell(commands):
|
|
146
|
+
def shell(commands: str | list[str]):
|
|
139
147
|
"""
|
|
140
148
|
Run raw shell code on server during a deploy. If the command would
|
|
141
149
|
modify data that would be in a fact, the fact would not be updated
|
|
@@ -162,7 +170,7 @@ def shell(commands):
|
|
|
162
170
|
|
|
163
171
|
|
|
164
172
|
@operation(is_idempotent=False)
|
|
165
|
-
def script(src, args=()):
|
|
173
|
+
def script(src: str, args=()):
|
|
166
174
|
"""
|
|
167
175
|
Upload and execute a local script on the remote host.
|
|
168
176
|
|
|
@@ -187,15 +195,15 @@ def script(src, args=()):
|
|
|
187
195
|
)
|
|
188
196
|
"""
|
|
189
197
|
|
|
190
|
-
temp_file =
|
|
191
|
-
yield from files.put(src, temp_file)
|
|
198
|
+
temp_file = host.get_temp_filename()
|
|
199
|
+
yield from files.put._inner(src=src, dest=temp_file)
|
|
192
200
|
|
|
193
201
|
yield chmod(temp_file, "+x")
|
|
194
202
|
yield StringCommand(temp_file, *args)
|
|
195
203
|
|
|
196
204
|
|
|
197
205
|
@operation(is_idempotent=False)
|
|
198
|
-
def script_template(src, args=(), **data):
|
|
206
|
+
def script_template(src: str, args=(), **data):
|
|
199
207
|
"""
|
|
200
208
|
Generate, upload and execute a local script template on the remote host.
|
|
201
209
|
|
|
@@ -217,15 +225,15 @@ def script_template(src, args=(), **data):
|
|
|
217
225
|
)
|
|
218
226
|
"""
|
|
219
227
|
|
|
220
|
-
temp_file =
|
|
221
|
-
yield from files.template(src, temp_file, **data)
|
|
228
|
+
temp_file = host.get_temp_filename("{0}{1}".format(src, data))
|
|
229
|
+
yield from files.template._inner(src, temp_file, **data)
|
|
222
230
|
|
|
223
231
|
yield chmod(temp_file, "+x")
|
|
224
232
|
yield StringCommand(temp_file, *args)
|
|
225
233
|
|
|
226
234
|
|
|
227
|
-
@operation
|
|
228
|
-
def modprobe(module, present=True, force=False):
|
|
235
|
+
@operation()
|
|
236
|
+
def modprobe(module: str, present=True, force=False):
|
|
229
237
|
"""
|
|
230
238
|
Load/unload kernel modules.
|
|
231
239
|
|
|
@@ -259,14 +267,10 @@ def modprobe(module, present=True, force=False):
|
|
|
259
267
|
# Module is loaded and we don't want it?
|
|
260
268
|
if not present and present_mods:
|
|
261
269
|
yield "modprobe{0} -r -a {1}".format(args, " ".join(present_mods))
|
|
262
|
-
for mod in present_mods:
|
|
263
|
-
modules.pop(mod)
|
|
264
270
|
|
|
265
271
|
# Module isn't loaded and we want it?
|
|
266
272
|
elif present and missing_mods:
|
|
267
273
|
yield "modprobe{0} -a {1}".format(args, " ".join(missing_mods))
|
|
268
|
-
for mod in missing_mods:
|
|
269
|
-
modules[mod] = {}
|
|
270
274
|
|
|
271
275
|
else:
|
|
272
276
|
host.noop(
|
|
@@ -279,13 +283,13 @@ def modprobe(module, present=True, force=False):
|
|
|
279
283
|
)
|
|
280
284
|
|
|
281
285
|
|
|
282
|
-
@operation
|
|
286
|
+
@operation()
|
|
283
287
|
def mount(
|
|
284
|
-
path,
|
|
288
|
+
path: str,
|
|
285
289
|
mounted=True,
|
|
286
|
-
options=None,
|
|
287
|
-
device=None,
|
|
288
|
-
fs_type=None,
|
|
290
|
+
options: list[str] | None = None,
|
|
291
|
+
device: str | None = None,
|
|
292
|
+
fs_type: str | None = None,
|
|
289
293
|
# TODO: do we want to manage fstab here?
|
|
290
294
|
# update_fstab=False,
|
|
291
295
|
):
|
|
@@ -322,13 +326,10 @@ def mount(
|
|
|
322
326
|
args.append(path)
|
|
323
327
|
|
|
324
328
|
yield StringCommand("mount", *args)
|
|
325
|
-
# Should we update facts with fs_type, device, etc?
|
|
326
|
-
mounts[path] = {"options": options}
|
|
327
329
|
|
|
328
330
|
# Want no mount but mounted?
|
|
329
331
|
elif mounted is False and is_mounted:
|
|
330
332
|
yield "umount {0}".format(path)
|
|
331
|
-
mounts.pop(path)
|
|
332
333
|
|
|
333
334
|
# Want mount and is mounted! Check the options
|
|
334
335
|
elif is_mounted and mounted and options:
|
|
@@ -336,7 +337,6 @@ def mount(
|
|
|
336
337
|
needed_options = set(options) - set(mounted_options)
|
|
337
338
|
if needed_options:
|
|
338
339
|
yield "mount -o remount,{0} {1}".format(options_string, path)
|
|
339
|
-
mounts[path]["options"] = options
|
|
340
340
|
|
|
341
341
|
else:
|
|
342
342
|
host.noop(
|
|
@@ -347,8 +347,8 @@ def mount(
|
|
|
347
347
|
)
|
|
348
348
|
|
|
349
349
|
|
|
350
|
-
@operation
|
|
351
|
-
def hostname(hostname, hostname_file=None):
|
|
350
|
+
@operation()
|
|
351
|
+
def hostname(hostname: str, hostname_file: str | None = None):
|
|
352
352
|
"""
|
|
353
353
|
Set the system hostname using ``hostnamectl`` or ``hostname`` on older systems.
|
|
354
354
|
|
|
@@ -379,7 +379,6 @@ def hostname(hostname, hostname_file=None):
|
|
|
379
379
|
if host.get_fact(Which, command="hostnamectl"):
|
|
380
380
|
if current_hostname != hostname:
|
|
381
381
|
yield "hostnamectl set-hostname {0}".format(hostname)
|
|
382
|
-
host.create_fact(Hostname, data=hostname)
|
|
383
382
|
else:
|
|
384
383
|
host.noop("hostname is set")
|
|
385
384
|
return
|
|
@@ -394,7 +393,6 @@ def hostname(hostname, hostname_file=None):
|
|
|
394
393
|
|
|
395
394
|
if current_hostname != hostname:
|
|
396
395
|
yield "hostname {0}".format(hostname)
|
|
397
|
-
host.create_fact(Hostname, data=hostname)
|
|
398
396
|
else:
|
|
399
397
|
host.noop("hostname is set")
|
|
400
398
|
|
|
@@ -403,13 +401,13 @@ def hostname(hostname, hostname_file=None):
|
|
|
403
401
|
file = StringIO("{0}\n".format(hostname))
|
|
404
402
|
|
|
405
403
|
# And ensure it exists
|
|
406
|
-
yield from files.put(file, hostname_file)
|
|
404
|
+
yield from files.put._inner(src=file, dest=hostname_file)
|
|
407
405
|
|
|
408
406
|
|
|
409
|
-
@operation
|
|
407
|
+
@operation()
|
|
410
408
|
def sysctl(
|
|
411
|
-
key,
|
|
412
|
-
value,
|
|
409
|
+
key: str,
|
|
410
|
+
value: str | int | list[str | int],
|
|
413
411
|
persist=False,
|
|
414
412
|
persist_file="/etc/sysctl.conf",
|
|
415
413
|
):
|
|
@@ -437,35 +435,34 @@ def sysctl(
|
|
|
437
435
|
|
|
438
436
|
value = [try_int(v) for v in value] if isinstance(value, list) else try_int(value)
|
|
439
437
|
|
|
440
|
-
existing_sysctls = host.get_fact(Sysctl)
|
|
441
|
-
|
|
438
|
+
existing_sysctls = host.get_fact(Sysctl, keys=[key])
|
|
442
439
|
existing_value = existing_sysctls.get(key)
|
|
440
|
+
|
|
443
441
|
if not existing_value or existing_value != value:
|
|
444
442
|
yield "sysctl {0}='{1}'".format(key, string_value)
|
|
445
|
-
existing_sysctls[key] = value
|
|
446
443
|
else:
|
|
447
444
|
host.noop("sysctl {0} is set to {1}".format(key, string_value))
|
|
448
445
|
|
|
449
446
|
if persist:
|
|
450
|
-
yield from files.line(
|
|
447
|
+
yield from files.line._inner(
|
|
451
448
|
path=persist_file,
|
|
452
449
|
line="{0}[[:space:]]*=[[:space:]]*{1}".format(key, string_value),
|
|
453
450
|
replace="{0} = {1}".format(key, string_value),
|
|
454
451
|
)
|
|
455
452
|
|
|
456
453
|
|
|
457
|
-
@operation
|
|
454
|
+
@operation()
|
|
458
455
|
def service(
|
|
459
|
-
service,
|
|
456
|
+
service: str,
|
|
460
457
|
running=True,
|
|
461
458
|
restarted=False,
|
|
462
459
|
reloaded=False,
|
|
463
|
-
command=None,
|
|
464
|
-
enabled=None,
|
|
460
|
+
command: str | None = None,
|
|
461
|
+
enabled: bool | None = None,
|
|
465
462
|
):
|
|
466
463
|
"""
|
|
467
464
|
Manage the state of services. This command checks for the presence of all the
|
|
468
|
-
Linux init systems
|
|
465
|
+
Linux init systems pyinfra can handle and executes the relevant operation.
|
|
469
466
|
|
|
470
467
|
+ service: name of the service to manage
|
|
471
468
|
+ running: whether the service should be running
|
|
@@ -485,6 +482,8 @@ def service(
|
|
|
485
482
|
)
|
|
486
483
|
"""
|
|
487
484
|
|
|
485
|
+
service_operation: "PyinfraOperation"
|
|
486
|
+
|
|
488
487
|
if host.get_fact(Which, command="systemctl"):
|
|
489
488
|
service_operation = systemd.service
|
|
490
489
|
|
|
@@ -494,6 +493,9 @@ def service(
|
|
|
494
493
|
elif host.get_fact(Which, command="initctl"):
|
|
495
494
|
service_operation = upstart.service
|
|
496
495
|
|
|
496
|
+
elif host.get_fact(Which, command="sv"):
|
|
497
|
+
service_operation = runit.service
|
|
498
|
+
|
|
497
499
|
elif (
|
|
498
500
|
host.get_fact(Which, command="service")
|
|
499
501
|
or host.get_fact(Link, path="/etc/init.d")
|
|
@@ -503,7 +505,7 @@ def service(
|
|
|
503
505
|
|
|
504
506
|
# NOTE: important that we are not Linux here because /etc/rc.d will exist but checking it's
|
|
505
507
|
# contents may trigger things (like a reboot: https://github.com/Fizzadar/pyinfra/issues/819)
|
|
506
|
-
elif host.get_fact(Os) != "Linux" and host.get_fact(Directory, path="/etc/rc.d"):
|
|
508
|
+
elif host.get_fact(Os) != "Linux" and bool(host.get_fact(Directory, path="/etc/rc.d")):
|
|
507
509
|
service_operation = bsdinit.service
|
|
508
510
|
|
|
509
511
|
else:
|
|
@@ -511,8 +513,8 @@ def service(
|
|
|
511
513
|
("No init system found " "(no systemctl, initctl, /etc/init.d or /etc/rc.d found)"),
|
|
512
514
|
)
|
|
513
515
|
|
|
514
|
-
yield from service_operation(
|
|
515
|
-
service,
|
|
516
|
+
yield from service_operation._inner(
|
|
517
|
+
service=service,
|
|
516
518
|
running=running,
|
|
517
519
|
restarted=restarted,
|
|
518
520
|
reloaded=reloaded,
|
|
@@ -521,14 +523,14 @@ def service(
|
|
|
521
523
|
)
|
|
522
524
|
|
|
523
525
|
|
|
524
|
-
@operation
|
|
526
|
+
@operation()
|
|
525
527
|
def packages(
|
|
526
|
-
packages,
|
|
528
|
+
packages: str | list[str],
|
|
527
529
|
present=True,
|
|
528
530
|
):
|
|
529
531
|
"""
|
|
530
532
|
Add or remove system packages. This command checks for the presence of all the
|
|
531
|
-
system package managers
|
|
533
|
+
system package managers pyinfra can handle and executes the relevant operation.
|
|
532
534
|
|
|
533
535
|
+ packages: list of packages to ensure
|
|
534
536
|
+ present: whether the packages should be installed
|
|
@@ -543,6 +545,8 @@ def packages(
|
|
|
543
545
|
)
|
|
544
546
|
"""
|
|
545
547
|
|
|
548
|
+
package_operation: "PyinfraOperation"
|
|
549
|
+
|
|
546
550
|
# TODO: improve this - use LinuxDistribution fact + mapping with fallback below?
|
|
547
551
|
# Here to be preferred on openSUSE which also provides aptitude
|
|
548
552
|
# See: https://github.com/Fizzadar/pyinfra/issues/799
|
|
@@ -581,21 +585,21 @@ def packages(
|
|
|
581
585
|
),
|
|
582
586
|
)
|
|
583
587
|
|
|
584
|
-
yield from package_operation(packages=packages, present=present)
|
|
588
|
+
yield from package_operation._inner(packages=packages, present=present)
|
|
585
589
|
|
|
586
590
|
|
|
587
|
-
@operation
|
|
591
|
+
@operation()
|
|
588
592
|
def crontab(
|
|
589
|
-
command,
|
|
593
|
+
command: str,
|
|
590
594
|
present=True,
|
|
591
|
-
user=None,
|
|
592
|
-
cron_name=None,
|
|
595
|
+
user: str | None = None,
|
|
596
|
+
cron_name: str | None = None,
|
|
593
597
|
minute="*",
|
|
594
598
|
hour="*",
|
|
595
599
|
month="*",
|
|
596
600
|
day_of_week="*",
|
|
597
601
|
day_of_month="*",
|
|
598
|
-
special_time=None,
|
|
602
|
+
special_time: str | None = None,
|
|
599
603
|
interpolate_variables=False,
|
|
600
604
|
):
|
|
601
605
|
"""
|
|
@@ -657,6 +661,8 @@ def crontab(
|
|
|
657
661
|
|
|
658
662
|
if not existing_crontab and cron_name: # find the crontab by name if provided
|
|
659
663
|
for cmd, details in crontab.items():
|
|
664
|
+
if not details["comments"]:
|
|
665
|
+
continue
|
|
660
666
|
if name_comment in details["comments"]:
|
|
661
667
|
existing_crontab = details
|
|
662
668
|
existing_crontab_match = cmd
|
|
@@ -664,8 +670,8 @@ def crontab(
|
|
|
664
670
|
|
|
665
671
|
exists = existing_crontab is not None
|
|
666
672
|
|
|
667
|
-
edit_commands = []
|
|
668
|
-
temp_filename =
|
|
673
|
+
edit_commands: list[str | StringCommand] = []
|
|
674
|
+
temp_filename = host.get_temp_filename()
|
|
669
675
|
|
|
670
676
|
if special_time:
|
|
671
677
|
new_crontab_line = "{0} {1}".format(special_time, command)
|
|
@@ -713,6 +719,7 @@ def crontab(
|
|
|
713
719
|
|
|
714
720
|
# We have the cron and it exists, do it's details? If not, replace the line
|
|
715
721
|
elif present and exists:
|
|
722
|
+
assert existing_crontab is not None
|
|
716
723
|
if any(
|
|
717
724
|
(
|
|
718
725
|
special_time != existing_crontab.get("special_time"),
|
|
@@ -748,20 +755,6 @@ def crontab(
|
|
|
748
755
|
|
|
749
756
|
# Finally, use the tempfile to write a new crontab
|
|
750
757
|
yield "crontab {0} {1}".format(" ".join(crontab_args), temp_filename)
|
|
751
|
-
|
|
752
|
-
# Update the crontab fact
|
|
753
|
-
if present:
|
|
754
|
-
crontab[command] = {
|
|
755
|
-
"special_time": special_time,
|
|
756
|
-
"minute": minute,
|
|
757
|
-
"hour": hour,
|
|
758
|
-
"month": month,
|
|
759
|
-
"day_of_week": day_of_week,
|
|
760
|
-
"day_of_month": day_of_month,
|
|
761
|
-
"comments": [cron_name] if cron_name else [],
|
|
762
|
-
}
|
|
763
|
-
else:
|
|
764
|
-
crontab.pop(command)
|
|
765
758
|
else:
|
|
766
759
|
host.noop(
|
|
767
760
|
"crontab {0} {1}".format(
|
|
@@ -771,8 +764,8 @@ def crontab(
|
|
|
771
764
|
)
|
|
772
765
|
|
|
773
766
|
|
|
774
|
-
@operation
|
|
775
|
-
def group(group, present=True, system=False, gid=None):
|
|
767
|
+
@operation()
|
|
768
|
+
def group(group: str, present=True, system=False, gid: int | str | None = None):
|
|
776
769
|
"""
|
|
777
770
|
Add/remove system groups.
|
|
778
771
|
|
|
@@ -811,7 +804,6 @@ def group(group, present=True, system=False, gid=None):
|
|
|
811
804
|
yield "pw groupdel -n {0}".format(group)
|
|
812
805
|
else:
|
|
813
806
|
yield "groupdel {0}".format(group)
|
|
814
|
-
groups.remove(group)
|
|
815
807
|
|
|
816
808
|
# Group doesn't exist and we want it?
|
|
817
809
|
elif present and not is_present:
|
|
@@ -837,18 +829,17 @@ def group(group, present=True, system=False, gid=None):
|
|
|
837
829
|
group_add_command = "groupadd"
|
|
838
830
|
if os_type == "FreeBSD":
|
|
839
831
|
group_add_command = "pw groupadd"
|
|
840
|
-
yield "
|
|
841
|
-
groups.append(group)
|
|
832
|
+
yield "{0} {1}".format(group_add_command, " ".join(args))
|
|
842
833
|
|
|
843
834
|
|
|
844
|
-
@operation
|
|
835
|
+
@operation()
|
|
845
836
|
def user_authorized_keys(
|
|
846
|
-
user,
|
|
847
|
-
public_keys,
|
|
848
|
-
group=None,
|
|
837
|
+
user: str,
|
|
838
|
+
public_keys: str | list[str],
|
|
839
|
+
group: str | None = None,
|
|
849
840
|
delete_keys=False,
|
|
850
|
-
authorized_key_directory=None,
|
|
851
|
-
authorized_key_filename=None,
|
|
841
|
+
authorized_key_directory: str | None = None,
|
|
842
|
+
authorized_key_filename: str | None = None,
|
|
852
843
|
):
|
|
853
844
|
"""
|
|
854
845
|
Manage `authorized_keys` of system users.
|
|
@@ -860,7 +851,7 @@ def user_authorized_keys(
|
|
|
860
851
|
|
|
861
852
|
Public keys:
|
|
862
853
|
These can be provided as strings containing the public key or as a path to
|
|
863
|
-
a public key file which
|
|
854
|
+
a public key file which pyinfra will read.
|
|
864
855
|
|
|
865
856
|
**Examples:**
|
|
866
857
|
|
|
@@ -872,8 +863,11 @@ def user_authorized_keys(
|
|
|
872
863
|
public_keys=["ed25519..."],
|
|
873
864
|
)
|
|
874
865
|
"""
|
|
866
|
+
|
|
875
867
|
if not authorized_key_directory:
|
|
876
|
-
|
|
868
|
+
home = host.get_fact(Home, user=user)
|
|
869
|
+
authorized_key_directory = f"{home}/.ssh"
|
|
870
|
+
|
|
877
871
|
if not authorized_key_filename:
|
|
878
872
|
authorized_key_filename = "authorized_keys"
|
|
879
873
|
|
|
@@ -889,15 +883,15 @@ def user_authorized_keys(
|
|
|
889
883
|
with open(try_path, "r") as f:
|
|
890
884
|
return f.read().strip()
|
|
891
885
|
|
|
892
|
-
return key
|
|
886
|
+
return key.strip()
|
|
893
887
|
|
|
894
888
|
public_keys = list(map(read_any_pub_key_file, public_keys))
|
|
895
889
|
|
|
896
890
|
# Ensure .ssh directory
|
|
897
891
|
# note that this always outputs commands unless the SSH user has access to the
|
|
898
892
|
# authorized_keys file, ie the SSH user is the user defined in this function
|
|
899
|
-
yield from files.directory(
|
|
900
|
-
authorized_key_directory,
|
|
893
|
+
yield from files.directory._inner(
|
|
894
|
+
path=authorized_key_directory,
|
|
901
895
|
user=user,
|
|
902
896
|
group=group or user,
|
|
903
897
|
mode=700,
|
|
@@ -914,7 +908,7 @@ def user_authorized_keys(
|
|
|
914
908
|
)
|
|
915
909
|
|
|
916
910
|
# And ensure it exists
|
|
917
|
-
yield from files.put(
|
|
911
|
+
yield from files.put._inner(
|
|
918
912
|
src=keys_file,
|
|
919
913
|
dest=authorized_key_file,
|
|
920
914
|
user=user,
|
|
@@ -924,7 +918,7 @@ def user_authorized_keys(
|
|
|
924
918
|
|
|
925
919
|
else:
|
|
926
920
|
# Ensure authorized_keys exists
|
|
927
|
-
yield from files.file(
|
|
921
|
+
yield from files.file._inner(
|
|
928
922
|
path=authorized_key_file,
|
|
929
923
|
user=user,
|
|
930
924
|
group=group or user,
|
|
@@ -933,27 +927,27 @@ def user_authorized_keys(
|
|
|
933
927
|
|
|
934
928
|
# And every public key is present
|
|
935
929
|
for key in public_keys:
|
|
936
|
-
yield from files.line(path=authorized_key_file, line=key, ensure_newline=True)
|
|
930
|
+
yield from files.line._inner(path=authorized_key_file, line=key, ensure_newline=True)
|
|
937
931
|
|
|
938
932
|
|
|
939
|
-
@operation
|
|
933
|
+
@operation()
|
|
940
934
|
def user(
|
|
941
|
-
user,
|
|
935
|
+
user: str,
|
|
942
936
|
present=True,
|
|
943
|
-
home=None,
|
|
944
|
-
shell=None,
|
|
945
|
-
group=None,
|
|
946
|
-
groups=None,
|
|
947
|
-
public_keys=None,
|
|
937
|
+
home: str | None = None,
|
|
938
|
+
shell: str | None = None,
|
|
939
|
+
group: str | None = None,
|
|
940
|
+
groups: list[str] | None = None,
|
|
941
|
+
public_keys: str | list[str] | None = None,
|
|
948
942
|
delete_keys=False,
|
|
949
943
|
ensure_home=True,
|
|
950
944
|
create_home=False,
|
|
951
945
|
system=False,
|
|
952
|
-
uid=None,
|
|
953
|
-
comment=None,
|
|
946
|
+
uid: int | None = None,
|
|
947
|
+
comment: str | None = None,
|
|
954
948
|
add_deploy_dir=True,
|
|
955
949
|
unique=True,
|
|
956
|
-
password=None,
|
|
950
|
+
password: str | None = None,
|
|
957
951
|
):
|
|
958
952
|
"""
|
|
959
953
|
Add/remove/update system users & their ssh `authorized_keys`.
|
|
@@ -983,7 +977,7 @@ def user(
|
|
|
983
977
|
|
|
984
978
|
Public keys:
|
|
985
979
|
These can be provided as strings containing the public key or as a path to
|
|
986
|
-
a public key file which
|
|
980
|
+
a public key file which pyinfra will read.
|
|
987
981
|
|
|
988
982
|
**Examples:**
|
|
989
983
|
|
|
@@ -1029,7 +1023,6 @@ def user(
|
|
|
1029
1023
|
yield "pw userdel -n {0}".format(user)
|
|
1030
1024
|
else:
|
|
1031
1025
|
yield "userdel {0}".format(user)
|
|
1032
|
-
users.pop(user)
|
|
1033
1026
|
return
|
|
1034
1027
|
|
|
1035
1028
|
# User doesn't exist but we want them?
|
|
@@ -1079,29 +1072,20 @@ def user(
|
|
|
1079
1072
|
|
|
1080
1073
|
# Users are often added by other operations (package installs), so check
|
|
1081
1074
|
# for the user at runtime before adding.
|
|
1082
|
-
|
|
1083
1075
|
add_user_command = "useradd"
|
|
1084
1076
|
if os_type == "FreeBSD":
|
|
1085
1077
|
add_user_command = "pw useradd"
|
|
1086
|
-
yield "
|
|
1078
|
+
yield "{0} -n {2} {1}".format(
|
|
1087
1079
|
add_user_command,
|
|
1088
1080
|
" ".join(args),
|
|
1089
1081
|
user,
|
|
1090
1082
|
)
|
|
1091
1083
|
else:
|
|
1092
|
-
yield "
|
|
1084
|
+
yield "{0} {1} {2}".format(
|
|
1093
1085
|
add_user_command,
|
|
1094
1086
|
" ".join(args),
|
|
1095
1087
|
user,
|
|
1096
1088
|
)
|
|
1097
|
-
users[user] = {
|
|
1098
|
-
"comment": comment,
|
|
1099
|
-
"home": home,
|
|
1100
|
-
"shell": shell,
|
|
1101
|
-
"group": group,
|
|
1102
|
-
"groups": groups,
|
|
1103
|
-
"password": password,
|
|
1104
|
-
}
|
|
1105
1089
|
|
|
1106
1090
|
# User exists and we want them, check home/shell/keys/password
|
|
1107
1091
|
else:
|
|
@@ -1149,9 +1133,9 @@ def user(
|
|
|
1149
1133
|
existing_user["password"] = password
|
|
1150
1134
|
|
|
1151
1135
|
# Ensure home directory ownership
|
|
1152
|
-
if ensure_home:
|
|
1153
|
-
yield from files.directory(
|
|
1154
|
-
home,
|
|
1136
|
+
if ensure_home and home:
|
|
1137
|
+
yield from files.directory._inner(
|
|
1138
|
+
path=home,
|
|
1155
1139
|
user=user,
|
|
1156
1140
|
group=group or user,
|
|
1157
1141
|
# Don't fail if the home directory exists as a link
|
|
@@ -1160,9 +1144,9 @@ def user(
|
|
|
1160
1144
|
|
|
1161
1145
|
# Add SSH keys
|
|
1162
1146
|
if public_keys is not None:
|
|
1163
|
-
yield from user_authorized_keys(
|
|
1164
|
-
user,
|
|
1165
|
-
public_keys,
|
|
1147
|
+
yield from user_authorized_keys._inner(
|
|
1148
|
+
user=user,
|
|
1149
|
+
public_keys=public_keys,
|
|
1166
1150
|
group=group,
|
|
1167
1151
|
delete_keys=delete_keys,
|
|
1168
1152
|
authorized_key_directory="{0}/.ssh".format(home),
|
|
@@ -1170,9 +1154,9 @@ def user(
|
|
|
1170
1154
|
)
|
|
1171
1155
|
|
|
1172
1156
|
|
|
1173
|
-
@operation
|
|
1157
|
+
@operation()
|
|
1174
1158
|
def locale(
|
|
1175
|
-
locale,
|
|
1159
|
+
locale: str,
|
|
1176
1160
|
present=True,
|
|
1177
1161
|
):
|
|
1178
1162
|
"""
|
|
@@ -1221,7 +1205,7 @@ def locale(
|
|
|
1221
1205
|
if not present and locale in locales:
|
|
1222
1206
|
logger.debug(f"Removing locale {locale}")
|
|
1223
1207
|
|
|
1224
|
-
yield from files.line(
|
|
1208
|
+
yield from files.line._inner(
|
|
1225
1209
|
path=locales_definitions_file, line=f"^{matching_line}$", replace=f"# {matching_line}"
|
|
1226
1210
|
)
|
|
1227
1211
|
|
|
@@ -1231,7 +1215,7 @@ def locale(
|
|
|
1231
1215
|
if present and locale not in locales:
|
|
1232
1216
|
logger.debug(f"Adding locale {locale}")
|
|
1233
1217
|
|
|
1234
|
-
yield from files.replace(
|
|
1218
|
+
yield from files.replace._inner(
|
|
1235
1219
|
path=locales_definitions_file,
|
|
1236
1220
|
text=f"^{matching_line}$",
|
|
1237
1221
|
replace=f"{matching_line}".replace("# ", ""),
|
|
@@ -1240,12 +1224,12 @@ def locale(
|
|
|
1240
1224
|
yield "locale-gen"
|
|
1241
1225
|
|
|
1242
1226
|
|
|
1243
|
-
@operation
|
|
1227
|
+
@operation()
|
|
1244
1228
|
def security_limit(
|
|
1245
|
-
domain,
|
|
1246
|
-
limit_type,
|
|
1247
|
-
item,
|
|
1248
|
-
value,
|
|
1229
|
+
domain: str,
|
|
1230
|
+
limit_type: str,
|
|
1231
|
+
item: str,
|
|
1232
|
+
value: int,
|
|
1249
1233
|
):
|
|
1250
1234
|
"""
|
|
1251
1235
|
Edit /etc/security/limits.conf configuration.
|
|
@@ -1264,13 +1248,13 @@ def security_limit(
|
|
|
1264
1248
|
domain='*',
|
|
1265
1249
|
limit_type='soft',
|
|
1266
1250
|
item='nofile',
|
|
1267
|
-
value=
|
|
1251
|
+
value=1024,
|
|
1268
1252
|
)
|
|
1269
1253
|
"""
|
|
1270
1254
|
|
|
1271
1255
|
line_format = f"{domain}\t{limit_type}\t{item}\t{value}"
|
|
1272
1256
|
|
|
1273
|
-
yield from files.line(
|
|
1257
|
+
yield from files.line._inner(
|
|
1274
1258
|
path="/etc/security/limits.conf",
|
|
1275
1259
|
line=f"^{domain}[[:space:]]+{limit_type}[[:space:]]+{item}",
|
|
1276
1260
|
replace=line_format,
|
pyinfra/operations/snap.py
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
Manage snap packages. See https://snapcraft.io/
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
from pyinfra import host
|
|
6
8
|
from pyinfra.api import operation
|
|
7
9
|
from pyinfra.facts.snap import SnapPackage, SnapPackages
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
@operation
|
|
12
|
+
@operation()
|
|
11
13
|
def package(
|
|
12
|
-
packages=None,
|
|
14
|
+
packages: str | list[str] | None = None,
|
|
13
15
|
channel="latest/stable",
|
|
14
16
|
classic=False,
|
|
15
17
|
present=True,
|
|
@@ -91,14 +93,12 @@ def package(
|
|
|
91
93
|
else:
|
|
92
94
|
# we don't want it
|
|
93
95
|
remove_packages.append(package)
|
|
94
|
-
snap_packages.remove(package)
|
|
95
96
|
|
|
96
97
|
# it's not installed
|
|
97
98
|
if package not in snap_packages:
|
|
98
99
|
# we want it
|
|
99
100
|
if present:
|
|
100
101
|
install_packages.append(package)
|
|
101
|
-
snap_packages.append(package)
|
|
102
102
|
|
|
103
103
|
# we don't want it
|
|
104
104
|
else:
|