1#!/usr/bin/python
2# Copyright: Ansible Project
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5from __future__ import absolute_import, division, print_function
6__metaclass__ = type
7
8
9ANSIBLE_METADATA = {'metadata_version': '1.1',
10                    'status': ['stableinterface'],
11                    'supported_by': 'community'}
12
13
14DOCUMENTATION = '''
15---
16module: iam
17short_description: Manage IAM users, groups, roles and keys
18description:
19     - Allows for the management of IAM users, user API keys, groups, roles.
20version_added: "2.0"
21options:
22  iam_type:
23    description:
24      - Type of IAM resource
25    choices: ["user", "group", "role"]
26    type: str
27  name:
28    description:
29      - Name of IAM resource to create or identify
30    required: true
31    type: str
32  new_name:
33    description:
34      - When state is update, will replace name with new_name on IAM resource
35    type: str
36  new_path:
37    description:
38      - When state is update, will replace the path with new_path on the IAM resource
39    type: str
40  state:
41    description:
42      - Whether to create, delete or update the IAM resource. Note, roles cannot be updated.
43    required: true
44    choices: [ "present", "absent", "update" ]
45    type: str
46  path:
47    description:
48      - When creating or updating, specify the desired path of the resource. If state is present,
49        it will replace the current path to match what is passed in when they do not match.
50    default: "/"
51    type: str
52  trust_policy:
53    description:
54      - The inline (JSON or YAML) trust policy document that grants an entity permission to assume the role. Mutually exclusive with C(trust_policy_filepath).
55    version_added: "2.2"
56    type: dict
57  trust_policy_filepath:
58    description:
59      - The path to the trust policy document that grants an entity permission to assume the role. Mutually exclusive with C(trust_policy).
60    version_added: "2.2"
61    type: str
62  access_key_state:
63    description:
64      - When type is user, it creates, removes, deactivates or activates a user's access key(s). Note that actions apply only to keys specified.
65    choices: [ "create", "remove", "active", "inactive", "Create", "Remove", "Active", "Inactive"]
66    type: str
67  key_count:
68    description:
69      - When access_key_state is create it will ensure this quantity of keys are present. Defaults to 1.
70    default: 1
71    type: int
72  access_key_ids:
73    description:
74      - A list of the keys that you want impacted by the access_key_state parameter.
75    type: list
76  groups:
77    description:
78      - A list of groups the user should belong to. When update, will gracefully remove groups not listed.
79    type: list
80  password:
81    description:
82      - When type is user and state is present, define the users login password. Also works with update. Note that always returns changed.
83    type: str
84  update_password:
85    default: always
86    choices: ['always', 'on_create']
87    description:
88     - C(always) will update passwords if they differ.  C(on_create) will only set the password for newly created users.
89    type: str
90notes:
91  - 'Currently boto does not support the removal of Managed Policies, the module will error out if your
92    user/group/role has managed policies when you try to do state=absent. They will need to be removed manually.'
93author:
94    - "Jonathan I. Davila (@defionscode)"
95    - "Paul Seiffert (@seiffert)"
96extends_documentation_fragment:
97  - aws
98  - ec2
99'''
100
101EXAMPLES = '''
102# Basic user creation example
103tasks:
104- name: Create two new IAM users with API keys
105  iam:
106    iam_type: user
107    name: "{{ item }}"
108    state: present
109    password: "{{ temp_pass }}"
110    access_key_state: create
111  loop:
112    - jcleese
113    - mpython
114
115# Advanced example, create two new groups and add the pre-existing user
116# jdavila to both groups.
117task:
118- name: Create Two Groups, Mario and Luigi
119  iam:
120    iam_type: group
121    name: "{{ item }}"
122    state: present
123  loop:
124     - Mario
125     - Luigi
126  register: new_groups
127
128- name:
129  iam:
130    iam_type: user
131    name: jdavila
132    state: update
133    groups: "{{ item.created_group.group_name }}"
134  loop: "{{ new_groups.results }}"
135
136# Example of role with custom trust policy for Lambda service
137- name: Create IAM role with custom trust relationship
138  iam:
139    iam_type: role
140    name: AAALambdaTestRole
141    state: present
142    trust_policy:
143      Version: '2012-10-17'
144      Statement:
145      - Action: sts:AssumeRole
146        Effect: Allow
147        Principal:
148          Service: lambda.amazonaws.com
149
150'''
151RETURN = '''
152role_result:
153    description: the IAM.role dict returned by Boto
154    type: str
155    returned: if iam_type=role and state=present
156    sample: {
157                "arn": "arn:aws:iam::A1B2C3D4E5F6:role/my-new-role",
158                "assume_role_policy_document": "...truncated...",
159                "create_date": "2017-09-02T14:32:23Z",
160                "path": "/",
161                "role_id": "AROAA1B2C3D4E5F6G7H8I",
162                "role_name": "my-new-role"
163            }
164roles:
165    description: a list containing the name of the currently defined roles
166    type: list
167    returned: if iam_type=role and state=present
168    sample: [
169        "my-new-role",
170        "my-existing-role-1",
171        "my-existing-role-2",
172        "my-existing-role-3",
173        "my-existing-role-...",
174    ]
175'''
176
177import json
178import traceback
179
180try:
181    import boto.exception
182    import boto.iam
183    import boto.iam.connection
184except ImportError:
185    pass  # Taken care of by ec2.HAS_BOTO
186
187from ansible.module_utils.basic import AnsibleModule
188from ansible.module_utils.ec2 import (HAS_BOTO, boto_exception, connect_to_aws, ec2_argument_spec,
189                                      get_aws_connection_info)
190
191
192def _paginate(func, attr):
193    '''
194    paginates the results from func by continuously passing in
195    the returned marker if the results were truncated. this returns
196    an iterator over the items in the returned response. `attr` is
197    the name of the attribute to iterate over in the response.
198    '''
199    finished, marker = False, None
200    while not finished:
201        res = func(marker=marker)
202        for item in getattr(res, attr):
203            yield item
204
205        finished = res.is_truncated == 'false'
206        if not finished:
207            marker = res.marker
208
209
210def list_all_groups(iam):
211    return [item['group_name'] for item in _paginate(iam.get_all_groups, 'groups')]
212
213
214def list_all_users(iam):
215    return [item['user_name'] for item in _paginate(iam.get_all_users, 'users')]
216
217
218def list_all_roles(iam):
219    return [item['role_name'] for item in _paginate(iam.list_roles, 'roles')]
220
221
222def list_all_instance_profiles(iam):
223    return [item['instance_profile_name'] for item in _paginate(iam.list_instance_profiles, 'instance_profiles')]
224
225
226def create_user(module, iam, name, pwd, path, key_state, key_count):
227    key_qty = 0
228    keys = []
229    try:
230        user_meta = iam.create_user(
231            name, path).create_user_response.create_user_result.user
232        changed = True
233        if pwd is not None:
234            pwd = iam.create_login_profile(name, pwd)
235        if key_state in ['create']:
236            if key_count:
237                while key_count > key_qty:
238                    keys.append(iam.create_access_key(
239                        user_name=name).create_access_key_response.
240                        create_access_key_result.
241                        access_key)
242                    key_qty += 1
243        else:
244            keys = None
245    except boto.exception.BotoServerError as err:
246        module.fail_json(changed=False, msg=str(err))
247    else:
248        user_info = dict(created_user=user_meta, password=pwd, access_keys=keys)
249        return (user_info, changed)
250
251
252def delete_dependencies_first(module, iam, name):
253    changed = False
254    # try to delete any keys
255    try:
256        current_keys = [ck['access_key_id'] for ck in
257                        iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
258        for key in current_keys:
259            iam.delete_access_key(key, name)
260        changed = True
261    except boto.exception.BotoServerError as err:
262        module.fail_json(changed=changed, msg="Failed to delete keys: %s" % err, exception=traceback.format_exc())
263
264    # try to delete login profiles
265    try:
266        login_profile = iam.get_login_profiles(name).get_login_profile_response
267        iam.delete_login_profile(name)
268        changed = True
269    except boto.exception.BotoServerError as err:
270        error_msg = boto_exception(err)
271        if 'Login Profile for User ' + name + ' cannot be found.' not in error_msg:
272            module.fail_json(changed=changed, msg="Failed to delete login profile: %s" % err, exception=traceback.format_exc())
273
274    # try to detach policies
275    try:
276        for policy in iam.get_all_user_policies(name).list_user_policies_result.policy_names:
277            iam.delete_user_policy(name, policy)
278        changed = True
279    except boto.exception.BotoServerError as err:
280        error_msg = boto_exception(err)
281        if 'must detach all policies first' in error_msg:
282            module.fail_json(changed=changed, msg="All inline policies have been removed. Though it appears"
283                                                  "that %s has Managed Polices. This is not "
284                                                  "currently supported by boto. Please detach the policies "
285                                                  "through the console and try again." % name)
286        module.fail_json(changed=changed, msg="Failed to delete policies: %s" % err, exception=traceback.format_exc())
287
288    # try to deactivate associated MFA devices
289    try:
290        mfa_devices = iam.get_all_mfa_devices(name).get('list_mfa_devices_response', {}).get('list_mfa_devices_result', {}).get('mfa_devices', [])
291        for device in mfa_devices:
292            iam.deactivate_mfa_device(name, device['serial_number'])
293        changed = True
294    except boto.exception.BotoServerError as err:
295        module.fail_json(changed=changed, msg="Failed to deactivate associated MFA devices: %s" % err, exception=traceback.format_exc())
296
297    return changed
298
299
300def delete_user(module, iam, name):
301    changed = delete_dependencies_first(module, iam, name)
302    try:
303        iam.delete_user(name)
304    except boto.exception.BotoServerError as ex:
305        module.fail_json(changed=changed, msg="Failed to delete user %s: %s" % (name, ex), exception=traceback.format_exc())
306    else:
307        changed = True
308    return name, changed
309
310
311def update_user(module, iam, name, new_name, new_path, key_state, key_count, keys, pwd, updated):
312    changed = False
313    name_change = False
314    if updated and new_name:
315        name = new_name
316    try:
317        current_keys = [ck['access_key_id'] for ck in
318                        iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
319        status = [ck['status'] for ck in
320                  iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
321        key_qty = len(current_keys)
322    except boto.exception.BotoServerError as err:
323        error_msg = boto_exception(err)
324        if 'cannot be found' in error_msg and updated:
325            current_keys = [ck['access_key_id'] for ck in
326                            iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
327            status = [ck['status'] for ck in
328                      iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
329            name = new_name
330        else:
331            module.fail_json(changed=False, msg=str(err))
332
333    updated_key_list = {}
334
335    if new_name or new_path:
336        c_path = iam.get_user(name).get_user_result.user['path']
337        if (name != new_name) or (c_path != new_path):
338            changed = True
339            try:
340                if not updated:
341                    user = iam.update_user(
342                        name, new_user_name=new_name, new_path=new_path).update_user_response.response_metadata
343                else:
344                    user = iam.update_user(
345                        name, new_path=new_path).update_user_response.response_metadata
346                user['updates'] = dict(
347                    old_username=name, new_username=new_name, old_path=c_path, new_path=new_path)
348            except boto.exception.BotoServerError as err:
349                error_msg = boto_exception(err)
350                module.fail_json(changed=False, msg=str(err))
351            else:
352                if not updated:
353                    name_change = True
354
355    if pwd:
356        try:
357            iam.update_login_profile(name, pwd)
358            changed = True
359        except boto.exception.BotoServerError:
360            try:
361                iam.create_login_profile(name, pwd)
362                changed = True
363            except boto.exception.BotoServerError as err:
364                error_msg = boto_exception(str(err))
365                if 'Password does not conform to the account password policy' in error_msg:
366                    module.fail_json(changed=False, msg="Password doesn't conform to policy")
367                else:
368                    module.fail_json(msg=error_msg)
369
370    try:
371        current_keys = [ck['access_key_id'] for ck in
372                        iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
373        status = [ck['status'] for ck in
374                  iam.get_all_access_keys(name).list_access_keys_result.access_key_metadata]
375        key_qty = len(current_keys)
376    except boto.exception.BotoServerError as err:
377        error_msg = boto_exception(err)
378        if 'cannot be found' in error_msg and updated:
379            current_keys = [ck['access_key_id'] for ck in
380                            iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
381            status = [ck['status'] for ck in
382                      iam.get_all_access_keys(new_name).list_access_keys_result.access_key_metadata]
383            name = new_name
384        else:
385            module.fail_json(changed=False, msg=str(err))
386
387    new_keys = []
388    if key_state == 'create':
389        try:
390            while key_count > key_qty:
391                new_keys.append(iam.create_access_key(
392                    user_name=name).create_access_key_response.create_access_key_result.access_key)
393                key_qty += 1
394                changed = True
395
396        except boto.exception.BotoServerError as err:
397            module.fail_json(changed=False, msg=str(err))
398
399    if keys and key_state:
400        for access_key in keys:
401            if key_state in ('active', 'inactive'):
402                if access_key in current_keys:
403                    for current_key, current_key_state in zip(current_keys, status):
404                        if key_state != current_key_state.lower():
405                            try:
406                                iam.update_access_key(access_key, key_state.capitalize(), user_name=name)
407                                changed = True
408                            except boto.exception.BotoServerError as err:
409                                module.fail_json(changed=False, msg=str(err))
410                else:
411                    module.fail_json(msg="Supplied keys not found for %s. "
412                                         "Current keys: %s. "
413                                         "Supplied key(s): %s" %
414                                         (name, current_keys, keys)
415                                     )
416
417            if key_state == 'remove':
418                if access_key in current_keys:
419                    try:
420                        iam.delete_access_key(access_key, user_name=name)
421                    except boto.exception.BotoServerError as err:
422                        module.fail_json(changed=False, msg=str(err))
423                    else:
424                        changed = True
425
426    try:
427        final_keys, final_key_status = \
428            [ck['access_key_id'] for ck in
429             iam.get_all_access_keys(name).
430             list_access_keys_result.
431             access_key_metadata],\
432            [ck['status'] for ck in
433                iam.get_all_access_keys(name).
434                list_access_keys_result.
435                access_key_metadata]
436    except boto.exception.BotoServerError as err:
437        module.fail_json(changed=changed, msg=str(err))
438
439    for fk, fks in zip(final_keys, final_key_status):
440        updated_key_list.update({fk: fks})
441
442    return name_change, updated_key_list, changed, new_keys
443
444
445def set_users_groups(module, iam, name, groups, updated=None,
446                     new_name=None):
447    """ Sets groups for a user, will purge groups not explicitly passed, while
448        retaining pre-existing groups that also are in the new list.
449    """
450    changed = False
451
452    if updated:
453        name = new_name
454
455    try:
456        orig_users_groups = [og['group_name'] for og in iam.get_groups_for_user(
457            name).list_groups_for_user_result.groups]
458        remove_groups = [
459            rg for rg in frozenset(orig_users_groups).difference(groups)]
460        new_groups = [
461            ng for ng in frozenset(groups).difference(orig_users_groups)]
462    except boto.exception.BotoServerError as err:
463        module.fail_json(changed=changed, msg=str(err))
464    else:
465        if len(orig_users_groups) > 0:
466            for new in new_groups:
467                iam.add_user_to_group(new, name)
468            for rm in remove_groups:
469                iam.remove_user_from_group(rm, name)
470        else:
471            for group in groups:
472                try:
473                    iam.add_user_to_group(group, name)
474                except boto.exception.BotoServerError as err:
475                    error_msg = boto_exception(err)
476                    if ('The group with name %s cannot be found.' % group) in error_msg:
477                        module.fail_json(changed=False, msg="Group %s doesn't exist" % group)
478
479    if len(remove_groups) > 0 or len(new_groups) > 0:
480        changed = True
481
482    return (groups, changed)
483
484
485def create_group(module=None, iam=None, name=None, path=None):
486    changed = False
487    try:
488        iam.create_group(
489            name, path).create_group_response.create_group_result.group
490    except boto.exception.BotoServerError as err:
491        module.fail_json(changed=changed, msg=str(err))
492    else:
493        changed = True
494    return name, changed
495
496
497def delete_group(module=None, iam=None, name=None):
498    changed = False
499    try:
500        iam.delete_group(name)
501    except boto.exception.BotoServerError as err:
502        error_msg = boto_exception(err)
503        if ('must delete policies first') in error_msg:
504            for policy in iam.get_all_group_policies(name).list_group_policies_result.policy_names:
505                iam.delete_group_policy(name, policy)
506            try:
507                iam.delete_group(name)
508            except boto.exception.BotoServerError as err:
509                error_msg = boto_exception(err)
510                if ('must delete policies first') in error_msg:
511                    module.fail_json(changed=changed, msg="All inline policies have been removed. Though it appears"
512                                                          "that %s has Managed Polices. This is not "
513                                                          "currently supported by boto. Please detach the policies "
514                                                          "through the console and try again." % name)
515                else:
516                    module.fail_json(changed=changed, msg=str(error_msg))
517            else:
518                changed = True
519        else:
520            module.fail_json(changed=changed, msg=str(error_msg))
521    else:
522        changed = True
523    return changed, name
524
525
526def update_group(module=None, iam=None, name=None, new_name=None, new_path=None):
527    changed = False
528    try:
529        current_group_path = iam.get_group(
530            name).get_group_response.get_group_result.group['path']
531        if new_path:
532            if current_group_path != new_path:
533                iam.update_group(name, new_path=new_path)
534                changed = True
535        if new_name:
536            if name != new_name:
537                iam.update_group(name, new_group_name=new_name, new_path=new_path)
538                changed = True
539                name = new_name
540    except boto.exception.BotoServerError as err:
541        module.fail_json(changed=changed, msg=str(err))
542
543    return changed, name, new_path, current_group_path
544
545
546def create_role(module, iam, name, path, role_list, prof_list, trust_policy_doc):
547    changed = False
548    iam_role_result = None
549    instance_profile_result = None
550    try:
551        if name not in role_list:
552            changed = True
553            iam_role_result = iam.create_role(name,
554                                              assume_role_policy_document=trust_policy_doc,
555                                              path=path).create_role_response.create_role_result.role
556
557            if name not in prof_list:
558                instance_profile_result = iam.create_instance_profile(name, path=path) \
559                    .create_instance_profile_response.create_instance_profile_result.instance_profile
560                iam.add_role_to_instance_profile(name, name)
561        else:
562            instance_profile_result = iam.get_instance_profile(name).get_instance_profile_response.get_instance_profile_result.instance_profile
563    except boto.exception.BotoServerError as err:
564        module.fail_json(changed=changed, msg=str(err))
565    else:
566        updated_role_list = list_all_roles(iam)
567        iam_role_result = iam.get_role(name).get_role_response.get_role_result.role
568    return changed, updated_role_list, iam_role_result, instance_profile_result
569
570
571def delete_role(module, iam, name, role_list, prof_list):
572    changed = False
573    iam_role_result = None
574    instance_profile_result = None
575    try:
576        if name in role_list:
577            cur_ins_prof = [rp['instance_profile_name'] for rp in
578                            iam.list_instance_profiles_for_role(name).
579                            list_instance_profiles_for_role_result.
580                            instance_profiles]
581            for profile in cur_ins_prof:
582                iam.remove_role_from_instance_profile(profile, name)
583            try:
584                iam.delete_role(name)
585            except boto.exception.BotoServerError as err:
586                error_msg = boto_exception(err)
587                if ('must detach all policies first') in error_msg:
588                    for policy in iam.list_role_policies(name).list_role_policies_result.policy_names:
589                        iam.delete_role_policy(name, policy)
590                try:
591                    iam_role_result = iam.delete_role(name)
592                except boto.exception.BotoServerError as err:
593                    error_msg = boto_exception(err)
594                    if ('must detach all policies first') in error_msg:
595                        module.fail_json(changed=changed, msg="All inline policies have been removed. Though it appears"
596                                                              "that %s has Managed Polices. This is not "
597                                                              "currently supported by boto. Please detach the policies "
598                                                              "through the console and try again." % name)
599                    else:
600                        module.fail_json(changed=changed, msg=str(err))
601                else:
602                    changed = True
603
604            else:
605                changed = True
606
607        for prof in prof_list:
608            if name == prof:
609                instance_profile_result = iam.delete_instance_profile(name)
610    except boto.exception.BotoServerError as err:
611        module.fail_json(changed=changed, msg=str(err))
612    else:
613        updated_role_list = list_all_roles(iam)
614    return changed, updated_role_list, iam_role_result, instance_profile_result
615
616
617def main():
618    argument_spec = ec2_argument_spec()
619    argument_spec.update(dict(
620        iam_type=dict(required=True, choices=['user', 'group', 'role']),
621        groups=dict(type='list', default=None, required=False),
622        state=dict(required=True, choices=['present', 'absent', 'update']),
623        password=dict(default=None, required=False, no_log=True),
624        update_password=dict(default='always', required=False, choices=['always', 'on_create']),
625        access_key_state=dict(default=None, required=False, choices=[
626            'active', 'inactive', 'create', 'remove',
627            'Active', 'Inactive', 'Create', 'Remove']),
628        access_key_ids=dict(type='list', default=None, required=False),
629        key_count=dict(type='int', default=1, required=False),
630        name=dict(default=None, required=False),
631        trust_policy_filepath=dict(default=None, required=False),
632        trust_policy=dict(type='dict', default=None, required=False),
633        new_name=dict(default=None, required=False),
634        path=dict(default='/', required=False),
635        new_path=dict(default=None, required=False)
636    )
637    )
638
639    module = AnsibleModule(
640        argument_spec=argument_spec,
641        mutually_exclusive=[['trust_policy', 'trust_policy_filepath']],
642    )
643
644    if not HAS_BOTO:
645        module.fail_json(msg='This module requires boto, please install it')
646
647    state = module.params.get('state').lower()
648    iam_type = module.params.get('iam_type').lower()
649    groups = module.params.get('groups')
650    name = module.params.get('name')
651    new_name = module.params.get('new_name')
652    password = module.params.get('password')
653    update_pw = module.params.get('update_password')
654    path = module.params.get('path')
655    new_path = module.params.get('new_path')
656    key_count = module.params.get('key_count')
657    key_state = module.params.get('access_key_state')
658    trust_policy = module.params.get('trust_policy')
659    trust_policy_filepath = module.params.get('trust_policy_filepath')
660    key_ids = module.params.get('access_key_ids')
661
662    if key_state:
663        key_state = key_state.lower()
664        if any([n in key_state for n in ['active', 'inactive']]) and not key_ids:
665            module.fail_json(changed=False, msg="At least one access key has to be defined in order"
666                                                " to use 'active' or 'inactive'")
667
668    if iam_type == 'user' and module.params.get('password') is not None:
669        pwd = module.params.get('password')
670    elif iam_type != 'user' and module.params.get('password') is not None:
671        module.fail_json(msg="a password is being specified when the iam_type "
672                             "is not user. Check parameters")
673    else:
674        pwd = None
675
676    if iam_type != 'user' and (module.params.get('access_key_state') is not None or
677                               module.params.get('access_key_id') is not None):
678        module.fail_json(msg="the IAM type must be user, when IAM access keys "
679                             "are being modified. Check parameters")
680
681    if iam_type == 'role' and state == 'update':
682        module.fail_json(changed=False, msg="iam_type: role, cannot currently be updated, "
683                         "please specify present or absent")
684
685    # check if trust_policy is present -- it can be inline JSON or a file path to a JSON file
686    if trust_policy_filepath:
687        try:
688            with open(trust_policy_filepath, 'r') as json_data:
689                trust_policy_doc = json.dumps(json.load(json_data))
690        except Exception as e:
691            module.fail_json(msg=str(e) + ': ' + trust_policy_filepath)
692    elif trust_policy:
693        try:
694            trust_policy_doc = json.dumps(trust_policy)
695        except Exception as e:
696            module.fail_json(msg=str(e) + ': ' + trust_policy)
697    else:
698        trust_policy_doc = None
699
700    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
701
702    try:
703        if region:
704            iam = connect_to_aws(boto.iam, region, **aws_connect_kwargs)
705        else:
706            iam = boto.iam.connection.IAMConnection(**aws_connect_kwargs)
707    except boto.exception.NoAuthHandlerFound as e:
708        module.fail_json(msg=str(e))
709
710    result = {}
711    changed = False
712
713    try:
714        orig_group_list = list_all_groups(iam)
715
716        orig_user_list = list_all_users(iam)
717
718        orig_role_list = list_all_roles(iam)
719
720        orig_prof_list = list_all_instance_profiles(iam)
721    except boto.exception.BotoServerError as err:
722        module.fail_json(msg=err.message)
723
724    if iam_type == 'user':
725        been_updated = False
726        user_groups = None
727        user_exists = any([n in [name, new_name] for n in orig_user_list])
728        if user_exists:
729            current_path = iam.get_user(name).get_user_result.user['path']
730            if not new_path and current_path != path:
731                new_path = path
732                path = current_path
733
734        if state == 'present' and not user_exists and not new_name:
735            (meta, changed) = create_user(
736                module, iam, name, password, path, key_state, key_count)
737            keys = iam.get_all_access_keys(name).list_access_keys_result.\
738                access_key_metadata
739            if groups:
740                (user_groups, changed) = set_users_groups(
741                    module, iam, name, groups, been_updated, new_name)
742            module.exit_json(
743                user_meta=meta, groups=user_groups, keys=keys, changed=changed)
744
745        elif state in ['present', 'update'] and user_exists:
746            if update_pw == 'on_create':
747                password = None
748            if name not in orig_user_list and new_name in orig_user_list:
749                been_updated = True
750            name_change, key_list, user_changed, new_key = update_user(
751                module, iam, name, new_name, new_path, key_state, key_count, key_ids, password, been_updated)
752            if new_key:
753                user_meta = {'access_keys': list(new_key)}
754                user_meta['access_keys'].extend(
755                    [{'access_key_id': key, 'status': value} for key, value in key_list.items() if
756                     key not in [it['access_key_id'] for it in new_key]])
757            else:
758                user_meta = {
759                    'access_keys': [{'access_key_id': key, 'status': value} for key, value in key_list.items()]}
760
761            if name_change and new_name:
762                orig_name = name
763                name = new_name
764            if isinstance(groups, list):
765                user_groups, groups_changed = set_users_groups(
766                    module, iam, name, groups, been_updated, new_name)
767                if groups_changed == user_changed:
768                    changed = groups_changed
769                else:
770                    changed = True
771            else:
772                changed = user_changed
773            if new_name and new_path:
774                module.exit_json(changed=changed, groups=user_groups, old_user_name=orig_name,
775                                 new_user_name=new_name, old_path=path, new_path=new_path, keys=key_list,
776                                 created_keys=new_key, user_meta=user_meta)
777            elif new_name and not new_path and not been_updated:
778                module.exit_json(
779                    changed=changed, groups=user_groups, old_user_name=orig_name, new_user_name=new_name, keys=key_list,
780                    created_keys=new_key, user_meta=user_meta)
781            elif new_name and not new_path and been_updated:
782                module.exit_json(
783                    changed=changed, groups=user_groups, user_name=new_name, keys=key_list, key_state=key_state,
784                    created_keys=new_key, user_meta=user_meta)
785            elif not new_name and new_path:
786                module.exit_json(
787                    changed=changed, groups=user_groups, user_name=name, old_path=path, new_path=new_path,
788                    keys=key_list, created_keys=new_key, user_meta=user_meta)
789            else:
790                module.exit_json(
791                    changed=changed, groups=user_groups, user_name=name, keys=key_list, created_keys=new_key,
792                    user_meta=user_meta)
793
794        elif state == 'update' and not user_exists:
795            module.fail_json(
796                msg="The user %s does not exist. No update made." % name)
797
798        elif state == 'absent':
799            if user_exists:
800                try:
801                    set_users_groups(module, iam, name, '')
802                    name, changed = delete_user(module, iam, name)
803                    module.exit_json(deleted_user=name, changed=changed)
804
805                except Exception as ex:
806                    module.fail_json(changed=changed, msg=str(ex))
807            else:
808                module.exit_json(
809                    changed=False, msg="User %s is already absent from your AWS IAM users" % name)
810
811    elif iam_type == 'group':
812        group_exists = name in orig_group_list
813
814        if state == 'present' and not group_exists:
815            new_group, changed = create_group(module=module, iam=iam, name=name, path=path)
816            module.exit_json(changed=changed, group_name=new_group)
817        elif state in ['present', 'update'] and group_exists:
818            changed, updated_name, updated_path, cur_path = update_group(
819                module=module, iam=iam, name=name, new_name=new_name,
820                new_path=new_path)
821
822            if new_path and new_name:
823                module.exit_json(changed=changed, old_group_name=name,
824                                 new_group_name=updated_name, old_path=cur_path,
825                                 new_group_path=updated_path)
826
827            if new_path and not new_name:
828                module.exit_json(changed=changed, group_name=name,
829                                 old_path=cur_path,
830                                 new_group_path=updated_path)
831
832            if not new_path and new_name:
833                module.exit_json(changed=changed, old_group_name=name,
834                                 new_group_name=updated_name, group_path=cur_path)
835
836            if not new_path and not new_name:
837                module.exit_json(
838                    changed=changed, group_name=name, group_path=cur_path)
839
840        elif state == 'update' and not group_exists:
841            module.fail_json(
842                changed=changed, msg="Update Failed. Group %s doesn't seem to exist!" % name)
843
844        elif state == 'absent':
845            if name in orig_group_list:
846                removed_group, changed = delete_group(module=module, iam=iam, name=name)
847                module.exit_json(changed=changed, delete_group=removed_group)
848            else:
849                module.exit_json(changed=changed, msg="Group already absent")
850
851    elif iam_type == 'role':
852        role_list = []
853        if state == 'present':
854            changed, role_list, role_result, instance_profile_result = create_role(
855                module, iam, name, path, orig_role_list, orig_prof_list, trust_policy_doc)
856        elif state == 'absent':
857            changed, role_list, role_result, instance_profile_result = delete_role(
858                module, iam, name, orig_role_list, orig_prof_list)
859        elif state == 'update':
860            module.fail_json(
861                changed=False, msg='Role update not currently supported by boto.')
862        module.exit_json(changed=changed, roles=role_list, role_result=role_result,
863                         instance_profile_result=instance_profile_result)
864
865
866if __name__ == '__main__':
867    main()
868