Skip to content

Commit a1aa105

Browse files
authored
Merge branch 'main' into ip_module
2 parents ddfcccf + 01db77f commit a1aa105

13 files changed

+58
-71
lines changed

CHANGELOG.rst

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
Changelog
33
=========
44

5+
9.0.0
6+
=====
7+
8+
* [BREAKING] pytest-testinfra now require python >= 3.9
9+
* [BREAKING] Drop deprecated module PipPackage
10+
* [NEW] Add support for the SSH ControlPath connection sharing option (#713)
11+
* [FIX] Retry SSH on ConnectionResetError (#708)
12+
* [FIX] List openSUSE Leap and Tumbleweed explicitly as rpm based distributions
13+
* [FIX] Make group name mandatory in group module
14+
515
8.1.0
616
=====
717

dev-requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
sphinx>=1.3
1+
sphinx>=7.1,<7.2
22
alabaster>=0.7.2
33
.

doc/source/modules.rst

-10
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ host
7070

7171
:class:`testinfra.modules.pip.Pip` class
7272

73-
.. attribute:: pip_package
74-
75-
:class:`testinfra.modules.pip.PipPackage` class
76-
7773
.. attribute:: podman
7874

7975
:class:`testinfra.modules.podman.Podman` class
@@ -215,12 +211,6 @@ Pip
215211
:members:
216212

217213

218-
PipPackage
219-
~~~~~~~~~~
220-
221-
.. autoclass:: testinfra.modules.pip.PipPackage
222-
223-
224214
Podman
225215
~~~~~~
226216

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ long_description = file:README.rst
66
long_description_content_type = text/x-rst
77
author = Philippe Pepiot
88
author_email = [email protected]
9-
license_file = LICENSE
9+
license_files = LICENSE
1010
classifiers =
1111
Development Status :: 5 - Production/Stable
1212
Environment :: Console

test/test_backends.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,14 @@ def get_vars(host):
183183
"c": "d",
184184
"x": "z",
185185
"inventory_hostname": "debian",
186-
"group_names": ["g"],
186+
"group_names": ["all", "g"],
187187
"groups": groups,
188188
}
189189
assert get_vars("rockylinux") == {
190190
"a": "a",
191191
"e": "f",
192192
"inventory_hostname": "rockylinux",
193-
"group_names": ["ungrouped"],
193+
"group_names": ["all", "ungrouped"],
194194
"groups": groups,
195195
}
196196

test/test_modules.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ def test_ansible_module(host):
348348
assert variables["myhostvar"] == "bar"
349349
assert variables["mygroupvar"] == "qux"
350350
assert variables["inventory_hostname"] == "debian_bookworm"
351-
assert variables["group_names"] == ["testgroup"]
351+
assert variables["group_names"] == ["all", "testgroup"]
352352
assert variables["groups"] == {
353353
"all": ["debian_bookworm"],
354354
"testgroup": ["debian_bookworm"],

testinfra/backend/paramiko.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def run(self, command: str, *args: str, **kwargs: Any) -> base.CommandResult:
148148
cmd = self.encode(command)
149149
try:
150150
rc, stdout, stderr = self._exec_command(cmd)
151-
except paramiko.ssh_exception.SSHException:
151+
except (paramiko.ssh_exception.SSHException, ConnectionResetError):
152152
transport = self.client.get_transport()
153153
assert transport is not None
154154
if not transport.is_active():

testinfra/backend/ssh.py

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
ssh_config: Optional[str] = None,
2828
ssh_identity_file: Optional[str] = None,
2929
timeout: int = 10,
30+
controlpath: str = "",
3031
controlpersist: int = 60,
3132
ssh_extra_args: Optional[str] = None,
3233
*args: Any,
@@ -36,6 +37,7 @@ def __init__(
3637
self.ssh_config = ssh_config
3738
self.ssh_identity_file = ssh_identity_file
3839
self.timeout = int(timeout)
40+
self.controlpath = controlpath
3941
self.controlpersist = int(controlpersist)
4042
self.ssh_extra_args = ssh_extra_args
4143
super().__init__(self.host.name, *args, **kwargs)
@@ -75,6 +77,12 @@ def _build_ssh_command(self, command: str) -> tuple[list[str], list[str]]:
7577
self.controlpersist
7678
)
7779
)
80+
if (
81+
"ControlMaster" in " ".join(cmd)
82+
and self.controlpath
83+
and ("controlpath" not in (self.ssh_extra_args or "").lower())
84+
):
85+
cmd.append("-o ControlPath={}".format(self.controlpath))
7886
cmd.append("%s %s")
7987
cmd_args.extend([self.host.name, command])
8088
return cmd, cmd_args

testinfra/modules/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"mount_point": "mountpoint:MountPoint",
3232
"package": "package:Package",
3333
"pip": "pip:Pip",
34-
"pip_package": "pip:PipPackage",
3534
"process": "process:Process",
3635
"puppet_resource": "puppet:PuppetResource",
3736
"facter": "puppet:Facter",

testinfra/modules/package.py

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def get_module_class(cls, host):
7777
"centos",
7878
"cloudlinux",
7979
"fedora",
80+
"opensuse-leap",
81+
"opensuse-tumbleweed",
8082
"rocky",
8183
)
8284
):

testinfra/modules/pip.py

+3-35
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import json
1414
import re
15-
import warnings
1615

1716
from testinfra.modules.base import Module
1817

