pyinfra 0.11.dev3__py3-none-any.whl → 3.6__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 (204) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +19 -3
  4. pyinfra/api/arguments.py +413 -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 +73 -18
  12. pyinfra/api/facts.py +267 -200
  13. pyinfra/api/host.py +416 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/metadata.py +69 -0
  16. pyinfra/api/operation.py +432 -262
  17. pyinfra/api/operations.py +273 -260
  18. pyinfra/api/state.py +302 -248
  19. pyinfra/api/util.py +309 -369
  20. pyinfra/connectors/base.py +173 -0
  21. pyinfra/connectors/chroot.py +212 -0
  22. pyinfra/connectors/docker.py +405 -0
  23. pyinfra/connectors/dockerssh.py +297 -0
  24. pyinfra/connectors/local.py +238 -0
  25. pyinfra/connectors/scp/__init__.py +1 -0
  26. pyinfra/connectors/scp/client.py +204 -0
  27. pyinfra/connectors/ssh.py +727 -0
  28. pyinfra/connectors/ssh_util.py +114 -0
  29. pyinfra/connectors/sshuserclient/client.py +309 -0
  30. pyinfra/connectors/sshuserclient/config.py +102 -0
  31. pyinfra/connectors/terraform.py +135 -0
  32. pyinfra/connectors/util.py +417 -0
  33. pyinfra/connectors/vagrant.py +183 -0
  34. pyinfra/context.py +145 -0
  35. pyinfra/facts/__init__.py +7 -6
  36. pyinfra/facts/apk.py +22 -7
  37. pyinfra/facts/apt.py +117 -60
  38. pyinfra/facts/brew.py +100 -15
  39. pyinfra/facts/bsdinit.py +23 -0
  40. pyinfra/facts/cargo.py +37 -0
  41. pyinfra/facts/choco.py +47 -0
  42. pyinfra/facts/crontab.py +195 -0
  43. pyinfra/facts/deb.py +94 -0
  44. pyinfra/facts/dnf.py +48 -0
  45. pyinfra/facts/docker.py +96 -23
  46. pyinfra/facts/efibootmgr.py +113 -0
  47. pyinfra/facts/files.py +629 -58
  48. pyinfra/facts/flatpak.py +77 -0
  49. pyinfra/facts/freebsd.py +70 -0
  50. pyinfra/facts/gem.py +19 -6
  51. pyinfra/facts/git.py +59 -14
  52. pyinfra/facts/gpg.py +150 -0
  53. pyinfra/facts/hardware.py +313 -167
  54. pyinfra/facts/iptables.py +72 -62
  55. pyinfra/facts/launchd.py +44 -0
  56. pyinfra/facts/lxd.py +17 -4
  57. pyinfra/facts/mysql.py +122 -86
  58. pyinfra/facts/npm.py +17 -9
  59. pyinfra/facts/openrc.py +71 -0
  60. pyinfra/facts/opkg.py +246 -0
  61. pyinfra/facts/pacman.py +50 -7
  62. pyinfra/facts/pip.py +24 -7
  63. pyinfra/facts/pipx.py +82 -0
  64. pyinfra/facts/pkg.py +15 -6
  65. pyinfra/facts/pkgin.py +35 -0
  66. pyinfra/facts/podman.py +54 -0
  67. pyinfra/facts/postgres.py +178 -0
  68. pyinfra/facts/postgresql.py +6 -147
  69. pyinfra/facts/rpm.py +105 -0
  70. pyinfra/facts/runit.py +77 -0
  71. pyinfra/facts/selinux.py +161 -0
  72. pyinfra/facts/server.py +762 -285
  73. pyinfra/facts/snap.py +88 -0
  74. pyinfra/facts/systemd.py +139 -0
  75. pyinfra/facts/sysvinit.py +59 -0
  76. pyinfra/facts/upstart.py +35 -0
  77. pyinfra/facts/util/__init__.py +17 -0
  78. pyinfra/facts/util/databases.py +4 -6
  79. pyinfra/facts/util/packaging.py +37 -6
  80. pyinfra/facts/util/units.py +30 -0
  81. pyinfra/facts/util/win_files.py +99 -0
  82. pyinfra/facts/vzctl.py +20 -13
  83. pyinfra/facts/xbps.py +35 -0
  84. pyinfra/facts/yum.py +34 -40
  85. pyinfra/facts/zfs.py +77 -0
  86. pyinfra/facts/zypper.py +42 -0
  87. pyinfra/local.py +45 -83
  88. pyinfra/operations/__init__.py +12 -0
  89. pyinfra/operations/apk.py +99 -0
  90. pyinfra/operations/apt.py +496 -0
  91. pyinfra/operations/brew.py +232 -0
  92. pyinfra/operations/bsdinit.py +59 -0
  93. pyinfra/operations/cargo.py +45 -0
  94. pyinfra/operations/choco.py +61 -0
  95. pyinfra/operations/crontab.py +194 -0
  96. pyinfra/operations/dnf.py +213 -0
  97. pyinfra/operations/docker.py +492 -0
  98. pyinfra/operations/files.py +2014 -0
  99. pyinfra/operations/flatpak.py +95 -0
  100. pyinfra/operations/freebsd/__init__.py +12 -0
  101. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  102. pyinfra/operations/freebsd/pkg.py +219 -0
  103. pyinfra/operations/freebsd/service.py +116 -0
  104. pyinfra/operations/freebsd/sysrc.py +92 -0
  105. pyinfra/operations/gem.py +48 -0
  106. pyinfra/operations/git.py +420 -0
  107. pyinfra/operations/iptables.py +312 -0
  108. pyinfra/operations/launchd.py +45 -0
  109. pyinfra/operations/lxd.py +69 -0
  110. pyinfra/operations/mysql.py +610 -0
  111. pyinfra/operations/npm.py +57 -0
  112. pyinfra/operations/openrc.py +63 -0
  113. pyinfra/operations/opkg.py +89 -0
  114. pyinfra/operations/pacman.py +82 -0
  115. pyinfra/operations/pip.py +206 -0
  116. pyinfra/operations/pipx.py +103 -0
  117. pyinfra/operations/pkg.py +71 -0
  118. pyinfra/operations/pkgin.py +92 -0
  119. pyinfra/operations/postgres.py +437 -0
  120. pyinfra/operations/postgresql.py +30 -0
  121. pyinfra/operations/puppet.py +41 -0
  122. pyinfra/operations/python.py +73 -0
  123. pyinfra/operations/runit.py +184 -0
  124. pyinfra/operations/selinux.py +190 -0
  125. pyinfra/operations/server.py +1100 -0
  126. pyinfra/operations/snap.py +118 -0
  127. pyinfra/operations/ssh.py +217 -0
  128. pyinfra/operations/systemd.py +150 -0
  129. pyinfra/operations/sysvinit.py +142 -0
  130. pyinfra/operations/upstart.py +68 -0
  131. pyinfra/operations/util/__init__.py +12 -0
  132. pyinfra/operations/util/docker.py +407 -0
  133. pyinfra/operations/util/files.py +247 -0
  134. pyinfra/operations/util/packaging.py +338 -0
  135. pyinfra/operations/util/service.py +46 -0
  136. pyinfra/operations/vzctl.py +137 -0
  137. pyinfra/operations/xbps.py +78 -0
  138. pyinfra/operations/yum.py +213 -0
  139. pyinfra/operations/zfs.py +176 -0
  140. pyinfra/operations/zypper.py +193 -0
  141. pyinfra/progress.py +44 -32
  142. pyinfra/py.typed +0 -0
  143. pyinfra/version.py +9 -1
  144. pyinfra-3.6.dist-info/METADATA +142 -0
  145. pyinfra-3.6.dist-info/RECORD +160 -0
  146. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
  147. pyinfra-3.6.dist-info/entry_points.txt +12 -0
  148. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
  149. pyinfra_cli/__init__.py +1 -0
  150. pyinfra_cli/cli.py +793 -0
  151. pyinfra_cli/commands.py +66 -0
  152. pyinfra_cli/exceptions.py +155 -65
  153. pyinfra_cli/inventory.py +233 -89
  154. pyinfra_cli/log.py +39 -43
  155. pyinfra_cli/main.py +26 -495
  156. pyinfra_cli/prints.py +215 -156
  157. pyinfra_cli/util.py +172 -105
  158. pyinfra_cli/virtualenv.py +25 -20
  159. pyinfra/api/connectors/__init__.py +0 -21
  160. pyinfra/api/connectors/ansible.py +0 -99
  161. pyinfra/api/connectors/docker.py +0 -178
  162. pyinfra/api/connectors/local.py +0 -169
  163. pyinfra/api/connectors/ssh.py +0 -402
  164. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  165. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  166. pyinfra/api/connectors/util.py +0 -63
  167. pyinfra/api/connectors/vagrant.py +0 -155
  168. pyinfra/facts/init.py +0 -176
  169. pyinfra/facts/util/files.py +0 -102
  170. pyinfra/hook.py +0 -41
  171. pyinfra/modules/__init__.py +0 -11
  172. pyinfra/modules/apk.py +0 -64
  173. pyinfra/modules/apt.py +0 -272
  174. pyinfra/modules/brew.py +0 -122
  175. pyinfra/modules/files.py +0 -711
  176. pyinfra/modules/gem.py +0 -30
  177. pyinfra/modules/git.py +0 -115
  178. pyinfra/modules/init.py +0 -344
  179. pyinfra/modules/iptables.py +0 -271
  180. pyinfra/modules/lxd.py +0 -45
  181. pyinfra/modules/mysql.py +0 -347
  182. pyinfra/modules/npm.py +0 -47
  183. pyinfra/modules/pacman.py +0 -60
  184. pyinfra/modules/pip.py +0 -99
  185. pyinfra/modules/pkg.py +0 -43
  186. pyinfra/modules/postgresql.py +0 -245
  187. pyinfra/modules/puppet.py +0 -20
  188. pyinfra/modules/python.py +0 -37
  189. pyinfra/modules/server.py +0 -524
  190. pyinfra/modules/ssh.py +0 -150
  191. pyinfra/modules/util/files.py +0 -52
  192. pyinfra/modules/util/packaging.py +0 -118
  193. pyinfra/modules/vzctl.py +0 -133
  194. pyinfra/modules/yum.py +0 -171
  195. pyinfra/pseudo_modules.py +0 -64
  196. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  197. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  198. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  199. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  200. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  201. pyinfra_cli/__main__.py +0 -40
  202. pyinfra_cli/config.py +0 -92
  203. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  204. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
