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,336 @@
|
|
|
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
|
+
|
|
176
|
+
packages: list[PkgInfo] = []
|
|
177
|
+
if isinstance(packages_to_ensure[0], PkgInfo):
|
|
178
|
+
packages = cast("list[PkgInfo]", packages_to_ensure)
|
|
179
|
+
if version_join is not None:
|
|
180
|
+
raise OperationValueError("cannot specify version_join and provide list[PkgInfo]")
|
|
181
|
+
else:
|
|
182
|
+
packages = [
|
|
183
|
+
PkgInfo.from_possible_pair(package, version_join)
|
|
184
|
+
for package in cast("list[str]", packages_to_ensure)
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
diff_packages = []
|
|
188
|
+
diff_expanded_packages = {}
|
|
189
|
+
|
|
190
|
+
upgrade_packages = []
|
|
191
|
+
|
|
192
|
+
if present is True:
|
|
193
|
+
for package in packages:
|
|
194
|
+
has_package, expanded_packages = _has_package(
|
|
195
|
+
package.lkup_name, current_packages, expand_package_fact
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if not has_package:
|
|
199
|
+
diff_packages.append(package.inst_vers)
|
|
200
|
+
diff_expanded_packages[package.name] = expanded_packages
|
|
201
|
+
else:
|
|
202
|
+
# Present packages w/o version specified - for upgrade if latest
|
|
203
|
+
if not package.has_version: # don't try to upgrade if a specific version requested
|
|
204
|
+
upgrade_packages.append(package.inst_vers)
|
|
205
|
+
|
|
206
|
+
if not latest:
|
|
207
|
+
if (pkg := package.name) in current_packages:
|
|
208
|
+
host.noop(f"package {pkg} is installed ({','.join(current_packages[pkg])})")
|
|
209
|
+
else:
|
|
210
|
+
host.noop(f"package {package.name} is installed")
|
|
211
|
+
if present is False:
|
|
212
|
+
for package in packages:
|
|
213
|
+
has_package, expanded_packages = _has_package(
|
|
214
|
+
package.lkup_name, current_packages, expand_package_fact, match_any=True
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if has_package:
|
|
218
|
+
diff_packages.append(package.inst_vers)
|
|
219
|
+
diff_expanded_packages[package.name] = expanded_packages
|
|
220
|
+
else:
|
|
221
|
+
host.noop(f"package {package.name} is not installed")
|
|
222
|
+
|
|
223
|
+
if diff_packages:
|
|
224
|
+
command = install_command if present else uninstall_command
|
|
225
|
+
yield f"{command} {' '.join([pkg for pkg in diff_packages])}"
|
|
226
|
+
|
|
227
|
+
if latest and upgrade_command and upgrade_packages:
|
|
228
|
+
yield f"{upgrade_command} {' '.join([pkg for pkg in upgrade_packages])}"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def ensure_rpm(state: State, host: Host, source: str, present: bool, package_manager_command: str):
|
|
232
|
+
original_source = source
|
|
233
|
+
|
|
234
|
+
# If source is a url
|
|
235
|
+
if urlparse(source).scheme:
|
|
236
|
+
# Generate a temp filename (with .rpm extension to please yum)
|
|
237
|
+
temp_filename = "{0}.rpm".format(host.get_temp_filename(source))
|
|
238
|
+
|
|
239
|
+
# Ensure it's downloaded
|
|
240
|
+
yield from files.download._inner(src=source, dest=temp_filename)
|
|
241
|
+
|
|
242
|
+
# Override the source with the downloaded file
|
|
243
|
+
source = temp_filename
|
|
244
|
+
|
|
245
|
+
# Check for file .rpm information
|
|
246
|
+
info = host.get_fact(RpmPackage, package=source)
|
|
247
|
+
exists = False
|
|
248
|
+
|
|
249
|
+
# We have info!
|
|
250
|
+
if info:
|
|
251
|
+
current_package = host.get_fact(RpmPackage, package=info["name"])
|
|
252
|
+
if current_package and current_package["version"] == info["version"]:
|
|
253
|
+
exists = True
|
|
254
|
+
|
|
255
|
+
# Package does not exist and we want?
|
|
256
|
+
if present and not exists:
|
|
257
|
+
# If we had info, always install
|
|
258
|
+
if info:
|
|
259
|
+
yield "rpm -i {0}".format(source)
|
|
260
|
+
# This happens if we download the package mid-deploy, so we have no info
|
|
261
|
+
# but also don't know if it's installed. So check at runtime, otherwise
|
|
262
|
+
# the install will fail.
|
|
263
|
+
else:
|
|
264
|
+
yield "rpm -q `rpm -qp {0}` 2> /dev/null || rpm -i {0}".format(source)
|
|
265
|
+
|
|
266
|
+
# Package exists but we don't want?
|
|
267
|
+
elif exists and not present:
|
|
268
|
+
yield "{0} remove -y {1}".format(package_manager_command, info["name"])
|
|
269
|
+
else:
|
|
270
|
+
host.noop(
|
|
271
|
+
"rpm {0} is {1}".format(
|
|
272
|
+
original_source,
|
|
273
|
+
"installed" if present else "not installed",
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def ensure_yum_repo(
|
|
279
|
+
host: Host,
|
|
280
|
+
name_or_url: str,
|
|
281
|
+
baseurl: str | None,
|
|
282
|
+
present: bool,
|
|
283
|
+
description: str | None,
|
|
284
|
+
enabled: bool,
|
|
285
|
+
gpgcheck: bool,
|
|
286
|
+
gpgkey: str | None,
|
|
287
|
+
repo_directory="/etc/yum.repos.d/",
|
|
288
|
+
type_: str | None = None,
|
|
289
|
+
):
|
|
290
|
+
url = None
|
|
291
|
+
url_parts = urlparse(name_or_url)
|
|
292
|
+
if url_parts.scheme:
|
|
293
|
+
url = name_or_url
|
|
294
|
+
name_or_url = url_parts.path.split("/")[-1]
|
|
295
|
+
if name_or_url.endswith(".repo"):
|
|
296
|
+
name_or_url = name_or_url[:-5]
|
|
297
|
+
|
|
298
|
+
filename = "{0}{1}.repo".format(repo_directory, name_or_url)
|
|
299
|
+
|
|
300
|
+
# If we don't want the repo, just remove any existing file
|
|
301
|
+
if not present:
|
|
302
|
+
yield from files.file._inner(path=filename, present=False)
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
# If we're a URL, download the repo if it doesn't exist
|
|
306
|
+
if url:
|
|
307
|
+
if not host.get_fact(File, path=filename):
|
|
308
|
+
yield from files.download._inner(src=url, dest=filename)
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
assert isinstance(baseurl, str)
|
|
312
|
+
|
|
313
|
+
# Description defaults to name
|
|
314
|
+
description = description or name_or_url
|
|
315
|
+
|
|
316
|
+
# Build the repo file from string
|
|
317
|
+
repo_lines = [
|
|
318
|
+
"[{0}]".format(name_or_url),
|
|
319
|
+
"name={0}".format(description),
|
|
320
|
+
"baseurl={0}".format(baseurl),
|
|
321
|
+
"enabled={0}".format(1 if enabled else 0),
|
|
322
|
+
"gpgcheck={0}".format(1 if gpgcheck else 0),
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
if type_:
|
|
326
|
+
repo_lines.append("type={0}".format(type_))
|
|
327
|
+
|
|
328
|
+
if gpgkey:
|
|
329
|
+
repo_lines.append("gpgkey={0}".format(gpgkey))
|
|
330
|
+
|
|
331
|
+
repo_lines.append("")
|
|
332
|
+
repo = "\n".join(repo_lines)
|
|
333
|
+
repo_file = StringIO(repo)
|
|
334
|
+
|
|
335
|
+
# Ensure this is the file on the server
|
|
336
|
+
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,77 @@
|
|
|
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
|
+
xbps.packages(
|
|
58
|
+
name="Install Vim and Vim Pager",
|
|
59
|
+
packages=["vimpager", "vim"],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
if update:
|
|
65
|
+
yield from _update()
|
|
66
|
+
|
|
67
|
+
if upgrade:
|
|
68
|
+
yield from _upgrade()
|
|
69
|
+
|
|
70
|
+
yield from ensure_packages(
|
|
71
|
+
host,
|
|
72
|
+
packages,
|
|
73
|
+
host.get_fact(XbpsPackages),
|
|
74
|
+
present,
|
|
75
|
+
install_command="xbps-install -y -u",
|
|
76
|
+
uninstall_command="xbps-remove -y",
|
|
77
|
+
)
|