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/vzctl.py
CHANGED
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
5
7
|
from pyinfra.api import FactBase
|
|
6
8
|
|
|
7
9
|
|
|
@@ -20,14 +22,17 @@ class OpenvzContainers(FactBase):
|
|
|
20
22
|
}
|
|
21
23
|
"""
|
|
22
24
|
|
|
25
|
+
@override
|
|
23
26
|
def command(self) -> str:
|
|
24
27
|
return "vzlist -a -j"
|
|
25
28
|
|
|
29
|
+
@override
|
|
26
30
|
def requires_command(self) -> str:
|
|
27
31
|
return "vzlist"
|
|
28
32
|
|
|
29
33
|
default = dict
|
|
30
34
|
|
|
35
|
+
@override
|
|
31
36
|
def process(self, output):
|
|
32
37
|
combined_json = "".join(output)
|
|
33
38
|
vz_data = json.loads(combined_json)
|
pyinfra/facts/xbps.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
3
5
|
from pyinfra.api import FactBase
|
|
4
6
|
|
|
5
7
|
from .util.packaging import parse_packages
|
|
@@ -16,15 +18,18 @@ class XbpsPackages(FactBase):
|
|
|
16
18
|
}
|
|
17
19
|
"""
|
|
18
20
|
|
|
21
|
+
@override
|
|
19
22
|
def requires_command(self) -> str:
|
|
20
23
|
return "xbps-query"
|
|
21
24
|
|
|
22
25
|
default = dict
|
|
23
26
|
|
|
24
|
-
regex = r"^.. ([a-zA-Z0-9_
|
|
27
|
+
regex = r"^.. ([a-zA-Z0-9_\-\+\.]+)\-([0-9a-z\.]+_[0-9]+)"
|
|
25
28
|
|
|
29
|
+
@override
|
|
26
30
|
def command(self):
|
|
27
31
|
return "xbps-query -l"
|
|
28
32
|
|
|
33
|
+
@override
|
|
29
34
|
def process(self, output):
|
|
30
35
|
return parse_packages(self.regex, output)
|
pyinfra/facts/yum.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
3
5
|
from pyinfra.api import FactBase
|
|
4
6
|
|
|
5
7
|
from .util import make_cat_files_command
|
|
@@ -23,16 +25,19 @@ class YumRepositories(FactBase):
|
|
|
23
25
|
]
|
|
24
26
|
"""
|
|
25
27
|
|
|
28
|
+
@override
|
|
26
29
|
def command(self) -> str:
|
|
27
30
|
return make_cat_files_command(
|
|
28
31
|
"/etc/yum.conf",
|
|
29
32
|
"/etc/yum.repos.d/*.repo",
|
|
30
33
|
)
|
|
31
34
|
|
|
35
|
+
@override
|
|
32
36
|
def requires_command(self) -> str:
|
|
33
37
|
return "yum"
|
|
34
38
|
|
|
35
39
|
default = list
|
|
36
40
|
|
|
41
|
+
@override
|
|
37
42
|
def process(self, output):
|
|
38
43
|
return parse_yum_repositories(output)
|
pyinfra/facts/zfs.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Gather information about ZFS filesystems.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
5
7
|
from pyinfra.api import FactBase, ShortFactBase
|
|
6
8
|
|
|
7
9
|
|
|
@@ -15,43 +17,61 @@ def _process_zfs_props_table(output):
|
|
|
15
17
|
return datasets
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
class
|
|
19
|
-
|
|
20
|
+
class ZfsPools(FactBase):
|
|
21
|
+
@override
|
|
22
|
+
def command(self) -> str:
|
|
20
23
|
return "zpool get -H all"
|
|
21
24
|
|
|
22
|
-
@
|
|
23
|
-
def
|
|
25
|
+
@override
|
|
26
|
+
def requires_command(self) -> str:
|
|
27
|
+
return "zpool"
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def process(self, output):
|
|
24
31
|
return _process_zfs_props_table(output)
|
|
25
32
|
|
|
26
33
|
|
|
27
|
-
class
|
|
28
|
-
|
|
34
|
+
class ZfsDatasets(FactBase):
|
|
35
|
+
@override
|
|
36
|
+
def command(self) -> str:
|
|
29
37
|
return "zfs get -H all"
|
|
30
38
|
|
|
31
|
-
@
|
|
32
|
-
def
|
|
39
|
+
@override
|
|
40
|
+
def requires_command(self) -> str:
|
|
41
|
+
return "zfs"
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
def process(self, output):
|
|
33
45
|
return _process_zfs_props_table(output)
|
|
34
46
|
|
|
35
47
|
|
|
36
|
-
class
|
|
37
|
-
fact =
|
|
48
|
+
class ZfsFilesystems(ShortFactBase):
|
|
49
|
+
fact = ZfsDatasets
|
|
38
50
|
|
|
39
|
-
@
|
|
40
|
-
def process_data(data):
|
|
51
|
+
@override
|
|
52
|
+
def process_data(self, data):
|
|
41
53
|
return {name: props for name, props in data.items() if props.get("type") == "filesystem"}
|
|
42
54
|
|
|
43
55
|
|
|
44
|
-
class
|
|
45
|
-
fact =
|
|
56
|
+
class ZfsSnapshots(ShortFactBase):
|
|
57
|
+
fact = ZfsDatasets
|
|
46
58
|
|
|
47
|
-
@
|
|
48
|
-
def process_data(data):
|
|
59
|
+
@override
|
|
60
|
+
def process_data(self, data):
|
|
49
61
|
return {name: props for name, props in data.items() if props.get("type") == "snapshot"}
|
|
50
62
|
|
|
51
63
|
|
|
52
|
-
class
|
|
53
|
-
fact =
|
|
64
|
+
class ZfsVolumes(ShortFactBase):
|
|
65
|
+
fact = ZfsDatasets
|
|
54
66
|
|
|
55
|
-
@
|
|
56
|
-
def process_data(data):
|
|
67
|
+
@override
|
|
68
|
+
def process_data(self, data):
|
|
57
69
|
return {name: props for name, props in data.items() if props.get("type") == "volume"}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# TODO: remove these in v4! Or flip the convention and remove all the other fact prefixes!
|
|
73
|
+
Pools = ZfsPools
|
|
74
|
+
Datasets = ZfsDatasets
|
|
75
|
+
Filesystems = ZfsFilesystems
|
|
76
|
+
Snapshots = ZfsSnapshots
|
|
77
|
+
Volumes = ZfsVolumes
|
pyinfra/facts/zypper.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
3
5
|
from pyinfra.api import FactBase
|
|
4
6
|
|
|
5
7
|
from .util import make_cat_files_command
|
|
@@ -23,15 +25,18 @@ class ZypperRepositories(FactBase):
|
|
|
23
25
|
]
|
|
24
26
|
"""
|
|
25
27
|
|
|
28
|
+
@override
|
|
26
29
|
def command(self) -> str:
|
|
27
30
|
return make_cat_files_command(
|
|
28
31
|
"/etc/zypp/repos.d/*.repo",
|
|
29
32
|
)
|
|
30
33
|
|
|
34
|
+
@override
|
|
31
35
|
def requires_command(self) -> str:
|
|
32
36
|
return "zypper"
|
|
33
37
|
|
|
34
38
|
default = list
|
|
35
39
|
|
|
40
|
+
@override
|
|
36
41
|
def process(self, output):
|
|
37
42
|
return parse_zypper_repositories(output)
|
pyinfra/local.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from os import path
|
|
2
|
+
from typing import Optional
|
|
2
3
|
|
|
3
4
|
import click
|
|
4
5
|
|
|
@@ -10,7 +11,7 @@ from pyinfra.connectors.util import run_local_process
|
|
|
10
11
|
from pyinfra.context import ctx_state
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
def include(filename: str):
|
|
14
|
+
def include(filename: str, data: Optional[dict] = None):
|
|
14
15
|
"""
|
|
15
16
|
Executes a local python file within the ``pyinfra.state.cwd``
|
|
16
17
|
directory.
|
|
@@ -33,7 +34,7 @@ def include(filename: str):
|
|
|
33
34
|
|
|
34
35
|
from pyinfra_cli.util import exec_file
|
|
35
36
|
|
|
36
|
-
with host.deploy(path.relpath(filename, state.cwd), None,
|
|
37
|
+
with host.deploy(path.relpath(filename, state.cwd), None, data, in_deploy=False):
|
|
37
38
|
exec_file(filename)
|
|
38
39
|
|
|
39
40
|
# One potential solution to the above is to add local as an actual
|
pyinfra/operations/apt.py
CHANGED
|
@@ -9,7 +9,13 @@ from urllib.parse import urlparse
|
|
|
9
9
|
|
|
10
10
|
from pyinfra import host
|
|
11
11
|
from pyinfra.api import OperationError, operation
|
|
12
|
-
from pyinfra.facts.apt import
|
|
12
|
+
from pyinfra.facts.apt import (
|
|
13
|
+
AptKeys,
|
|
14
|
+
AptSources,
|
|
15
|
+
SimulateOperationWillChange,
|
|
16
|
+
noninteractive_apt,
|
|
17
|
+
parse_apt_repo,
|
|
18
|
+
)
|
|
13
19
|
from pyinfra.facts.deb import DebPackage, DebPackages
|
|
14
20
|
from pyinfra.facts.files import File
|
|
15
21
|
from pyinfra.facts.gpg import GpgKey
|
|
@@ -21,21 +27,22 @@ from .util.packaging import ensure_packages
|
|
|
21
27
|
APT_UPDATE_FILENAME = "/var/lib/apt/periodic/update-success-stamp"
|
|
22
28
|
|
|
23
29
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
def _simulate_then_perform(command: str):
|
|
31
|
+
changes = host.get_fact(SimulateOperationWillChange, command)
|
|
32
|
+
|
|
33
|
+
if not changes:
|
|
34
|
+
# Simulating apt-get command failed, so the actual
|
|
35
|
+
# operation will probably fail too:
|
|
36
|
+
yield noninteractive_apt(command)
|
|
37
|
+
elif (
|
|
38
|
+
changes["upgraded"] == 0
|
|
39
|
+
and changes["newly_installed"] == 0
|
|
40
|
+
and changes["removed"] == 0
|
|
41
|
+
and changes["not_upgraded"] == 0
|
|
42
|
+
):
|
|
43
|
+
host.noop(f"{command} skipped, no changes would be performed")
|
|
44
|
+
else:
|
|
45
|
+
yield noninteractive_apt(command)
|
|
39
46
|
|
|
40
47
|
|
|
41
48
|
@operation()
|
|
@@ -321,12 +328,12 @@ def update(cache_time: int | None = None):
|
|
|
321
328
|
_update = update # noqa: E305
|
|
322
329
|
|
|
323
330
|
|
|
324
|
-
@operation(
|
|
331
|
+
@operation()
|
|
325
332
|
def upgrade(auto_remove: bool = False):
|
|
326
333
|
"""
|
|
327
334
|
Upgrades all apt packages.
|
|
328
335
|
|
|
329
|
-
+
|
|
336
|
+
+ auto_remove: removes transitive dependencies that are no longer needed.
|
|
330
337
|
|
|
331
338
|
**Example:**
|
|
332
339
|
|
|
@@ -349,17 +356,19 @@ def upgrade(auto_remove: bool = False):
|
|
|
349
356
|
if auto_remove:
|
|
350
357
|
command.append("--autoremove")
|
|
351
358
|
|
|
352
|
-
yield
|
|
359
|
+
yield from _simulate_then_perform(" ".join(command))
|
|
353
360
|
|
|
354
361
|
|
|
355
362
|
_upgrade = upgrade # noqa: E305 (for use below where update is a kwarg)
|
|
356
363
|
|
|
357
364
|
|
|
358
|
-
@operation(
|
|
359
|
-
def dist_upgrade():
|
|
365
|
+
@operation()
|
|
366
|
+
def dist_upgrade(auto_remove: bool = False):
|
|
360
367
|
"""
|
|
361
368
|
Updates all apt packages, employing dist-upgrade.
|
|
362
369
|
|
|
370
|
+
+ auto_remove: removes transitive dependencies that are no longer needed.
|
|
371
|
+
|
|
363
372
|
**Example:**
|
|
364
373
|
|
|
365
374
|
.. code:: python
|
|
@@ -369,7 +378,12 @@ def dist_upgrade():
|
|
|
369
378
|
)
|
|
370
379
|
"""
|
|
371
380
|
|
|
372
|
-
|
|
381
|
+
command = ["dist-upgrade"]
|
|
382
|
+
|
|
383
|
+
if auto_remove:
|
|
384
|
+
command.append("--autoremove")
|
|
385
|
+
|
|
386
|
+
yield from _simulate_then_perform(" ".join(command))
|
|
373
387
|
|
|
374
388
|
|
|
375
389
|
@operation()
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
|
|
5
|
+
from pyinfra import host
|
|
6
|
+
from pyinfra.api import StringCommand, operation
|
|
7
|
+
from pyinfra.api.util import try_int
|
|
8
|
+
from pyinfra.facts.crontab import Crontab, CrontabFile
|
|
9
|
+
from pyinfra.operations.util.files import sed_replace
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@operation()
|
|
13
|
+
def crontab(
|
|
14
|
+
command: str,
|
|
15
|
+
present=True,
|
|
16
|
+
user: str | None = None,
|
|
17
|
+
cron_name: str | None = None,
|
|
18
|
+
minute="*",
|
|
19
|
+
hour="*",
|
|
20
|
+
month="*",
|
|
21
|
+
day_of_week="*",
|
|
22
|
+
day_of_month="*",
|
|
23
|
+
special_time: str | None = None,
|
|
24
|
+
interpolate_variables=False,
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Add/remove/update crontab entries.
|
|
28
|
+
|
|
29
|
+
+ command: the command for the cron
|
|
30
|
+
+ present: whether this cron command should exist
|
|
31
|
+
+ user: the user whose crontab to manage
|
|
32
|
+
+ cron_name: name the cronjob so future changes to the command will overwrite
|
|
33
|
+
+ modify_cron_name: modify the cron name
|
|
34
|
+
+ minute: which minutes to execute the cron
|
|
35
|
+
+ hour: which hours to execute the cron
|
|
36
|
+
+ month: which months to execute the cron
|
|
37
|
+
+ day_of_week: which day of the week to execute the cron
|
|
38
|
+
+ day_of_month: which day of the month to execute the cron
|
|
39
|
+
+ special_time: cron "nickname" time (@reboot, @daily, etc), overrides others
|
|
40
|
+
+ interpolate_variables: whether to interpolate variables in ``command``
|
|
41
|
+
|
|
42
|
+
Cron commands:
|
|
43
|
+
Unless ``name`` is specified the command is used to identify crontab entries.
|
|
44
|
+
This means commands must be unique within a given users crontab. If you require
|
|
45
|
+
multiple identical commands, provide a different name argument for each.
|
|
46
|
+
|
|
47
|
+
Special times:
|
|
48
|
+
When provided, ``special_time`` will be used instead of any values passed in
|
|
49
|
+
for ``minute``/``hour``/``month``/``day_of_week``/``day_of_month``.
|
|
50
|
+
|
|
51
|
+
**Example:**
|
|
52
|
+
|
|
53
|
+
.. code:: python
|
|
54
|
+
|
|
55
|
+
# simple example for a crontab
|
|
56
|
+
crontab.crontab(
|
|
57
|
+
name="Backup /etc weekly",
|
|
58
|
+
command="/bin/tar cf /tmp/etc_bup.tar /etc",
|
|
59
|
+
name="backup_etc",
|
|
60
|
+
day_of_week=0,
|
|
61
|
+
hour=1,
|
|
62
|
+
minute=0,
|
|
63
|
+
)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def comma_sep(value):
|
|
67
|
+
if isinstance(value, (list, tuple)):
|
|
68
|
+
return ",".join("{0}".format(v) for v in value)
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
minute = comma_sep(minute)
|
|
72
|
+
hour = comma_sep(hour)
|
|
73
|
+
month = comma_sep(month)
|
|
74
|
+
day_of_week = comma_sep(day_of_week)
|
|
75
|
+
day_of_month = comma_sep(day_of_month)
|
|
76
|
+
|
|
77
|
+
ctb0: CrontabFile | dict = host.get_fact(Crontab, user=user)
|
|
78
|
+
# facts from test are in dict
|
|
79
|
+
if isinstance(ctb0, dict):
|
|
80
|
+
ctb = CrontabFile(ctb0)
|
|
81
|
+
else:
|
|
82
|
+
ctb = ctb0
|
|
83
|
+
name_comment = "# pyinfra-name={0}".format(cron_name)
|
|
84
|
+
|
|
85
|
+
existing_crontab = ctb.get_command(command=command, name=cron_name)
|
|
86
|
+
existing_crontab_command = existing_crontab["command"] if existing_crontab else command
|
|
87
|
+
existing_crontab_match = existing_crontab["command"] if existing_crontab else command
|
|
88
|
+
|
|
89
|
+
exists = existing_crontab is not None
|
|
90
|
+
exists_name = existing_crontab is not None and name_comment in existing_crontab.get(
|
|
91
|
+
"comments", ""
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
edit_commands: list[str | StringCommand] = []
|
|
95
|
+
temp_filename = host.get_temp_filename()
|
|
96
|
+
|
|
97
|
+
if special_time:
|
|
98
|
+
new_crontab_line = "{0} {1}".format(special_time, command)
|
|
99
|
+
else:
|
|
100
|
+
new_crontab_line = "{minute} {hour} {day_of_month} {month} {day_of_week} {command}".format(
|
|
101
|
+
minute=minute,
|
|
102
|
+
hour=hour,
|
|
103
|
+
day_of_month=day_of_month,
|
|
104
|
+
month=month,
|
|
105
|
+
day_of_week=day_of_week,
|
|
106
|
+
command=command,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
existing_crontab_match = ".*{0}.*".format(existing_crontab_match)
|
|
110
|
+
|
|
111
|
+
# Don't want the cron and it does exist? Remove the line
|
|
112
|
+
if not present and exists:
|
|
113
|
+
edit_commands.append(
|
|
114
|
+
sed_replace(
|
|
115
|
+
temp_filename,
|
|
116
|
+
existing_crontab_match,
|
|
117
|
+
"",
|
|
118
|
+
interpolate_variables=interpolate_variables,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Want the cron but it doesn't exist? Append the line
|
|
123
|
+
elif present and not exists:
|
|
124
|
+
print("present", present, "exists", exists)
|
|
125
|
+
if ctb: # append a blank line if cron entries already exist
|
|
126
|
+
edit_commands.append("echo '' >> {0}".format(temp_filename))
|
|
127
|
+
if cron_name:
|
|
128
|
+
edit_commands.append(
|
|
129
|
+
"echo {0} >> {1}".format(
|
|
130
|
+
shlex.quote(name_comment),
|
|
131
|
+
temp_filename,
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
edit_commands.append(
|
|
136
|
+
"echo {0} >> {1}".format(
|
|
137
|
+
shlex.quote(new_crontab_line),
|
|
138
|
+
temp_filename,
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# We have the cron and it exists, do it's details? If not, replace the line
|
|
143
|
+
elif present and exists:
|
|
144
|
+
assert existing_crontab is not None
|
|
145
|
+
if any(
|
|
146
|
+
(
|
|
147
|
+
exists_name != (cron_name is not None),
|
|
148
|
+
special_time != existing_crontab.get("special_time"),
|
|
149
|
+
try_int(minute) != existing_crontab.get("minute"),
|
|
150
|
+
try_int(hour) != existing_crontab.get("hour"),
|
|
151
|
+
try_int(month) != existing_crontab.get("month"),
|
|
152
|
+
try_int(day_of_week) != existing_crontab.get("day_of_week"),
|
|
153
|
+
try_int(day_of_month) != existing_crontab.get("day_of_month"),
|
|
154
|
+
existing_crontab_command != command,
|
|
155
|
+
),
|
|
156
|
+
):
|
|
157
|
+
if not exists_name and cron_name:
|
|
158
|
+
new_crontab_line = f"{name_comment}\n{new_crontab_line}"
|
|
159
|
+
edit_commands.append(
|
|
160
|
+
sed_replace(
|
|
161
|
+
temp_filename,
|
|
162
|
+
existing_crontab_match,
|
|
163
|
+
new_crontab_line,
|
|
164
|
+
interpolate_variables=interpolate_variables,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if edit_commands:
|
|
169
|
+
crontab_args = []
|
|
170
|
+
if user:
|
|
171
|
+
crontab_args.append("-u {0}".format(user))
|
|
172
|
+
|
|
173
|
+
# List the crontab into a temporary file if it exists
|
|
174
|
+
if ctb:
|
|
175
|
+
yield "crontab -l {0} > {1}".format(" ".join(crontab_args), temp_filename)
|
|
176
|
+
|
|
177
|
+
# Now yield any edits
|
|
178
|
+
for edit_command in edit_commands:
|
|
179
|
+
yield edit_command
|
|
180
|
+
|
|
181
|
+
# Finally, use the tempfile to write a new crontab
|
|
182
|
+
yield "crontab {0} {1}".format(" ".join(crontab_args), temp_filename)
|
|
183
|
+
else:
|
|
184
|
+
host.noop(
|
|
185
|
+
"crontab {0} {1}".format(
|
|
186
|
+
command,
|
|
187
|
+
"exists" if present else "does not exist",
|
|
188
|
+
),
|
|
189
|
+
)
|