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:
parent
b05e00e99a
commit
4997063b4a
3 changed files with 78 additions and 10 deletions
|
@ -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)
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue