Repository: ansible/ansible

Issue:
iam module fails silently when deleting groups with inline policies still attached
##### ISSUE TYPE

 - Bug Report

##### COMPONENT NAME

iam module

##### ANSIBLE VERSION

```
ansible 2.4.0 (iam_delete_group 1f8575ec97) last updated 2017/06/26 14:11:30 (GMT +000)
  config file = /etc/ansible/ansible.cfg
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
```

##### OS / ENVIRONMENT

Linux bc76ce7758ea 4.8.0-54-generic #57~16.04.1-Ubuntu SMP Wed May 24 16:22:28 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

##### SUMMARY

The iam module is failing silently when trying to remove a iam group, which still has inline policies attached to it. 
The issue seems to be caused by the fact that the exception string generated by the boto library has changed and for that reason we are catching the exception, but since it doesn't match the string we are looking for ('must detach all policies first'), we are not handling it properly.

##### STEPS TO REPRODUCE

Create a IAM Group with one or more inline policies attached and run the following task to delete the group.

```yaml
- name: Remove IAM groups
  iam:
    iam_type: group
    name: test_group
    state: absent
```

##### EXPECTED RESULTS

All inline policies attached to the group should have been detached and the group should have been deleted.

##### ACTUAL RESULTS

The group is not being deleted and no error is being shown in the output.

```
changed: [localhost] => (item={u'state': u'absent', u'name': u'test_group', u'iam_type': u'group'}) => {
    "changed": "test_group", 
    "delete_group": false, 
    "invocation": {
        "module_args": {
            "access_key_ids": null, 
            "access_key_state": null, 
            "aws_access_key": "XXXXXXXXXXXXXX", 
            "aws_secret_key": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "ec2_url": null, 
            "groups": null, 
            "iam_type": "group", 
            "key_count": 1, 
            "name": "test_group", 
            "new_name": null, 
            "new_path": null, 
            "password": null, 
            "path": "/", 
            "profile": null, 
            "region": null, 
            "security_token": null, 
            "state": "absent", 
            "trust_policy": null, 
            "trust_policy_filepath": null, 
            "update_password": "always", 
            "validate_certs": false
        }
    }, 
    "item": {
        "iam_type": "group", 
        "name": "test_group", 
        "state": "absent"
    }
}
```



Discussion:
I'm already working on a PR and will submit it for review shortly.

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/iam.py b/lib/ansible/modules/cloud/amazon/iam.py
--- a/lib/ansible/modules/cloud/amazon/iam.py
+++ b/lib/ansible/modules/cloud/amazon/iam.py
@@ -245,7 +245,7 @@ def create_user(module, iam, name, pwd, path, key_state, key_count):
 
 
 def delete_user(module, iam, name):
-    del_meta = ''
+    changed = False
     try:
         current_keys = [ck['access_key_id'] for ck in
             iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
@@ -256,17 +256,17 @@ def delete_user(module, iam, name):
         except boto.exception.BotoServerError as err:
             error_msg = boto_exception(err)
             if ('Cannot find Login Profile') in error_msg:
-                del_meta = iam.delete_user(name).delete_user_response
+                iam.delete_user(name)
         else:
             iam.delete_login_profile(name)
-            del_meta = iam.delete_user(name).delete_user_response
+            iam.delete_user(name)
     except Exception as ex:
         module.fail_json(changed=False, msg="delete failed %s" %ex)
         if ('must detach all policies first') in error_msg:
             for policy in iam.get_all_user_policies(name).list_user_policies_result.policy_names:
                 iam.delete_user_policy(name, policy)
             try:
-                del_meta = iam.delete_user(name)
+                iam.delete_user(name)
             except boto.exception.BotoServerError as err:
                 error_msg = boto_exception(err)
                 if ('must detach all policies first') in error_msg:
@@ -278,10 +278,11 @@ def delete_user(module, iam, name):
                     module.fail_json(changed=changed, msg=str(error_msg))
             else:
                 changed = True
-                return del_meta, name, changed
+        else:
+            module.fail_json(changed=changed, msg=str(error_msg))
     else:
         changed = True
-        return del_meta, name, changed
+    return name, changed
 
 
 def update_user(module, iam, name, new_name, new_path, key_state, key_count, keys, pwd, updated):
@@ -455,22 +456,24 @@ def delete_group(module=None, iam=None, name=None):
         iam.delete_group(name)
     except boto.exception.BotoServerError as err:
         error_msg = boto_exception(err)
-        if ('must detach all policies first') in error_msg:
+        if ('must delete policies first') in error_msg:
             for policy in iam.get_all_group_policies(name).list_group_policies_result.policy_names:
                 iam.delete_group_policy(name, policy)
             try:
                 iam.delete_group(name)
             except boto.exception.BotoServerError as err:
                 error_msg = boto_exception(err)
-                if ('must detach all policies first') in error_msg:
+                if ('must delete policies first') in error_msg:
                     module.fail_json(changed=changed, msg="All inline polices have been removed. Though it appears"
                                                           "that %s has Managed Polices. This is not "
                                                           "currently supported by boto. Please detach the polices "
                                                           "through the console and try again." % name)
                 else:
-                    module.fail_json(changed=changed, msg=str(err))
+                    module.fail_json(changed=changed, msg=str(error_msg))
             else:
                 changed = True
+        else:
+            module.fail_json(changed=changed, msg=str(error_msg))
     else:
         changed = True
     return changed, name
@@ -736,7 +739,7 @@ def main():
             if user_exists:
                 try:
                     set_users_groups(module, iam, name, '')
-                    del_meta, name, changed = delete_user(module, iam, name)
+                    name, changed = delete_user(module, iam, name)
                     module.exit_json(deleted_user=name, changed=changed)
 
                 except Exception as ex:


Test patch: