fixes saltstack/salt#63982 aptpkg.latest_version calls apt-cache too much

This commit is contained in:
nicholasmhughes 2023-03-28 13:02:51 -04:00 committed by Megan Wilhite
parent e4ba3fd7d2
commit 4730bea00b
3 changed files with 68 additions and 20 deletions

1
changelog/63982.fixed.md Normal file
View file

@ -0,0 +1 @@
Fix aptpkg.latest_version performance, reducing number of times to 'shell out'

View file

@ -423,6 +423,8 @@ def parse_arch(name):
def latest_version(*names, **kwargs):
"""
.. versionchanged:: 3007.0
Return the latest version of the named package available for upgrade or
installation. If more than one package name is specified, a dict of
name/version pairs is returned.
@ -469,38 +471,47 @@ def latest_version(*names, **kwargs):
if refresh:
refresh_db(cache_valid_time)
cmd = ["apt-cache", "-q", "policy"]
cmd.extend(names)
if repo is not None:
cmd.extend(repo)
out = _call_apt(cmd, scope=False)
short_names = [nom.split(":", maxsplit=1)[0] for nom in names]
candidates = {}
for line in salt.utils.itertools.split(out["stdout"], "\n"):
if line.endswith(":") and line[:-1] in short_names:
this_pkg = names[short_names.index(line[:-1])]
elif "Candidate" in line:
candidate = ""
comps = line.split()
if len(comps) >= 2:
candidate = comps[-1]
if candidate.lower() == "(none)":
candidate = ""
candidates[this_pkg] = candidate
for name in names:
cmd = ["apt-cache", "-q", "policy", name]
if repo is not None:
cmd.extend(repo)
out = _call_apt(cmd, scope=False)
candidate = ""
for line in salt.utils.itertools.split(out["stdout"], "\n"):
if "Candidate" in line:
comps = line.split()
if len(comps) >= 2:
candidate = comps[-1]
if candidate.lower() == "(none)":
candidate = ""
break
installed = pkgs.get(name, [])
if not installed:
ret[name] = candidate
ret[name] = candidates.get(name, "")
elif installed and show_installed:
ret[name] = candidate
elif candidate:
ret[name] = candidates.get(name, "")
elif candidates.get(name):
# If there are no installed versions that are greater than or equal
# to the install candidate, then the candidate is an upgrade, so
# add it to the return dict
if not any(
salt.utils.versions.compare(
ver1=x, oper=">=", ver2=candidate, cmp_func=version_cmp
ver1=x,
oper=">=",
ver2=candidates.get(name, ""),
cmp_func=version_cmp,
)
for x in installed
):
ret[name] = candidate
ret[name] = candidates.get(name, "")
# Return a string if only one package name passed
if len(names) == 1:

View file

@ -1401,3 +1401,39 @@ def test_sourceslist_architectures(repo_line):
assert source.architectures == ["amd64", "armel"]
else:
assert source.architectures == ["amd64"]
def test_latest_version_calls_aptcache_once_per_run():
"""
Performance Test - don't call apt-cache once for each pkg, call once and parse output
"""
mock_list_pkgs = MagicMock(return_value={"sudo": "1.8.27-1+deb10u5"})
apt_cache_ret = {
"stdout": textwrap.dedent(
"""sudo:
Installed: 1.8.27-1+deb10u5
Candidate: 1.8.27-1+deb10u5
Version table:
*** 1.8.27-1+deb10u5 500
500 http://security.debian.org/debian-security buster/updates/main amd64 Packages
100 /var/lib/dpkg/status
1.8.27-1+deb10u3 500
500 http://deb.debian.org/debian buster/main amd64 Packages
unzip:
Installed: (none)
Candidate: 6.0-23+deb10u3
Version table:
6.0-23+deb10u3 500
500 http://security.debian.org/debian-security buster/updates/main amd64 Packages
6.0-23+deb10u2 500
500 http://deb.debian.org/debian buster/main amd64 Packages
"""
)
}
mock_apt_cache = MagicMock(return_value=apt_cache_ret)
with patch("salt.modules.aptpkg._call_apt", mock_apt_cache), patch(
"salt.modules.aptpkg.list_pkgs", mock_list_pkgs
):
ret = aptpkg.latest_version("sudo", "unzip", refresh=False)
mock_apt_cache.assert_called_once()
assert ret == {"sudo": "6.0-23+deb10u3", "unzip": ""}