#!/usr/bin/python
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = r'''
---
module: iam
version_added: 1.0.0
short_description: Manage IAM users, groups, roles and keys
description:
     - Allows for the management of IAM users, user API keys, groups, roles.
options:
  iam_type:
    description:
      - Type of IAM resource.
    choices: ["user", "group", "role"]
    type: str
    required: true
  name:
    description:
      - Name of IAM resource to create or identify.
    required: true
    type: str
  new_name:
    description:
      - When I(state=update), will replace I(name) with I(new_name) on IAM resource.
    type: str
  new_path:
    description:
      - When I(state=update), will replace the path with new_path on the IAM resource.
    type: str
  state:
    description:
      - Whether to create, delete or update the IAM resource. Note, roles cannot be updated.
    required: true
    choices: [ "present", "absent", "update" ]
    type: str
  path:
    description:
      - When creating or updating, specify the desired path of the resource.
      - If I(state=present), it will replace the current path to match what is passed in when they do not match.
    default: "/"
    type: str
  trust_policy:
    description:
      - The inline (JSON or YAML) trust policy document that grants an entity permission to assume the role.
      - Mutually exclusive with I(trust_policy_filepath).
    type: dict
  trust_policy_filepath:
    description:
      - The path to the trust policy document that grants an entity permission to assume the role.
      - Mutually exclusive with I(trust_policy).
    type: str
  access_key_state:
    description:
      - When type is user, it creates, removes, deactivates or activates a user's access key(s). Note that actions apply only to keys specified.
    choices: [ "create", "remove", "active", "inactive", "Create", "Remove", "Active", "Inactive"]
    type: str
  key_count:
    description:
      - When I(access_key_state=create) it will ensure this quantity of keys are present.
    default: 1
    type: int
  access_key_ids:
    description:
      - A list of the keys that you want affected by the I(access_key_state) parameter.
    type: list
    elements: str
  groups:
    description:
      - A list of groups the user should belong to. When I(state=update), will gracefully remove groups not listed.
    type: list
    elements: str
  password:
    description:
      - When I(type=user) and either I(state=present) or I(state=update), define the users login password.
      - Note that this will always return 'changed'.
    type: str
  update_password:
    default: always
    choices: ['always', 'on_create']
    description:
      - When to update user passwords.
      - I(update_password=always) will ensure the password is set to I(password).
      - I(update_password=on_create) will only set the password for newly created users.
    type: str
notes:
  - 'Currently boto does not support the removal of Managed Policies, the module will error out if your
    user/group/role has managed policies when you try to do state=absent. They will need to be removed manually.'
author:
    - "Jonathan I. Davila (@defionscode)"
    - "Paul Seiffert (@seiffert)"
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2

'''

EXAMPLES = r'''
# Basic user creation example
- name: Create two new IAM users with API keys
  community.aws.iam:
    iam_type: user
    name: "{{ item }}"
    state: present
    password: "{{ temp_pass }}"
    access_key_state: create
  loop:
    - jcleese
    - mpython

# Advanced example, create two new groups and add the pre-existing user
# jdavila to both groups.
- name: Create Two Groups, Mario and Luigi
  community.aws.iam:
    iam_type: group
    name: "{{ item }}"
    state: present
  loop:
     - Mario
     - Luigi
  register: new_groups

- name: Update user
  community.aws.iam:
    iam_type: user
    name: jdavila
    state: update
    groups: "{{ item.created_group.group_name }}"
  loop: "{{ new_groups.results }}"

# Example of role with custom trust policy for Lambda service
- name: Create IAM role with custom trust relationship
  community.aws.iam:
    iam_type: role
    name: AAALambdaTestRole
    state: present
    trust_policy:
      Version: '2012-10-17'
      Statement:
      - Action: sts:AssumeRole
        Effect: Allow
        Principal:
          Service: lambda.amazonaws.com

'''
RETURN = r'''
role_result:
    description: the IAM.role dict returned by Boto
    type: str
    returned: if iam_type=role and state=present
    sample: {
                "arn": "arn:aws:iam::A1B2C3D4E5F6:role/my-new-role",
                "assume_role_policy_document": "...truncated...",
                "create_date": "2017-09-02T14:32:23Z",
                "path": "/",
                "role_id": "AROAA1B2C3D4E5F6G7H8I",
                "role_name": "my-new-role"
            }
roles:
    description: a list containing the name of the currently defined roles
    type: list
    returned: if iam_type=role and state=present
    sample: [
        "my-new-role",
        "my-existing-role-1",
        "my-existing-role-2",
        "my-existing-role-3",
        "my-existing-role-...",
    ]
'''

