Repository: ansible/ansible-modules-core

Issue:
Ansible, apt module ignores force
<!--- Verify first that your issue/request is not already reported in GitHub -->
##### ISSUE TYPE

<!--- Pick one below and delete the rest: -->
- Bug Report
##### COMPONENT NAME

apt
##### ANSIBLE VERSION

```
ansible 2.1.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
v2.1.0.0-0.1.rc1
```
##### CONFIGURATION

<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
##### OS / ENVIRONMENT

<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->

```
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
```
##### SUMMARY

Adding force=yes to apt has no effect.
##### STEPS TO REPRODUCE

<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

```
- name: Remove old nginx
  apt: name=nginx state=absent purge=yes force=yes
```

<!--- You can also paste gist.github.com links for larger files -->
##### EXPECTED RESULTS

<!--- What did you expect to happen when running the steps above? -->

OK
##### ACTUAL RESULTS

<!--- What actually happened? If possible run with high verbosity (-vvvv) -->

Getting error from apt: **There are problems and -y was used without --force-yes**

``` javascript
TASK [static_servers_test : Remove old nginx] **********************************
task path: /etc/ansible/roles/static_servers_test/tasks/main.yml:17
<static8.example.com> ESTABLISH SSH CONNECTION FOR USER: root
<static8.example.com> SSH: EXEC ssh -C -vvv -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=root -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/ansible-ssh-%h-%p-%r static8.example.com '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1463401603.18-217457151249942 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1463401603.18-217457151249942 `" )'"'"''
<static8.example.com> PUT /tmp/tmphplGFX TO /root/.ansible/tmp/ansible-tmp-1463401603.18-217457151249942/apt
<static8.example.com> SSH: EXEC sftp -b - -C -vvv -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=root -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/ansible-ssh-%h-%p-%r '[static8.example.com]'
<static8.example.com> ESTABLISH SSH CONNECTION FOR USER: root
<static8.example.com> SSH: EXEC ssh -C -vvv -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=root -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/ansible-ssh-%h-%p-%r -tt static8.example.com '/bin/sh -c '"'"'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1463401603.18-217457151249942/apt; rm -rf "/root/.ansible/tmp/ansible-tmp-1463401603.18-217457151249942/" > /dev/null 2>&1'"'"''
fatal: [static8.example.com]: FAILED! => {
    "changed": false,
    "failed": true,
    "invocation": {
        "module_args": {
            "allow_unauthenticated": false,
            "autoremove": false,
            "cache_valid_time": null,
            "deb": null,
            "default_release": null,
            "dpkg_options": "force-confdef,force-confold",
            "force": true,
            "install_recommends": null,
            "name": "nginx",
            "only_upgrade": false,
            "package": ["nginx"],
            "purge": true,
            "state": "absent",
            "update_cache": false,
            "upgrade": null
        },
        "module_name": "apt"
    },
    "msg": "'apt-get remove 'nginx'' failed: E: There are problems and -y was used without --force-yes\n",
    "stderr": "E: There are problems and -y was used without --force-yes\n",
    "stdout": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nThe following packages were automatically installed and are no longer required:\n  linux-headers-3.19.0-30 linux-headers-3.19.0-30-generic\n  linux-image-3.19.0-30-generic linux-image-extra-3.19.0-30-generic\nUse 'apt-get autoremove' to remove them.\nThe following packages will be REMOVED:\n  nginx*\nThe following held packages will be changed:\n  nginx\n0 upgraded, 0 newly installed, 1 to remove and 3 not upgraded.\nAfter this operation, 2386 kB disk space will be freed.\n",
    "stdout_lines": ["Reading package lists...", "Building dependency tree...", "Reading state information...", "The following packages were automatically installed and are no longer required:", "  linux-headers-3.19.0-30 linux-headers-3.19.0-30-generic", "  linux-image-3.19.0-30-generic linux-image-extra-3.19.0-30-generic", "Use 'apt-get autoremove' to remove them.", "The following packages will be REMOVED:", "  nginx*", "The following held packages will be changed:", "  nginx", "0 upgraded, 0 newly installed, 1 to remove and 3 not upgraded.", "After this operation, 2386 kB disk space will be freed."]
}

```



Discussion:

Resolving patch:
diff --git a/packaging/os/apt.py b/packaging/os/apt.py
--- a/packaging/os/apt.py
+++ b/packaging/os/apt.py
@@ -550,7 +550,7 @@ def install_deb(m, debs, cache, force, install_recommends, allow_unauthenticated
     else:
         m.exit_json(changed=changed, stdout=retvals.get('stdout',''), stderr=retvals.get('stderr',''), diff=retvals.get('diff', ''))
 
-def remove(m, pkgspec, cache, purge=False,
+def remove(m, pkgspec, cache, purge=False, force=False,
            dpkg_options=expand_dpkg_options(DPKG_OPTIONS), autoremove=False):
     pkg_list = []
     pkgspec = expand_pkgspec_from_fnmatches(m, pkgspec, cache)
@@ -564,6 +564,11 @@ def remove(m, pkgspec, cache, purge=False,
     if len(packages) == 0:
         m.exit_json(changed=False)
     else:
+        if force:
+            force_yes = '--force-yes'
+        else:
+            force_yes = ''
+
         if purge:
             purge = '--purge'
         else:
@@ -579,7 +584,7 @@ def remove(m, pkgspec, cache, purge=False,
         else:
             check_arg = ''
 
-        cmd = "%s -q -y %s %s %s %s remove %s" % (APT_GET_CMD, dpkg_options, purge, autoremove, check_arg, packages)
+        cmd = "%s -q -y %s %s %s %s %s remove %s" % (APT_GET_CMD, dpkg_options, purge, force_yes ,autoremove, check_arg, packages)
 
         rc, out, err = m.run_command(cmd)
         if m._diff:
@@ -827,7 +832,7 @@ def main():
             else:
                 module.fail_json(**retvals)
         elif p['state'] == 'absent':
-            remove(module, packages, cache, p['purge'], dpkg_options, autoremove)
+            remove(module, packages, cache, p['purge'], force=force_yes, dpkg_options=dpkg_options, autoremove=autoremove)
 
     except apt.cache.LockFailedException:
         module.fail_json(msg="Failed to lock apt for exclusive operation")


Test patch: