Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(apt): add candidate_version argument to add_package #116

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions lib/charms/operator_libs_linux/v0/apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,11 +721,39 @@ def __ne__(self, other) -> bool:
return not self.__eq__(other)


def get_candidate_version(package: str) -> Optional[str]:
"""Get candiate version of package from apt-cache.

Args:
package: package name
Returns:
A version string
Raises:
PackageError if fail to use apt-cache policy command
PackageNotFoundError if fail to get Candidate version
"""
try:
output = check_output(
["apt-cache", "policy", package], stderr=PIPE, universal_newlines=True
)
except CalledProcessError as e:
raise PackageError(f"Could not list packages in apt-cache: {e.output}") from None

lines = [line.strip() for line in output.strip().split("\n")]
for line in lines:
candidate_matcher = re.compile(r"^Candidate:\s(?P<version>(.*))")
matches = candidate_matcher.search(line)
if matches:
return matches.groupdict().get("version")
raise PackageNotFoundError(f"Could not find candidate version package in apt-cache: {output}")


def add_package(
package_names: Union[str, List[str]],
version: Optional[str] = "",
arch: Optional[str] = "",
update_cache: Optional[bool] = False,
candidate_version: bool = False,
) -> Union[DebianPackage, List[DebianPackage]]:
"""Add a package or list of packages to the system.

Expand All @@ -735,6 +763,7 @@ def add_package(
version: an (Optional) version as a string. Defaults to the latest known
arch: an optional architecture for the package
update_cache: whether or not to run `apt-get update` prior to operating
candidate_version: whether or not to use `apt-cache policy` to get and install candidate version

Raises:
TypeError if no package name is given, or explicit version is set for multiple packages
Expand All @@ -758,6 +787,8 @@ def add_package(
)

for p in package_names:
if candidate_version:
version = get_candidate_version(p)
pkg, success = _add(p, version, arch)
if success:
packages["success"].append(pkg)
Expand Down
173 changes: 173 additions & 0 deletions tests/unit/test_apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,113 @@
Description-md5: e7f99df3aa92cf870d335784e155ec33
"""

apt_cache_freeipmi_tools = """
Package: freeipmi-tools
Architecture: amd64
Version: 1.6.9-2~bpo20.04.1
Priority: extra
Section: admin
Source: freeipmi
Origin: Ubuntu
Maintainer: Ubuntu Developers <[email protected]>
Original-Maintainer: Fabio Fantoni <[email protected]>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 3102
Depends: freeipmi-common (= 1.6.9-2~bpo20.04.1), libc6 (>= 2.15), libfreeipmi17 (>= 1.6.2), libipmiconsole2 (>= 1.4.4), libipmidetect0 (>= 1.1.5)
Suggests: freeipmi-bmc-watchdog, freeipmi-ipmidetect
Filename: pool/main/f/freeipmi/freeipmi-tools_1.6.9-2~bpo20.04.1_amd64.deb
Size: 637216
MD5sum: fa4105fb6b0fb48969d56f005c7d32e8
SHA1: 4625f8601a3af2e787389a30b7c5b8027c908cad
SHA256: 247667a2835c5e775a9f68ec12e27f4a01c30dfd6a6306c29f10a3b52a255947
SHA512: b553c00327ec3304a0249ba238bbbe226fd293af6f3fc5aeb52b7ee3f90a8216d316e34ea4e8c69b9781beb9a746fc210fee34186d96202251439c33302b24db
Homepage: https://www.gnu.org/software/freeipmi/
Description-en: GNU implementation of the IPMI protocol - tools
FreeIPMI is a collection of Intelligent Platform Management IPMI
system software. It provides in-band and out-of-band software and a
development library conforming to the Intelligent Platform Management
Interface (IPMI v1.5 and v2.0) standards.
.
This package contains assorted IPMI-related tools:
* bmc-config - configure BMC values
* bmc-info - display BMC information
* ipmi-chassis - IPMI chassis management utility
* ipmi-fru - display FRU information
* ipmi-locate - IPMI probing utility
* ipmi-oem - IPMI OEM utility
* ipmi-pet - decode Platform Event Traps
* ipmi-raw - IPMI raw communication utility
* ipmi-sel - display SEL entries
* ipmi-sensors - display IPMI sensor information
* ipmi-sensors-config - configure sensors
* ipmiconsole - IPMI console utility
* ipmiping - send IPMI Get Authentication Capability request
* ipmipower - IPMI power control utility
* pef-config - configure PEF values
* rmcpping - send RMCP Ping to network hosts
Description-md5: 6752c6921b38f7d4192531a8ab33783c


Package: freeipmi-tools
Architecture: amd64
Version: 1.6.4-3ubuntu1.1
Priority: extra
Section: admin
Source: freeipmi
Origin: Ubuntu
Maintainer: Ubuntu Developers <[email protected]>
Original-Maintainer: Bernd Zeimetz <[email protected]>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 3099
Depends: libc6 (>= 2.15), libfreeipmi17 (>= 1.6.2), libipmiconsole2 (>= 1.4.4), libipmidetect0 (>= 1.1.5), freeipmi-common (= 1.6.4-3ubuntu1.1)
Suggests: freeipmi-ipmidetect, freeipmi-bmc-watchdog
Filename: pool/main/f/freeipmi/freeipmi-tools_1.6.4-3ubuntu1.1_amd64.deb
Size: 636384
MD5sum: bc7c1ec3484d07d3627ba92bb0300693
SHA1: b5851b2160d5139d141e3c1b29b946f6fd895871
SHA256: 6d0a643fcb62404b17d7574baf854b9b39443a2f0067c2076f5f663437d39968
SHA512: 8f89796c86a8a410c71996d8fb229293492c949a664476e0fa63fd3fe7b1523a3174997172fe82f6eed6fb59c0a4fde221273d6149b45d4e18da7e99faed02d6
Homepage: http://www.gnu.org/software/freeipmi/
Description-en: GNU implementation of the IPMI protocol - tools
FreeIPMI is a collection of Intelligent Platform Management IPMI
system software. It provides in-band and out-of-band software and a
development library conforming to the Intelligent Platform Management
Interface (IPMI v1.5 and v2.0) standards.
.
This package contains assorted IPMI-related tools:
* bmc-config - configure BMC values
* bmc-info - display BMC information
* ipmi-chassis - IPMI chassis management utility
* ipmi-fru - display FRU information
* ipmi-locate - IPMI probing utility
* ipmi-oem - IPMI OEM utility
* ipmi-pet - decode Platform Event Traps
* ipmi-raw - IPMI raw communication utility
* ipmi-sel - display SEL entries
* ipmi-sensors - display IPMI sensor information
* ipmi-sensors-config - configure sensors
* ipmiconsole - IPMI console utility
* ipmiping - send IPMI Get Authentication Capability request
* ipmipower - IPMI power control utility
* pef-config - configure PEF values
* rmcpping - send RMCP Ping to network hosts
Description-md5: 6752c6921b38f7d4192531a8ab33783c
"""