import json
import traceback

try:
    import boto.exception
    import boto.iam
    import boto.iam.connection
except ImportError:
    pass  # Taken care of by ec2.HAS_BOTO

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto_exception
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import connect_to_aws
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import get_aws_connection_info


def _paginate(func, attr):
    '''
    paginates the results from func by continuously passing in
    the returned marker if the results were truncated. this returns
    an iterator over the items in the returned response. `attr` is
    the name of the attribute to iterate over in the response.
    '''
    finished, marker = False, None
    while not finished:
        res = func(marker=marker)
        for item in getattr(res, attr):
            yield item

        finished = res.is_truncated == 'false'
        if not finished:
            marker = res.marker


def list_all_groups(iam):
    return [item['group_name'] for item in _paginate(iam.get_all_groups, 'groups')]


def list_all_users(iam):
    return [item['user_name'] for item in _paginate(iam.get_all_users, 'users')]


def list_all_roles(iam):
    return [item['role_name'] for item in _paginate(iam.list_roles, 'roles')]


def list_all_instance_profiles(iam):
    return [item['instance_profile_name'] for item in _paginate(iam.list_instance_profiles, 'instance_profiles')]


def create_user(module, iam, name, pwd, path, key_state, key_count):
    key_qty = 0
    keys = []
    try:
        user_meta = iam.create_user(
            name, path).create_user_response.create_user_result.user
        changed = True
        if pwd is not None:
            pwd = iam.create_login_profile(name, pwd)
        if key_state in ['create']:
            if key_count:
                while key_count > key_qty:
                    keys.append(iam.create_access_key(
                        user_name=name).create_access_key_response.
                        create_access_key_result.
                        access_key)
                    key_qty += 1
        else:
            keys = None
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=False, msg=str(err))
    else:
        user_info = dict(created_user=user_meta, password=pwd, access_keys=keys)
        return (user_info, changed)


def delete_dependencies_first(module, iam, name):
    changed = False
    # try to delete any keys
    try:
        current_keys = [ck['access_key_id'] for ck in
                        iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
        for key in current_keys:
            iam.delete_access_key(key, name)
        changed = True
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg="Failed to delete keys: %s" % err, exception=traceback.format_exc())

    # try to delete login profiles
    try:
        login_profile = iam.get_login_profiles(name).get_login_profile_response
        iam.delete_login_profile(name)
        changed = True
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'Login Profile for User ' + name + ' cannot be found.' not in error_msg:
            module.fail_json(changed=changed, msg="Failed to delete login profile: %s" % err, exception=traceback.format_exc())

    # try to detach policies
    try:
        for policy in iam.get_all_user_policies(name).list_user_policies_result.policy_names:
            iam.delete_user_policy(name, policy)
        changed = True
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'must detach all policies first' in error_msg:
            module.fail_json(changed=changed, msg="All inline policies have been removed. Though it appears"
                                                  "that %s has Managed Polices. This is not "
                                                  "currently supported by boto. Please detach the policies "
                                                  "through the console and try again." % name)
        module.fail_json(changed=changed, msg="Failed to delete policies: %s" % err, exception=traceback.format_exc())

    # try to deactivate associated MFA devices
    try:
        mfa_devices = iam.get_all_mfa_devices(name).get('list_mfa_devices_response', {}).get('list_mfa_devices_result', {}).get('mfa_devices', [])
        for device in mfa_devices:
            iam.deactivate_mfa_device(name, device['serial_number'])
        changed = True
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg="Failed to deactivate associated MFA devices: %s" % err, exception=traceback.format_exc())

    return changed


