pyinfra 3.0b0__py2.py3-none-any.whl → 3.0b2__py2.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/api/__init__.py +3 -0
- pyinfra/api/arguments.py +12 -5
- pyinfra/api/arguments_typed.py +19 -6
- pyinfra/api/command.py +5 -3
- pyinfra/api/config.py +115 -13
- pyinfra/api/connectors.py +5 -2
- pyinfra/api/exceptions.py +19 -0
- pyinfra/api/facts.py +34 -33
- pyinfra/api/host.py +51 -12
- pyinfra/api/inventory.py +4 -0
- pyinfra/api/operation.py +88 -42
- pyinfra/api/operations.py +10 -11
- pyinfra/api/state.py +11 -2
- pyinfra/api/util.py +24 -16
- pyinfra/connectors/base.py +4 -7
- pyinfra/connectors/chroot.py +5 -6
- pyinfra/connectors/docker.py +13 -19
- pyinfra/connectors/dockerssh.py +5 -4
- pyinfra/connectors/local.py +7 -7
- pyinfra/connectors/ssh.py +46 -25
- pyinfra/connectors/terraform.py +9 -6
- pyinfra/connectors/util.py +7 -8
- pyinfra/connectors/vagrant.py +11 -10
- pyinfra/context.py +1 -0
- pyinfra/facts/apk.py +2 -0
- pyinfra/facts/apt.py +2 -0
- pyinfra/facts/brew.py +2 -0
- pyinfra/facts/bsdinit.py +2 -0
- pyinfra/facts/cargo.py +2 -0
- pyinfra/facts/choco.py +3 -1
- pyinfra/facts/deb.py +9 -4
- pyinfra/facts/dnf.py +2 -0
- pyinfra/facts/docker.py +2 -0
- pyinfra/facts/files.py +2 -0
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/gpg.py +2 -0
- pyinfra/facts/hardware.py +30 -22
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +12 -6
- pyinfra/facts/npm.py +1 -0
- pyinfra/facts/openrc.py +2 -0
- pyinfra/facts/pacman.py +6 -2
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +2 -0
- pyinfra/facts/pkgin.py +2 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +5 -162
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/server.py +10 -13
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +28 -10
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +3 -2
- pyinfra/facts/vzctl.py +2 -0
- pyinfra/facts/xbps.py +2 -0
- pyinfra/facts/yum.py +2 -0
- pyinfra/facts/zypper.py +2 -0
- pyinfra/operations/apk.py +3 -1
- pyinfra/operations/apt.py +16 -18
- pyinfra/operations/brew.py +10 -8
- pyinfra/operations/bsdinit.py +5 -3
- pyinfra/operations/cargo.py +3 -1
- pyinfra/operations/choco.py +3 -1
- pyinfra/operations/dnf.py +15 -19
- pyinfra/operations/files.py +86 -69
- pyinfra/operations/gem.py +3 -1
- pyinfra/operations/git.py +18 -16
- pyinfra/operations/iptables.py +33 -25
- pyinfra/operations/launchd.py +5 -6
- pyinfra/operations/lxd.py +7 -4
- pyinfra/operations/mysql.py +57 -53
- pyinfra/operations/npm.py +8 -1
- pyinfra/operations/openrc.py +5 -3
- pyinfra/operations/pacman.py +4 -5
- pyinfra/operations/pip.py +16 -9
- pyinfra/operations/pkg.py +3 -1
- pyinfra/operations/pkgin.py +3 -1
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -335
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +7 -3
- pyinfra/operations/selinux.py +42 -16
- pyinfra/operations/server.py +48 -43
- pyinfra/operations/snap.py +3 -1
- pyinfra/operations/ssh.py +12 -10
- pyinfra/operations/systemd.py +13 -9
- pyinfra/operations/sysvinit.py +6 -4
- pyinfra/operations/upstart.py +5 -3
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +53 -37
- pyinfra/operations/util/service.py +18 -13
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +3 -1
- pyinfra/operations/yum.py +14 -18
- pyinfra/operations/zypper.py +8 -9
- pyinfra/version.py +5 -2
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/METADATA +31 -29
- pyinfra-3.0b2.dist-info/RECORD +163 -0
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/WHEEL +1 -1
- pyinfra_cli/commands.py +3 -2
- pyinfra_cli/inventory.py +38 -19
- pyinfra_cli/main.py +2 -0
- pyinfra_cli/prints.py +27 -105
- pyinfra_cli/util.py +3 -1
- tests/test_api/test_api_deploys.py +5 -5
- tests/test_api/test_api_operations.py +5 -5
- tests/test_connectors/test_ssh.py +105 -0
- tests/test_connectors/test_terraform.py +11 -8
- tests/test_connectors/test_vagrant.py +6 -6
- pyinfra-3.0b0.dist-info/RECORD +0 -162
- pyinfra_cli/inventory_dsl.py +0 -23
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/entry_points.txt +0 -0
- {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/top_level.txt +0 -0
pyinfra/connectors/ssh.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import shlex
|
|
4
|
-
from
|
|
4
|
+
from random import uniform
|
|
5
|
+
from shutil import which
|
|
5
6
|
from socket import error as socket_error, gaierror
|
|
7
|
+
from time import sleep
|
|
6
8
|
from typing import TYPE_CHECKING, Any, Iterable, Optional, Tuple
|
|
7
9
|
|
|
8
10
|
import click
|
|
@@ -48,6 +50,10 @@ class ConnectorData(TypedDict):
|
|
|
48
50
|
|
|
49
51
|
ssh_paramiko_connect_kwargs: dict
|
|
50
52
|
|
|
53
|
+
ssh_connect_retries: int
|
|
54
|
+
ssh_connect_retry_min_delay: float
|
|
55
|
+
ssh_connect_retry_max_delay: float
|
|
56
|
+
|
|
51
57
|
|
|
52
58
|
connector_data_meta: dict[str, DataMeta] = {
|
|
53
59
|
"ssh_hostname": DataMeta("SSH hostname"),
|
|
@@ -77,6 +83,15 @@ connector_data_meta: dict[str, DataMeta] = {
|
|
|
77
83
|
"ssh_paramiko_connect_kwargs": DataMeta(
|
|
78
84
|
"Override keyword arguments passed into Paramiko's ``SSHClient.connect``"
|
|
79
85
|
),
|
|
86
|
+
"ssh_connect_retries": DataMeta("Number of tries to connect via ssh", 0),
|
|
87
|
+
"ssh_connect_retry_min_delay": DataMeta(
|
|
88
|
+
"Lower bound for random delay between retries",
|
|
89
|
+
0.1,
|
|
90
|
+
),
|
|
91
|
+
"ssh_connect_retry_max_delay": DataMeta(
|
|
92
|
+
"Upper bound for random delay between retries",
|
|
93
|
+
0.5,
|
|
94
|
+
),
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
|
|
@@ -125,8 +140,9 @@ class SSHConnector(BaseConnector):
|
|
|
125
140
|
|
|
126
141
|
client: Optional[SSHClient] = None
|
|
127
142
|
|
|
128
|
-
|
|
129
|
-
|
|
143
|
+
@staticmethod
|
|
144
|
+
def make_names_data(name):
|
|
145
|
+
yield "@ssh/{0}".format(name), {"ssh_hostname": name}, []
|
|
130
146
|
|
|
131
147
|
def make_paramiko_kwargs(self) -> dict[str, Any]:
|
|
132
148
|
kwargs = {
|
|
@@ -172,6 +188,29 @@ class SSHConnector(BaseConnector):
|
|
|
172
188
|
return kwargs
|
|
173
189
|
|
|
174
190
|
def connect(self) -> None:
|
|
191
|
+
retries = self.data["ssh_connect_retries"]
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
while True:
|
|
195
|
+
try:
|
|
196
|
+
return self._connect()
|
|
197
|
+
except (SSHException, gaierror, socket_error, EOFError):
|
|
198
|
+
if retries == 0:
|
|
199
|
+
raise
|
|
200
|
+
retries -= 1
|
|
201
|
+
min_delay = self.data["ssh_connect_retry_min_delay"]
|
|
202
|
+
max_delay = self.data["ssh_connect_retry_max_delay"]
|
|
203
|
+
sleep(uniform(min_delay, max_delay))
|
|
204
|
+
except SSHException as e:
|
|
205
|
+
raise_connect_error(self.host, "SSH error", e)
|
|
206
|
+
except gaierror as e:
|
|
207
|
+
raise_connect_error(self.host, "Could not resolve hostname", e)
|
|
208
|
+
except socket_error as e:
|
|
209
|
+
raise_connect_error(self.host, "Could not connect", e)
|
|
210
|
+
except EOFError as e:
|
|
211
|
+
raise_connect_error(self.host, "EOF error", e)
|
|
212
|
+
|
|
213
|
+
def _connect(self) -> None:
|
|
175
214
|
"""
|
|
176
215
|
Connect to a single host. Returns the SSH client if successful. Stateless by
|
|
177
216
|
design so can be run in parallel.
|
|
@@ -221,18 +260,6 @@ class SSHConnector(BaseConnector):
|
|
|
221
260
|
f"Host key for {e.hostname} does not match.",
|
|
222
261
|
)
|
|
223
262
|
|
|
224
|
-
except SSHException as e:
|
|
225
|
-
raise_connect_error(self.host, "SSH error", e)
|
|
226
|
-
|
|
227
|
-
except gaierror:
|
|
228
|
-
raise_connect_error(self.host, "Could not resolve hostname", hostname)
|
|
229
|
-
|
|
230
|
-
except socket_error as e:
|
|
231
|
-
raise_connect_error(self.host, "Could not connect", e)
|
|
232
|
-
|
|
233
|
-
except EOFError as e:
|
|
234
|
-
raise_connect_error(self.host, "EOF error", e)
|
|
235
|
-
|
|
236
263
|
def run_shell_command(
|
|
237
264
|
self,
|
|
238
265
|
command: StringCommand,
|
|
@@ -450,16 +477,10 @@ class SSHConnector(BaseConnector):
|
|
|
450
477
|
self._put_file(filename_or_io, temp_file)
|
|
451
478
|
|
|
452
479
|
# Make sure our sudo/su user can access the file
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
elif _sudo_user:
|
|
456
|
-
command = StringCommand("setfacl", "-m", "u:{0}:r".format(_sudo_user), temp_file)
|
|
457
|
-
elif _doas_user:
|
|
458
|
-
command = StringCommand("setfacl", "-m", "u:{0}:r".format(_doas_user), temp_file)
|
|
459
|
-
|
|
460
|
-
if _su_user or _sudo_user or _doas_user:
|
|
480
|
+
other_user = _su_user or _sudo_user or _doas_user
|
|
481
|
+
if other_user:
|
|
461
482
|
status, output = self.run_shell_command(
|
|
462
|
-
|
|
483
|
+
StringCommand("setfacl", "-m", f"u:{other_user}:r", temp_file),
|
|
463
484
|
print_output=print_output,
|
|
464
485
|
print_input=print_input,
|
|
465
486
|
**arguments,
|
|
@@ -518,7 +539,7 @@ class SSHConnector(BaseConnector):
|
|
|
518
539
|
if self.data["ssh_password"]:
|
|
519
540
|
raise NotImplementedError("Rsync does not currently work with SSH passwords.")
|
|
520
541
|
|
|
521
|
-
if not
|
|
542
|
+
if not which("rsync"):
|
|
522
543
|
raise NotImplementedError("The `rsync` binary is not available on this system.")
|
|
523
544
|
|
|
524
545
|
def rsync(
|
pyinfra/connectors/terraform.py
CHANGED
|
@@ -28,7 +28,8 @@ def _flatten_dict(d: dict, parent_key: str = "", sep: str = "."):
|
|
|
28
28
|
|
|
29
29
|
class TerraformInventoryConnector(BaseConnector):
|
|
30
30
|
"""
|
|
31
|
-
Generate one or more SSH hosts from a Terraform output variable. The variable
|
|
31
|
+
Generate one or more SSH hosts from a Terraform output variable. The variable
|
|
32
|
+
must be a list of hostnames or dictionaries.
|
|
32
33
|
|
|
33
34
|
Output is fetched from a flattened JSON dictionary output from ``terraform output
|
|
34
35
|
-json``. For example the following object:
|
|
@@ -77,21 +78,23 @@ class TerraformInventoryConnector(BaseConnector):
|
|
|
77
78
|
"""
|
|
78
79
|
|
|
79
80
|
@staticmethod
|
|
80
|
-
def make_names_data(
|
|
81
|
+
def make_names_data(name=None):
|
|
81
82
|
show_warning()
|
|
82
83
|
|
|
83
|
-
if not
|
|
84
|
-
|
|
84
|
+
if not name:
|
|
85
|
+
name = ""
|
|
85
86
|
|
|
86
87
|
with progress_spinner({"fetch terraform output"}):
|
|
87
88
|
tf_output_raw = local.shell("terraform output -json")
|
|
88
89
|
|
|
90
|
+
assert isinstance(tf_output_raw, str)
|
|
89
91
|
tf_output = json.loads(tf_output_raw)
|
|
90
92
|
tf_output = _flatten_dict(tf_output)
|
|
91
93
|
|
|
92
|
-
tf_output_value = tf_output.get(
|
|
94
|
+
tf_output_value = tf_output.get(name)
|
|
93
95
|
if tf_output_value is None:
|
|
94
|
-
|
|
96
|
+
keys = "\n".join(f" - {k}" for k in tf_output.keys())
|
|
97
|
+
raise InventoryError(f"No Terraform output with key: `{name}`, valid keys:\n{keys}")
|
|
95
98
|
|
|
96
99
|
if not isinstance(tf_output_value, list):
|
|
97
100
|
raise InventoryError(
|
pyinfra/connectors/util.py
CHANGED
|
@@ -40,7 +40,7 @@ def run_local_process(
|
|
|
40
40
|
stdin=None,
|
|
41
41
|
timeout: Optional[int] = None,
|
|
42
42
|
print_output: bool = False,
|
|
43
|
-
print_prefix=
|
|
43
|
+
print_prefix: str = "",
|
|
44
44
|
) -> tuple[int, "CommandOutput"]:
|
|
45
45
|
process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
|
46
46
|
|
|
@@ -274,12 +274,11 @@ def make_unix_command_for_host(
|
|
|
274
274
|
# If no sudo, we've nothing to do here
|
|
275
275
|
return make_unix_command(command, **command_arguments)
|
|
276
276
|
|
|
277
|
-
#
|
|
278
|
-
# connector data value.
|
|
279
|
-
command_arguments["_sudo_password"]
|
|
280
|
-
"_sudo_password"
|
|
281
|
-
|
|
282
|
-
)
|
|
277
|
+
# If the sudo password is not set in the direct arguments,
|
|
278
|
+
# set it from the connector data value.
|
|
279
|
+
if "_sudo_password" not in command_arguments or not command_arguments["_sudo_password"]:
|
|
280
|
+
command_arguments["_sudo_password"] = host.connector_data.get("prompted_sudo_password")
|
|
281
|
+
|
|
283
282
|
if command_arguments["_sudo_password"]:
|
|
284
283
|
# Ensure the askpass path is correctly set and passed through
|
|
285
284
|
_ensure_sudo_askpass_set_for_host(host)
|
|
@@ -339,7 +338,7 @@ def make_unix_command(
|
|
|
339
338
|
[
|
|
340
339
|
"env",
|
|
341
340
|
"SUDO_ASKPASS={0}".format(_sudo_askpass_path),
|
|
342
|
-
MaskString("{0}={1}".format(SUDO_ASKPASS_ENV_VAR, _sudo_password)),
|
|
341
|
+
MaskString("{0}={1}".format(SUDO_ASKPASS_ENV_VAR, shlex.quote(_sudo_password))),
|
|
343
342
|
],
|
|
344
343
|
)
|
|
345
344
|
|
pyinfra/connectors/vagrant.py
CHANGED
|
@@ -93,13 +93,13 @@ def _make_name_data(host):
|
|
|
93
93
|
"ssh_hostname": host["HostName"],
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
for config_key, data_key in (
|
|
97
|
-
("Port", "ssh_port"),
|
|
98
|
-
("User", "ssh_user"),
|
|
99
|
-
("IdentityFile", "ssh_key"),
|
|
96
|
+
for config_key, data_key, data_cast in (
|
|
97
|
+
("Port", "ssh_port", int),
|
|
98
|
+
("User", "ssh_user", str),
|
|
99
|
+
("IdentityFile", "ssh_key", str),
|
|
100
100
|
):
|
|
101
101
|
if config_key in host:
|
|
102
|
-
data[data_key] = host[config_key]
|
|
102
|
+
data[data_key] = data_cast(host[config_key])
|
|
103
103
|
|
|
104
104
|
# Update any configured JSON data
|
|
105
105
|
if vagrant_host in vagrant_options.get("data", {}):
|
|
@@ -132,8 +132,8 @@ class VagrantInventoryConnector(BaseConnector):
|
|
|
132
132
|
"""
|
|
133
133
|
|
|
134
134
|
@staticmethod
|
|
135
|
-
def make_names_data(
|
|
136
|
-
vagrant_ssh_info = get_vagrant_config(
|
|
135
|
+
def make_names_data(name=None):
|
|
136
|
+
vagrant_ssh_info = get_vagrant_config(name)
|
|
137
137
|
|
|
138
138
|
logger.debug("Got Vagrant SSH info: \n%s", vagrant_ssh_info)
|
|
139
139
|
|
|
@@ -170,10 +170,11 @@ class VagrantInventoryConnector(BaseConnector):
|
|
|
170
170
|
hosts.append(_make_name_data(current_host))
|
|
171
171
|
|
|
172
172
|
if not hosts:
|
|
173
|
-
if
|
|
173
|
+
if name:
|
|
174
174
|
raise InventoryError(
|
|
175
|
-
"No running Vagrant instances matching `{0}` found!".format(
|
|
175
|
+
"No running Vagrant instances matching `{0}` found!".format(name)
|
|
176
176
|
)
|
|
177
177
|
raise InventoryError("No running Vagrant instances found!")
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
for host in hosts:
|
|
180
|
+
yield host
|
pyinfra/context.py
CHANGED
pyinfra/facts/apk.py
CHANGED
pyinfra/facts/apt.py
CHANGED
pyinfra/facts/brew.py
CHANGED
pyinfra/facts/bsdinit.py
CHANGED
pyinfra/facts/cargo.py
CHANGED
pyinfra/facts/choco.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pyinfra.api import FactBase
|
|
2
4
|
|
|
3
5
|
from .util.packaging import parse_packages
|
|
@@ -16,7 +18,7 @@ class ChocoPackages(FactBase):
|
|
|
16
18
|
}
|
|
17
19
|
"""
|
|
18
20
|
|
|
19
|
-
command = "choco list
|
|
21
|
+
command = "choco list"
|
|
20
22
|
shell_executable = "ps"
|
|
21
23
|
|
|
22
24
|
default = dict
|
pyinfra/facts/deb.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
4
|
+
import shlex
|
|
2
5
|
|
|
3
6
|
from pyinfra.api import FactBase
|
|
4
7
|
|
|
@@ -48,14 +51,16 @@ class DebPackage(FactBase):
|
|
|
48
51
|
"""
|
|
49
52
|
|
|
50
53
|
_regexes = {
|
|
51
|
-
"name": r"^Package
|
|
52
|
-
"version": r"^Version
|
|
54
|
+
"name": r"^Package:\s+({0})$".format(DEB_PACKAGE_NAME_REGEX),
|
|
55
|
+
"version": r"^Version:\s+({0})$".format(DEB_PACKAGE_VERSION_REGEX),
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
requires_command = "dpkg"
|
|
56
59
|
|
|
57
|
-
def command(self,
|
|
58
|
-
return "! test -e {0} && (dpkg -s {0} 2>/dev/null || true) || dpkg -I {0}".format(
|
|
60
|
+
def command(self, package):
|
|
61
|
+
return "! test -e {0} && (dpkg -s {0} 2>/dev/null || true) || dpkg -I {0}".format(
|
|
62
|
+
shlex.quote(package)
|
|
63
|
+
)
|
|
59
64
|
|
|
60
65
|
def process(self, output):
|
|
61
66
|
data = {}
|
pyinfra/facts/dnf.py
CHANGED
pyinfra/facts/docker.py
CHANGED
pyinfra/facts/files.py
CHANGED
pyinfra/facts/gem.py
CHANGED
pyinfra/facts/gpg.py
CHANGED
pyinfra/facts/hardware.py
CHANGED
|
@@ -195,28 +195,36 @@ class NetworkDevices(FactBase):
|
|
|
195
195
|
output = "\n".join(map(str.strip, output))
|
|
196
196
|
|
|
197
197
|
# Splitting the output into sections per network device
|
|
198
|
-
device_sections = re.split(r"\n(?=\d+:
|
|
198
|
+
device_sections = re.split(r"\n(?=\d+: [^\s/:]|[^\s/:]+:.*mtu )", output)
|
|
199
199
|
|
|
200
200
|
# Dictionary to hold all device information
|
|
201
201
|
all_devices = {}
|
|
202
202
|
|
|
203
203
|
for section in device_sections:
|
|
204
204
|
# Extracting the device name
|
|
205
|
-
device_name_match = re.match(r"^(?:\d+: )?([
|
|
205
|
+
device_name_match = re.match(r"^(?:\d+: )?([^\s/:]+):", section)
|
|
206
206
|
if not device_name_match:
|
|
207
207
|
continue
|
|
208
208
|
device_name = device_name_match.group(1)
|
|
209
209
|
|
|
210
210
|
# Regular expressions to match different parts of the output
|
|
211
|
-
ether_re = re.compile(r"([0-9A-Fa-f:]{17})")
|
|
211
|
+
ether_re = re.compile(r"ether ([0-9A-Fa-f:]{17})")
|
|
212
212
|
mtu_re = re.compile(r"mtu (\d+)")
|
|
213
213
|
ipv4_re = (
|
|
214
|
+
# ip a
|
|
214
215
|
re.compile(
|
|
215
|
-
r"inet (
|
|
216
|
-
),
|
|
216
|
+
r"inet (?P<address>\d+\.\d+\.\d+\.\d+)/(?P<mask>\d+)(?: metric \d+)?(?: brd (?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
|
|
217
|
+
),
|
|
218
|
+
# ifconfig -a
|
|
217
219
|
re.compile(
|
|
218
|
-
r"inet (
|
|
219
|
-
),
|
|
220
|
+
r"inet (?P<address>\d+\.\d+\.\d+\.\d+)\s+netmask\s+(?P<mask>(?:\d+\.\d+\.\d+\.\d+)|(?:[0-9a-fA-FxX]+))(?:\s+broadcast\s+(?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
ipv6_re = (
|
|
224
|
+
# ip a
|
|
225
|
+
re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)/(?P<mask>\d+)"),
|
|
226
|
+
# ifconfig -a
|
|
227
|
+
re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)\s+prefixlen\s+(?P<mask>\d+)"),
|
|
220
228
|
)
|
|
221
229
|
|
|
222
230
|
# Parsing the output
|
|
@@ -235,18 +243,22 @@ class NetworkDevices(FactBase):
|
|
|
235
243
|
)
|
|
236
244
|
|
|
237
245
|
# IPv4 Addresses
|
|
246
|
+
ipv4_matches: list[re.Match[str]]
|
|
238
247
|
for ipv4_re_ in ipv4_re:
|
|
239
|
-
ipv4_matches = ipv4_re_.
|
|
240
|
-
if ipv4_matches:
|
|
248
|
+
ipv4_matches = list(ipv4_re_.finditer(section))
|
|
249
|
+
if len(ipv4_matches):
|
|
241
250
|
break
|
|
242
251
|
|
|
243
|
-
if ipv4_matches:
|
|
252
|
+
if len(ipv4_matches):
|
|
244
253
|
ipv4_info = []
|
|
245
254
|
for ipv4 in ipv4_matches:
|
|
246
|
-
address = ipv4
|
|
247
|
-
mask_value = ipv4
|
|
255
|
+
address = ipv4.group("address")
|
|
256
|
+
mask_value = ipv4.group("mask")
|
|
248
257
|
mask_bits, netmask = mask(mask_value)
|
|
249
|
-
|
|
258
|
+
try:
|
|
259
|
+
broadcast = ipv4.group("broadcast")
|
|
260
|
+
except IndexError:
|
|
261
|
+
broadcast = None
|
|
250
262
|
|
|
251
263
|
ipv4_info.append(
|
|
252
264
|
{
|
|
@@ -261,21 +273,17 @@ class NetworkDevices(FactBase):
|
|
|
261
273
|
device_info["ipv4"]["additional_ips"] = ipv4_info[1:] # type: ignore[index]
|
|
262
274
|
|
|
263
275
|
# IPv6 Addresses
|
|
264
|
-
|
|
265
|
-
re.compile(r"inet6\s+([0-9a-fA-F:]+)/(\d+)"),
|
|
266
|
-
re.compile(r"inet6\s+([0-9a-fA-F:]+)\s+prefixlen\s+(\d+)"),
|
|
267
|
-
)
|
|
268
|
-
|
|
276
|
+
ipv6_matches: list[re.Match[str]]
|
|
269
277
|
for ipv6_re_ in ipv6_re:
|
|
270
|
-
ipv6_matches = ipv6_re_.
|
|
278
|
+
ipv6_matches = list(ipv6_re_.finditer(section))
|
|
271
279
|
if ipv6_matches:
|
|
272
280
|
break
|
|
273
281
|
|
|
274
|
-
if ipv6_matches:
|
|
282
|
+
if len(ipv6_matches):
|
|
275
283
|
ipv6_info = []
|
|
276
284
|
for ipv6 in ipv6_matches:
|
|
277
|
-
address = ipv6
|
|
278
|
-
mask_bits = ipv6
|
|
285
|
+
address = ipv6.group("address")
|
|
286
|
+
mask_bits = ipv6.group("mask")
|
|
279
287
|
ipv6_info.append({"address": address, "mask_bits": int(mask_bits)})
|
|
280
288
|
device_info["ipv6"] = ipv6_info[0]
|
|
281
289
|
if len(ipv6_matches) > 1:
|
pyinfra/facts/launchd.py
CHANGED
pyinfra/facts/lxd.py
CHANGED
pyinfra/facts/mysql.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
from collections import defaultdict
|
|
3
5
|
|
|
@@ -8,11 +10,11 @@ from .util.databases import parse_columns_and_rows
|
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def make_mysql_command(
|
|
11
|
-
database=None,
|
|
12
|
-
user=None,
|
|
13
|
-
password=None,
|
|
14
|
-
host=None,
|
|
15
|
-
port=None,
|
|
13
|
+
database: str | None = None,
|
|
14
|
+
user: str | None = None,
|
|
15
|
+
password: str | None = None,
|
|
16
|
+
host: str | None = None,
|
|
17
|
+
port: int | None = None,
|
|
16
18
|
executable="mysql",
|
|
17
19
|
):
|
|
18
20
|
target_bits = [executable]
|
|
@@ -37,7 +39,11 @@ def make_mysql_command(
|
|
|
37
39
|
return StringCommand(*target_bits)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def make_execute_mysql_command(
|
|
42
|
+
def make_execute_mysql_command(
|
|
43
|
+
command: str | StringCommand,
|
|
44
|
+
ignore_errors=False,
|
|
45
|
+
**mysql_kwargs,
|
|
46
|
+
):
|
|
41
47
|
commands_bits = [
|
|
42
48
|
make_mysql_command(**mysql_kwargs),
|
|
43
49
|
"-Be",
|
pyinfra/facts/npm.py
CHANGED
pyinfra/facts/openrc.py
CHANGED
pyinfra/facts/pacman.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
|
|
1
5
|
from pyinfra.api import FactBase
|
|
2
6
|
|
|
3
7
|
from .util.packaging import parse_packages
|
|
@@ -21,9 +25,9 @@ class PacmanUnpackGroup(FactBase):
|
|
|
21
25
|
|
|
22
26
|
default = list
|
|
23
27
|
|
|
24
|
-
def command(self,
|
|
28
|
+
def command(self, package):
|
|
25
29
|
# Accept failure here (|| true) for invalid/unknown packages
|
|
26
|
-
return 'pacman -S --print-format "%n" {0} || true'.format(
|
|
30
|
+
return 'pacman -S --print-format "%n" {0} || true'.format(shlex.quote(package))
|
|
27
31
|
|
|
28
32
|
def process(self, output):
|
|
29
33
|
return output
|
pyinfra/facts/pip.py
CHANGED
pyinfra/facts/pkg.py
CHANGED