@@ -58,7 +57,7 @@ def version(self):
5857
def check(cls, pip_path="pip"):
5958
"""Verify installed packages have compatible dependencies.
6059
61-
>>> cmd = host.pip_package.check()
60+
>>> cmd = host.pip.check()
6261
>>> cmd.rc
6362
0
6463
>>> cmd.stdout
@@ -76,7 +75,7 @@ def check(cls, pip_path="pip"):
7675
def get_packages(cls, pip_path="pip"):
7776
"""Get all installed packages and versions returned by `pip list`:
7877
79-
>>> host.pip_package.get_packages(pip_path='~/venv/website/bin/pip')
78+
>>> host.pip.get_packages(pip_path='~/venv/website/bin/pip')
8079
{'Django': {'version': '1.10.2'},
8180
'mywebsite': {'version': '1.0a3', 'path': '/srv/website'},
8281
'psycopg2': {'version': '2.6.2'}}
@@ -106,7 +105,7 @@ def get_packages(cls, pip_path="pip"):
106105
def get_outdated_packages(cls, pip_path="pip"):
107106
"""Get all outdated packages with current and latest version
108107
109-
>>> host.pip_package.get_outdated_packages(
108+
>>> host.pip.get_outdated_packages(
110109
... pip_path='~/venv/website/bin/pip')
111110
{'Django': {'current': '1.10.2', 'latest': '1.10.3'}}
112111
"""
@@ -134,34 +133,3 @@ def get_outdated_packages(cls, pip_path="pip"):
134133
name, current, latest = _re_match(line, output_re)
135134
pkgs[name] = {"current": current, "latest": latest}
136135
return pkgs
137-
138-
139-
class PipPackage(Pip):
140-
""".. deprecated:: 6.2
141-
142-
Use :class:`~testinfra.modules.pip.Pip` instead.
143-
"""
144-
145-
@staticmethod
146-
def _deprecated():
147-
"""Raise a `DeprecationWarning`"""
148-
warnings.warn(
149-
"Calling host.pip_package is deprecated, call host.pip instead",
150-
category=DeprecationWarning,
151-
stacklevel=2,
152-
)
153-
154-
@classmethod
155-
def check(cls, pip_path="pip"):
156-
PipPackage._deprecated()
157-
return super().check(pip_path=pip_path)
158-
159-
@classmethod
160-
def get_packages(cls, pip_path="pip"):
161-
PipPackage._deprecated()
162-
return super().get_packages(pip_path=pip_path)
163-
164-
@classmethod
165-
def get_outdated_packages(cls, pip_path="pip"):
166-
PipPackage._deprecated()
167-
return super().get_outdated_packages(pip_path=pip_path)

testinfra/modules/systeminfo.py

+15-15
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,6 @@ def sysinfo(self):
5050
def _get_linux_sysinfo(self):
5151
sysinfo = {}
5252

53-
# LSB
54-
lsb = self.run("lsb_release -a")
55-
if lsb.rc == 0:
56-
for line in lsb.stdout.splitlines():
57-
key, value = line.split(":", 1)
58-
key = key.strip().lower()
59-
value = value.strip().lower()
60-
if key == "distributor id":
61-
sysinfo["distribution"] = value
62-
elif key == "release":
63-
sysinfo["release"] = value
64-
elif key == "codename":
65-
sysinfo["codename"] = value
66-
return sysinfo
67-
6853
# https://www.freedesktop.org/software/systemd/man/os-release.html
6954
os_release = self.run("cat /etc/os-release")
7055
if os_release.rc == 0:
@@ -100,6 +85,21 @@ def _get_linux_sysinfo(self):
10085
sysinfo["release"] = alpine_release.stdout.strip()
10186
return sysinfo
10287

88+
# LSB
89+
lsb = self.run("lsb_release -a")
90+
if lsb.rc == 0:
91+
for line in lsb.stdout.splitlines():
92+
key, value = line.split(":", 1)
93+
key = key.strip().lower()
94+
value = value.strip().lower()
95+
if key == "distributor id":
96+
sysinfo["distribution"] = value
97+
elif key == "release":
98+
sysinfo["release"] = value
99+
elif key == "codename":
100+
sysinfo["codename"] = value
101+
return sysinfo
102+
103103
return sysinfo
104104

105105
def _get_darwin_sysinfo(self):

testinfra/utils/ansible_runner.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,16 @@ def get_config(
185185
]
186186
).strip()
187187

188+
control_path = config.get("ssh_connection", "control_path", fallback="", raw=True)
189+
if control_path:
190+
directory = config.get(
191+
"persistent_connection", "control_path_dir", fallback="~/.ansible/cp"
192+
)
193+
control_path = control_path % ({"directory": directory}) # noqa: S001
194+
# restore original "%%"
195+
control_path = control_path.replace("%", "%%")
196+
kwargs["controlpath"] = control_path
197+
188198
spec = "{}://".format(connection)
189199

190200
# Fallback to user:password auth when identity file is not used
@@ -301,12 +311,14 @@ def get_variables(self, host: str) -> dict[str, Any]:
301311
hostvars.setdefault("inventory_hostname", host)
302312
group_names = []
303313
groups = {}
314+
304315
for group in sorted(inventory):
305316
if group == "_meta":
306317
continue
307318
groups[group] = sorted(itergroup(inventory, group))
308-
if group != "all" and host in inventory[group].get("hosts", []):
319+
if host in groups[group]:
309320
group_names.append(group)
321+
310322
hostvars.setdefault("group_names", group_names)
311323
hostvars.setdefault("groups", groups)
312324
return hostvars
@@ -383,9 +395,7 @@ def run_module(
383395
"msg": "Skipped. You might want to try check=False",
384396
}
385397
if not files:
386-
raise RuntimeError(
387-
"Error while running {}: {}".format(" ".join(cmd), out)
388-
)
398+
raise RuntimeError(f"{out}")
389399
fpath = os.path.join(d, files[0])
390400
try:
391401
with open(fpath, "r", encoding="ascii") as f:

0 commit comments

Comments
 (0)