diff --git a/lib/ansible/modules/packaging/os/apt.py b/lib/ansible/modules/packaging/os/apt.py index 7452dd6e784..93972ce0481 100644 --- a/lib/ansible/modules/packaging/os/apt.py +++ b/lib/ansible/modules/packaging/os/apt.py @@ -275,6 +275,7 @@ APT_GET_ZERO = "\n0 upgraded, 0 newly installed" APTITUDE_ZERO = "\n0 packages upgraded, 0 newly installed" APT_LISTS_PATH = "/var/lib/apt/lists" APT_UPDATE_SUCCESS_STAMP_PATH = "/var/lib/apt/periodic/update-success-stamp" +APT_MARK_INVALID_OP = 'Invalid operation' HAS_PYTHON_APT = True try: @@ -469,6 +470,19 @@ def parse_diff(output): return {'prepared': '\n'.join(diff[diff_start:diff_end])} +def mark_installed_manually(m, packages): + apt_mark_cmd_path = m.get_bin_path("apt-mark", required=True) + cmd = "%s manual %s" % (apt_mark_cmd_path, ' '.join(packages)) + rc, out, err = m.run_command(cmd) + + if APT_MARK_INVALID_OP in err: + cmd = "%s unmarkauto %s" % (apt_mark_cmd_path, ' '.join(packages)) + rc, out, err = m.run_command(cmd) + + if rc != 0: + m.fail_json(msg="'%s' failed: %s" % (cmd, err), stdout=out, stderr=err, rc=rc) + + def install(m, pkgspec, cache, upgrade=False, default_release=None, install_recommends=None, force=False, dpkg_options=expand_dpkg_options(DPKG_OPTIONS), @@ -477,6 +491,7 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None, pkg_list = [] packages = "" pkgspec = expand_pkgspec_from_fnmatches(m, pkgspec, cache) + package_names = [] for package in pkgspec: if build_dep: # Let apt decide what to install @@ -484,6 +499,7 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None, continue name, version = package_split(package) + package_names.append(name) installed, upgradable, has_files = package_status(m, name, version, cache, state='install') if (not installed and not only_upgrade) or (upgrade and upgradable): pkg_list.append("'%s'" % package) @@ -545,9 +561,14 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None, if rc: status = False data = dict(msg="'%s' failed: %s" % (cmd, err), stdout=out, stderr=err, rc=rc) - return (status, data) else: - return (True, dict(changed=False)) + status = True + data = dict(changed=False) + + if not build_dep: + mark_installed_manually(m, package_names) + + return (status, data) def get_field_of_deb(m, deb_file, field="Version"): diff --git a/test/integration/targets/apt/tasks/apt.yml b/test/integration/targets/apt/tasks/apt.yml index 22ecd608b97..4d01cead40e 100644 --- a/test/integration/targets/apt/tasks/apt.yml +++ b/test/integration/targets/apt/tasks/apt.yml @@ -185,6 +185,41 @@ name: /etc/apt/sources.list.d/non-existing.list state: absent +# https://github.com/ansible/ansible/issues/28907 +- name: Install parent package + apt: + name: libcaca-dev + +- name: Install child package + apt: + name: libslang2-dev + +- shell: apt-mark showmanual | grep libcaca-dev + ignore_errors: yes + register: parent_output + +- name: Check that parent package is marked as installed manually + assert: + that: + - "'libcaca-dev' in parent_output.stdout" + +- shell: apt-mark showmanual | grep libslang2-dev + ignore_errors: yes + register: child_output + +- name: Check that child package is marked as installed manually + assert: + that: + - "'libslang2-dev' in child_output.stdout" + +- name: Clean up + apt: + name: "{{ item }}" + state: absent + with_items: + - libcaca-dev + - libslang2-dev + - name: autoclean during install apt: pkg=hello state=present autoclean=yes