@@ -0,0 +1,118 @@
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
+ from pyinfra.operations import snap
36
+ # Install vlc via snap
37
+ snap.package(
38
+ name="Install vlc",
39
+ packages="vlc",
40
+ )
41
+
42
+ # Install multiple snaps
43
+ snap.package(
44
+ name="Install vlc and hello-world",
45
+ packages=["vlc", "hello-world"],
46
+ )
47
+
48
+ # Remove vlc
49
+ snap.package(
50
+ name="Remove vlc",
51
+ packages="vlc",
52
+ present=False,
53
+ )
54
+
55
+ # Install LXD using "4.0/stable" channel
56
+ snap.package(
57
+ name="Install LXD 4.0/stable",
58
+ packages=["lxd"],
59
+ channel="4.0/stable",
60
+ )
61
+
62
+ # Install neovim with classic confinement
63
+ snap.package(
64
+ name="Install Neovim",
65
+ packages=["nvim"],
66
+ classic=True,
67
+ )
68
+ """
69
+
70
+ if packages is None:
71
+ return
72
+
73
+ if isinstance(packages, str):
74
+ packages = [packages]
75
+
76
+ snap_packages = host.get_fact(SnapPackages)
77
+
78
+ install_packages = []
79
+ remove_packages = []
80
+ refresh_packages = []
81
+
82
+ for package in packages:
83
+ # it's installed
84
+ if package in snap_packages:
85
+ # we want the package
86
+ if present:
87
+ pkg_info = host.get_fact(SnapPackage, package=package)
88
+
89
+ # the channel is different
90
+ if pkg_info and "channel" in pkg_info and channel != pkg_info["channel"]:
91
+ refresh_packages.append(package)
92
+ pkg_info["channel"] = channel
93
+
94
+ else:
95
+ # we don't want it
96
+ remove_packages.append(package)
97
+
98
+ # it's not installed
99
+ if package not in snap_packages:
100
+ # we want it
101
+ if present:
102
+ install_packages.append(package)
103
+
104
+ # we don't want it
105
+ else:
106
+ host.noop(f"snap package {package} is not installed")
107
+
108
+ install_cmd = ["snap", "install"]
109
+ if classic:
110
+ install_cmd.append("--classic")
111
+ if install_packages:
112
+ yield " ".join(install_cmd + install_packages + [f"--channel={channel}"])
113
+
114
+ if remove_packages:
115
+ yield " ".join(["snap", "remove"] + remove_packages)
116
+
117
+ if refresh_packages:
118
+ yield " ".join(["snap", "refresh"] + refresh_packages + [f"--channel={channel}"])
@@ -0,0 +1,217 @@
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
+ from pyinfra.operations import ssh
32
+ ssh.keyscan(
33
+ name="Set add server two to known_hosts on one",
34
+ hostname="two.example.com",
35
+ )
36
+ """
37
+
38
+ homedir = host.get_fact(Home)
39
+
40
+ yield from files.directory._inner(
41
+ "{0}/.ssh".format(homedir),
42
+ mode=700,
43
+ )
44
+
45
+ hostname_present = host.get_fact(
46
+ FindInFile,
47
+ path="{0}/.ssh/known_hosts".format(homedir),
48
+ pattern=hostname,
49
+ )
50
+
51
+ keyscan_command = "ssh-keyscan -p {0} {1} >> {2}/.ssh/known_hosts".format(
52
+ port,
53
+ hostname,
54
+ homedir,
55
+ )
56
+
57
+ if not hostname_present:
58
+ yield keyscan_command
59
+
60
+ elif force:
61
+ yield "ssh-keygen -R {0}".format(hostname)
62
+ yield keyscan_command
63
+
64
+ else:
65
+ host.noop("host key for {0} already exists".format(hostname))
66
+
67
+
68
+ @operation(is_idempotent=False)
69
+ def command(hostname: str, command: str, user: str | None = None, port=22):
70
+ """
71
+ Execute commands on other servers over SSH.
72
+
73
+ + hostname: the hostname to connect to
74
+ + command: the command to execute
75
+ + user: connect with this user
76
+ + port: connect to this port
77
+
78
+ **Example:**
79
+
80
+ .. code:: python
81
+
82
+ ssh.command(
83
+ name="Create file by running echo from host one to host two",
84
+ hostname="two.example.com",
85
+ command="echo 'one was here' > /tmp/one.txt",
86
+ user="vagrant",
87
+ )
88
+ """
89
+
90
+ command = shlex.quote(command)
91
+
92
+ connection_target = hostname
93
+ if user:
94
+ connection_target = "@".join((user, hostname))
95
+
96
+ yield "ssh -p {0} {1} {2}".format(port, connection_target, command)
97
+
98
+
99
+ @operation(is_idempotent=False)
100
+ def upload(
101
+ hostname: str,
102
+ filename: str,
103
+ remote_filename: str | None = None,
104
+ port=22,
105
+ user: str | None = None,
106
+ use_remote_sudo=False,
107
+ ssh_keyscan=False,
108
+ ):
109
+ """
110
+ Upload files to other servers using ``scp``.
111
+
112
+ + hostname: hostname to upload to
113
+ + filename: file to upload
114
+ + remote_filename: where to upload the file to (defaults to ``filename``)
115
+ + port: connect to this port
116
+ + user: connect with this user
117
+ + use_remote_sudo: upload to a temporary location and move using sudo
118
+ + ssh_keyscan: execute ``ssh.keyscan`` before uploading the file
119
+ """
120
+
121
+ remote_filename = remote_filename or filename
122
+
123
+ # Figure out where we're connecting (host or user@host)
124
+ connection_target = hostname
125
+ if user:
126
+ connection_target = "@".join((user, hostname))
127
+
128
+ if ssh_keyscan:
129
+ yield from keyscan._inner(hostname)
130
+
131
+ # If we're not using sudo on the remote side, just scp the file over
132
+ if not use_remote_sudo:
133
+ yield "scp -P {0} {1} {2}:{3}".format(
134
+ port,
135
+ filename,
136
+ connection_target,
137
+ remote_filename,
138
+ )
139
+
140
+ else:
141
+ # Otherwise - we need a temporary location for the file
142
+ temp_remote_filename = host.get_temp_filename()
143
+
144
+ # scp it to the temporary location
145
+ upload_cmd = "scp -P {0} {1} {2}:{3}".format(
146
+ port,
147
+ filename,
148
+ connection_target,
149
+ temp_remote_filename,
150
+ )
151
+
152
+ yield upload_cmd
153
+
154
+ # And sudo sudo to move it
155
+ yield from command._inner(
156
+ hostname=hostname,
157
+ command="sudo mv {0} {1}".format(temp_remote_filename, remote_filename),
158
+ port=port,
159
+ user=user,
160
+ )
161
+
162
+
163
+ @operation()
164
+ def download(
165
+ hostname: str,
166
+ filename: str,
167
+ local_filename: str | None = None,
168
+ force=False,
169
+ port=22,
170
+ user: str | None = None,
171
+ ssh_keyscan=False,
172
+ ):
173
+ """
174
+ Download files from other servers using ``scp``.
175
+
176
+ + hostname: hostname to upload to
177
+ + filename: file to download
178
+ + local_filename: where to download the file to (defaults to ``filename``)
179
+ + force: always download the file, even if present locally
180
+ + port: connect to this port
181
+ + user: connect with this user
182
+ + ssh_keyscan: execute ``ssh.keyscan`` before uploading the file
183
+ """
184
+
185
+ local_filename = local_filename or filename
186
+
187
+ # Get local file info
188
+ local_file_info = host.get_fact(File, path=local_filename)
189
+
190
+ # Local file exists but isn't a file?
191
+ if local_file_info is False:
192
+ raise OperationError(
193
+ "Local destination {0} already exists and is not a file".format(
194
+ local_filename,
195
+ ),
196
+ )
197
+
198
+ # If the local file exists and we're not forcing a re-download, no-op
199
+ if local_file_info and not force:
200
+ host.noop("file {0} is already downloaded".format(filename))
201
+ return
202
+
203
+ # Figure out where we're connecting (host or user@host)
204
+ connection_target = hostname
205
+ if user:
206
+ connection_target = "@".join((user, hostname))
207
+
208
+ if ssh_keyscan:
209
+ yield from keyscan._inner(hostname)
210
+
211
+ # Download the file with scp
212
+ yield "scp -P {0} {1}:{2} {3}".format(
213
+ port,
214
+ connection_target,
215
+ filename,
216
+ local_filename,
217
+ )
@@ -0,0 +1,150 @@
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
+ from pyinfra.operations import systemd
70
+ systemd.service(
71
+ name="Restart and enable the dnsmasq service",
72
+ service="dnsmasq.service",
73
+ running=True,
74
+ restarted=True,
75
+ enabled=True,
76
+ )
77
+
78
+ systemd.service(
79
+ name="Enable logrotate timer",
80
+ service="logrotate.timer",
81
+ running=True,
82
+ enabled=True,
83
+ )
84
+
85
+ """
86
+
87
+ systemctl_cmd = _make_systemctl_cmd(
88
+ user_mode=user_mode,
89
+ machine=machine,
90
+ user_name=user_name,
91
+ )
92
+
93
+ if not service.endswith(
94
+ (
95
+ ".service",
96
+ ".socket",
97
+ ".device",
98
+ ".mount",
99
+ ".automount",
100
+ ".swap",
101
+ ".target",
102
+ ".path",
103
+ ".timer",
104
+ ".slice",
105
+ ".scope",
106
+ )
107
+ ):
108
+ service = "{0}.service".format(service)
109
+
110
+ if daemon_reload:
111
+ yield from _daemon_reload(
112
+ user_mode=user_mode,
113
+ machine=machine,
114
+ user_name=user_name,
115
+ )
116
+
117
+ yield from handle_service_control(
118
+ host,
119
+ service,
120
+ host.get_fact(
121
+ SystemdStatus,
122
+ user_mode=user_mode,
123
+ machine=machine,
124
+ user_name=user_name,
125
+ services=[service],
126
+ ),
127
+ " ".join([systemctl_cmd.get_raw_value(), "{1}", "{0}"]),
128
+ running,
129
+ restarted,
130
+ reloaded,
131
+ command,
132
+ )
133
+
134
+ if isinstance(enabled, bool):
135
+ systemd_enabled = host.get_fact(
136
+ SystemdEnabled,
137
+ user_mode=user_mode,
138
+ machine=machine,
139
+ user_name=user_name,
140
+ services=[service],
141
+ )
142
+ is_enabled = systemd_enabled.get(service, False)
143
+
144
+ # Isn't enabled and want enabled?
145
+ if not is_enabled and enabled is True:
146
+ yield "{0} enable {1}".format(systemctl_cmd, shlex.quote(service))
147
+
148
+ # Is enabled and want disabled?
149
+ elif is_enabled and enabled is False:
150
+ yield "{0} disable {1}".format(systemctl_cmd, shlex.quote(service))
@@ -0,0 +1,142 @@
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
+ + 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
+ from pyinfra.operations import sysvinit
52
+ sysvinit.service(
53
+ name="Restart and enable rsyslog",
54
+ service="rsyslog",
55
+ restarted=True,
56
+ enabled=True,
57
+ )
58
+ """
59
+
60
+ yield from handle_service_control(
61
+ host,
62
+ service,
63
+ host.get_fact(InitdStatus),
64
+ "/etc/init.d/{0} {1}",
65
+ running,
66
+ restarted,
67
+ reloaded,
68
+ command,
69
+ )
70
+
71
+ if isinstance(enabled, bool):
72
+ start_links = host.get_fact(
73
+ FindLinks,
74
+ path="/etc/rc*.d/S*{0}".format(service),
75
+ quote_path=False, # enable path glob matching
76
+ )
77
+
78
+ # If no links exist, attempt to enable the service using distro-specific commands
79
+ if enabled is True and not start_links:
80
+ distro = host.get_fact(LinuxDistribution).get("name")
81
+
82
+ if distro in ("Ubuntu", "Debian"):
83
+ yield "update-rc.d {0} defaults".format(service)
84
+
85
+ elif distro in ("CentOS", "Fedora", "Red Hat Enterprise Linux"):
86
+ yield "chkconfig {0} --add".format(service)
87
+ yield "chkconfig {0} on".format(service)
88
+
89
+ elif distro == "Gentoo":
90
+ yield "rc-update add {0} default".format(service)
91
+
92
+ # Remove any /etc/rcX.d/<service> start links
93
+ elif enabled is False:
94
+ # No state checking, just blindly remove any that exist
95
+ for link in start_links:
96
+ yield "rm -f {0}".format(link)
97
+
98
+
99
+ @operation()
100
+ def enable(
101
+ service: str,
102
+ start_priority=20,
103
+ stop_priority=80,
104
+ start_levels=(2, 3, 4, 5),
105
+ stop_levels=(0, 1, 6),
106
+ ):
107
+ """
108
+ Manually enable /etc/init.d scripts by creating /etc/rcX.d/Y links.
109
+
110
+ + service: name of the service to enable
111
+ + start_priority: priority to start the service
112
+ + stop_priority: priority to stop the service
113
+ + start_levels: which runlevels should the service run when enabled
114
+ + stop_levels: which runlevels should the service stop when enabled
115
+
116
+ **Example:**
117
+
118
+ .. code:: python
119
+
120
+ init.d_enable(
121
+ name="Finer control on which runlevels rsyslog should run",
122
+ service="rsyslog",
123
+ start_levels=(3, 4, 5),
124
+ stop_levels=(0, 1, 2, 6),
125
+ )
126
+ """
127
+
128
+ # Build link list
129
+ links = []
130
+
131
+ for level in start_levels:
132
+ links.append("/etc/rc{0}.d/S{1}{2}".format(level, start_priority, service))
133
+
134
+ for level in stop_levels:
135
+ links.append("/etc/rc{0}.d/K{1}{2}".format(level, stop_priority, service))
136
+
137
+ # Ensure all the new links exist
138
+ for link in links:
139
+ yield from files.link._inner(
140
+ path=link,
141
+ target="/etc/init.d/{0}".format(service),
142
+ )
@@ -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))