apt - add fail_on_autoremove option to avoid unintended package removals (#70056)

* Ensure not to remove existing packages while installing apt packages.
* Make all lines shorter than 160 characters
* Allow removing packages only when upgrading.
* Add integration tests
This commit is contained in:
psi / Ryo Hirafuji 2020-06-30 22:53:14 +09:00 committed by GitHub
parent b05e00e99a
commit 4997063b4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 10 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- apt - add ``fail_on_autoremove`` param to apt module to avoid unintended package removals (https://github.com/ansible/ansible/issues/63231)

View file

@ -135,6 +135,14 @@ options:
type: bool
default: 'no'
version_added: "2.1"
fail_on_autoremove:
description:
- 'Corresponds to the C(--no-remove) option for C(apt).'
- 'If C(yes), it is ensured that no packages will be removed or the task will fail.'
- 'C(fail_on_autoremove) is only supported with state except C(absent)'
type: bool
default: 'no'
version_added: "2.11"
force_apt_get:
description:
- Force usage of apt-get instead of aptitude
@ -194,6 +202,12 @@ EXAMPLES = '''
default_release: squeeze-backports
update_cache: yes
- name: Install zfsutils-linux with ensuring conflicted packages (e.g. zfs-fuse) will not be removed.
apt:
name: zfsutils-linux
state: latest
fail_on_autoremove: yes
- name: Install latest version of "openjdk-6-jdk" ignoring "install-recommends"
apt:
name: openjdk-6-jdk
@ -613,7 +627,7 @@ def mark_installed_manually(m, packages):
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, only_upgrade=False,
build_dep=False, fixed=False, autoremove=False, fail_on_autoremove=False, only_upgrade=False,
allow_unauthenticated=False):
pkg_list = []
packages = ""
@ -656,6 +670,11 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None,
else:
autoremove = ''
if fail_on_autoremove:
fail_on_autoremove = '--no-remove'
else:
fail_on_autoremove = ''
if only_upgrade:
only_upgrade = '--only-upgrade'
else:
@ -667,9 +686,10 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None,
fixed = ''
if build_dep:
cmd = "%s -y %s %s %s %s %s build-dep %s" % (APT_GET_CMD, dpkg_options, only_upgrade, fixed, force_yes, check_arg, packages)
cmd = "%s -y %s %s %s %s %s %s build-dep %s" % (APT_GET_CMD, dpkg_options, only_upgrade, fixed, force_yes, fail_on_autoremove, check_arg, packages)
else:
cmd = "%s -y %s %s %s %s %s %s install %s" % (APT_GET_CMD, dpkg_options, only_upgrade, fixed, force_yes, autoremove, check_arg, packages)
cmd = "%s -y %s %s %s %s %s %s %s install %s" % \
(APT_GET_CMD, dpkg_options, only_upgrade, fixed, force_yes, autoremove, fail_on_autoremove, check_arg, packages)
if default_release:
cmd += " -t '%s'" % (default_release,)
@ -719,7 +739,7 @@ def get_field_of_deb(m, deb_file, field="Version"):
return to_native(stdout).strip('\n')
def install_deb(m, debs, cache, force, install_recommends, allow_unauthenticated, dpkg_options):
def install_deb(m, debs, cache, force, fail_on_autoremove, install_recommends, allow_unauthenticated, dpkg_options):
changed = False
deps_to_install = []
pkgs_to_install = []
@ -761,6 +781,7 @@ def install_deb(m, debs, cache, force, install_recommends, allow_unauthenticated
if deps_to_install:
(success, retvals) = install(m=m, pkgspec=deps_to_install, cache=cache,
install_recommends=install_recommends,
fail_on_autoremove=fail_on_autoremove,
allow_unauthenticated=allow_unauthenticated,
dpkg_options=expand_dpkg_options(dpkg_options))
if not success:
@ -890,7 +911,7 @@ def cleanup(m, purge=False, force=False, operation=None,
def upgrade(m, mode="yes", force=False, default_release=None,
use_apt_get=False,
dpkg_options=expand_dpkg_options(DPKG_OPTIONS), autoremove=False,
dpkg_options=expand_dpkg_options(DPKG_OPTIONS), autoremove=False, fail_on_autoremove=False,
allow_unauthenticated=False,
):
@ -932,6 +953,11 @@ def upgrade(m, mode="yes", force=False, default_release=None,
else:
force_yes = ''
if fail_on_autoremove:
fail_on_autoremove = '--no-remove'
else:
fail_on_autoremove = ''
allow_unauthenticated = '--allow-unauthenticated' if allow_unauthenticated else ''
if apt_cmd is None:
@ -942,8 +968,7 @@ def upgrade(m, mode="yes", force=False, default_release=None,
"to have APTITUDE in path or use 'force_apt_get=True'")
apt_cmd_path = m.get_bin_path(apt_cmd, required=True)
cmd = '%s -y %s %s %s %s %s' % (apt_cmd_path, dpkg_options, force_yes, allow_unauthenticated,
check_arg, upgrade_command)
cmd = '%s -y %s %s %s %s %s %s' % (apt_cmd_path, dpkg_options, force_yes, fail_on_autoremove, allow_unauthenticated, check_arg, upgrade_command)
if default_release:
cmd += " -t '%s'" % (default_release,)
@ -1029,6 +1054,7 @@ def main():
dpkg_options=dict(type='str', default=DPKG_OPTIONS),
autoremove=dict(type='bool', default=False),
autoclean=dict(type='bool', default=False),
fail_on_autoremove=dict(type='bool', default=False),
policy_rc_d=dict(type='int', default=None),
only_upgrade=dict(type='bool', default=False),
force_apt_get=dict(type='bool', default=False),
@ -1084,6 +1110,7 @@ def main():
allow_unauthenticated = p['allow_unauthenticated']
dpkg_options = expand_dpkg_options(p['dpkg_options'])
autoremove = p['autoremove']
fail_on_autoremove = p['fail_on_autoremove']
autoclean = p['autoclean']
# Get the cache object
@ -1145,7 +1172,7 @@ def main():
force_yes = p['force']
if p['upgrade']:
upgrade(module, p['upgrade'], force_yes, p['default_release'], use_apt_get, dpkg_options, autoremove, allow_unauthenticated)
upgrade(module, p['upgrade'], force_yes, p['default_release'], use_apt_get, dpkg_options, autoremove, fail_on_autoremove, allow_unauthenticated)
if p['deb']:
if p['state'] != 'present':
@ -1155,7 +1182,7 @@ def main():
install_deb(module, p['deb'], cache,
install_recommends=install_recommends,
allow_unauthenticated=allow_unauthenticated,
force=force_yes, dpkg_options=p['dpkg_options'])
force=force_yes, fail_on_autoremove=fail_on_autoremove, dpkg_options=p['dpkg_options'])
unfiltered_packages = p['package'] or ()
packages = [package.strip() for package in unfiltered_packages if package != '*']
@ -1165,7 +1192,7 @@ def main():
if latest and all_installed:
if packages:
module.fail_json(msg='unable to install additional packages when upgrading all installed packages')
upgrade(module, 'yes', force_yes, p['default_release'], use_apt_get, dpkg_options, autoremove, allow_unauthenticated)
upgrade(module, 'yes', force_yes, p['default_release'], use_apt_get, dpkg_options, autoremove, fail_on_autoremove, allow_unauthenticated)
if packages:
for package in packages:
@ -1203,6 +1230,7 @@ def main():
build_dep=state_builddep,
fixed=state_fixed,
autoremove=autoremove,
fail_on_autoremove=fail_on_autoremove,
only_upgrade=p['only_upgrade'],
allow_unauthenticated=allow_unauthenticated
)

View file

@ -179,6 +179,44 @@
- name: uninstall hello with apt
apt: pkg=hello state=absent purge=yes
# INSTALL WITHOUT REMOVALS
- name: Install hello, that conflicts with hello-traditional
apt:
pkg: hello
state: present
update_cache: no
- name: check hello
shell: dpkg-query -l hello
register: dpkg_result
- name: verify installation of hello
assert:
that:
- "apt_result.changed"
- "dpkg_result.rc == 0"
- name: Try installing hello-traditional, that conflicts with hello
apt:
pkg: hello-traditional
state: present
fail_on_autoremove: yes
ignore_errors: yes
register: apt_result
- name: verify failure of installing hello-traditional, because it is required to remove hello to install.
assert:
that:
- apt_result is failed
- '"Packages need to be removed but remove is disabled." in apt_result.msg'
- name: uninstall hello with apt
apt:
pkg: hello
state: absent
purge: yes
update_cache: no
- name: install deb file
apt: deb="/var/cache/apt/archives/hello_{{ hello_version.stdout }}_{{ hello_architecture.stdout }}.deb"
register: apt_initial