Skip to content

Commit eb926c9

Browse files
Add Package.get_packages() to return all installed packages
1 parent 50f6027 commit eb926c9

File tree

2 files changed

+127
-9
lines changed

2 files changed

+127
-9
lines changed

test/test_modules.py

+36-9
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,55 @@
3131
]
3232
)
3333

34+
release_shortcuts = {
35+
"rockylinux9": ".el9",
36+
"debian_bookworm": None,
37+
}
38+
39+
all_ssh_versions = {
40+
"rockylinux9": "8.",
41+
"debian_bookworm": "1:9.2",
42+
}
43+
3444

3545
@all_images
3646
def test_package(host, docker_image):
3747
assert not host.package("zsh").is_installed
3848
ssh = host.package("openssh-server")
39-
version = {
40-
"rockylinux9": "8.",
41-
"debian_bookworm": "1:9.2",
42-
}[docker_image]
49+
version = all_ssh_versions[docker_image]
4350
assert ssh.is_installed
4451
assert ssh.version.startswith(version)
45-
release = {
46-
"rockylinux9": ".el9",
47-
"debian_bookworm": None,
48-
}[docker_image]
52+
release = release_shortcuts[docker_image]
4953
if release is None:
5054
with pytest.raises(NotImplementedError):
5155
ssh.release
5256
else:
5357
assert release in ssh.release
5458

5559

60+
@all_images
61+
def test_get_packages(host, docker_image):
62+
arch = "amd64"
63+
name = "openssh-server"
64+
release = release_shortcuts[docker_image]
65+
package_ssh = host.package(name)
66+
assert package_ssh.is_installed
67+
name_arch = f"{name}.{arch}"
68+
all_pkgs = host.package.get_packages()
69+
assert f"zsh.{arch}" not in all_pkgs
70+
assert name_arch in all_pkgs
71+
pkg = all_pkgs[name_arch]
72+
assert pkg["version"] == package_ssh.version
73+
assert pkg["arch"] == arch
74+
assert pkg["name"] == name
75+
if release is None:
76+
with pytest.raises(NotImplementedError):
77+
package_ssh.release
78+
else:
79+
assert release in pkg["release"]
80+
assert pkg["release"] == package_ssh.release
81+
82+
5683
def test_held_package(host):
5784
python = host.package("python3")
5885
assert python.is_installed
@@ -318,7 +345,7 @@ def test_file(host):
318345

319346

320347
def test_ansible_unavailable(host):
321-
expected = "Ansible module is only available with " "ansible connection backend"
348+
expected = "Ansible module is only available with ansible connection backend"
322349
with pytest.raises(RuntimeError) as excinfo:
323350
host.ansible("setup")
324351
assert expected in str(excinfo.value)

testinfra/modules/package.py

+91
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@ def __init__(self, name):
2020
self.name = name
2121
super().__init__()
2222

23+
@classmethod
24+
def get_packages(cls):
25+
"""Get all installed packages with name version number, release number (if available) and architecture
26+
27+
>>> host.package.get_packages()
28+
{'acl.x86_64': {'arch': 'x86_64',
29+
'name': 'acl',
30+
'release': '4.3.1',
31+
'version': '2.2.52'},
32+
<redacted>
33+
'zypper.x86_64': {'arch': 'x86_64',
34+
'name': 'zypper',
35+
'release': '150200.39.1',
36+
'version': '1.14.57'}}
37+
"""
38+
raise NotImplementedError
39+
2340
@property
2441
def is_installed(self):
2542
"""Test if the package is installed
@@ -94,6 +111,29 @@ def get_module_class(cls, host):
94111

95112

96113
class DebianPackage(Package):
114+
@classmethod
115+
def get_packages(cls):
116+
out = cls.run(r"dpkg-query -f '${Package}|${Version}|${Architecture}\n' -W")
117+
assert not out.stderr
118+
pkgs = {}
119+
for line in out.stdout.splitlines():
120+
line = line.strip()
121+
if not line:
122+
continue
123+
name, version, arch = line.split("|")
124+
pkg_key = f"{name}.{arch}"
125+
assert pkg_key not in pkgs, (
126+
f"Package {pkg_key} already added to package list. "
127+
"Check for duplicate package in command output"
128+
)
129+
pkgs[pkg_key] = {
130+
"name": name,
131+
"version": version,
132+
"release": None,
133+
"arch": arch,
134+
}
135+
return pkgs
136+
97137
@property
98138
def is_installed(self):
99139
result = self.run_test("dpkg-query -f '${Status}' -W %s", self.name)
@@ -155,6 +195,34 @@ def version(self):
155195

156196

157197
class RpmPackage(Package):
198+
@classmethod
199+
def get_packages(cls):
200+
out = cls.run(
201+
r'rpm -qa --queryformat "%{NAME}|%{VERSION}|%{RELEASE}|%{ARCH}\n"'
202+
)
203+
assert not out.stderr
204+
pkgs = {}
205+
for line in out.stdout.splitlines():
206+
line = line.strip()
207+
if not line:
208+
continue
209+
name, version, release, arch = line.split("|")
210+
# Ignore GPG keys imported with "rpm --import" e.g. "gpg-pubkey|50a3dd1c|50f35137|(none)"
211+
if name == "gpg-pubkey" and arch == "(none)":
212+
continue
213+
pkg_key = f"{name}.{arch}"
214+
assert pkg_key not in pkgs, (
215+
f"Package {pkg_key} already added to package list. "
216+
"Check for duplicate package in command output"
217+
)
218+
pkgs[pkg_key] = {
219+
"name": name,
220+
"version": version,
221+
"release": release,
222+
"arch": arch,
223+
}
224+
return pkgs
225+
158226
@property
159227
def is_installed(self):
160228
return self.run_test("rpm -q %s", self.name).rc == 0
@@ -185,6 +253,29 @@ def release(self):
185253

186254

187255
class ArchPackage(Package):
256+
@classmethod
257+
def get_packages(cls):
258+
out = cls.run(r'expac "%n|%v|%a"')
259+
assert not out.stderr
260+
pkgs = {}
261+
for line in out.stdout.splitlines():
262+
line = line.strip()
263+
if not line:
264+
continue
265+
name, version, arch = line.split("|")
266+
pkg_key = f"{name}.{arch}"
267+
assert pkg_key not in pkgs, (
268+
f"Package {pkg_key} already added to package list. "
269+
"Check for duplicate package in command output"
270+
)
271+
pkgs[pkg_key] = {
272+
"name": name,
273+
"version": version,
274+
"release": None,
275+
"arch": arch,
276+
}
277+
return pkgs
278+
188279
@property
189280
def is_installed(self):
190281
return self.run_test("pacman -Q %s", self.name).rc == 0

0 commit comments

Comments
 (0)