def delete_user(module, iam, name):
    changed = delete_dependencies_first(module, iam, name)
    try:
        iam.delete_user(name)
    except boto.exception.BotoServerError as ex:
        module.fail_json(changed=changed, msg="Failed to delete user %s: %s" % (name, ex), exception=traceback.format_exc())
    else:
        changed = True
    return name, changed


def update_user(module, iam, name, new_name, new_path, key_state, key_count, keys, pwd, updated):
    changed = False
    name_change = False
    if updated and new_name:
        name = new_name
    try:
        current_keys = [ck['access_key_id'] for ck in
                        iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
        status = [ck['status'] for ck in
                  iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
        key_qty = len(current_keys)
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'cannot be found' in error_msg and updated:
            current_keys = [ck['access_key_id'] for ck in
                            iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
            status = [ck['status'] for ck in
                      iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
            name = new_name
        else:
            module.fail_json(changed=False, msg=str(err))

    updated_key_list = {}

    if new_name or new_path:
        c_path = iam.get_user(name).get_user_result.user['path']
        if (name != new_name) or (c_path != new_path):
            changed = True
            try:
                if not updated:
                    user = iam.update_user(
                        name, new_user_name=new_name, new_path=new_path).update_user_response.response_metadata
                else:
                    user = iam.update_user(
                        name, new_path=new_path).update_user_response.response_metadata
                user['updates'] = dict(
                    old_username=name, new_username=new_name, old_path=c_path, new_path=new_path)
            except boto.exception.BotoServerError as err:
                error_msg = boto_exception(err)
                module.fail_json(changed=False, msg=str(err))
            else:
                if not updated:
                    name_change = True

    if pwd:
        try:
            iam.update_login_profile(name, pwd)
            changed = True
        except boto.exception.BotoServerError:
            try:
                iam.create_login_profile(name, pwd)
                changed = True
            except boto.exception.BotoServerError as err:
                error_msg = boto_exception(str(err))
                if 'Password does not conform to the account password policy' in error_msg:
                    module.fail_json(changed=False, msg="Password doesn't conform to policy")
                else:
                    module.fail_json(msg=error_msg)

    try:
        current_keys = [ck['access_key_id'] for ck in
                        iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
        status = [ck['status'] for ck in
                  iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
        key_qty = len(current_keys)
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        if 'cannot be found' in error_msg and updated:
            current_keys = [ck['access_key_id'] for ck in
                            iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
            status = [ck['status'] for ck in
                      iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
            name = new_name
        else:
            module.fail_json(changed=False, msg=str(err))

    new_keys = []
    if key_state == 'create':
        try:
            while key_count > key_qty:
                new_keys.append(iam.create_access_key(
                    user_name=name).create_access_key_response.create_access_key_result.access_key)
                key_qty += 1
                changed = True

        except boto.exception.BotoServerError as err:
            module.fail_json(changed=False, msg=str(err))

    if keys and key_state:
        for access_key in keys:
            if key_state in ('active', 'inactive'):
                if access_key in current_keys:
                    for current_key, current_key_state in zip(current_keys, status):
                        if key_state != current_key_state.lower():
                            try:
                                iam.update_access_key(access_key, key_state.capitalize(), user_name=name)
                                changed = True
                            except boto.exception.BotoServerError as err:
                                module.fail_json(changed=False, msg=str(err))
                else:
                    module.fail_json(msg="Supplied keys not found for %s. "
                                         "Current keys: %s. "
                                         "Supplied key(s): %s" %
                                         (name, current_keys, keys)
                                     )

            if key_state == 'remove':
                if access_key in current_keys:
                    try:
                        iam.delete_access_key(access_key, user_name=name)
                    except boto.exception.BotoServerError as err:
                        module.fail_json(changed=False, msg=str(err))
                    else:
                        changed = True

    try:
        final_keys, final_key_status = \
            [ck['access_key_id'] for ck in
             iam.get_all_access_keys(name).
             list_access_keys_result.
             access_key_metadata],\
            [ck['status'] for ck in
                iam.get_all_access_keys(name).
                list_access_keys_result.
                access_key_metadata]
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))

    for fk, fks in zip(final_keys, final_key_status):
        updated_key_list.update({fk: fks})

    return name_change, updated_key_list, changed, new_keys


def set_users_groups(module, iam, name, groups, updated=None,
                     new_name=None):
    """ Sets groups for a user, will purge groups not explicitly passed, while
        retaining pre-existing groups that also are in the new list.
    """
    changed = False

    if updated:
        name = new_name

    try:
        orig_users_groups = [og['group_name'] for og in iam.get_groups_for_user(
            name).list_groups_for_user_result.groups]
        remove_groups = [
            rg for rg in frozenset(orig_users_groups).difference(groups)]
        new_groups = [
            ng for ng in frozenset(groups).difference(orig_users_groups)]
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))
    else:
        if len(orig_users_groups) > 0:
            for new in new_groups:
                iam.add_user_to_group(new, name)
            for rm in remove_groups:
                iam.remove_user_from_group(rm, name)
        else:
            for group in groups:
                try:
                    iam.add_user_to_group(group, name)
                except boto.exception.BotoServerError as err:
                    error_msg = boto_exception(err)
                    if ('The group with name %s cannot be found.' % group) in error_msg:
                        module.fail_json(changed=False, msg="Group %s doesn't exist" % group)

    if len(remove_groups) > 0 or len(new_groups) > 0:
        changed = True

    return (groups, changed)


def create_group(module=None, iam=None, name=None, path=None):
    changed = False
    try:
        iam.create_group(
            name, path).create_group_response.create_group_result.group
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))
    else:
        changed = True
    return name, changed


def delete_group(module=None, iam=None, name=None):
    changed = False
    try:
        iam.delete_group(name)
    except boto.exception.BotoServerError as err:
        error_msg = boto_exception(err)
        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 delete policies first') in error_msg:
                    module.fail_json(changed=changed, msg="All inline policies have been removed. Though it appears"
                                                          "that %s has Managed Polices. This is not "
                                                          "currently supported by boto. Please detach the policies "
                                                          "through the console and try again." % name)
                else:
                    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


def update_group(module=None, iam=None, name=None, new_name=None, new_path=None):
    changed = False
    try:
        current_group_path = iam.get_group(
            name).get_group_response.get_group_result.group['path']
        if new_path:
            if current_group_path != new_path:
                iam.update_group(name, new_path=new_path)
                changed = True
        if new_name:
            if name != new_name:
                iam.update_group(name, new_group_name=new_name, new_path=new_path)
                changed = True
                name = new_name
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))

    return changed, name, new_path, current_group_path