apt_cache_policy_freeipmi_tools_focal = """
freeipmi-tools:
Installed: (none)
Candidate: 1.6.4-3ubuntu1.1
Version table:
1.6.9-2~bpo20.04.1 100
100 http://archive.ubuntu.com/ubuntu focal-backports/main amd64 Packages
1.6.4-3ubuntu1.1 500
500 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages
1.6.4-3ubuntu1 500
500 http://archive.ubuntu.com/ubuntu focal/main amd64 Packages
"""


class TestApt(unittest.TestCase):
@patch("charms.operator_libs_linux.v0.apt.check_output")
Expand Down Expand Up @@ -510,3 +617,69 @@ def test_remove_package_not_installed(self, mock_subprocess, mock_subprocess_out
packages = apt.remove_package("ubuntu-advantage-tools")
mock_subprocess.assert_not_called()
self.assertEqual(packages, [])

@patch("charms.operator_libs_linux.v0.apt.check_output")
def test_get_candidate_version(self, mock_subprocess_output):
mock_subprocess_output.return_value = apt_cache_policy_freeipmi_tools_focal

version = apt.get_candidate_version("freeipmi_tools")
self.assertEqual(version, "1.6.4-3ubuntu1.1")

@patch("charms.operator_libs_linux.v0.apt.check_output")
def test_get_candidate_version_package_not_found_error(self, mock_subprocess_output):
mock_subprocess_output.side_effect = subprocess.CalledProcessError(
returncode=-1, cmd=["apt-cache", "policy", "fake_package_name"]
)

with self.assertRaises(apt.PackageError) as ctx:
apt.get_candidate_version("fake_package_name")

self.assertEqual("<charms.operator_libs_linux.v0.apt.PackageError>", ctx.exception.name)
self.assertIn("Could not list packages in apt-cache:", ctx.exception.message)

@patch("charms.operator_libs_linux.v0.apt.check_output")
def test_get_candidate_version_can_not_found_candidate(self, mock_subprocess_output):
output = apt_cache_policy_freeipmi_tools_focal.replace("Candidate", "candidate")
mock_subprocess_output.return_value = output
with self.assertRaises(apt.PackageNotFoundError) as ctx:
apt.get_candidate_version("freeipmi_tools")

self.assertEqual(
"<charms.operator_libs_linux.v0.apt.PackageNotFoundError>", ctx.exception.name
)
self.assertIn(
f"Could not find candidate version package in apt-cache: {output}",
ctx.exception.message,
)

@patch("charms.operator_libs_linux.v0.apt.check_output")
@patch("charms.operator_libs_linux.v0.apt.subprocess.run")
@patch("os.environ.copy")
def test_can_run_bare_changes_on_single_package_with_candidate_version(
self, mock_environ, mock_subprocess, mock_subprocess_output
):
mock_subprocess.return_value = 0
mock_subprocess_output.side_effect = [
apt_cache_policy_freeipmi_tools_focal,
"amd64",
subprocess.CalledProcessError(returncode=100, cmd=["dpkg", "-l", "freeipmi-tools"]),
"amd64",
apt_cache_freeipmi_tools,
]
mock_environ.return_value = {}

# foo = apt.add_package("freeipmi_tools", candidate_version=True)
foo = apt.add_package("freeipmi_tools", candidate_version=True)
mock_subprocess.assert_called_with(
[
"apt-get",
"-y",
"--option=Dpkg::Options::=--force-confold",
"install",
"freeipmi-tools=1.6.4-3ubuntu1.1",
],
capture_output=True,
check=True,
env={"DEBIAN_FRONTEND": "noninteractive"},
)
self.assertEqual(foo.present, True)