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,338 @@
1
+ from __future__ import annotations
2
+
3
+ import shlex
4
+ from collections import defaultdict
5
+ from io import StringIO
6
+ from typing import Callable, NamedTuple, cast
7
+ from urllib.parse import urlparse
8
+
9
+ from packaging.requirements import InvalidRequirement, Requirement
10
+
11
+ from pyinfra import logger
12
+ from pyinfra.api import Host, OperationValueError, State
13
+ from pyinfra.facts.files import File
14
+ from pyinfra.facts.rpm import RpmPackage
15
+ from pyinfra.operations import files
16
+
17
+
18
+ def default_inst_vers_format_fn(name: str, operator: str, version: str):
19
+ return "{name}{operator}{version}".format(name=name, operator=operator, version=version)
20
+
21
+
22
+ class PkgInfo(NamedTuple):
23
+ name: str
24
+ version: str
25
+ operator: str
26
+ url: str
27
+ inst_vers_format_fn: Callable = default_inst_vers_format_fn
28
+ """
29
+ The key packaging information needed: version, operator and url are optional.
30
+ """
31
+
32
+ @property
33
+ def lkup_name(self) -> str | list[str]:
34
+ return self.name if self.version == "" else [self.name, self.version]
35
+
36
+ @property
37
+ def has_version(self) -> bool:
38
+ return self.version != ""
39
+
40
+ @property
41
+ def inst_vers(self) -> str:
42
+ """String that represents how a program can be installed.
43
+
44
+ - If self.url exists, then url is always returned.
45
+ - If self.version exists, then inst_vers_format_fn is used
46
+ to create the string. The default template is '{name}{operator}{version}'.
47
+ - Otherwise, self.name is returned.
48
+
49
+ Note, the result string will be quoted, so input is shell safe.
50
+ """
51
+
52
+ if self.url:
53
+ return shlex.quote(self.url)
54
+
55
+ if self.version:
56
+ return shlex.quote(self.inst_vers_format_fn(self.name, self.operator, self.version))
57
+ return shlex.quote(self.name)
58
+
59
+ @classmethod
60
+ def from_possible_pair(cls, s: str, join: str | None) -> PkgInfo:
61
+ if join is not None:
62
+ pieces = s.rsplit(join, 1)
63
+ return cls(pieces[0], pieces[1] if len(pieces) > 1 else "", join, "")
64
+
65
+ return cls(s, "", "", "")
66
+
67
+ @classmethod
68
+ def from_pep508(cls, s: str) -> PkgInfo | None:
69
+ """
70
+ Separate out the useful parts (name, url, operator, version) of a PEP-508 dependency.
71
+ Note: only one specifier is allowed.
72
+ PEP-0426 states that Python packages should be compared using lowercase; thus
73
+ the name is lower-cased
74
+ For backwards compatibility, invalid requirements are assumed to be package names with a
75
+ warning that this will change in the next major release
76
+ """
77
+ pep_508 = "PEP 508 non-compliant "
78
+ treatment = "requirement treated as package name"
79
+ will_change = "4.x will make this an error" # pip and pipx already throw away None's
80
+ try:
81
+ reqt = Requirement(s)
82
+ except InvalidRequirement as e:
83
+ logger.warning(f"{pep_508} :{e}\n{will_change}")
84
+ return cls(s, "", "", "")
85
+ else:
86
+ if (len(reqt.specifier) > 0) and (len(reqt.specifier) > 1):
87
+ logger.warning(f"{pep_508}/unsupported specifier ({s}) {treatment}\n{will_change}")
88
+ return cls(s, "", "", "")
89
+ else:
90
+ spec = next(iter(reqt.specifier), None)
91
+ return cls(
92
+ reqt.name.lower(),
93
+ spec.version if spec is not None else "",
94
+ spec.operator if spec is not None else "",
95
+ reqt.url or "",
96
+ )
97
+
98
+
99
+ def _has_package(
100
+ package: str | list[str],
101
+ packages: dict[str, set[str]],
102
+ expand_package_fact: Callable[[str], list[str | list[str]]] | None = None,
103
+ match_any=False,
104
+ ) -> tuple[bool, dict]:
105
+ def in_packages(pkg_name, pkg_versions):
106
+ if not pkg_versions:
107
+ return pkg_name in packages
108
+ return pkg_name in packages and any(
109
+ version in packages[pkg_name] for version in pkg_versions
110
+ )
111
+
112
+ packages_to_check: list[str | list[str]] = [package]
113
+ if expand_package_fact:
114
+ if isinstance(package, list):
115
+ packages_to_check = expand_package_fact(package[0]) or packages_to_check
116
+ else:
117
+ packages_to_check = expand_package_fact(package) or packages_to_check
118
+
119
+ package_name_to_versions = defaultdict(set)
120
+ for pkg in packages_to_check:
121
+ if isinstance(pkg, list):
122
+ package_name_to_versions[pkg[0]].add(pkg[1])
123
+ else:
124
+ package_name_to_versions[pkg] # just make sure it exists
125
+
126
+ checks = (
127
+ in_packages(pkg_name, pkg_versions)
128
+ for pkg_name, pkg_versions in package_name_to_versions.items()
129
+ )
130
+
131
+ if match_any:
132
+ return any(checks), package_name_to_versions
133
+ return all(checks), package_name_to_versions
134
+
135
+
136
+ def ensure_packages(
137
+ host: Host,
138
+ packages_to_ensure: str | list[str] | list[PkgInfo] | None,
139
+ current_packages: dict[str, set[str]],
140
+ present: bool,
141
+ install_command: str,
142
+ uninstall_command: str,
143
+ latest: bool = False,
144
+ upgrade_command: str | None = None,
145
+ version_join: str | None = None,
146
+ expand_package_fact: Callable[[str], list[str | list[str]]] | None = None,
147
+ ):
148
+ """
149
+ Handles this common scenario:
150
+
151
+ + We have a list of packages(/versions/urls) to ensure
152
+ + We have a map of existing package -> versions
153
+ + We have the common command bits (install, uninstall, version "joiner")
154
+ + Outputs commands to ensure our desired packages/versions
155
+ + Optionally upgrades packages w/o specified version when present
156
+
157
+ Args:
158
+ packages_to_ensure (list): list of packages or package/versions or PkgInfo's
159
+ current_packages (dict): dict of package names -> version
160
+ present (bool): whether packages should exist or not
161
+ install_command (str): command to prefix to list of packages to install
162
+ uninstall_command (str): as above for uninstalling packages
163
+ latest (bool): whether to upgrade installed packages when present
164
+ upgrade_command (str): as above for upgrading
165
+ version_join (str): the package manager specific "joiner", ie ``=`` for \
166
+ ``<apt_pkg>=<version>``. Not allowed if (pkg, ver, url) tuples are provided.
167
+ expand_package_fact: fact returning packages providing a capability \
168
+ (ie ``yum whatprovides``)
169
+ """
170
+
171
+ if packages_to_ensure is None:
172
+ return
173
+ if isinstance(packages_to_ensure, str):
174
+ packages_to_ensure = [packages_to_ensure]
175
+ if len(packages_to_ensure) == 0:
176
+ return
177
+
178
+ packages: list[PkgInfo] = []
179
+ if isinstance(packages_to_ensure[0], PkgInfo):
180
+ packages = cast("list[PkgInfo]", packages_to_ensure)
181
+ if version_join is not None:
182
+ raise OperationValueError("cannot specify version_join and provide list[PkgInfo]")
183
+ else:
184
+ packages = [
185
+ PkgInfo.from_possible_pair(package, version_join)
186
+ for package in cast("list[str]", packages_to_ensure)
187
+ ]
188
+
189
+ diff_packages = []
190
+ diff_expanded_packages = {}
191
+
192
+ upgrade_packages = []
193
+
194
+ if present is True:
195
+ for package in packages:
196
+ has_package, expanded_packages = _has_package(
197
+ package.lkup_name, current_packages, expand_package_fact
198
+ )
199
+
200
+ if not has_package:
201
+ diff_packages.append(package.inst_vers)
202
+ diff_expanded_packages[package.name] = expanded_packages
203
+ else:
204
+ # Present packages w/o version specified - for upgrade if latest
205
+ if not package.has_version: # don't try to upgrade if a specific version requested
206
+ upgrade_packages.append(package.inst_vers)
207
+
208
+ if not latest:
209
+ if (pkg := package.name) in current_packages:
210
+ host.noop(f"package {pkg} is installed ({','.join(current_packages[pkg])})")
211
+ else:
212
+ host.noop(f"package {package.name} is installed")
213
+ if present is False:
214
+ for package in packages:
215
+ has_package, expanded_packages = _has_package(
216
+ package.lkup_name, current_packages, expand_package_fact, match_any=True
217
+ )
218
+
219
+ if has_package:
220
+ diff_packages.append(package.inst_vers)
221
+ diff_expanded_packages[package.name] = expanded_packages
222
+ else:
223
+ host.noop(f"package {package.name} is not installed")
224
+
225
+ if diff_packages:
226
+ command = install_command if present else uninstall_command
227
+ yield f"{command} {' '.join([pkg for pkg in diff_packages])}"
228
+
229
+ if latest and upgrade_command and upgrade_packages:
230
+ yield f"{upgrade_command} {' '.join([pkg for pkg in upgrade_packages])}"
231
+
232
+
233
+ def ensure_rpm(state: State, host: Host, source: str, present: bool, package_manager_command: str):
234
+ original_source = source
235
+
236
+ # If source is a url
237
+ if urlparse(source).scheme:
238
+ # Generate a temp filename (with .rpm extension to please yum)
239
+ temp_filename = "{0}.rpm".format(host.get_temp_filename(source))
240
+
241
+ # Ensure it's downloaded
242
+ yield from files.download._inner(src=source, dest=temp_filename)
243
+
244
+ # Override the source with the downloaded file
245
+ source = temp_filename
246
+
247
+ # Check for file .rpm information
248
+ info = host.get_fact(RpmPackage, package=source)
249
+ exists = False
250
+
251
+ # We have info!
252
+ if info:
253
+ current_package = host.get_fact(RpmPackage, package=info["name"])
254
+ if current_package and current_package["version"] == info["version"]:
255
+ exists = True
256
+
257
+ # Package does not exist and we want?
258
+ if present and not exists:
259
+ # If we had info, always install
260
+ if info:
261
+ yield "rpm -i {0}".format(source)
262
+ # This happens if we download the package mid-deploy, so we have no info
263
+ # but also don't know if it's installed. So check at runtime, otherwise
264
+ # the install will fail.
265
+ else:
266
+ yield "rpm -q `rpm -qp {0}` 2> /dev/null || rpm -i {0}".format(source)
267
+
268
+ # Package exists but we don't want?
269
+ elif exists and not present:
270
+ yield "{0} remove -y {1}".format(package_manager_command, info["name"])
271
+ else:
272
+ host.noop(
273
+ "rpm {0} is {1}".format(
274
+ original_source,
275
+ "installed" if present else "not installed",
276
+ ),
277
+ )
278
+
279
+
280
+ def ensure_yum_repo(
281
+ host: Host,
282
+ name_or_url: str,
283
+ baseurl: str | None,
284
+ present: bool,
285
+ description: str | None,
286
+ enabled: bool,
287
+ gpgcheck: bool,
288
+ gpgkey: str | None,
289
+ repo_directory="/etc/yum.repos.d/",
290
+ type_: str | None = None,
291
+ ):
292
+ url = None
293
+ url_parts = urlparse(name_or_url)
294
+ if url_parts.scheme:
295
+ url = name_or_url
296
+ name_or_url = url_parts.path.split("/")[-1]
297
+ if name_or_url.endswith(".repo"):
298
+ name_or_url = name_or_url[:-5]
299
+
300
+ filename = "{0}{1}.repo".format(repo_directory, name_or_url)
301
+
302
+ # If we don't want the repo, just remove any existing file
303
+ if not present:
304
+ yield from files.file._inner(path=filename, present=False)
305
+ return
306
+
307
+ # If we're a URL, download the repo if it doesn't exist
308
+ if url:
309
+ if not host.get_fact(File, path=filename):
310
+ yield from files.download._inner(src=url, dest=filename)
311
+ return
312
+
313
+ assert isinstance(baseurl, str)
314
+
315
+ # Description defaults to name
316
+ description = description or name_or_url
317
+
318
+ # Build the repo file from string
319
+ repo_lines = [
320
+ "[{0}]".format(name_or_url),
321
+ "name={0}".format(description),
322
+ "baseurl={0}".format(baseurl),
323
+ "enabled={0}".format(1 if enabled else 0),
324
+ "gpgcheck={0}".format(1 if gpgcheck else 0),
325
+ ]
326
+
327
+ if type_:
328
+ repo_lines.append("type={0}".format(type_))
329
+
330
+ if gpgkey:
331
+ repo_lines.append("gpgkey={0}".format(gpgkey))
332
+
333
+ repo_lines.append("")
334
+ repo = "\n".join(repo_lines)
335
+ repo_file = StringIO(repo)
336
+
337
+ # Ensure this is the file on the server
338
+ yield from files.put._inner(src=repo_file, dest=filename)
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import shlex
4
+
5
+ from pyinfra.api import Host
6
+
7
+
8
+ def handle_service_control(
9
+ host: Host,
10
+ name: str,
11
+ statuses: dict[str, bool],
12
+ formatter: str,
13
+ running: bool | None = None,
14
+ restarted: bool | None = None,
15
+ reloaded: bool | None = None,
16
+ command: str | None = None,
17
+ status_argument="status",
18
+ ):
19
+ is_running = statuses.get(name, None)
20
+
21
+ # Need down but running
22
+ if running is False:
23
+ if is_running:
24
+ yield formatter.format(shlex.quote(name), "stop")
25
+ else:
26
+ host.noop("service {0} is stopped".format(name))
27
+
28
+ # Need running but down
29
+ if running is True:
30
+ if not is_running:
31
+ yield formatter.format(shlex.quote(name), "start")
32
+ else:
33
+ host.noop("service {0} is running".format(name))
34
+
35
+ # Only restart if the service is already running
36
+ if restarted and is_running:
37
+ yield formatter.format(shlex.quote(name), "restart")
38
+
39
+ # Only reload if the service is already reloaded
40
+ if reloaded and is_running:
41
+ yield formatter.format(shlex.quote(name), "reload")
42
+
43
+ # Always execute arbitrary commands as these may or may not rely on the service
44
+ # being up or down
45
+ if command:
46
+ yield formatter.format(shlex.quote(name), command)
@@ -0,0 +1,137 @@
1
+ """
2
+ Manage OpenVZ containers with ``vzctl``.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from pyinfra import host
8
+ from pyinfra.api import OperationError, operation
9
+ from pyinfra.facts.vzctl import OpenvzContainers
10
+
11
+
12
+ @operation(is_idempotent=False)
13
+ def start(ctid: str, force=False):
14
+ """
15
+ Start OpenVZ containers.
16
+
17
+ + ctid: CTID of the container to start
18
+ + force: whether to force container start
19
+ """
20
+
21
+ args = ["{0}".format(ctid)]
22
+
23
+ if force:
24
+ args.append("--force")
25
+
26
+ yield "vzctl start {0}".format(" ".join(args))
27
+
28
+
29
+ @operation(is_idempotent=False)
30
+ def stop(ctid: str):
31
+ """
32
+ Stop OpenVZ containers.
33
+
34
+ + ctid: CTID of the container to stop
35
+ """
36
+
37
+ args = ["{0}".format(ctid)]
38
+
39
+ yield "vzctl stop {0}".format(" ".join(args))
40
+
41
+
42
+ @operation(is_idempotent=False)
43
+ def restart(ctid: str, force=False):
44
+ """
45
+ Restart OpenVZ containers.
46
+
47
+ + ctid: CTID of the container to restart
48
+ + force: whether to force container start
49
+ """
50
+
51
+ yield from stop._inner(ctid=ctid)
52
+ yield from start._inner(ctid=ctid, force=force)
53
+
54
+
55
+ @operation(is_idempotent=False)
56
+ def mount(ctid: str):
57
+ """
58
+ Mount OpenVZ container filesystems.
59
+
60
+ + ctid: CTID of the container to mount
61
+ """
62
+
63
+ yield "vzctl mount {0}".format(ctid)
64
+
65
+
66
+ @operation(is_idempotent=False)
67
+ def unmount(ctid: str):
68
+ """
69
+ Unmount OpenVZ container filesystems.
70
+
71
+ + ctid: CTID of the container to unmount
72
+ """
73
+
74
+ yield "vzctl umount {0}".format(ctid)
75
+
76
+
77
+ @operation(is_idempotent=False)
78
+ def delete(ctid: str):
79
+ """
80
+ Delete OpenVZ containers.
81
+
82
+ + ctid: CTID of the container to delete
83
+ """
84
+
85
+ yield "vzctl delete {0}".format(ctid)
86
+
87
+
88
+ @operation(is_idempotent=False)
89
+ def create(ctid: str, template: str | None = None):
90
+ """
91
+ Create OpenVZ containers.
92
+
93
+ + ctid: CTID of the container to create
94
+ """
95
+
96
+ # Check we don't already have a container with this CTID
97
+ current_containers = host.get_fact(OpenvzContainers)
98
+ if ctid in current_containers:
99
+ raise OperationError(
100
+ "An OpenVZ container with CTID {0} already exists".format(ctid),
101
+ )
102
+
103
+ args = ["{0}".format(ctid)]
104
+
105
+ if template:
106
+ args.append("--ostemplate {0}".format(template))
107
+
108
+ yield "vzctl create {0}".format(" ".join(args))
109
+
110
+
111
+ @operation(is_idempotent=False)
112
+ def set(ctid: str, save=True, **settings):
113
+ """
114
+ Set OpenVZ container details.
115
+
116
+ + ctid: CTID of the container to set
117
+ + save: whether to save the changes
118
+ + settings: settings/arguments to apply to the container
119
+
120
+ Settings/arguments:
121
+ these are mapped directly to ``vztctl`` arguments, eg
122
+ ``hostname='my-host.net'`` becomes ``--hostname my-host.net``.
123
+ """
124
+
125
+ args = ["{0}".format(ctid)]
126
+
127
+ if save:
128
+ args.append("--save")
129
+
130
+ for key, value in settings.items():
131
+ # Handle list values (e.g. --nameserver X --nameserver X)
132
+ if isinstance(value, list):
133
+ args.extend("--{0} {1}".format(key, v) for v in value)
134
+ else:
135
+ args.append("--{0} {1}".format(key, value))
136
+
137
+ yield "vzctl set {0}".format(" ".join(args))
@@ -0,0 +1,78 @@
1
+ """
2
+ Manage XBPS packages and repositories. Note that XBPS package names are case-sensitive.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from pyinfra import host
8
+ from pyinfra.api import operation
9
+ from pyinfra.facts.xbps import XbpsPackages
10
+
11
+ from .util.packaging import ensure_packages
12
+
13
+
14
+ @operation(is_idempotent=False)
15
+ def upgrade():
16
+ """
17
+ Upgrades all XBPS packages.
18
+ """
19
+
20
+ yield "xbps-install -y -u"
21
+
22
+
23
+ _upgrade = upgrade._inner # noqa: E305
24
+
25
+
26
+ @operation(is_idempotent=False)
27
+ def update():
28
+ """
29
+ Update XBPS repositories.
30
+ """
31
+
32
+ yield "xbps-install -S"
33
+
34
+
35
+ _update = update._inner # noqa: E305
36
+
37
+
38
+ @operation()
39
+ def packages(
40
+ packages: str | list[str] | None = None,
41
+ present=True,
42
+ update=False,
43
+ upgrade=False,
44
+ ):
45
+ """
46
+ Install/remove/update XBPS packages.
47
+
48
+ + packages: list of packages to ensure
49
+ + present: whether the packages should be installed
50
+ + update: run ``xbps-install -S`` before installing packages
51
+ + upgrade: run ``xbps-install -y -u`` before installing packages
52
+
53
+ **Example:**
54
+
55
+ .. code:: python
56
+
57
+ from pyinfra.operations import xbps
58
+ xbps.packages(
59
+ name="Install Vim and Vim Pager",
60
+ packages=["vimpager", "vim"],
61
+ )
62
+
63
+ """
64
+
65
+ if update:
66
+ yield from _update()
67
+
68
+ if upgrade:
69
+ yield from _upgrade()
70
+
71
+ yield from ensure_packages(
72
+ host,
73
+ packages,
74
+ host.get_fact(XbpsPackages),
75
+ present,
76
+ install_command="xbps-install -y -u",
77
+ uninstall_command="xbps-remove -y",
78
+ )