def create_role(module, iam, name, path, role_list, prof_list, trust_policy_doc):
    changed = False
    iam_role_result = None
    instance_profile_result = None
    try:
        if name not in role_list:
            changed = True
            iam_role_result = iam.create_role(name,
                                              assume_role_policy_document=trust_policy_doc,
                                              path=path).create_role_response.create_role_result.role

            if name not in prof_list:
                instance_profile_result = iam.create_instance_profile(name, path=path) \
                    .create_instance_profile_response.create_instance_profile_result.instance_profile
                iam.add_role_to_instance_profile(name, name)
        else:
            instance_profile_result = iam.get_instance_profile(name).get_instance_profile_response.get_instance_profile_result.instance_profile
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))
    else:
        updated_role_list = list_all_roles(iam)
        iam_role_result = iam.get_role(name).get_role_response.get_role_result.role
    return changed, updated_role_list, iam_role_result, instance_profile_result


def delete_role(module, iam, name, role_list, prof_list):
    changed = False
    iam_role_result = None
    instance_profile_result = None
    try:
        if name in role_list:
            cur_ins_prof = [rp['instance_profile_name'] for rp in
                            iam.list_instance_profiles_for_role(name).
                            list_instance_profiles_for_role_result.
                            instance_profiles]
            for profile in cur_ins_prof:
                iam.remove_role_from_instance_profile(profile, name)
            try:
                iam.delete_role(name)
            except boto.exception.BotoServerError as err:
                error_msg = boto_exception(err)
                if ('must detach all policies first') in error_msg:
                    for policy in iam.list_role_policies(name).list_role_policies_result.policy_names:
                        iam.delete_role_policy(name, policy)
                try:
                    iam_role_result = iam.delete_role(name)
                except boto.exception.BotoServerError as err:
                    error_msg = boto_exception(err)
                    if ('must detach all policies first') in error_msg:
                        module.fail_json(changed=changed, msg="All inline policies have been removed. Though it appears"
                                                              "that %s has Managed Polices. This is not "
                                                              "currently supported by boto. Please detach the policies "
                                                              "through the console and try again." % name)
                    else:
                        module.fail_json(changed=changed, msg=str(err))
                else:
                    changed = True

            else:
                changed = True

        for prof in prof_list:
            if name == prof:
                instance_profile_result = iam.delete_instance_profile(name)
    except boto.exception.BotoServerError as err:
        module.fail_json(changed=changed, msg=str(err))
    else:
        updated_role_list = list_all_roles(iam)
    return changed, updated_role_list, iam_role_result, instance_profile_result


