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,488 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage apt packages and repositories.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import timedelta
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
from pyinfra import host
|
|
11
|
+
from pyinfra.api import OperationError, operation
|
|
12
|
+
from pyinfra.facts.apt import (
|
|
13
|
+
AptKeys,
|
|
14
|
+
AptSources,
|
|
15
|
+
SimulateOperationWillChange,
|
|
16
|
+
noninteractive_apt,
|
|
17
|
+
parse_apt_repo,
|
|
18
|
+
)
|
|
19
|
+
from pyinfra.facts.deb import DebPackage, DebPackages
|
|
20
|
+
from pyinfra.facts.files import File
|
|
21
|
+
from pyinfra.facts.gpg import GpgKey
|
|
22
|
+
from pyinfra.facts.server import Date
|
|
23
|
+
|
|
24
|
+
from . import files
|
|
25
|
+
from .util.packaging import ensure_packages
|
|
26
|
+
|
|
27
|
+
APT_UPDATE_FILENAME = "/var/lib/apt/periodic/update-success-stamp"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _simulate_then_perform(command: str):
|
|
31
|
+
changes = host.get_fact(SimulateOperationWillChange, command)
|
|
32
|
+
|
|
33
|
+
if not changes:
|
|
34
|
+
# Simulating apt-get command failed, so the actual
|
|
35
|
+
# operation will probably fail too:
|
|
36
|
+
yield noninteractive_apt(command)
|
|
37
|
+
elif (
|
|
38
|
+
changes["upgraded"] == 0
|
|
39
|
+
and changes["newly_installed"] == 0
|
|
40
|
+
and changes["removed"] == 0
|
|
41
|
+
and changes["not_upgraded"] == 0
|
|
42
|
+
):
|
|
43
|
+
host.noop(f"{command} skipped, no changes would be performed")
|
|
44
|
+
else:
|
|
45
|
+
yield noninteractive_apt(command)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@operation()
|
|
49
|
+
def key(src: str | None = None, keyserver: str | None = None, keyid: str | list[str] | None = None):
|
|
50
|
+
"""
|
|
51
|
+
Add apt gpg keys with ``apt-key``.
|
|
52
|
+
|
|
53
|
+
+ src: filename or URL
|
|
54
|
+
+ keyserver: URL of keyserver to fetch key from
|
|
55
|
+
+ keyid: key ID or list of key IDs when using keyserver
|
|
56
|
+
|
|
57
|
+
keyserver/id:
|
|
58
|
+
These must be provided together.
|
|
59
|
+
|
|
60
|
+
.. warning::
|
|
61
|
+
``apt-key`` is deprecated in Debian, it is recommended NOT to use this
|
|
62
|
+
operation and instead follow the instructions here:
|
|
63
|
+
|
|
64
|
+
https://wiki.debian.org/DebianRepository/UseThirdParty
|
|
65
|
+
|
|
66
|
+
**Examples:**
|
|
67
|
+
|
|
68
|
+
.. code:: python
|
|
69
|
+
|
|
70
|
+
# Note: If using URL, wget is assumed to be installed.
|
|
71
|
+
apt.key(
|
|
72
|
+
name="Add the Docker apt gpg key",
|
|
73
|
+
src="https://download.docker.com/linux/ubuntu/gpg",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
apt.key(
|
|
77
|
+
name="Install VirtualBox key",
|
|
78
|
+
src="https://www.virtualbox.org/download/oracle_vbox_2016.asc",
|
|
79
|
+
)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
existing_keys = host.get_fact(AptKeys)
|
|
83
|
+
|
|
84
|
+
if src:
|
|
85
|
+
key_data = host.get_fact(GpgKey, src=src)
|
|
86
|
+
if key_data:
|
|
87
|
+
keyid = list(key_data.keys())
|
|
88
|
+
|
|
89
|
+
if not keyid or not all(kid in existing_keys for kid in keyid):
|
|
90
|
+
# If URL, wget the key to stdout and pipe into apt-key, because the "adv"
|
|
91
|
+
# apt-key passes to gpg which doesn't always support https!
|
|
92
|
+
if urlparse(src).scheme:
|
|
93
|
+
yield "(wget -O - {0} || curl -sSLf {0}) | apt-key add -".format(src)
|
|
94
|
+
else:
|
|
95
|
+
yield "apt-key add {0}".format(src)
|
|
96
|
+
else:
|
|
97
|
+
host.noop("All keys from {0} are already available in the apt keychain".format(src))
|
|
98
|
+
|
|
99
|
+
if keyserver:
|
|
100
|
+
if not keyid:
|
|
101
|
+
raise OperationError("`keyid` must be provided with `keyserver`")
|
|
102
|
+
|
|
103
|
+
if isinstance(keyid, str):
|
|
104
|
+
keyid = [keyid]
|
|
105
|
+
|
|
106
|
+
needed_keys = sorted(set(keyid) - set(existing_keys.keys()))
|
|
107
|
+
if needed_keys:
|
|
108
|
+
yield "apt-key adv --keyserver {0} --recv-keys {1}".format(
|
|
109
|
+
keyserver,
|
|
110
|
+
" ".join(needed_keys),
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
host.noop(
|
|
114
|
+
"Keys {0} are already available in the apt keychain".format(
|
|
115
|
+
", ".join(keyid),
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@operation()
|
|
121
|
+
def repo(src: str, present=True, filename: str | None = None):
|
|
122
|
+
"""
|
|
123
|
+
Add/remove apt repositories.
|
|
124
|
+
|
|
125
|
+
+ src: apt source string eg ``deb http://X hardy main``
|
|
126
|
+
+ present: whether the repo should exist on the system
|
|
127
|
+
+ filename: optional filename to use ``/etc/apt/sources.list.d/<filename>.list``. By
|
|
128
|
+
default uses ``/etc/apt/sources.list``.
|
|
129
|
+
|
|
130
|
+
**Example:**
|
|
131
|
+
|
|
132
|
+
.. code:: python
|
|
133
|
+
|
|
134
|
+
apt.repo(
|
|
135
|
+
name="Install VirtualBox repo",
|
|
136
|
+
src="deb https://download.virtualbox.org/virtualbox/debian bionic contrib",
|
|
137
|
+
)
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# Get the target .list file to manage
|
|
141
|
+
if filename:
|
|
142
|
+
filename = "/etc/apt/sources.list.d/{0}.list".format(filename)
|
|
143
|
+
else:
|
|
144
|
+
filename = "/etc/apt/sources.list"
|
|
145
|
+
|
|
146
|
+
# Work out if the repo exists already
|
|
147
|
+
apt_sources = host.get_fact(AptSources)
|
|
148
|
+
|
|
149
|
+
is_present = False
|
|
150
|
+
repo = parse_apt_repo(src)
|
|
151
|
+
if repo and repo in apt_sources:
|
|
152
|
+
is_present = True
|
|
153
|
+
|
|
154
|
+
# Doesn't exist and we want it
|
|
155
|
+
if not is_present and present:
|
|
156
|
+
yield from files.line._inner(
|
|
157
|
+
path=filename,
|
|
158
|
+
line=src,
|
|
159
|
+
escape_regex_characters=True,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Exists and we don't want it
|
|
163
|
+
elif is_present and not present:
|
|
164
|
+
yield from files.line._inner(
|
|
165
|
+
path=filename,
|
|
166
|
+
line=src,
|
|
167
|
+
present=False,
|
|
168
|
+
escape_regex_characters=True,
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
host.noop(
|
|
172
|
+
'apt repo "{0}" {1}'.format(
|
|
173
|
+
src,
|
|
174
|
+
"exists" if present else "does not exist",
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@operation(is_idempotent=False)
|
|
180
|
+
def ppa(src: str, present=True):
|
|
181
|
+
"""
|
|
182
|
+
Add/remove Ubuntu ppa repositories.
|
|
183
|
+
|
|
184
|
+
+ src: the PPA name (full ppa:user/repo format)
|
|
185
|
+
+ present: whether it should exist
|
|
186
|
+
|
|
187
|
+
Note:
|
|
188
|
+
requires ``apt-add-repository`` on the remote host
|
|
189
|
+
|
|
190
|
+
**Example:**
|
|
191
|
+
|
|
192
|
+
.. code:: python
|
|
193
|
+
|
|
194
|
+
# Note: Assumes software-properties-common is installed.
|
|
195
|
+
apt.ppa(
|
|
196
|
+
name="Add the Bitcoin ppa",
|
|
197
|
+
src="ppa:bitcoin/bitcoin",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
if present:
|
|
203
|
+
yield 'apt-add-repository -y "{0}"'.format(src)
|
|
204
|
+
|
|
205
|
+
if not present:
|
|
206
|
+
yield 'apt-add-repository -y --remove "{0}"'.format(src)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@operation()
|
|
210
|
+
def deb(src: str, present=True, force=False):
|
|
211
|
+
"""
|
|
212
|
+
Add/remove ``.deb`` file packages.
|
|
213
|
+
|
|
214
|
+
+ src: filename or URL of the ``.deb`` file
|
|
215
|
+
+ present: whether or not the package should exist on the system
|
|
216
|
+
+ force: whether to force the package install by passing `--force-yes` to apt
|
|
217
|
+
|
|
218
|
+
Note:
|
|
219
|
+
When installing, ``apt-get install -f`` will be run to install any unmet
|
|
220
|
+
dependencies.
|
|
221
|
+
|
|
222
|
+
URL sources with ``present=False``:
|
|
223
|
+
If the ``.deb`` file isn't downloaded, pyinfra can't remove any existing
|
|
224
|
+
package as the file won't exist until mid-deploy.
|
|
225
|
+
|
|
226
|
+
**Example:**
|
|
227
|
+
|
|
228
|
+
.. code:: python
|
|
229
|
+
|
|
230
|
+
# Note: Assumes wget is installed.
|
|
231
|
+
apt.deb(
|
|
232
|
+
name="Install Chrome via deb",
|
|
233
|
+
src="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb",
|
|
234
|
+
)
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
original_src = src
|
|
238
|
+
|
|
239
|
+
# If source is a url
|
|
240
|
+
if urlparse(src).scheme:
|
|
241
|
+
# Generate a temp filename
|
|
242
|
+
temp_filename = host.get_temp_filename(src)
|
|
243
|
+
|
|
244
|
+
# Ensure it's downloaded
|
|
245
|
+
yield from files.download._inner(src=src, dest=temp_filename)
|
|
246
|
+
|
|
247
|
+
# Override the source with the downloaded file
|
|
248
|
+
src = temp_filename
|
|
249
|
+
|
|
250
|
+
# Check for file .deb information (if file is present)
|
|
251
|
+
info = host.get_fact(DebPackage, package=src)
|
|
252
|
+
current_packages = host.get_fact(DebPackages)
|
|
253
|
+
|
|
254
|
+
exists = False
|
|
255
|
+
|
|
256
|
+
# We have deb info! Check against installed packages
|
|
257
|
+
if info and info.get("version") in current_packages.get(info.get("name"), {}):
|
|
258
|
+
exists = True
|
|
259
|
+
|
|
260
|
+
# Package does not exist and we want?
|
|
261
|
+
if present:
|
|
262
|
+
if not exists:
|
|
263
|
+
# Install .deb file - ignoring failure (on unmet dependencies)
|
|
264
|
+
yield "dpkg --force-confdef --force-confold -i {0} 2> /dev/null || true".format(src)
|
|
265
|
+
# Attempt to install any missing dependencies
|
|
266
|
+
yield "{0} -f".format(noninteractive_apt("install", force=force))
|
|
267
|
+
# Now reinstall, and critically configure, the package - if there are still
|
|
268
|
+
# missing deps, now we error
|
|
269
|
+
yield "dpkg --force-confdef --force-confold -i {0}".format(src)
|
|
270
|
+
else:
|
|
271
|
+
host.noop("deb {0} is installed".format(original_src))
|
|
272
|
+
|
|
273
|
+
# Package exists but we don't want?
|
|
274
|
+
if not present:
|
|
275
|
+
if exists:
|
|
276
|
+
yield "{0} {1}".format(
|
|
277
|
+
noninteractive_apt("remove", force=force),
|
|
278
|
+
info["name"],
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
host.noop("deb {0} is not installed".format(original_src))
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@operation(
|
|
285
|
+
is_idempotent=False,
|
|
286
|
+
idempotent_notice=(
|
|
287
|
+
"This operation will always execute commands "
|
|
288
|
+
"unless the ``cache_time`` argument is provided."
|
|
289
|
+
),
|
|
290
|
+
)
|
|
291
|
+
def update(cache_time: int | None = None):
|
|
292
|
+
"""
|
|
293
|
+
Updates apt repositories.
|
|
294
|
+
|
|
295
|
+
+ cache_time: cache updates for this many seconds
|
|
296
|
+
|
|
297
|
+
**Example:**
|
|
298
|
+
|
|
299
|
+
.. code:: python
|
|
300
|
+
|
|
301
|
+
apt.update(
|
|
302
|
+
name="Update apt repositories",
|
|
303
|
+
cache_time=3600,
|
|
304
|
+
)
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
# If cache_time check when apt was last updated, prevent updates if within time
|
|
308
|
+
if cache_time:
|
|
309
|
+
# Ubuntu provides this handy file
|
|
310
|
+
cache_info = host.get_fact(File, path=APT_UPDATE_FILENAME)
|
|
311
|
+
|
|
312
|
+
# Time on files is not tz-aware, and will be the same tz as the server's time,
|
|
313
|
+
# so we can safely remove the tzinfo from the Date fact before comparison.
|
|
314
|
+
host_cache_time = host.get_fact(Date).replace(tzinfo=None) - timedelta(seconds=cache_time)
|
|
315
|
+
if cache_info and cache_info["mtime"] and cache_info["mtime"] > host_cache_time:
|
|
316
|
+
host.noop("apt is already up to date")
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
yield "apt-get update"
|
|
320
|
+
|
|
321
|
+
# Some apt systems (Debian) have the /var/lib/apt/periodic directory, but
|
|
322
|
+
# don't bother touching anything in there - so pyinfra does it, enabling
|
|
323
|
+
# cache_time to work.
|
|
324
|
+
if cache_time:
|
|
325
|
+
yield "touch {0}".format(APT_UPDATE_FILENAME)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
_update = update # noqa: E305
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@operation()
|
|
332
|
+
def upgrade(auto_remove: bool = False):
|
|
333
|
+
"""
|
|
334
|
+
Upgrades all apt packages.
|
|
335
|
+
|
|
336
|
+
+ auto_remove: removes transitive dependencies that are no longer needed.
|
|
337
|
+
|
|
338
|
+
**Example:**
|
|
339
|
+
|
|
340
|
+
.. code:: python
|
|
341
|
+
|
|
342
|
+
# Upgrade all packages
|
|
343
|
+
apt.upgrade(
|
|
344
|
+
name="Upgrade apt packages",
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# Upgrade all packages and remove unneeded transitive dependencies
|
|
348
|
+
apt.upgrade(
|
|
349
|
+
name="Upgrade apt packages and remove unneeded dependencies",
|
|
350
|
+
auto_remove=True
|
|
351
|
+
)
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
command = ["upgrade"]
|
|
355
|
+
|
|
356
|
+
if auto_remove:
|
|
357
|
+
command.append("--autoremove")
|
|
358
|
+
|
|
359
|
+
yield from _simulate_then_perform(" ".join(command))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
_upgrade = upgrade # noqa: E305 (for use below where update is a kwarg)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@operation()
|
|
366
|
+
def dist_upgrade(auto_remove: bool = False):
|
|
367
|
+
"""
|
|
368
|
+
Updates all apt packages, employing dist-upgrade.
|
|
369
|
+
|
|
370
|
+
+ auto_remove: removes transitive dependencies that are no longer needed.
|
|
371
|
+
|
|
372
|
+
**Example:**
|
|
373
|
+
|
|
374
|
+
.. code:: python
|
|
375
|
+
|
|
376
|
+
apt.dist_upgrade(
|
|
377
|
+
name="Upgrade apt packages using dist-upgrade",
|
|
378
|
+
)
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
command = ["dist-upgrade"]
|
|
382
|
+
|
|
383
|
+
if auto_remove:
|
|
384
|
+
command.append("--autoremove")
|
|
385
|
+
|
|
386
|
+
yield from _simulate_then_perform(" ".join(command))
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@operation()
|
|
390
|
+
def packages(
|
|
391
|
+
packages: str | list[str] | None = None,
|
|
392
|
+
present=True,
|
|
393
|
+
latest=False,
|
|
394
|
+
update=False,
|
|
395
|
+
cache_time: int | None = None,
|
|
396
|
+
upgrade=False,
|
|
397
|
+
force=False,
|
|
398
|
+
no_recommends=False,
|
|
399
|
+
allow_downgrades=False,
|
|
400
|
+
extra_install_args: str | None = None,
|
|
401
|
+
extra_uninstall_args: str | None = None,
|
|
402
|
+
):
|
|
403
|
+
"""
|
|
404
|
+
Install/remove/update packages & update apt.
|
|
405
|
+
|
|
406
|
+
+ packages: list of packages to ensure
|
|
407
|
+
+ present: whether the packages should be installed
|
|
408
|
+
+ latest: whether to upgrade packages without a specified version
|
|
409
|
+
+ update: run ``apt update`` before installing packages
|
|
410
|
+
+ cache_time: when used with ``update``, cache for this many seconds
|
|
411
|
+
+ upgrade: run ``apt upgrade`` before installing packages
|
|
412
|
+
+ force: whether to force package installs by passing `--force-yes` to apt
|
|
413
|
+
+ no_recommends: don't install recommended packages
|
|
414
|
+
+ allow_downgrades: allow downgrading packages with version (--allow-downgrades)
|
|
415
|
+
+ extra_install_args: additional arguments to the apt install command
|
|
416
|
+
+ extra_uninstall_args: additional arguments to the apt uninstall command
|
|
417
|
+
|
|
418
|
+
Versions:
|
|
419
|
+
Package versions can be pinned like apt: ``<pkg>=<version>``
|
|
420
|
+
|
|
421
|
+
Cache time:
|
|
422
|
+
When ``cache_time`` is set the ``/var/lib/apt/periodic/update-success-stamp`` file
|
|
423
|
+
is touched upon successful update. Some distros already do this (Ubuntu), but others
|
|
424
|
+
simply leave the periodic directory empty (Debian).
|
|
425
|
+
|
|
426
|
+
**Examples:**
|
|
427
|
+
|
|
428
|
+
.. code:: python
|
|
429
|
+
|
|
430
|
+
# Update package list and install packages
|
|
431
|
+
apt.packages(
|
|
432
|
+
name="Install Asterisk and Vim",
|
|
433
|
+
packages=["asterisk", "vim"],
|
|
434
|
+
update=True,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# Install the latest versions of packages (always check)
|
|
438
|
+
apt.packages(
|
|
439
|
+
name="Install latest Vim",
|
|
440
|
+
packages=["vim"],
|
|
441
|
+
latest=True,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Note: host.get_fact(OsVersion) is the same as `uname -r` (ex: '4.15.0-72-generic')
|
|
445
|
+
apt.packages(
|
|
446
|
+
name="Install kernel headers",
|
|
447
|
+
packages=[f"linux-headers-{host.get_fact(OsVersion)}"],
|
|
448
|
+
update=True,
|
|
449
|
+
)
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
if update:
|
|
453
|
+
yield from _update._inner(cache_time=cache_time)
|
|
454
|
+
|
|
455
|
+
if upgrade:
|
|
456
|
+
yield from _upgrade._inner()
|
|
457
|
+
|
|
458
|
+
install_command_args = ["install"]
|
|
459
|
+
if no_recommends is True:
|
|
460
|
+
install_command_args.append("--no-install-recommends")
|
|
461
|
+
if allow_downgrades:
|
|
462
|
+
install_command_args.append("--allow-downgrades")
|
|
463
|
+
|
|
464
|
+
upgrade_command = " ".join(install_command_args)
|
|
465
|
+
|
|
466
|
+
if extra_install_args:
|
|
467
|
+
install_command_args.append(extra_install_args)
|
|
468
|
+
|
|
469
|
+
install_command = " ".join(install_command_args)
|
|
470
|
+
|
|
471
|
+
uninstall_command_args = ["remove"]
|
|
472
|
+
if extra_uninstall_args:
|
|
473
|
+
uninstall_command_args.append(extra_uninstall_args)
|
|
474
|
+
|
|
475
|
+
uninstall_command = " ".join(uninstall_command_args)
|
|
476
|
+
|
|
477
|
+
# Compare/ensure packages are present/not
|
|
478
|
+
yield from ensure_packages(
|
|
479
|
+
host,
|
|
480
|
+
packages,
|
|
481
|
+
host.get_fact(DebPackages),
|
|
482
|
+
present,
|
|
483
|
+
install_command=noninteractive_apt(install_command, force=force),
|
|
484
|
+
uninstall_command=noninteractive_apt(uninstall_command, force=force),
|
|
485
|
+
upgrade_command=noninteractive_apt(upgrade_command, force=force),
|
|
486
|
+
version_join="=",
|
|
487
|
+
latest=latest,
|
|
488
|
+
)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage brew packages on mac/OSX. See https://brew.sh/
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import urllib.parse
|
|
8
|
+
|
|
9
|
+
from pyinfra import host
|
|
10
|
+
from pyinfra.api import operation
|
|
11
|
+
from pyinfra.facts.brew import BrewCasks, BrewPackages, BrewTaps, BrewVersion, new_cask_cli
|
|
12
|
+
|
|
13
|
+
from .util.packaging import ensure_packages
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@operation(is_idempotent=False)
|
|
17
|
+
def update():
|
|
18
|
+
"""
|
|
19
|
+
Updates brew repositories.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
yield "brew update"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_update = update # noqa: E305
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@operation(is_idempotent=False)
|
|
29
|
+
def upgrade():
|
|
30
|
+
"""
|
|
31
|
+
Upgrades all brew packages.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
yield "brew upgrade"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_upgrade = upgrade # noqa: E305
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@operation()
|
|
41
|
+
def packages(
|
|
42
|
+
packages: str | list[str] | None = None,
|
|
43
|
+
present=True,
|
|
44
|
+
latest=False,
|
|
45
|
+
update=False,
|
|
46
|
+
upgrade=False,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Add/remove/update brew packages.
|
|
50
|
+
|
|
51
|
+
+ packages: list of packages to ensure
|
|
52
|
+
+ present: whether the packages should be installed
|
|
53
|
+
+ latest: whether to upgrade packages without a specified version
|
|
54
|
+
+ update: run ``brew update`` before installing packages
|
|
55
|
+
+ upgrade: run ``brew upgrade`` before installing packages
|
|
56
|
+
|
|
57
|
+
Versions:
|
|
58
|
+
Package versions can be pinned like brew: ``<pkg>@<version>``.
|
|
59
|
+
|
|
60
|
+
**Examples:**
|
|
61
|
+
|
|
62
|
+
.. code:: python
|
|
63
|
+
|
|
64
|
+
# Update package list and install packages
|
|
65
|
+
brew.packages(
|
|
66
|
+
name='Install Vim and vimpager',
|
|
67
|
+
packages=["vimpager", "vim"],
|
|
68
|
+
update=True,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Install the latest versions of packages (always check)
|
|
72
|
+
brew.packages(
|
|
73
|
+
name="Install latest Vim",
|
|
74
|
+
packages=["vim"],
|
|
75
|
+
latest=True,
|
|
76
|
+
)
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if update:
|
|
80
|
+
yield from _update._inner()
|
|
81
|
+
|
|
82
|
+
if upgrade:
|
|
83
|
+
yield from _upgrade._inner()
|
|
84
|
+
|
|
85
|
+
yield from ensure_packages(
|
|
86
|
+
host,
|
|
87
|
+
packages,
|
|
88
|
+
host.get_fact(BrewPackages),
|
|
89
|
+
present,
|
|
90
|
+
install_command="brew install",
|
|
91
|
+
uninstall_command="brew uninstall",
|
|
92
|
+
upgrade_command="brew upgrade",
|
|
93
|
+
version_join="@",
|
|
94
|
+
latest=latest,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def cask_args():
|
|
99
|
+
return ("", " --cask") if new_cask_cli(host.get_fact(BrewVersion)) else ("cask ", "")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@operation(is_idempotent=False)
|
|
103
|
+
def cask_upgrade():
|
|
104
|
+
"""
|
|
105
|
+
Upgrades all brew casks.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
yield "brew %supgrade%s" % cask_args()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@operation()
|
|
112
|
+
def casks(
|
|
113
|
+
casks: str | list[str] | None = None,
|
|
114
|
+
present=True,
|
|
115
|
+
latest=False,
|
|
116
|
+
upgrade=False,
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Add/remove/update brew casks.
|
|
120
|
+
|
|
121
|
+
+ casks: list of casks to ensure
|
|
122
|
+
+ present: whether the casks should be installed
|
|
123
|
+
+ latest: whether to upgrade casks without a specified version
|
|
124
|
+
+ upgrade: run brew cask upgrade before installing casks
|
|
125
|
+
|
|
126
|
+
Versions:
|
|
127
|
+
Cask versions can be pinned like brew: ``<pkg>@<version>``.
|
|
128
|
+
|
|
129
|
+
**Example:**
|
|
130
|
+
|
|
131
|
+
.. code:: python
|
|
132
|
+
|
|
133
|
+
brew.casks(
|
|
134
|
+
name='Upgrade and install the latest cask',
|
|
135
|
+
casks=["godot"],
|
|
136
|
+
upgrade=True,
|
|
137
|
+
latest=True,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
if upgrade:
|
|
143
|
+
yield from cask_upgrade._inner()
|
|
144
|
+
|
|
145
|
+
args = cask_args()
|
|
146
|
+
|
|
147
|
+
yield from ensure_packages(
|
|
148
|
+
host,
|
|
149
|
+
casks,
|
|
150
|
+
host.get_fact(BrewCasks),
|
|
151
|
+
present,
|
|
152
|
+
install_command="brew %sinstall%s" % args,
|
|
153
|
+
uninstall_command="brew %suninstall%s" % args,
|
|
154
|
+
upgrade_command="brew %supgrade%s" % args,
|
|
155
|
+
version_join="@",
|
|
156
|
+
latest=latest,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@operation()
|
|
161
|
+
def tap(src: str | None = None, present=True, url: str | None = None):
|
|
162
|
+
"""
|
|
163
|
+
Add/remove brew taps.
|
|
164
|
+
|
|
165
|
+
+ src: the name of the tap
|
|
166
|
+
+ present: whether this tap should be present or not
|
|
167
|
+
+ url: the url of the tap. See https://docs.brew.sh/Taps
|
|
168
|
+
|
|
169
|
+
**Examples:**
|
|
170
|
+
|
|
171
|
+
.. code:: python
|
|
172
|
+
|
|
173
|
+
brew.tap(
|
|
174
|
+
name="Add a brew tap",
|
|
175
|
+
src="includeos/includeos",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Just url is equivalent to
|
|
179
|
+
# `brew tap kptdev/kpt https://github.com/kptdev/kpt`
|
|
180
|
+
brew.tap(
|
|
181
|
+
url="https://github.com/kptdev/kpt",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# src and url is equivalent to
|
|
185
|
+
# `brew tap example/project https://github.example.com/project`
|
|
186
|
+
brew.tap(
|
|
187
|
+
src="example/project",
|
|
188
|
+
url="https://github.example.com/project",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Multiple taps
|
|
192
|
+
for tap in ["includeos/includeos", "ktr0731/evans"]:
|
|
193
|
+
brew.tap(
|
|
194
|
+
name={f"Add brew tap {tap}"},
|
|
195
|
+
src=tap,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
if not (src or url):
|
|
201
|
+
host.noop("no tap was specified")
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
src = src or str(urllib.parse.urlparse(url).path).strip("/")
|
|
205
|
+
|
|
206
|
+
if len(src.split("/")) != 2:
|
|
207
|
+
host.noop("src '{0}' doesn't have two components.".format(src))
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
taps = host.get_fact(BrewTaps)
|
|
211
|
+
already_tapped = src in taps
|
|
212
|
+
|
|
213
|
+
if present and already_tapped:
|
|
214
|
+
host.noop("tap {0} already exists".format(src))
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
if already_tapped:
|
|
218
|
+
yield "brew untap {0}".format(src)
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
if not present:
|
|
222
|
+
host.noop("tap {0} does not exist".format(src))
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
cmd = "brew tap {0}".format(src)
|
|
226
|
+
|
|
227
|
+
if url is not None:
|
|
228
|
+
cmd = " ".join([cmd, url])
|
|
229
|
+
|
|
230
|
+
yield cmd
|
|
231
|
+
return
|