Source code for reemote.operations.builtin.apt

# Copyright (c) 2025 Kim Jarvis TPF Software Services S.A. kim.jarvis@tpfsystems.com 
# This software is licensed under the MIT License. See the LICENSE file for details.
#
from reemote.command import Command


[docs] class Apt: """ Apt. A class to encapsulate the functionality of apt package management in Debian/Ubuntu systems. It generates appropriate apt commands that can be passed to the Shell class for execution. Attributes ---------- name : list A list of package names, like ``['foo']``, or a package specifier with version, like ``['foo=1.0']``. state : str Indicates the desired package state. Choices: ``"absent"``, ``"build-dep"``, ``"latest"``, ``"present"``, ``"fixed"``. allow_change_held_packages : bool Allows changing the version of a package which is on the apt hold list. allow_downgrade : bool Corresponds to the ``--allow-downgrades`` option for apt. allow_unauthenticated : bool Ignore if packages cannot be authenticated. auto_install_module_deps : bool Automatically install dependencies required to run this module. autoclean : bool If true, cleans the local repository of retrieved package files (``apt-get autoclean``). autoremove : bool If true, remove unused dependency packages (``apt-get autoremove``). cache_valid_time : int Update the apt cache if it is older than this time in seconds. clean : bool Run the equivalent of ``apt-get clean`` to clear out the local repository. deb : str Path to a ``.deb`` package on the remote machine. default_release : str Corresponds to the ``-t`` option for apt and sets pin priorities. dpkg_options : str Add dpkg options to the apt command. fail_on_autoremove : bool Corresponds to the ``--no-remove`` option for apt. force : bool Corresponds to the ``--force-yes`` to apt-get (destructive operation). force_apt_get : bool Force usage of ``apt-get`` instead of ``aptitude``. install_recommends : bool Whether to install recommended packages. lock_timeout : int Seconds to wait to acquire a lock on the apt db. only_upgrade : bool Only upgrade a package if it is already installed. policy_rc_d : int Force the exit code of ``/usr/sbin/policy-rc.d``. purge : bool Will force purging of configuration files if ``state="absent"`` or ``autoremove=True``. update_cache : bool Run ``apt-get update`` before the operation. update_cache_retries : int Number of retries if the cache update fails. update_cache_retry_max_delay : int Max delay for exponential backoff during cache update retries. upgrade : str Type of upgrade to perform. Choices: ``"dist"``, ``"full"``, ``"no"``, ``"safe"``, ``"yes"``. guard : bool If ``False``, the commands will not be executed. sudo : bool If ``True``, execute commands with ``sudo`` privileges. su : bool If ``True``, execute commands with ``su`` privileges. Examples -------- Install apache httpd (state="present" is the default): .. code-block:: python yield Apt( name="apache2", state="present", ) Update repositories cache and install the "foo" package: .. code-block:: python yield Apt( name="foo", update_cache=True, ) Remove the "foo" package: .. code-block:: python yield Apt( name="foo", state="absent", ) Install a list of packages: .. code-block:: python yield Apt( name=["foo", "foo-tools"], ) Install a specific version of a package: .. code-block:: python yield Apt( name="foo=1.00", ) Update cache and upgrade "nginx" from a specific release: .. code-block:: python yield Apt( name="nginx", state="latest", default_release="squeeze-backports", update_cache=True, ) Install a specific version of "nginx", allowing downgrades: .. code-block:: python yield Apt( name="nginx=1.18.0", state="present", allow_downgrade=True, ) Install a package without removing conflicting packages: .. code-block:: python yield Apt( name="zfsutils-linux", state="latest", fail_on_autoremove=True, ) Install a package without its recommended dependencies: .. code-block:: python yield Apt( name="openjdk-6-jdk", state="latest", install_recommends=False, ) Update all packages to their latest version: .. code-block:: python yield Apt( name="*", state="latest", ) Upgrade the OS (equivalent to `apt-get dist-upgrade`): .. code-block:: python yield Apt( upgrade="dist", ) Run `apt-get update` as a standalone operation: .. code-block:: python yield Apt( update_cache=True, ) Update the cache only if it's older than 3600 seconds: .. code-block:: python yield Apt( update_cache=True, cache_valid_time=3600, ) Pass custom options to dpkg during an upgrade: .. code-block:: python yield Apt( upgrade="dist", update_cache=True, dpkg_options="force-confold,force-confdef", ) Install a local .deb package: .. code-block:: python yield Apt( deb="/tmp/mypackage.deb", ) Install a ``.deb`` package from a URL: .. code-block:: python yield Apt( deb="https://example.com/python-ppq_0.1-1_all.deb ", ) Install the build dependencies for a package: .. code-block:: python yield Apt( name="foo", state="build-dep", ) Remove useless packages from the cache (`autoclean`): .. code-block:: python yield Apt( autoclean=True, ) Remove dependencies that are no longer required (`autoremove`): .. code-block:: python yield Apt( autoremove=True, ) Autoremove packages and purge their configuration files: .. code-block:: python yield Apt( autoremove=True, purge=True, ) Clear the entire local package cache (`clean`): .. code-block:: python yield Apt( clean=True, ) """ def __init__(self, name: list = None, state: str = "present", allow_change_held_packages: bool = False, allow_downgrade: bool = False, allow_unauthenticated: bool = False, auto_install_module_deps: bool = True, autoclean: bool = False, autoremove: bool = False, cache_valid_time: int = 0, clean: bool = False, deb: str = None, default_release: str = None, dpkg_options: str = "force-confdef,force-confold", fail_on_autoremove: bool = False, force: bool = False, force_apt_get: bool = False, install_recommends: bool = None, lock_timeout: int = 60, only_upgrade: bool = False, policy_rc_d: int = None, purge: bool = False, update_cache: bool = False, update_cache_retries: int = 5, update_cache_retry_max_delay: int = 12, upgrade: str = "no", guard: bool = True, sudo: bool = False, su: bool = False): self.name = name self.state = state self.allow_change_held_packages = allow_change_held_packages self.allow_downgrade = allow_downgrade self.allow_unauthenticated = allow_unauthenticated self.auto_install_module_deps = auto_install_module_deps self.autoclean = autoclean self.autoremove = autoremove self.cache_valid_time = cache_valid_time self.clean = clean self.deb = deb self.default_release = default_release self.dpkg_options = dpkg_options self.fail_on_autoremove = fail_on_autoremove self.force = force self.force_apt_get = force_apt_get self.install_recommends = install_recommends self.lock_timeout = lock_timeout self.only_upgrade = only_upgrade self.policy_rc_d = policy_rc_d self.purge = purge self.update_cache = update_cache self.update_cache_retries = update_cache_retries self.update_cache_retry_max_delay = update_cache_retry_max_delay self.upgrade = upgrade self.guard = guard self.sudo = sudo self.su = su def __repr__(self): return (f"Apt(name={self.name!r}, state={self.state!r}, " f"allow_change_held_packages={self.allow_change_held_packages!r}, " f"allow_downgrade={self.allow_downgrade!r}, " f"allow_unauthenticated={self.allow_unauthenticated!r}, " f"auto_install_module_deps={self.auto_install_module_deps!r}, " f"autoclean={self.autoclean!r}, autoremove={self.autoremove!r}, " f"cache_valid_time={self.cache_valid_time!r}, clean={self.clean!r}, " f"deb={self.deb!r}, default_release={self.default_release!r}, " f"dpkg_options={self.dpkg_options!r}, " f"fail_on_autoremove={self.fail_on_autoremove!r}, force={self.force!r}, " f"force_apt_get={self.force_apt_get!r}, " f"install_recommends={self.install_recommends!r}, " f"lock_timeout={self.lock_timeout!r}, only_upgrade={self.only_upgrade!r}, " f"policy_rc_d={self.policy_rc_d!r}, purge={self.purge!r}, " f"update_cache={self.update_cache!r}, " f"update_cache_retries={self.update_cache_retries!r}, " f"update_cache_retry_max_delay={self.update_cache_retry_max_delay!r}, " f"upgrade={self.upgrade!r}, guard={self.guard!r}, " f"sudo={self.sudo!r}, su={self.su!r})") def execute(self): # Build the apt command based on the provided parameters cmd_parts = [] # Determine which apt tool to use apt_tool = "apt-get" if self.force_apt_get else "apt" # Handle state-specific commands if self.state == "absent": cmd_parts.append(apt_tool) cmd_parts.append("remove") if self.purge: cmd_parts.append("--purge") elif self.state == "build-dep": cmd_parts.append(apt_tool) cmd_parts.append("build-dep") elif self.state == "latest": cmd_parts.append(apt_tool) cmd_parts.append("install") elif self.state == "present": cmd_parts.append(apt_tool) cmd_parts.append("install") elif self.state == "fixed": cmd_parts.append(apt_tool) cmd_parts.append("install") cmd_parts.append("--fix-broken") # Add package names or .deb file if self.deb: cmd_parts.append(self.deb) elif self.name: cmd_parts.extend(self.name) # Add various options if self.allow_change_held_packages: cmd_parts.append("--allow-change-held-packages") if self.allow_downgrade: cmd_parts.append("--allow-downgrades") if self.allow_unauthenticated: cmd_parts.append("--allow-unauthenticated") if self.autoclean: cmd_parts.append("autoclean") if self.autoremove: cmd_parts.append("autoremove") if self.clean: cmd_parts.append("clean") if self.default_release: cmd_parts.extend(["-t", self.default_release]) # Add dpkg options if self.dpkg_options: options = self.dpkg_options.split(",") for option in options: cmd_parts.extend(["-o", f"Dpkg::Options::=--{option}"]) if self.fail_on_autoremove: cmd_parts.append("--no-remove") if self.force: cmd_parts.append("--force-yes") if self.install_recommends is not None: if self.install_recommends: cmd_parts.append("--install-recommends") else: cmd_parts.append("--no-install-recommends") if self.only_upgrade: cmd_parts.append("--only-upgrade") if self.purge and self.state != "absent": cmd_parts.append("--purge") # Handle upgrade separately as it's a different operation if self.upgrade != "no" and self.state not in ["absent", "present", "latest"]: if self.upgrade in ["safe", "yes"]: cmd_parts = [apt_tool, "safe-upgrade"] elif self.upgrade == "full": cmd_parts = [apt_tool, "full-upgrade"] elif self.upgrade == "dist": cmd_parts = [apt_tool, "dist-upgrade"] # Handle cache update if self.update_cache: update_cmd = " ".join([apt_tool, "update"]) # First execute cache update yield Command(update_cmd, guard=self.guard, sudo=self.sudo, su=self.su) # Build the final command cmd = " ".join(cmd_parts) # Execute the main apt command r = yield Command(cmd, guard=self.guard, sudo=self.sudo, su=self.su) r.changed = True