From cd2d8b77fe699f7cc8529dfa296b0d2894630599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20St=C3=A4hle?= Date: Mon, 29 Nov 2021 16:31:54 +0100 Subject: [PATCH] apt - add allow_change_held_packages option (#73629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add changelog fragment and integration tests * Break up function signature into multiple lines It passed the 160 character width. Change it to multi-line for easier reading and better diffs in the future. * Remove unused import * Update version added for new feature * Move changelog fragment to correct location Co-authored-by: Thomas Stähle Co-authored-by: Sam Doran --- .../73629_allow-change-held-packages.yml | 3 + lib/ansible/modules/apt.py | 28 ++++++- .../integration/targets/apt/defaults/main.yml | 1 + .../integration/targets/apt/handlers/main.yml | 4 + test/integration/targets/apt/tasks/apt.yml | 76 ++++++++++++++++++- 5 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/73629_allow-change-held-packages.yml create mode 100644 test/integration/targets/apt/handlers/main.yml diff --git a/changelogs/fragments/73629_allow-change-held-packages.yml b/changelogs/fragments/73629_allow-change-held-packages.yml new file mode 100644 index 00000000000..2f6caee5d43 --- /dev/null +++ b/changelogs/fragments/73629_allow-change-held-packages.yml @@ -0,0 +1,3 @@ +minor_changes: + - apt - Adds APT option ``--allow-change-held-packages`` as module parameter ``allow_change_held_packages`` to allow APT up- or + downgrading a package which is on APTs hold list (https://github.com/ansible/ansible/issues/65325) diff --git a/lib/ansible/modules/apt.py b/lib/ansible/modules/apt.py index bcedb3b639e..a9fa544a776 100644 --- a/lib/ansible/modules/apt.py +++ b/lib/ansible/modules/apt.py @@ -101,6 +101,12 @@ options: type: bool default: 'no' version_added: "2.12" + allow_change_held_packages: + description: + - Allows changing the version of a package which is on the apt hold list + type: bool + default: 'no' + version_added: '2.13' upgrade: description: - If yes or safe, performs an aptitude safe-upgrade. @@ -341,7 +347,7 @@ import time from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module -from ansible.module_utils._text import to_bytes, to_native +from ansible.module_utils._text import to_native from ansible.module_utils.six import PY3 from ansible.module_utils.urls import fetch_file @@ -655,7 +661,7 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None, install_recommends=None, force=False, dpkg_options=expand_dpkg_options(DPKG_OPTIONS), build_dep=False, fixed=False, autoremove=False, fail_on_autoremove=False, only_upgrade=False, - allow_unauthenticated=False, allow_downgrade=False): + allow_unauthenticated=False, allow_downgrade=False, allow_change_held_packages=False): pkg_list = [] packages = "" pkgspec = expand_pkgspec_from_fnmatches(m, pkgspec, cache) @@ -733,6 +739,9 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None, if allow_downgrade: cmd += " --allow-downgrades" + if allow_change_held_packages: + cmd += " --allow-change-held-packages" + with PolicyRcD(m): rc, out, err = m.run_command(cmd) @@ -769,7 +778,13 @@ def get_field_of_deb(m, deb_file, field="Version"): return to_native(stdout).strip('\n') -def install_deb(m, debs, cache, force, fail_on_autoremove, install_recommends, allow_unauthenticated, allow_downgrade, dpkg_options): +def install_deb( + m, debs, cache, force, fail_on_autoremove, install_recommends, + allow_unauthenticated, + allow_downgrade, + allow_change_held_packages, + dpkg_options, +): changed = False deps_to_install = [] pkgs_to_install = [] @@ -817,6 +832,7 @@ def install_deb(m, debs, cache, force, fail_on_autoremove, install_recommends, a fail_on_autoremove=fail_on_autoremove, allow_unauthenticated=allow_unauthenticated, allow_downgrade=allow_downgrade, + allow_change_held_packages=allow_change_held_packages, dpkg_options=expand_dpkg_options(dpkg_options)) if not success: m.fail_json(**retvals) @@ -1106,6 +1122,7 @@ def main(): force_apt_get=dict(type='bool', default=False), allow_unauthenticated=dict(type='bool', default=False, aliases=['allow-unauthenticated']), allow_downgrade=dict(type='bool', default=False, aliases=['allow-downgrade', 'allow_downgrades', 'allow-downgrades']), + allow_change_held_packages=dict(type='bool', default=False), lock_timeout=dict(type='int', default=60), ), mutually_exclusive=[['deb', 'package', 'upgrade']], @@ -1203,6 +1220,7 @@ def main(): install_recommends = p['install_recommends'] allow_unauthenticated = p['allow_unauthenticated'] allow_downgrade = p['allow_downgrade'] + allow_change_held_packages = p['allow_change_held_packages'] dpkg_options = expand_dpkg_options(p['dpkg_options']) autoremove = p['autoremove'] fail_on_autoremove = p['fail_on_autoremove'] @@ -1294,6 +1312,7 @@ def main(): install_deb(module, p['deb'], cache, install_recommends=install_recommends, allow_unauthenticated=allow_unauthenticated, + allow_change_held_packages=allow_change_held_packages, allow_downgrade=allow_downgrade, force=force_yes, fail_on_autoremove=fail_on_autoremove, dpkg_options=p['dpkg_options']) @@ -1357,7 +1376,8 @@ def main(): fail_on_autoremove=fail_on_autoremove, only_upgrade=p['only_upgrade'], allow_unauthenticated=allow_unauthenticated, - allow_downgrade=allow_downgrade + allow_downgrade=allow_downgrade, + allow_change_held_packages=allow_change_held_packages, ) # Store if the cache has been updated diff --git a/test/integration/targets/apt/defaults/main.yml b/test/integration/targets/apt/defaults/main.yml index 05a5780fd34..7ad2497d470 100644 --- a/test/integration/targets/apt/defaults/main.yml +++ b/test/integration/targets/apt/defaults/main.yml @@ -1 +1,2 @@ apt_foreign_arch: i386 +hello_old_version: 2.6-1 diff --git a/test/integration/targets/apt/handlers/main.yml b/test/integration/targets/apt/handlers/main.yml new file mode 100644 index 00000000000..0b6a98f7fc5 --- /dev/null +++ b/test/integration/targets/apt/handlers/main.yml @@ -0,0 +1,4 @@ +- name: remove package hello + apt: + name: hello + state: absent diff --git a/test/integration/targets/apt/tasks/apt.yml b/test/integration/targets/apt/tasks/apt.yml index 1b2d9206dae..5f95fe74570 100644 --- a/test/integration/targets/apt/tasks/apt.yml +++ b/test/integration/targets/apt/tasks/apt.yml @@ -205,7 +205,7 @@ # INSTALL WITHOUT REMOVALS - name: Install hello, that conflicts with hello-traditional - apt: + apt: pkg: hello state: present update_cache: no @@ -221,7 +221,7 @@ - "dpkg_result.rc == 0" - name: Try installing hello-traditional, that conflicts with hello - apt: + apt: pkg: hello-traditional state: present fail_on_autoremove: yes @@ -235,7 +235,7 @@ - '"Packages need to be removed but remove is disabled." in apt_result.msg' - name: uninstall hello with apt - apt: + apt: pkg: hello state: absent purge: yes @@ -437,3 +437,73 @@ file: path: /usr/sbin/policy-rc.d state: absent + +# https://github.com/ansible/ansible/issues/65325 +- name: Download and install old version of hello (to test allow_change_held_packages option) + apt: "deb=https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/dpkg_selections/hello_{{ hello_old_version }}_amd64.deb" + notify: + - remove package hello + +- name: Put hello on hold + shell: apt-mark hold hello + +- name: Get hold list + shell: apt-mark showhold + register: allow_change_held_packages_hold + +- name: Check that the package hello is on the hold list + assert: + that: + - "'hello' in allow_change_held_packages_hold.stdout" + +- name: Try updating package to the latest version (allow_change_held_packages=no) + apt: + name: hello + state: latest + ignore_errors: True + register: allow_change_held_packages_failed_update + +- name: Get the version of the package + shell: dpkg -s hello | grep Version | awk '{print $2}' + register: allow_change_held_packages_hello_version + +- name: Verify that the package was not updated (apt returns with an error) + assert: + that: + - "allow_change_held_packages_failed_update is failed" + - "'--allow-change-held-packages' in allow_change_held_packages_failed_update.stderr" + - "allow_change_held_packages_hello_version.stdout == hello_old_version" + +- name: Try updating package to the latest version (allow_change_held_packages=yes) + apt: + name: hello + state: latest + allow_change_held_packages: yes + register: allow_change_held_packages_successful_update + +- name: Get the version of the package + shell: dpkg -s hello | grep Version | awk '{print $2}' + register: allow_change_held_packages_hello_version + +- name: Verify that the package was updated + assert: + that: + - "allow_change_held_packages_successful_update is changed" + - "allow_change_held_packages_hello_version.stdout != hello_old_version" + +- name: Try updating package to the latest version again + apt: + name: hello + state: latest + allow_change_held_packages: yes + register: allow_change_held_packages_no_update + +- name: Get the version of the package + shell: dpkg -s hello | grep Version | awk '{print $2}' + register: allow_change_held_packages_hello_version_again + +- name: Verify that the package was not updated + assert: + that: + - "allow_change_held_packages_no_update is not changed" + - "allow_change_held_packages_hello_version.stdout == allow_change_held_packages_hello_version_again.stdout"