pyinfra 0.11.dev3__py3-none-any.whl → 3.5.1__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 +18 -3
- pyinfra/api/arguments.py +406 -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 +67 -18
- pyinfra/api/facts.py +253 -202
- pyinfra/api/host.py +413 -50
- pyinfra/api/inventory.py +121 -160
- pyinfra/api/operation.py +432 -262
- pyinfra/api/operations.py +273 -260
- pyinfra/api/state.py +302 -248
- pyinfra/api/util.py +291 -368
- pyinfra/connectors/base.py +173 -0
- pyinfra/connectors/chroot.py +212 -0
- pyinfra/connectors/docker.py +381 -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 +670 -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 +410 -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 +630 -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 +746 -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 +98 -0
- pyinfra/operations/apt.py +488 -0
- pyinfra/operations/brew.py +231 -0
- pyinfra/operations/bsdinit.py +59 -0
- pyinfra/operations/cargo.py +45 -0
- pyinfra/operations/choco.py +61 -0
- pyinfra/operations/crontab.py +191 -0
- pyinfra/operations/dnf.py +210 -0
- pyinfra/operations/docker.py +446 -0
- pyinfra/operations/files.py +1939 -0
- pyinfra/operations/flatpak.py +94 -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 +47 -0
- pyinfra/operations/git.py +419 -0
- pyinfra/operations/iptables.py +311 -0
- pyinfra/operations/launchd.py +45 -0
- pyinfra/operations/lxd.py +68 -0
- pyinfra/operations/mysql.py +609 -0
- pyinfra/operations/npm.py +57 -0
- pyinfra/operations/openrc.py +63 -0
- pyinfra/operations/opkg.py +88 -0
- pyinfra/operations/pacman.py +81 -0
- pyinfra/operations/pip.py +205 -0
- pyinfra/operations/pipx.py +102 -0
- pyinfra/operations/pkg.py +70 -0
- pyinfra/operations/pkgin.py +91 -0
- pyinfra/operations/postgres.py +436 -0
- pyinfra/operations/postgresql.py +30 -0
- pyinfra/operations/puppet.py +40 -0
- pyinfra/operations/python.py +72 -0
- pyinfra/operations/runit.py +184 -0
- pyinfra/operations/selinux.py +189 -0
- pyinfra/operations/server.py +1099 -0
- pyinfra/operations/snap.py +117 -0
- pyinfra/operations/ssh.py +216 -0
- pyinfra/operations/systemd.py +149 -0
- pyinfra/operations/sysvinit.py +141 -0
- pyinfra/operations/upstart.py +68 -0
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +251 -0
- pyinfra/operations/util/files.py +247 -0
- pyinfra/operations/util/packaging.py +336 -0
- pyinfra/operations/util/service.py +46 -0
- pyinfra/operations/vzctl.py +137 -0
- pyinfra/operations/xbps.py +77 -0
- pyinfra/operations/yum.py +210 -0
- pyinfra/operations/zfs.py +175 -0
- pyinfra/operations/zypper.py +192 -0
- pyinfra/progress.py +44 -32
- pyinfra/py.typed +0 -0
- pyinfra/version.py +9 -1
- pyinfra-3.5.1.dist-info/METADATA +141 -0
- pyinfra-3.5.1.dist-info/RECORD +159 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
- pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
- pyinfra_cli/__init__.py +1 -0
- pyinfra_cli/cli.py +780 -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,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage snap packages. See https://snapcraft.io/
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pyinfra import host
|
|
8
|
+
from pyinfra.api import operation
|
|
9
|
+
from pyinfra.facts.snap import SnapPackage, SnapPackages
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@operation()
|
|
13
|
+
def package(
|
|
14
|
+
packages: str | list[str] | None = None,
|
|
15
|
+
channel="latest/stable",
|
|
16
|
+
classic=False,
|
|
17
|
+
present=True,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Install/remove a snap package
|
|
21
|
+
|
|
22
|
+
+ packages: List of packages
|
|
23
|
+
+ channel: tracking channel
|
|
24
|
+
+ classic: Use classic confinement
|
|
25
|
+
+ present: whether the package should be installed
|
|
26
|
+
|
|
27
|
+
``classic``:
|
|
28
|
+
Allows access to your system’s resources in much the same way traditional
|
|
29
|
+
packages do. This option corresponds to the ``--classic`` argument.
|
|
30
|
+
|
|
31
|
+
**Examples:**
|
|
32
|
+
|
|
33
|
+
.. code:: python
|
|
34
|
+
|
|
35
|
+
# Install vlc snap
|
|
36
|
+
snap.package(
|
|
37
|
+
name="Install vlc",
|
|
38
|
+
packages="vlc",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Install multiple snaps
|
|
42
|
+
snap.package(
|
|
43
|
+
name="Install vlc and hello-world",
|
|
44
|
+
packages=["vlc", "hello-world"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Remove vlc
|
|
48
|
+
snap.package(
|
|
49
|
+
name="Remove vlc",
|
|
50
|
+
packages="vlc",
|
|
51
|
+
present=False,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Install LXD using "4.0/stable" channel
|
|
55
|
+
snap.package(
|
|
56
|
+
name="Install LXD 4.0/stable",
|
|
57
|
+
packages=["lxd"],
|
|
58
|
+
channel="4.0/stable",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Install neovim with classic confinement
|
|
62
|
+
snap.package(
|
|
63
|
+
name="Install Neovim",
|
|
64
|
+
packages=["nvim"],
|
|
65
|
+
classic=True,
|
|
66
|
+
)
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
if packages is None:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
if isinstance(packages, str):
|
|
73
|
+
packages = [packages]
|
|
74
|
+
|
|
75
|
+
snap_packages = host.get_fact(SnapPackages)
|
|
76
|
+
|
|
77
|
+
install_packages = []
|
|
78
|
+
remove_packages = []
|
|
79
|
+
refresh_packages = []
|
|
80
|
+
|
|
81
|
+
for package in packages:
|
|
82
|
+
# it's installed
|
|
83
|
+
if package in snap_packages:
|
|
84
|
+
# we want the package
|
|
85
|
+
if present:
|
|
86
|
+
pkg_info = host.get_fact(SnapPackage, package=package)
|
|
87
|
+
|
|
88
|
+
# the channel is different
|
|
89
|
+
if pkg_info and "channel" in pkg_info and channel != pkg_info["channel"]:
|
|
90
|
+
refresh_packages.append(package)
|
|
91
|
+
pkg_info["channel"] = channel
|
|
92
|
+
|
|
93
|
+
else:
|
|
94
|
+
# we don't want it
|
|
95
|
+
remove_packages.append(package)
|
|
96
|
+
|
|
97
|
+
# it's not installed
|
|
98
|
+
if package not in snap_packages:
|
|
99
|
+
# we want it
|
|
100
|
+
if present:
|
|
101
|
+
install_packages.append(package)
|
|
102
|
+
|
|
103
|
+
# we don't want it
|
|
104
|
+
else:
|
|
105
|
+
host.noop(f"snap package {package} is not installed")
|
|
106
|
+
|
|
107
|
+
install_cmd = ["snap", "install"]
|
|
108
|
+
if classic:
|
|
109
|
+
install_cmd.append("--classic")
|
|
110
|
+
if install_packages:
|
|
111
|
+
yield " ".join(install_cmd + install_packages + [f"--channel={channel}"])
|
|
112
|
+
|
|
113
|
+
if remove_packages:
|
|
114
|
+
yield " ".join(["snap", "remove"] + remove_packages)
|
|
115
|
+
|
|
116
|
+
if refresh_packages:
|
|
117
|
+
yield " ".join(["snap", "refresh"] + refresh_packages + [f"--channel={channel}"])
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execute commands and up/download files *from* the remote host.
|
|
3
|
+
|
|
4
|
+
Eg: ``pyinfra -> inventory-host.net <-> another-host.net``
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shlex
|
|
10
|
+
|
|
11
|
+
from pyinfra import host
|
|
12
|
+
from pyinfra.api import OperationError, operation
|
|
13
|
+
from pyinfra.facts.files import File, FindInFile
|
|
14
|
+
from pyinfra.facts.server import Home
|
|
15
|
+
|
|
16
|
+
from . import files
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@operation()
|
|
20
|
+
def keyscan(hostname: str, force=False, port=22):
|
|
21
|
+
"""
|
|
22
|
+
Check/add hosts to the ``~/.ssh/known_hosts`` file.
|
|
23
|
+
|
|
24
|
+
+ hostname: hostname that should have a key in ``known_hosts``
|
|
25
|
+
+ force: if the key already exists, remove and rescan
|
|
26
|
+
|
|
27
|
+
**Example:**
|
|
28
|
+
|
|
29
|
+
.. code:: python
|
|
30
|
+
|
|
31
|
+
ssh.keyscan(
|
|
32
|
+
name="Set add server two to known_hosts on one",
|
|
33
|
+
hostname="two.example.com",
|
|
34
|
+
)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
homedir = host.get_fact(Home)
|
|
38
|
+
|
|
39
|
+
yield from files.directory._inner(
|
|
40
|
+
"{0}/.ssh".format(homedir),
|
|
41
|
+
mode=700,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
hostname_present = host.get_fact(
|
|
45
|
+
FindInFile,
|
|
46
|
+
path="{0}/.ssh/known_hosts".format(homedir),
|
|
47
|
+
pattern=hostname,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
keyscan_command = "ssh-keyscan -p {0} {1} >> {2}/.ssh/known_hosts".format(
|
|
51
|
+
port,
|
|
52
|
+
hostname,
|
|
53
|
+
homedir,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if not hostname_present:
|
|
57
|
+
yield keyscan_command
|
|
58
|
+
|
|
59
|
+
elif force:
|
|
60
|
+
yield "ssh-keygen -R {0}".format(hostname)
|
|
61
|
+
yield keyscan_command
|
|
62
|
+
|
|
63
|
+
else:
|
|
64
|
+
host.noop("host key for {0} already exists".format(hostname))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@operation(is_idempotent=False)
|
|
68
|
+
def command(hostname: str, command: str, user: str | None = None, port=22):
|
|
69
|
+
"""
|
|
70
|
+
Execute commands on other servers over SSH.
|
|
71
|
+
|
|
72
|
+
+ hostname: the hostname to connect to
|
|
73
|
+
+ command: the command to execute
|
|
74
|
+
+ user: connect with this user
|
|
75
|
+
+ port: connect to this port
|
|
76
|
+
|
|
77
|
+
**Example:**
|
|
78
|
+
|
|
79
|
+
.. code:: python
|
|
80
|
+
|
|
81
|
+
ssh.command(
|
|
82
|
+
name="Create file by running echo from host one to host two",
|
|
83
|
+
hostname="two.example.com",
|
|
84
|
+
command="echo 'one was here' > /tmp/one.txt",
|
|
85
|
+
user="vagrant",
|
|
86
|
+
)
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
command = shlex.quote(command)
|
|
90
|
+
|
|
91
|
+
connection_target = hostname
|
|
92
|
+
if user:
|
|
93
|
+
connection_target = "@".join((user, hostname))
|
|
94
|
+
|
|
95
|
+
yield "ssh -p {0} {1} {2}".format(port, connection_target, command)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@operation(is_idempotent=False)
|
|
99
|
+
def upload(
|
|
100
|
+
hostname: str,
|
|
101
|
+
filename: str,
|
|
102
|
+
remote_filename: str | None = None,
|
|
103
|
+
port=22,
|
|
104
|
+
user: str | None = None,
|
|
105
|
+
use_remote_sudo=False,
|
|
106
|
+
ssh_keyscan=False,
|
|
107
|
+
):
|
|
108
|
+
"""
|
|
109
|
+
Upload files to other servers using ``scp``.
|
|
110
|
+
|
|
111
|
+
+ hostname: hostname to upload to
|
|
112
|
+
+ filename: file to upload
|
|
113
|
+
+ remote_filename: where to upload the file to (defaults to ``filename``)
|
|
114
|
+
+ port: connect to this port
|
|
115
|
+
+ user: connect with this user
|
|
116
|
+
+ use_remote_sudo: upload to a temporary location and move using sudo
|
|
117
|
+
+ ssh_keyscan: execute ``ssh.keyscan`` before uploading the file
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
remote_filename = remote_filename or filename
|
|
121
|
+
|
|
122
|
+
# Figure out where we're connecting (host or user@host)
|
|
123
|
+
connection_target = hostname
|
|
124
|
+
if user:
|
|
125
|
+
connection_target = "@".join((user, hostname))
|
|
126
|
+
|
|
127
|
+
if ssh_keyscan:
|
|
128
|
+
yield from keyscan._inner(hostname)
|
|
129
|
+
|
|
130
|
+
# If we're not using sudo on the remote side, just scp the file over
|
|
131
|
+
if not use_remote_sudo:
|
|
132
|
+
yield "scp -P {0} {1} {2}:{3}".format(
|
|
133
|
+
port,
|
|
134
|
+
filename,
|
|
135
|
+
connection_target,
|
|
136
|
+
remote_filename,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
else:
|
|
140
|
+
# Otherwise - we need a temporary location for the file
|
|
141
|
+
temp_remote_filename = host.get_temp_filename()
|
|
142
|
+
|
|
143
|
+
# scp it to the temporary location
|
|
144
|
+
upload_cmd = "scp -P {0} {1} {2}:{3}".format(
|
|
145
|
+
port,
|
|
146
|
+
filename,
|
|
147
|
+
connection_target,
|
|
148
|
+
temp_remote_filename,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
yield upload_cmd
|
|
152
|
+
|
|
153
|
+
# And sudo sudo to move it
|
|
154
|
+
yield from command._inner(
|
|
155
|
+
hostname=hostname,
|
|
156
|
+
command="sudo mv {0} {1}".format(temp_remote_filename, remote_filename),
|
|
157
|
+
port=port,
|
|
158
|
+
user=user,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@operation()
|
|
163
|
+
def download(
|
|
164
|
+
hostname: str,
|
|
165
|
+
filename: str,
|
|
166
|
+
local_filename: str | None = None,
|
|
167
|
+
force=False,
|
|
168
|
+
port=22,
|
|
169
|
+
user: str | None = None,
|
|
170
|
+
ssh_keyscan=False,
|
|
171
|
+
):
|
|
172
|
+
"""
|
|
173
|
+
Download files from other servers using ``scp``.
|
|
174
|
+
|
|
175
|
+
+ hostname: hostname to upload to
|
|
176
|
+
+ filename: file to download
|
|
177
|
+
+ local_filename: where to download the file to (defaults to ``filename``)
|
|
178
|
+
+ force: always download the file, even if present locally
|
|
179
|
+
+ port: connect to this port
|
|
180
|
+
+ user: connect with this user
|
|
181
|
+
+ ssh_keyscan: execute ``ssh.keyscan`` before uploading the file
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
local_filename = local_filename or filename
|
|
185
|
+
|
|
186
|
+
# Get local file info
|
|
187
|
+
local_file_info = host.get_fact(File, path=local_filename)
|
|
188
|
+
|
|
189
|
+
# Local file exists but isn't a file?
|
|
190
|
+
if local_file_info is False:
|
|
191
|
+
raise OperationError(
|
|
192
|
+
"Local destination {0} already exists and is not a file".format(
|
|
193
|
+
local_filename,
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# If the local file exists and we're not forcing a re-download, no-op
|
|
198
|
+
if local_file_info and not force:
|
|
199
|
+
host.noop("file {0} is already downloaded".format(filename))
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Figure out where we're connecting (host or user@host)
|
|
203
|
+
connection_target = hostname
|
|
204
|
+
if user:
|
|
205
|
+
connection_target = "@".join((user, hostname))
|
|
206
|
+
|
|
207
|
+
if ssh_keyscan:
|
|
208
|
+
yield from keyscan._inner(hostname)
|
|
209
|
+
|
|
210
|
+
# Download the file with scp
|
|
211
|
+
yield "scp -P {0} {1}:{2} {3}".format(
|
|
212
|
+
port,
|
|
213
|
+
connection_target,
|
|
214
|
+
filename,
|
|
215
|
+
local_filename,
|
|
216
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage systemd services.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import shlex
|
|
8
|
+
|
|
9
|
+
from pyinfra import host
|
|
10
|
+
from pyinfra.api import StringCommand, operation
|
|
11
|
+
from pyinfra.facts.systemd import SystemdEnabled, SystemdStatus, _make_systemctl_cmd
|
|
12
|
+
|
|
13
|
+
from .util.service import handle_service_control
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@operation(is_idempotent=False)
|
|
17
|
+
def daemon_reload(user_mode=False, machine: str | None = None, user_name: str | None = None):
|
|
18
|
+
"""
|
|
19
|
+
Reload the systemd daemon to read unit file changes.
|
|
20
|
+
|
|
21
|
+
+ user_mode: whether to use per-user systemd (systemctl --user) or not
|
|
22
|
+
+ machine: the machine name to connect to
|
|
23
|
+
+ user_name: connect to a specific user's systemd session
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
systemctl_cmd = _make_systemctl_cmd(
|
|
27
|
+
user_mode=user_mode,
|
|
28
|
+
machine=machine,
|
|
29
|
+
user_name=user_name,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
yield StringCommand(systemctl_cmd, "daemon-reload")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_daemon_reload = daemon_reload._inner # noqa: E305
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@operation()
|
|
39
|
+
def service(
|
|
40
|
+
service: str,
|
|
41
|
+
running=True,
|
|
42
|
+
restarted=False,
|
|
43
|
+
reloaded=False,
|
|
44
|
+
command: str | None = None,
|
|
45
|
+
enabled: bool | None = None,
|
|
46
|
+
daemon_reload=False,
|
|
47
|
+
user_mode=False,
|
|
48
|
+
machine: str | None = None,
|
|
49
|
+
user_name: str | None = None,
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Manage the state of systemd managed units.
|
|
53
|
+
|
|
54
|
+
+ service: name of the systemd unit to manage
|
|
55
|
+
+ running: whether the unit should be running
|
|
56
|
+
+ restarted: whether the unit should be restarted
|
|
57
|
+
+ reloaded: whether the unit should be reloaded
|
|
58
|
+
+ command: custom command to pass like: ``/etc/rc.d/<name> <command>``
|
|
59
|
+
+ enabled: whether this unit should be enabled/disabled on boot
|
|
60
|
+
+ daemon_reload: reload the systemd daemon to read updated unit files
|
|
61
|
+
+ user_mode: whether to use per-user systemd (systemctl --user) or not
|
|
62
|
+
+ machine: the machine name to connect to
|
|
63
|
+
+ user_name: connect to a specific user's systemd session
|
|
64
|
+
|
|
65
|
+
**Examples:**
|
|
66
|
+
|
|
67
|
+
.. code:: python
|
|
68
|
+
|
|
69
|
+
systemd.service(
|
|
70
|
+
name="Restart and enable the dnsmasq service",
|
|
71
|
+
service="dnsmasq.service",
|
|
72
|
+
running=True,
|
|
73
|
+
restarted=True,
|
|
74
|
+
enabled=True,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
systemd.service(
|
|
78
|
+
name="Enable logrotate timer",
|
|
79
|
+
service="logrotate.timer",
|
|
80
|
+
running=True,
|
|
81
|
+
enabled=True,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
systemctl_cmd = _make_systemctl_cmd(
|
|
87
|
+
user_mode=user_mode,
|
|
88
|
+
machine=machine,
|
|
89
|
+
user_name=user_name,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if not service.endswith(
|
|
93
|
+
(
|
|
94
|
+
".service",
|
|
95
|
+
".socket",
|
|
96
|
+
".device",
|
|
97
|
+
".mount",
|
|
98
|
+
".automount",
|
|
99
|
+
".swap",
|
|
100
|
+
".target",
|
|
101
|
+
".path",
|
|
102
|
+
".timer",
|
|
103
|
+
".slice",
|
|
104
|
+
".scope",
|
|
105
|
+
)
|
|
106
|
+
):
|
|
107
|
+
service = "{0}.service".format(service)
|
|
108
|
+
|
|
109
|
+
if daemon_reload:
|
|
110
|
+
yield from _daemon_reload(
|
|
111
|
+
user_mode=user_mode,
|
|
112
|
+
machine=machine,
|
|
113
|
+
user_name=user_name,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
yield from handle_service_control(
|
|
117
|
+
host,
|
|
118
|
+
service,
|
|
119
|
+
host.get_fact(
|
|
120
|
+
SystemdStatus,
|
|
121
|
+
user_mode=user_mode,
|
|
122
|
+
machine=machine,
|
|
123
|
+
user_name=user_name,
|
|
124
|
+
services=[service],
|
|
125
|
+
),
|
|
126
|
+
" ".join([systemctl_cmd.get_raw_value(), "{1}", "{0}"]),
|
|
127
|
+
running,
|
|
128
|
+
restarted,
|
|
129
|
+
reloaded,
|
|
130
|
+
command,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if isinstance(enabled, bool):
|
|
134
|
+
systemd_enabled = host.get_fact(
|
|
135
|
+
SystemdEnabled,
|
|
136
|
+
user_mode=user_mode,
|
|
137
|
+
machine=machine,
|
|
138
|
+
user_name=user_name,
|
|
139
|
+
services=[service],
|
|
140
|
+
)
|
|
141
|
+
is_enabled = systemd_enabled.get(service, False)
|
|
142
|
+
|
|
143
|
+
# Isn't enabled and want enabled?
|
|
144
|
+
if not is_enabled and enabled is True:
|
|
145
|
+
yield "{0} enable {1}".format(systemctl_cmd, shlex.quote(service))
|
|
146
|
+
|
|
147
|
+
# Is enabled and want disabled?
|
|
148
|
+
elif is_enabled and enabled is False:
|
|
149
|
+
yield "{0} disable {1}".format(systemctl_cmd, shlex.quote(service))
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage sysvinit services (``/etc/init.d``).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pyinfra import host
|
|
8
|
+
from pyinfra.api import operation
|
|
9
|
+
from pyinfra.facts.files import FindLinks
|
|
10
|
+
from pyinfra.facts.server import LinuxDistribution
|
|
11
|
+
from pyinfra.facts.sysvinit import InitdStatus
|
|
12
|
+
|
|
13
|
+
from . import files
|
|
14
|
+
from .util.service import handle_service_control
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@operation()
|
|
18
|
+
def service(
|
|
19
|
+
service: str,
|
|
20
|
+
running=True,
|
|
21
|
+
restarted=False,
|
|
22
|
+
reloaded=False,
|
|
23
|
+
enabled: bool | None = None,
|
|
24
|
+
command: str | None = None,
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Manage the state of SysV Init (/etc/init.d) services.
|
|
28
|
+
|
|
29
|
+
+ service: name of the service to manage
|
|
30
|
+
+ running: whether the service should be running
|
|
31
|
+
+ restarted: whether the service should be restarted
|
|
32
|
+
+ reloaded: whether the service should be reloaded
|
|
33
|
+
+ enabled: whether this service should be enabled/disabled
|
|
34
|
+
+ command: command (eg. reload) to run like: ``/etc/init.d/<service> <command>``
|
|
35
|
+
|
|
36
|
+
Enabled:
|
|
37
|
+
Because managing /etc/rc.d/X files is a mess, only certain Linux distributions
|
|
38
|
+
support enabling/disabling services:
|
|
39
|
+
|
|
40
|
+
+ Ubuntu/Debian (``update-rc.d``)
|
|
41
|
+
+ CentOS/Fedora/RHEL (``chkconfig``)
|
|
42
|
+
+ Gentoo (``rc-update``)
|
|
43
|
+
|
|
44
|
+
For other distributions and more granular service control, see the
|
|
45
|
+
``sysvinit.enable`` operation.
|
|
46
|
+
|
|
47
|
+
**Example:**
|
|
48
|
+
|
|
49
|
+
.. code:: python
|
|
50
|
+
|
|
51
|
+
sysvinit.service(
|
|
52
|
+
name="Restart and enable rsyslog",
|
|
53
|
+
service="rsyslog",
|
|
54
|
+
restarted=True,
|
|
55
|
+
enabled=True,
|
|
56
|
+
)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
yield from handle_service_control(
|
|
60
|
+
host,
|
|
61
|
+
service,
|
|
62
|
+
host.get_fact(InitdStatus),
|
|
63
|
+
"/etc/init.d/{0} {1}",
|
|
64
|
+
running,
|
|
65
|
+
restarted,
|
|
66
|
+
reloaded,
|
|
67
|
+
command,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if isinstance(enabled, bool):
|
|
71
|
+
start_links = host.get_fact(
|
|
72
|
+
FindLinks,
|
|
73
|
+
path="/etc/rc*.d/S*{0}".format(service),
|
|
74
|
+
quote_path=False, # enable path glob matching
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# If no links exist, attempt to enable the service using distro-specific commands
|
|
78
|
+
if enabled is True and not start_links:
|
|
79
|
+
distro = host.get_fact(LinuxDistribution).get("name")
|
|
80
|
+
|
|
81
|
+
if distro in ("Ubuntu", "Debian"):
|
|
82
|
+
yield "update-rc.d {0} defaults".format(service)
|
|
83
|
+
|
|
84
|
+
elif distro in ("CentOS", "Fedora", "Red Hat Enterprise Linux"):
|
|
85
|
+
yield "chkconfig {0} --add".format(service)
|
|
86
|
+
yield "chkconfig {0} on".format(service)
|
|
87
|
+
|
|
88
|
+
elif distro == "Gentoo":
|
|
89
|
+
yield "rc-update add {0} default".format(service)
|
|
90
|
+
|
|
91
|
+
# Remove any /etc/rcX.d/<service> start links
|
|
92
|
+
elif enabled is False:
|
|
93
|
+
# No state checking, just blindly remove any that exist
|
|
94
|
+
for link in start_links:
|
|
95
|
+
yield "rm -f {0}".format(link)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@operation()
|
|
99
|
+
def enable(
|
|
100
|
+
service: str,
|
|
101
|
+
start_priority=20,
|
|
102
|
+
stop_priority=80,
|
|
103
|
+
start_levels=(2, 3, 4, 5),
|
|
104
|
+
stop_levels=(0, 1, 6),
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Manually enable /etc/init.d scripts by creating /etc/rcX.d/Y links.
|
|
108
|
+
|
|
109
|
+
+ service: name of the service to enable
|
|
110
|
+
+ start_priority: priority to start the service
|
|
111
|
+
+ stop_priority: priority to stop the service
|
|
112
|
+
+ start_levels: which runlevels should the service run when enabled
|
|
113
|
+
+ stop_levels: which runlevels should the service stop when enabled
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
|
|
117
|
+
.. code:: python
|
|
118
|
+
|
|
119
|
+
init.d_enable(
|
|
120
|
+
name="Finer control on which runlevels rsyslog should run",
|
|
121
|
+
service="rsyslog",
|
|
122
|
+
start_levels=(3, 4, 5),
|
|
123
|
+
stop_levels=(0, 1, 2, 6),
|
|
124
|
+
)
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
# Build link list
|
|
128
|
+
links = []
|
|
129
|
+
|
|
130
|
+
for level in start_levels:
|
|
131
|
+
links.append("/etc/rc{0}.d/S{1}{2}".format(level, start_priority, service))
|
|
132
|
+
|
|
133
|
+
for level in stop_levels:
|
|
134
|
+
links.append("/etc/rc{0}.d/K{1}{2}".format(level, stop_priority, service))
|
|
135
|
+
|
|
136
|
+
# Ensure all the new links exist
|
|
137
|
+
for link in links:
|
|
138
|
+
yield from files.link._inner(
|
|
139
|
+
path=link,
|
|
140
|
+
target="/etc/init.d/{0}".format(service),
|
|
141
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage upstart services.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from io import StringIO
|
|
8
|
+
|
|
9
|
+
from pyinfra import host
|
|
10
|
+
from pyinfra.api import operation
|
|
11
|
+
from pyinfra.facts.upstart import UpstartStatus
|
|
12
|
+
|
|
13
|
+
from . import files
|
|
14
|
+
from .util.service import handle_service_control
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@operation()
|
|
18
|
+
def service(
|
|
19
|
+
service: str,
|
|
20
|
+
running=True,
|
|
21
|
+
restarted=False,
|
|
22
|
+
reloaded=False,
|
|
23
|
+
command: str | None = None,
|
|
24
|
+
enabled: bool | None = None,
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Manage the state of upstart managed services.
|
|
28
|
+
|
|
29
|
+
+ service: name of the service to manage
|
|
30
|
+
+ running: whether the service should be running
|
|
31
|
+
+ restarted: whether the service should be restarted
|
|
32
|
+
+ reloaded: whether the service should be reloaded
|
|
33
|
+
+ command: custom command to pass like: ``/etc/rc.d/<service> <command>``
|
|
34
|
+
+ enabled: whether this service should be enabled/disabled on boot
|
|
35
|
+
|
|
36
|
+
Enabling/disabling services:
|
|
37
|
+
Upstart jobs define runlevels in their config files - as such there is no way to
|
|
38
|
+
edit/list these without fiddling with the config. So pyinfra simply manages the
|
|
39
|
+
existence of a ``/etc/init/<service>.override`` file, and sets its content to
|
|
40
|
+
"manual" to disable automatic start of services.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
yield from handle_service_control(
|
|
44
|
+
host,
|
|
45
|
+
service,
|
|
46
|
+
host.get_fact(UpstartStatus),
|
|
47
|
+
"initctl {1} {0}",
|
|
48
|
+
running,
|
|
49
|
+
restarted,
|
|
50
|
+
reloaded,
|
|
51
|
+
command,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Upstart jobs are setup w/runlevels etc in their config files, so here we just check
|
|
55
|
+
# there's no override file.
|
|
56
|
+
if enabled is True:
|
|
57
|
+
yield from files.file._inner(
|
|
58
|
+
path="/etc/init/{0}.override".format(service),
|
|
59
|
+
present=False,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Set the override file to "manual" to disable automatic start
|
|
63
|
+
elif enabled is False:
|
|
64
|
+
file = StringIO("manual\n")
|
|
65
|
+
yield from files.put._inner(
|
|
66
|
+
src=file,
|
|
67
|
+
dest="/etc/init/{0}.override".format(service),
|
|
68
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Callable
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from pyinfra.api.operation import OperationMeta
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def any_changed(*args: "OperationMeta") -> Callable[[], bool]:
|
|
8
|
+
return lambda: any((meta.did_change() for meta in args))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def all_changed(*args: "OperationMeta") -> Callable[[], bool]:
|
|
12
|
+
return lambda: all((meta.did_change() for meta in args))
|