def main():
    argument_spec = dict(
        iam_type=dict(required=True, choices=['user', 'group', 'role']),
        groups=dict(type='list', default=None, required=False, elements='str'),
        state=dict(required=True, choices=['present', 'absent', 'update']),
        password=dict(default=None, required=False, no_log=True),
        # setting no_log=False on update_password avoids a false positive warning about not setting no_log
        update_password=dict(default='always', required=False, choices=['always', 'on_create'], no_log=False),
        access_key_state=dict(default=None, required=False, choices=[
            'active', 'inactive', 'create', 'remove',
            'Active', 'Inactive', 'Create', 'Remove']),
        access_key_ids=dict(type='list', default=None, required=False, elements='str', no_log=False),
        key_count=dict(type='int', default=1, required=False),
        name=dict(required=True),
        trust_policy_filepath=dict(default=None, required=False),
        trust_policy=dict(type='dict', default=None, required=False),
        new_name=dict(default=None, required=False),
        path=dict(default='/', required=False),
        new_path=dict(default=None, required=False),
    )

    module = AnsibleAWSModule(
        argument_spec=argument_spec,
        mutually_exclusive=[['trust_policy', 'trust_policy_filepath']],
        check_boto3=False,
    )

    if not HAS_BOTO:
        module.fail_json(msg='This module requires boto, please install it')

    state = module.params.get('state').lower()
    iam_type = module.params.get('iam_type').lower()
    groups = module.params.get('groups')
    name = module.params.get('name')
    new_name = module.params.get('new_name')
    password = module.params.get('password')
    update_pw = module.params.get('update_password')
    path = module.params.get('path')
    new_path = module.params.get('new_path')
    key_count = module.params.get('key_count')
    key_state = module.params.get('access_key_state')
    trust_policy = module.params.get('trust_policy')
    trust_policy_filepath = module.params.get('trust_policy_filepath')
    key_ids = module.params.get('access_key_ids')

    if key_state:
        key_state = key_state.lower()
        if any([n in key_state for n in ['active', 'inactive']]) and not key_ids:
            module.fail_json(changed=False, msg="At least one access key has to be defined in order"
                                                " to use 'active' or 'inactive'")

    if iam_type == 'user' and module.params.get('password') is not None:
        pwd = module.params.get('password')
    elif iam_type != 'user' and module.params.get('password') is not None:
        module.fail_json(msg="a password is being specified when the iam_type "
                             "is not user. Check parameters")
    else:
        pwd = None

    if iam_type != 'user' and (module.params.get('access_key_state') is not None or
                               module.params.get('access_key_id') is not None):
        module.fail_json(msg="the IAM type must be user, when IAM access keys "
                             "are being modified. Check parameters")

    if iam_type == 'role' and state == 'update':
        module.fail_json(changed=False, msg="iam_type: role, cannot currently be updated, "
                         "please specify present or absent")

    # check if trust_policy is present -- it can be inline JSON or a file path to a JSON file
    if trust_policy_filepath:
        try:
            with open(trust_policy_filepath, 'r') as json_data:
                trust_policy_doc = json.dumps(json.load(json_data))
        except Exception as e:
            module.fail_json(msg=str(e) + ': ' + trust_policy_filepath)
    elif trust_policy:
        try:
            trust_policy_doc = json.dumps(trust_policy)
        except Exception as e:
            module.fail_json(msg=str(e) + ': ' + trust_policy)
    else:
        trust_policy_doc = None

    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)

    try:
        if region:
            iam = connect_to_aws(boto.iam, region, **aws_connect_kwargs)
        else:
            iam = boto.iam.connection.IAMConnection(**aws_connect_kwargs)
    except boto.exception.NoAuthHandlerFound as e:
        module.fail_json(msg=str(e))

    result = {}
    changed = False

    try:
        orig_group_list = list_all_groups(iam)

        orig_user_list = list_all_users(iam)

        orig_role_list = list_all_roles(iam)

        orig_prof_list = list_all_instance_profiles(iam)
    except boto.exception.BotoServerError as err:
        module.fail_json(msg=err.message)

    if iam_type == 'user':
        been_updated = False
        user_groups = None
        user_exists = any([n in [name, new_name] for n in orig_user_list])
        if user_exists:
            current_path = iam.get_user(name).get_user_result.user['path']
            if not new_path and current_path != path:
                new_path = path
                path = current_path

        if state == 'present' and not user_exists and not new_name:
            (meta, changed) = create_user(
                module, iam, name, password, path, key_state, key_count)
            keys = iam.get_all_access_keys(name).list_access_keys_result.\
                access_key_metadata
            if groups:
                (user_groups, changed) = set_users_groups(
                    module, iam, name, groups, been_updated, new_name)
            module.exit_json(
                user_meta=meta, groups=user_groups, user_name=meta['created_user']['user_name'], keys=keys, changed=changed)

        elif state in ['present', 'update'] and user_exists:
            if update_pw == 'on_create':
                password = None
            if name not in orig_user_list and new_name in orig_user_list:
                been_updated = True
            name_change, key_list, user_changed, new_key = update_user(
                module, iam, name, new_name, new_path, key_state, key_count, key_ids, password, been_updated)
            if new_key:
                user_meta = {'access_keys': list(new_key)}
                user_meta['access_keys'].extend(
                    [{'access_key_id': key, 'status': value} for key, value in key_list.items() if
                     key not in [it['access_key_id'] for it in new_key]])
            else:
                user_meta = {
                    'access_keys': [{'access_key_id': key, 'status': value} for key, value in key_list.items()]}

            if name_change and new_name:
                orig_name = name
                name = new_name
            if isinstance(groups, list):
                user_groups, groups_changed = set_users_groups(
                    module, iam, name, groups, been_updated, new_name)
                if groups_changed == user_changed:
                    changed = groups_changed
                else:
                    changed = True
            else:
                changed = user_changed
            if new_name and new_path:
                module.exit_json(changed=changed, groups=user_groups, old_user_name=orig_name,
                                 new_user_name=new_name, old_path=path, new_path=new_path, keys=key_list,
                                 created_keys=new_key, user_meta=user_meta)
            elif new_name and not new_path and not been_updated:
                module.exit_json(
                    changed=changed, groups=user_groups, old_user_name=orig_name, user_name=new_name, new_user_name=new_name, keys=key_list,
                    created_keys=new_key, user_meta=user_meta)
            elif new_name and not new_path and been_updated:
                module.exit_json(
                    changed=changed, groups=user_groups, user_name=new_name, keys=key_list, key_state=key_state,
                    created_keys=new_key, user_meta=user_meta)
            elif not new_name and new_path:
                module.exit_json(
                    changed=changed, groups=user_groups, user_name=name, old_path=path, new_path=new_path,
                    keys=key_list, created_keys=new_key, user_meta=user_meta)
            else:
                module.exit_json(
                    changed=changed, groups=user_groups, user_name=name, keys=key_list, created_keys=new_key,
                    user_meta=user_meta)

        elif state == 'update' and not user_exists:
            module.fail_json(
                msg="The user %s does not exist. No update made." % name)

        elif state == 'absent':
            if user_exists:
                try:
                    set_users_groups(module, iam, name, '')
                    name, changed = delete_user(module, iam, name)
                    module.exit_json(deleted_user=name, user_name=name, changed=changed)

                except Exception as ex:
                    module.fail_json(changed=changed, msg=str(ex))
            else:
                module.exit_json(
                    changed=False, msg="User %s is already absent from your AWS IAM users" % name)

    elif iam_type == 'group':
        group_exists = name in orig_group_list

        if state == 'present' and not group_exists:
            new_group, changed = create_group(module=module, iam=iam, name=name, path=path)
            module.exit_json(changed=changed, group_name=new_group)
        elif state in ['present', 'update'] and group_exists:
            changed, updated_name, updated_path, cur_path = update_group(
                module=module, iam=iam, name=name, new_name=new_name,
                new_path=new_path)

            if new_path and new_name:
                module.exit_json(changed=changed, old_group_name=name,
                                 new_group_name=updated_name, old_path=cur_path,
                                 new_group_path=updated_path)

            if new_path and not new_name:
                module.exit_json(changed=changed, group_name=name,
                                 old_path=cur_path,
                                 new_group_path=updated_path)

            if not new_path and new_name:
                module.exit_json(changed=changed, old_group_name=name,
                                 new_group_name=updated_name, group_path=cur_path)

            if not new_path and not new_name:
                module.exit_json(
                    changed=changed, group_name=name, group_path=cur_path)

        elif state == 'update' and not group_exists:
            module.fail_json(
                changed=changed, msg="Update Failed. Group %s doesn't seem to exist!" % name)

        elif state == 'absent':
            if name in orig_group_list:
                removed_group, changed = delete_group(module=module, iam=iam, name=name)
                module.exit_json(changed=changed, delete_group=removed_group)
            else:
                module.exit_json(changed=changed, msg="Group already absent")

    elif iam_type == 'role':
        role_list = []
        if state == 'present':
            changed, role_list, role_result, instance_profile_result = create_role(
                module, iam, name, path, orig_role_list, orig_prof_list, trust_policy_doc)
        elif state == 'absent':
            changed, role_list, role_result, instance_profile_result = delete_role(
                module, iam, name, orig_role_list, orig_prof_list)
        elif state == 'update':
            module.fail_json(
                changed=False, msg='Role update not currently supported by boto.')
        module.exit_json(changed=changed, roles=role_list, role_result=role_result,
                         instance_profile_result=instance_profile_result)


if __name__ == '__main__':
    main()
