Skip to content

Commit

Permalink
repoentitlement: check for kernels when disabling with --purge
Browse files Browse the repository at this point in the history
If a kernel package is part of the removal and we cannot detect any other
Ubuntu kernel, block the operation. Otherwise, prompt the user.

Signed-off-by: Renan Rodrigo <[email protected]>
  • Loading branch information
renanrodrigo committed Oct 2, 2023
1 parent 3f613a7 commit ceeb745
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 0 deletions.
42 changes: 42 additions & 0 deletions uaclient/entitlements/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
event = event_logger.get_event_logger()
LOG = logging.getLogger(util.replace_top_level_logger_name(__name__))

RE_KERNEL_PKG = r"^linux-image-[\d]+[.-][\d]+[.-][\d]+-[\d]+-[A-Za-z0-9_-]+$"


class RepoEntitlement(base.UAEntitlement):
repo_list_file_tmpl = "/etc/apt/sources.list.d/ubuntu-{name}.list"
Expand Down Expand Up @@ -125,6 +127,10 @@ def _perform_disable(self, silent=False):
repo_origin_packages = apt.get_installed_packages_by_origin(
self.origin
)

if not self.purge_kernel_check(repo_origin_packages):
return False

packages_to_reinstall = []
packages_to_remove = []
for package in repo_origin_packages:
Expand Down Expand Up @@ -153,6 +159,42 @@ def _perform_disable(self, silent=False):
self.execute_purge(packages_to_remove, packages_to_reinstall)
return True

def purge_kernel_check(self, package_list):
linux_image_versions = [
package.name[len("linux-image-") :]
for package in package_list
if re.match(RE_KERNEL_PKG, package.name)
]
if linux_image_versions:
print(messages.PURGE_KERNEL_REMOVAL)
print(" ".join(linux_image_versions))

current_kernel = system.get_kernel_info().uname_release
if current_kernel in linux_image_versions:
print(
messages.PURGE_CURRENT_KERNEL.format(
kernel_version=current_kernel
)
)

installed_kernels = system.get_installed_ubuntu_kernels()
alternative_kernels = [
version
for version in installed_kernels
if version not in linux_image_versions
]

if not alternative_kernels:
print(messages.PURGE_NO_ALTERNATIVE_KERNEL)
return False

if not util.prompt_for_confirmation(
messages.PURGE_KERNEL_CONFIRMATION
):
return False

return True

def prompt_for_purge(self, packages_to_remove, packages_to_reinstall):
prompt = False
if packages_to_remove:
Expand Down
18 changes: 18 additions & 0 deletions uaclient/messages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,24 @@ class TxtColor:
APT_REMOVING_SOURCE_FILE = "Removing apt source file: {filename}"
APT_REMOVING_PREFERENCES_FILE = "Removing apt preferences file: {filename}"

# Kernel checks for Purge
PURGE_KERNEL_REMOVAL = (
"Purging the fips packages would uninstall the following kernel(s):"
)
PURGE_CURRENT_KERNEL = "{kernel_version} is the current running kernel."
PURGE_NO_ALTERNATIVE_KERNEL = """\
No other valid Ubuntu kernel was found in the system.
Removing the package would potentially make the system unbootable.
Aborting.
"""
PURGE_KERNEL_CONFIRMATION = (
"""\
If you cannot guarantee that other kernels in this system are bootable and
working properly, *do not proceed*. You may end up with an unbootable system.
"""
+ PROCEED_YES_NO
)

# These are for the retry-auto-attach functionality
AUTO_ATTACH_RETRY_NOTICE = """\
Failed to automatically attach to Ubuntu Pro services {num_attempts} time(s).
Expand Down
33 changes: 33 additions & 0 deletions uaclient/system.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import glob
import logging
import os
import pathlib
Expand Down Expand Up @@ -178,6 +179,38 @@ def get_kernel_info() -> KernelInfo:
)


# This only works if we are root (because of permissions to 'file' the kernels
# in /boot), but can't assert_root here due to circular imports.
def get_installed_ubuntu_kernels():
# cursed circular import
from uaclient.apt import get_installed_packages_names

linux_image = [
package
for package in get_installed_packages_names()
if "linux-image-" in package
]
vmlinux_kernel_files = [
file
for file in glob.glob("/boot/vmlinu[x|z]-*")
if "Linux kernel" in subp(["file", file])[0]
]

linux_image_versions = [
package_name[len("linux-image-") :] for package_name in linux_image
]
vmlinuz_versions = [
file_name[len("/boot/vmlinux-") :]
for file_name in vmlinux_kernel_files
]

return [
version
for version in vmlinuz_versions
if version in linux_image_versions
]


@lru_cache(maxsize=None)
def get_dpkg_arch() -> str:
out, _err = subp(["dpkg", "--print-architecture"])
Expand Down

0 comments on commit ceeb745

Please sign in to comment.