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.
Files changed (203) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +18 -3
  4. pyinfra/api/arguments.py +406 -0
  5. pyinfra/api/arguments_typed.py +79 -0
  6. pyinfra/api/command.py +274 -0
  7. pyinfra/api/config.py +222 -28
  8. pyinfra/api/connect.py +33 -13
  9. pyinfra/api/connectors.py +27 -0
  10. pyinfra/api/deploy.py +65 -66
  11. pyinfra/api/exceptions.py +67 -18
  12. pyinfra/api/facts.py +253 -202
  13. pyinfra/api/host.py +413 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/operation.py +432 -262
  16. pyinfra/api/operations.py +273 -260
  17. pyinfra/api/state.py +302 -248
  18. pyinfra/api/util.py +291 -368
  19. pyinfra/connectors/base.py +173 -0
  20. pyinfra/connectors/chroot.py +212 -0
  21. pyinfra/connectors/docker.py +381 -0
  22. pyinfra/connectors/dockerssh.py +297 -0
  23. pyinfra/connectors/local.py +238 -0
  24. pyinfra/connectors/scp/__init__.py +1 -0
  25. pyinfra/connectors/scp/client.py +204 -0
  26. pyinfra/connectors/ssh.py +670 -0
  27. pyinfra/connectors/ssh_util.py +114 -0
  28. pyinfra/connectors/sshuserclient/client.py +309 -0
  29. pyinfra/connectors/sshuserclient/config.py +102 -0
  30. pyinfra/connectors/terraform.py +135 -0
  31. pyinfra/connectors/util.py +410 -0
  32. pyinfra/connectors/vagrant.py +183 -0
  33. pyinfra/context.py +145 -0
  34. pyinfra/facts/__init__.py +7 -6
  35. pyinfra/facts/apk.py +22 -7
  36. pyinfra/facts/apt.py +117 -60
  37. pyinfra/facts/brew.py +100 -15
  38. pyinfra/facts/bsdinit.py +23 -0
  39. pyinfra/facts/cargo.py +37 -0
  40. pyinfra/facts/choco.py +47 -0
  41. pyinfra/facts/crontab.py +195 -0
  42. pyinfra/facts/deb.py +94 -0
  43. pyinfra/facts/dnf.py +48 -0
  44. pyinfra/facts/docker.py +96 -23
  45. pyinfra/facts/efibootmgr.py +113 -0
  46. pyinfra/facts/files.py +630 -58
  47. pyinfra/facts/flatpak.py +77 -0
  48. pyinfra/facts/freebsd.py +70 -0
  49. pyinfra/facts/gem.py +19 -6
  50. pyinfra/facts/git.py +59 -14
  51. pyinfra/facts/gpg.py +150 -0
  52. pyinfra/facts/hardware.py +313 -167
  53. pyinfra/facts/iptables.py +72 -62
  54. pyinfra/facts/launchd.py +44 -0
  55. pyinfra/facts/lxd.py +17 -4
  56. pyinfra/facts/mysql.py +122 -86
  57. pyinfra/facts/npm.py +17 -9
  58. pyinfra/facts/openrc.py +71 -0
  59. pyinfra/facts/opkg.py +246 -0
  60. pyinfra/facts/pacman.py +50 -7
  61. pyinfra/facts/pip.py +24 -7
  62. pyinfra/facts/pipx.py +82 -0
  63. pyinfra/facts/pkg.py +15 -6
  64. pyinfra/facts/pkgin.py +35 -0
  65. pyinfra/facts/podman.py +54 -0
  66. pyinfra/facts/postgres.py +178 -0
  67. pyinfra/facts/postgresql.py +6 -147
  68. pyinfra/facts/rpm.py +105 -0
  69. pyinfra/facts/runit.py +77 -0
  70. pyinfra/facts/selinux.py +161 -0
  71. pyinfra/facts/server.py +746 -285
  72. pyinfra/facts/snap.py +88 -0
  73. pyinfra/facts/systemd.py +139 -0
  74. pyinfra/facts/sysvinit.py +59 -0
  75. pyinfra/facts/upstart.py +35 -0
  76. pyinfra/facts/util/__init__.py +17 -0
  77. pyinfra/facts/util/databases.py +4 -6
  78. pyinfra/facts/util/packaging.py +37 -6
  79. pyinfra/facts/util/units.py +30 -0
  80. pyinfra/facts/util/win_files.py +99 -0
  81. pyinfra/facts/vzctl.py +20 -13
  82. pyinfra/facts/xbps.py +35 -0
  83. pyinfra/facts/yum.py +34 -40
  84. pyinfra/facts/zfs.py +77 -0
  85. pyinfra/facts/zypper.py +42 -0
  86. pyinfra/local.py +45 -83
  87. pyinfra/operations/__init__.py +12 -0
  88. pyinfra/operations/apk.py +98 -0
  89. pyinfra/operations/apt.py +488 -0
  90. pyinfra/operations/brew.py +231 -0
  91. pyinfra/operations/bsdinit.py +59 -0
  92. pyinfra/operations/cargo.py +45 -0
  93. pyinfra/operations/choco.py +61 -0
  94. pyinfra/operations/crontab.py +191 -0
  95. pyinfra/operations/dnf.py +210 -0
  96. pyinfra/operations/docker.py +446 -0
  97. pyinfra/operations/files.py +1939 -0
  98. pyinfra/operations/flatpak.py +94 -0
  99. pyinfra/operations/freebsd/__init__.py +12 -0
  100. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  101. pyinfra/operations/freebsd/pkg.py +219 -0
  102. pyinfra/operations/freebsd/service.py +116 -0
  103. pyinfra/operations/freebsd/sysrc.py +92 -0
  104. pyinfra/operations/gem.py +47 -0
  105. pyinfra/operations/git.py +419 -0
  106. pyinfra/operations/iptables.py +311 -0
  107. pyinfra/operations/launchd.py +45 -0
  108. pyinfra/operations/lxd.py +68 -0
  109. pyinfra/operations/mysql.py +609 -0
  110. pyinfra/operations/npm.py +57 -0
  111. pyinfra/operations/openrc.py +63 -0
  112. pyinfra/operations/opkg.py +88 -0
  113. pyinfra/operations/pacman.py +81 -0
  114. pyinfra/operations/pip.py +205 -0
  115. pyinfra/operations/pipx.py +102 -0
  116. pyinfra/operations/pkg.py +70 -0
  117. pyinfra/operations/pkgin.py +91 -0
  118. pyinfra/operations/postgres.py +436 -0
  119. pyinfra/operations/postgresql.py +30 -0
  120. pyinfra/operations/puppet.py +40 -0
  121. pyinfra/operations/python.py +72 -0
  122. pyinfra/operations/runit.py +184 -0
  123. pyinfra/operations/selinux.py +189 -0
  124. pyinfra/operations/server.py +1099 -0
  125. pyinfra/operations/snap.py +117 -0
  126. pyinfra/operations/ssh.py +216 -0
  127. pyinfra/operations/systemd.py +149 -0
  128. pyinfra/operations/sysvinit.py +141 -0
  129. pyinfra/operations/upstart.py +68 -0
  130. pyinfra/operations/util/__init__.py +12 -0
  131. pyinfra/operations/util/docker.py +251 -0
  132. pyinfra/operations/util/files.py +247 -0
  133. pyinfra/operations/util/packaging.py +336 -0
  134. pyinfra/operations/util/service.py +46 -0
  135. pyinfra/operations/vzctl.py +137 -0
  136. pyinfra/operations/xbps.py +77 -0
  137. pyinfra/operations/yum.py +210 -0
  138. pyinfra/operations/zfs.py +175 -0
  139. pyinfra/operations/zypper.py +192 -0
  140. pyinfra/progress.py +44 -32
  141. pyinfra/py.typed +0 -0
  142. pyinfra/version.py +9 -1
  143. pyinfra-3.5.1.dist-info/METADATA +141 -0
  144. pyinfra-3.5.1.dist-info/RECORD +159 -0
  145. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
  146. pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
  147. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
  148. pyinfra_cli/__init__.py +1 -0
  149. pyinfra_cli/cli.py +780 -0
  150. pyinfra_cli/commands.py +66 -0
  151. pyinfra_cli/exceptions.py +155 -65
  152. pyinfra_cli/inventory.py +233 -89
  153. pyinfra_cli/log.py +39 -43
  154. pyinfra_cli/main.py +26 -495
  155. pyinfra_cli/prints.py +215 -156
  156. pyinfra_cli/util.py +172 -105
  157. pyinfra_cli/virtualenv.py +25 -20
  158. pyinfra/api/connectors/__init__.py +0 -21
  159. pyinfra/api/connectors/ansible.py +0 -99
  160. pyinfra/api/connectors/docker.py +0 -178
  161. pyinfra/api/connectors/local.py +0 -169
  162. pyinfra/api/connectors/ssh.py +0 -402
  163. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  164. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  165. pyinfra/api/connectors/util.py +0 -63
  166. pyinfra/api/connectors/vagrant.py +0 -155
  167. pyinfra/facts/init.py +0 -176
  168. pyinfra/facts/util/files.py +0 -102
  169. pyinfra/hook.py +0 -41
  170. pyinfra/modules/__init__.py +0 -11
  171. pyinfra/modules/apk.py +0 -64
  172. pyinfra/modules/apt.py +0 -272
  173. pyinfra/modules/brew.py +0 -122
  174. pyinfra/modules/files.py +0 -711
  175. pyinfra/modules/gem.py +0 -30
  176. pyinfra/modules/git.py +0 -115
  177. pyinfra/modules/init.py +0 -344
  178. pyinfra/modules/iptables.py +0 -271
  179. pyinfra/modules/lxd.py +0 -45
  180. pyinfra/modules/mysql.py +0 -347
  181. pyinfra/modules/npm.py +0 -47
  182. pyinfra/modules/pacman.py +0 -60
  183. pyinfra/modules/pip.py +0 -99
  184. pyinfra/modules/pkg.py +0 -43
  185. pyinfra/modules/postgresql.py +0 -245
  186. pyinfra/modules/puppet.py +0 -20
  187. pyinfra/modules/python.py +0 -37
  188. pyinfra/modules/server.py +0 -524
  189. pyinfra/modules/ssh.py +0 -150
  190. pyinfra/modules/util/files.py +0 -52
  191. pyinfra/modules/util/packaging.py +0 -118
  192. pyinfra/modules/vzctl.py +0 -133
  193. pyinfra/modules/yum.py +0 -171
  194. pyinfra/pseudo_modules.py +0 -64
  195. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  196. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  197. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  198. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  199. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  200. pyinfra_cli/__main__.py +0 -40
  201. pyinfra_cli/config.py +0 -92
  202. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  203. /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))