diff --git a/lib/ansible/config/module_defaults.yml b/lib/ansible/config/module_defaults.yml index 3d300d7b244..8ccd7f9aeb4 100644 --- a/lib/ansible/config/module_defaults.yml +++ b/lib/ansible/config/module_defaults.yml @@ -412,6 +412,8 @@ groupings: - aws iam_user: - aws + iam_user_info: + - aws kinesis_stream: - aws lambda: diff --git a/lib/ansible/modules/cloud/amazon/iam_user_info.py b/lib/ansible/modules/cloud/amazon/iam_user_info.py new file mode 100644 index 00000000000..c7a25409b71 --- /dev/null +++ b/lib/ansible/modules/cloud/amazon/iam_user_info.py @@ -0,0 +1,185 @@ +#!/usr/bin/python + +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, 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 + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + + +DOCUMENTATION = ''' +--- +module: iam_user_info +short_description: Gather IAM user(s) facts in AWS +description: + - This module can be used to gather IAM user(s) facts in AWS. +version_added: "2.10" +author: + - Constantin Bugneac (@Constantin07) + - Abhijeet Kasurde (@Akasurde) +options: + name: + description: + - The name of the IAM user to look for. + required: false + type: str + group: + description: + - The group name name of the IAM user to look for. Mutually exclusive with C(path). + required: false + type: str + path: + description: + - The path to the IAM user. Mutually exclusive with C(group). + - If specified, then would get all user names whose path starts with user provided value. + required: false + default: '/' + type: str +requirements: + - botocore + - boto3 +extends_documentation_fragment: + - aws + - ec2 +''' + +EXAMPLES = r''' +# Note: These examples do not set authentication details, see the AWS Guide for details. +# Gather facts about "test" user. +- name: Get IAM user facts + iam_user_info: + name: "test" + +# Gather facts about all users in the "dev" group. +- name: Get IAM user facts + iam_user_info: + group: "dev" + +# Gather facts about all users with "/division_abc/subdivision_xyz/" path. +- name: Get IAM user facts + iam_user_info: + path: "/division_abc/subdivision_xyz/" +''' + +RETURN = r''' +iam_users: + description: list of maching iam users + returned: success + type: complex + contains: + arn: + description: the ARN of the user + returned: if user exists + type: str + sample: "arn:aws:iam::156360693172:user/dev/test_user" + create_date: + description: the datetime user was created + returned: if user exists + type: str + sample: "2016-05-24T12:24:59+00:00" + password_last_used: + description: the last datetime the password was used by user + returned: if password was used at least once + type: str + sample: "2016-05-25T13:39:11+00:00" + path: + description: the path to user + returned: if user exists + type: str + sample: "/dev/" + user_id: + description: the unique user id + returned: if user exists + type: str + sample: "AIDUIOOCQKTUGI6QJLGH2" + user_name: + description: the user name + returned: if user exists + type: str + sample: "test_user" +''' + +from ansible.module_utils.aws.core import AnsibleAWSModule +from ansible.module_utils.ec2 import camel_dict_to_snake_dict, AWSRetry + +try: + import botocore + from botocore.exceptions import BotoCoreError, ClientError +except ImportError: + pass # caught by AnsibleAWSModule + + +@AWSRetry.exponential_backoff() +def list_iam_users_with_backoff(client, operation, **kwargs): + paginator = client.get_paginator(operation) + return paginator.paginate(**kwargs).build_full_result() + + +def list_iam_users(connection, module): + + name = module.params.get('name') + group = module.params.get('group') + path = module.params.get('path') + + params = dict() + iam_users = [] + + if not group and not path: + if name: + params['UserName'] = name + try: + iam_users.append(connection.get_user(**params)['User']) + except (ClientError, BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't get IAM user info for user %s" % name) + + if group: + params['GroupName'] = group + try: + iam_users = list_iam_users_with_backoff(connection, 'get_group', **params)['Users'] + except (ClientError, BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't get IAM user info for group %s" % group) + if name: + iam_users = [user for user in iam_users if user['UserName'] == name] + + if path and not group: + params['PathPrefix'] = path + try: + iam_users = list_iam_users_with_backoff(connection, 'list_users', **params)['Users'] + except (ClientError, BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't get IAM user info for path %s" % path) + if name: + iam_users = [user for user in iam_users if user['UserName'] == name] + + module.exit_json(iam_users=[camel_dict_to_snake_dict(user) for user in iam_users]) + + +def main(): + argument_spec = dict( + name=dict(), + group=dict(), + path=dict(default='/') + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ['group', 'path'] + ], + supports_check_mode=True + ) + + connection = module.client('iam') + + list_iam_users(connection, module) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/iam_user/aliases b/test/integration/targets/iam_user/aliases new file mode 100644 index 00000000000..c7a4b8abe0e --- /dev/null +++ b/test/integration/targets/iam_user/aliases @@ -0,0 +1,3 @@ +cloud/aws +iam_user_info +unsupported diff --git a/test/integration/targets/iam_user/defaults/main.yml b/test/integration/targets/iam_user/defaults/main.yml new file mode 100644 index 00000000000..8a69ca0931c --- /dev/null +++ b/test/integration/targets/iam_user/defaults/main.yml @@ -0,0 +1,7 @@ +--- +test_group: '{{ resource_prefix }}-group' +test_path: '/' +test_user: '{{ test_users[0] }}' +test_users: + - '{{ resource_prefix }}-user-a' + - '{{ resource_prefix }}-user-b' diff --git a/test/integration/targets/iam_user/meta/main.yml b/test/integration/targets/iam_user/meta/main.yml new file mode 100644 index 00000000000..1f64f1169a9 --- /dev/null +++ b/test/integration/targets/iam_user/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - prepare_tests + - setup_ec2 diff --git a/test/integration/targets/iam_user/tasks/main.yml b/test/integration/targets/iam_user/tasks/main.yml new file mode 100644 index 00000000000..87359b761fb --- /dev/null +++ b/test/integration/targets/iam_user/tasks/main.yml @@ -0,0 +1,228 @@ +--- +- name: set up aws connection info + module_defaults: + group/aws: + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token | default(omit) }}" + region: "{{ aws_region }}" + block: + + - name: ensure improper usage of parameters fails gracefully + iam_user_info: + path: '{{ test_path }}' + group: '{{ test_group }}' + ignore_errors: yes + register: iam_user_info_path_group + - assert: + that: + - iam_user_info_path_group is failed + - 'iam_user_info_path_group.msg == "parameters are mutually exclusive: group|path"' + + - name: ensure exception handling fails as expected + iam_user_info: + region: 'bogus' + path: '' + ignore_errors: yes + register: iam_user_info + - assert: + that: + - iam_user_info is failed + - '"user" in iam_user_info.msg' + + - name: ensure exception handling fails as expected with group + iam_user_info: + region: 'bogus' + group: '{{ test_group }}' + ignore_errors: yes + register: iam_user_info + - assert: + that: + - iam_user_info is failed + - '"group" in iam_user_info.msg' + + - name: ensure exception handling fails as expected with default path + iam_user_info: + region: 'bogus' + ignore_errors: yes + register: iam_user_info + - assert: + that: + - iam_user_info is failed + - '"path" in iam_user_info.msg' + + - name: ensure ansible user exists + iam_user: + name: '{{ test_user }}' + state: present + register: iam_user + + - name: ensure the info used to validate other tests is valid + set_fact: + test_iam_user: '{{ iam_user.iam_user.user }}' + - assert: + that: + - 'test_iam_user.arn.startswith("arn:aws:iam")' + - 'test_iam_user.arn.endswith("user/" + test_user )' + - test_iam_user.create_date is not none + - test_iam_user.path == '{{ test_path }}' + - test_iam_user.user_id is not none + - test_iam_user.user_name == '{{ test_user }}' + + - name: get info on IAM user(s) + iam_user_info: + register: iam_user_info + - assert: + that: + - iam_user_info.iam_users | length != 0 + + - name: get info on IAM user(s) with name + iam_user_info: + name: '{{ test_user }}' + register: iam_user_info + - debug: var=iam_user_info + - assert: + that: + - iam_user_info.iam_users | length == 1 + - iam_user_info.iam_users[0].arn == test_iam_user.arn + - iam_user_info.iam_users[0].create_date == test_iam_user.create_date + - iam_user_info.iam_users[0].path == test_iam_user.path + - iam_user_info.iam_users[0].user_id == test_iam_user.user_id + - iam_user_info.iam_users[0].user_name == test_iam_user.user_name + + - name: get info on IAM user(s) on path + iam_user_info: + path: '{{ test_path }}' + name: '{{ test_user }}' + register: iam_user_info + - assert: + that: + - iam_user_info.iam_users | length == 1 + - iam_user_info.iam_users[0].arn == test_iam_user.arn + - iam_user_info.iam_users[0].create_date == test_iam_user.create_date + - iam_user_info.iam_users[0].path == test_iam_user.path + - iam_user_info.iam_users[0].user_id == test_iam_user.user_id + - iam_user_info.iam_users[0].user_name == test_iam_user.user_name + + - name: ensure group exists + iam_group: + name: '{{ test_group }}' + users: + - '{{ test_user }}' + state: present + register: iam_group + + - name: get info on IAM user(s) in group + iam_user_info: + group: '{{ test_group }}' + name: '{{ test_user }}' + register: iam_user_info + - assert: + that: + - iam_user_info.iam_users | length == 1 + - iam_user_info.iam_users[0].arn == test_iam_user.arn + - iam_user_info.iam_users[0].create_date == test_iam_user.create_date + - iam_user_info.iam_users[0].path == test_iam_user.path + - iam_user_info.iam_users[0].user_id == test_iam_user.user_id + - iam_user_info.iam_users[0].user_name == test_iam_user.user_name + + - name: remove user from group + iam_group: + name: '{{ test_group }}' + purge_users: True + users: [] + state: present + register: iam_group + + - name: get info on IAM user(s) after removing from group + iam_user_info: + group: '{{ test_group }}' + name: '{{ test_user }}' + register: iam_user_info + + - name: assert empty list of users for group are returned + assert: + that: + - iam_user_info.iam_users | length == 0 + + - name: ensure ansible users exist + iam_user: + name: '{{ item }}' + state: present + with_items: '{{ test_users }}' + + - name: get info on multiple IAM user(s) + iam_user_info: + register: iam_user_info + - assert: + that: + - iam_user_info.iam_users | length != 0 + + - name: ensure multiple user group exists with single user + iam_group: + name: '{{ test_group }}' + users: + - '{{ test_user }}' + state: present + register: iam_group + + - name: get info on IAM user(s) in group + iam_user_info: + group: '{{ test_group }}' + register: iam_user_info + - assert: + that: + - iam_user_info.iam_users | length == 1 + + - name: add all users to group + iam_group: + name: '{{ test_group }}' + users: '{{ test_users }}' + state: present + register: iam_group + + - name: get info on multiple IAM user(s) in group + iam_user_info: + group: '{{ test_group }}' + register: iam_user_info + - assert: + that: + - iam_user_info.iam_users | length == test_users | length + + - name: purge users from group + iam_group: + name: '{{ test_group }}' + purge_users: True + users: [] + state: present + register: iam_group + + - name: ensure info is empty for empty group + iam_user_info: + group: '{{ test_group }}' + register: iam_user_info + - assert: + that: + - iam_user_info.iam_users | length == 0 + + - name: get info on IAM user(s) after removing from group + iam_user_info: + group: '{{ test_group }}' + register: iam_user_info + + - name: assert empty list of users for group are returned + assert: + that: + - iam_user_info.iam_users | length == 0 + + always: + - name: remove group + iam_group: + name: '{{ test_group }}' + state: absent + + - name: remove ansible users + iam_user: + name: '{{ item }}' + state: absent + with_items: '{{ test_users }}'