From 4cd9c3d8d4ce42c464edbec157f7724366f81ab1 Mon Sep 17 00:00:00 2001 From: jillr Date: Mon, 2 Mar 2020 19:25:18 +0000 Subject: [PATCH 01/37] Initial commit This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/eb75681585a23ea79e642b86a0f8e64e0f40a6d7 --- plugins/modules/iam_role.py | 665 ++++++++ plugins/modules/iam_role_facts.py | 1 + plugins/modules/iam_role_info.py | 258 +++ tests/integration/targets/iam_role/aliases | 3 + .../targets/iam_role/defaults/main.yml | 8 + .../targets/iam_role/files/deny-all-a.json | 13 + .../targets/iam_role/files/deny-all-b.json | 13 + .../targets/iam_role/files/deny-all.json | 12 + .../targets/iam_role/files/deny-assume.json | 10 + .../targets/iam_role/meta/main.yml | 3 + .../targets/iam_role/tasks/main.yml | 1519 +++++++++++++++++ 11 files changed, 2505 insertions(+) create mode 100644 plugins/modules/iam_role.py create mode 120000 plugins/modules/iam_role_facts.py create mode 100644 plugins/modules/iam_role_info.py create mode 100644 tests/integration/targets/iam_role/aliases create mode 100644 tests/integration/targets/iam_role/defaults/main.yml create mode 100644 tests/integration/targets/iam_role/files/deny-all-a.json create mode 100644 tests/integration/targets/iam_role/files/deny-all-b.json create mode 100644 tests/integration/targets/iam_role/files/deny-all.json create mode 100644 tests/integration/targets/iam_role/files/deny-assume.json create mode 100644 tests/integration/targets/iam_role/meta/main.yml create mode 100644 tests/integration/targets/iam_role/tasks/main.yml diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py new file mode 100644 index 00000000000..7b865efe896 --- /dev/null +++ b/plugins/modules/iam_role.py @@ -0,0 +1,665 @@ +#!/usr/bin/python +# 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_role +short_description: Manage AWS IAM roles +description: + - Manage AWS IAM roles. +author: "Rob White (@wimnat)" +options: + path: + description: + - The path to the role. For more information about paths, see U(https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html). + default: "/" + type: str + name: + description: + - The name of the role to create. + required: true + type: str + description: + description: + - Provides a description of the role. + type: str + boundary: + description: + - The ARN of an IAM managed policy to use to restrict the permissions this role can pass on to IAM roles/users that it creates. + - Boundaries cannot be set on Instance Profiles, as such if this option is specified then I(create_instance_profile) must be C(false). + - This is intended for roles/users that have permissions to create new IAM objects. + - For more information on boundaries, see U(https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html). + - Requires botocore 1.10.57 or above. + aliases: [boundary_policy_arn] + type: str + assume_role_policy_document: + description: + - The trust relationship policy document that grants an entity permission to assume the role. + - This parameter is required when I(state=present). + type: json + managed_policies: + description: + - A list of managed policy ARNs or, since Ansible 2.4, a list of either managed policy ARNs or friendly names. + - To remove all policies set I(purge_polices=true) and I(managed_policies=[None]). + - To embed an inline policy, use M(iam_policy). + aliases: ['managed_policy'] + type: list + max_session_duration: + description: + - The maximum duration (in seconds) of a session when assuming the role. + - Valid values are between 1 and 12 hours (3600 and 43200 seconds). + type: int + purge_policies: + description: + - When I(purge_policies=true) any managed policies not listed in I(managed_policies) will be detatched. + - By default I(purge_policies=true). In Ansible 2.14 this will be changed to I(purge_policies=false). + type: bool + aliases: ['purge_policy', 'purge_managed_policies'] + state: + description: + - Create or remove the IAM role. + default: present + choices: [ present, absent ] + type: str + create_instance_profile: + description: + - Creates an IAM instance profile along with the role. + default: true + type: bool + delete_instance_profile: + description: + - When I(delete_instance_profile=true) and I(state=absent) deleting a role will also delete the instance + profile created with the same I(name) as the role. + - Only applies when I(state=absent). + default: false + type: bool + tags: + description: + - Tag dict to apply to the queue. + - Requires botocore 1.12.46 or above. + type: dict + purge_tags: + description: + - Remove tags not listed in I(tags) when tags is specified. + default: true + type: bool +requirements: [ botocore, boto3 ] +extends_documentation_fragment: +- ansible.amazon.aws +- ansible.amazon.ec2 + +''' + +EXAMPLES = ''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +- name: Create a role with description and tags + iam_role: + name: mynewrole + assume_role_policy_document: "{{ lookup('file','policy.json') }}" + description: This is My New Role + tags: + env: dev + +- name: "Create a role and attach a managed policy called 'PowerUserAccess'" + iam_role: + name: mynewrole + assume_role_policy_document: "{{ lookup('file','policy.json') }}" + managed_policies: + - arn:aws:iam::aws:policy/PowerUserAccess + +- name: Keep the role created above but remove all managed policies + iam_role: + name: mynewrole + assume_role_policy_document: "{{ lookup('file','policy.json') }}" + managed_policies: [] + +- name: Delete the role + iam_role: + name: mynewrole + assume_role_policy_document: "{{ lookup('file', 'policy.json') }}" + state: absent + +''' +RETURN = ''' +iam_role: + description: dictionary containing the IAM Role data + returned: success + type: complex + contains: + path: + description: the path to the role + type: str + returned: always + sample: / + role_name: + description: the friendly name that identifies the role + type: str + returned: always + sample: myrole + role_id: + description: the stable and unique string identifying the role + type: str + returned: always + sample: ABCDEFF4EZ4ABCDEFV4ZC + arn: + description: the Amazon Resource Name (ARN) specifying the role + type: str + returned: always + sample: "arn:aws:iam::1234567890:role/mynewrole" + create_date: + description: the date and time, in ISO 8601 date-time format, when the role was created + type: str + returned: always + sample: "2016-08-14T04:36:28+00:00" + assume_role_policy_document: + description: the policy that grants an entity permission to assume the role + type: str + returned: always + sample: { + 'statement': [ + { + 'action': 'sts:AssumeRole', + 'effect': 'Allow', + 'principal': { + 'service': 'ec2.amazonaws.com' + }, + 'sid': '' + } + ], + 'version': '2012-10-17' + } + attached_policies: + description: a list of dicts containing the name and ARN of the managed IAM policies attached to the role + type: list + returned: always + sample: [ + { + 'policy_arn': 'arn:aws:iam::aws:policy/PowerUserAccess', + 'policy_name': 'PowerUserAccess' + } + ] + tags: + description: role tags + type: dict + returned: always + sample: '{"Env": "Prod"}' +''' + +import json + +from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import camel_dict_to_snake_dict, compare_policies +from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import AWSRetry, ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict, compare_aws_tags + +try: + from botocore.exceptions import ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + + +def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc): + if not compare_policies(current_policy_doc, json.loads(new_policy_doc)): + return True + else: + return False + + +@AWSRetry.jittered_backoff() +def _list_policies(connection): + paginator = connection.get_paginator('list_policies') + return paginator.paginate().build_full_result()['Policies'] + + +def convert_friendly_names_to_arns(connection, module, policy_names): + if not any([not policy.startswith('arn:') for policy in policy_names]): + return policy_names + allpolicies = {} + policies = _list_policies(connection) + + for policy in policies: + allpolicies[policy['PolicyName']] = policy['Arn'] + allpolicies[policy['Arn']] = policy['Arn'] + try: + return [allpolicies[policy] for policy in policy_names] + except KeyError as e: + module.fail_json_aws(e, msg="Couldn't find policy") + + +def attach_policies(connection, module, policies_to_attach, params): + changed = False + for policy_arn in policies_to_attach: + try: + if not module.check_mode: + connection.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn, aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, params['RoleName'])) + changed = True + return changed + + +def remove_policies(connection, module, policies_to_remove, params): + changed = False + for policy in policies_to_remove: + try: + if not module.check_mode: + connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy, aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, params['RoleName'])) + changed = True + return changed + + +def generate_create_params(module): + params = dict() + params['Path'] = module.params.get('path') + params['RoleName'] = module.params.get('name') + params['AssumeRolePolicyDocument'] = module.params.get('assume_role_policy_document') + if module.params.get('description') is not None: + params['Description'] = module.params.get('description') + if module.params.get('max_session_duration') is not None: + params['MaxSessionDuration'] = module.params.get('max_session_duration') + if module.params.get('boundary') is not None: + params['PermissionsBoundary'] = module.params.get('boundary') + if module.params.get('tags') is not None: + params['Tags'] = ansible_dict_to_boto3_tag_list(module.params.get('tags')) + + return params + + +def create_basic_role(connection, module, params): + """ + Perform the Role creation. + Assumes tests for the role existing have already been performed. + """ + + try: + if not module.check_mode: + role = connection.create_role(aws_retry=True, **params) + # 'Description' is documented as key of the role returned by create_role + # but appears to be an AWS bug (the value is not returned using the AWS CLI either). + # Get the role after creating it. + role = get_role_with_backoff(connection, module, params['RoleName']) + else: + role = {'MadeInCheckMode': True} + role['AssumeRolePolicyDocument'] = json.loads(params['AssumeRolePolicyDocument']) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to create role") + + return role + + +def update_role_assumed_policy(connection, module, params, role): + # Check Assumed Policy document + if compare_assume_role_policy_doc(role['AssumeRolePolicyDocument'], params['AssumeRolePolicyDocument']): + return False + + if module.check_mode: + return True + + try: + connection.update_assume_role_policy( + RoleName=params['RoleName'], + PolicyDocument=json.dumps(json.loads(params['AssumeRolePolicyDocument'])), + aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(params['RoleName'])) + return True + + +def update_role_description(connection, module, params, role): + # Check Description update + if params.get('Description') is None: + return False + if role.get('Description') == params['Description']: + return False + + if module.check_mode: + return True + + try: + connection.update_role_description(RoleName=params['RoleName'], Description=params['Description'], aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to update description for role {0}".format(params['RoleName'])) + return True + + +def update_role_max_session_duration(connection, module, params, role): + # Check MaxSessionDuration update + if params.get('MaxSessionDuration') is None: + return False + if role.get('MaxSessionDuration') == params['MaxSessionDuration']: + return False + + if module.check_mode: + return True + + try: + connection.update_role(RoleName=params['RoleName'], MaxSessionDuration=params['MaxSessionDuration'], aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to update maximum session duration for role {0}".format(params['RoleName'])) + return True + + +def update_role_permissions_boundary(connection, module, params, role): + # Check PermissionsBoundary + if params.get('PermissionsBoundary') is None: + return False + if params.get('PermissionsBoundary') == role.get('PermissionsBoundary', {}).get('PermissionsBoundaryArn', ''): + return False + + if module.check_mode: + return True + + if params.get('PermissionsBoundary') == '': + try: + connection.delete_role_permissions_boundary(RoleName=params['RoleName'], aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(params['RoleName'])) + else: + try: + connection.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'], aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(params['RoleName'])) + return True + + +def update_managed_policies(connection, module, params, role, managed_policies, purge_policies): + # Check Managed Policies + if managed_policies is None: + return False + + # If we're manipulating a fake role + if role.get('MadeInCheckMode', False): + role['AttachedPolicies'] = list(map(lambda x: {'PolicyArn': x, 'PolicyName': x.split(':')[5]}, managed_policies)) + return True + + # Get list of current attached managed policies + current_attached_policies = get_attached_policy_list(connection, module, params['RoleName']) + current_attached_policies_arn_list = [policy['PolicyArn'] for policy in current_attached_policies] + + if len(managed_policies) == 1 and managed_policies[0] is None: + managed_policies = [] + + policies_to_remove = set(current_attached_policies_arn_list) - set(managed_policies) + policies_to_attach = set(managed_policies) - set(current_attached_policies_arn_list) + + changed = False + + if purge_policies: + changed |= remove_policies(connection, module, policies_to_remove, params) + + changed |= attach_policies(connection, module, policies_to_attach, params) + + return changed + + +def create_or_update_role(connection, module): + + params = generate_create_params(module) + role_name = params['RoleName'] + create_instance_profile = module.params.get('create_instance_profile') + purge_policies = module.params.get('purge_policies') + if purge_policies is None: + purge_policies = True + managed_policies = module.params.get('managed_policies') + if managed_policies: + # Attempt to list the policies early so we don't leave things behind if we can't find them. + managed_policies = convert_friendly_names_to_arns(connection, module, managed_policies) + + changed = False + + # Get role + role = get_role(connection, module, role_name) + + # If role is None, create it + if role is None: + role = create_basic_role(connection, module, params) + changed = True + else: + changed |= update_role_tags(connection, module, params, role) + changed |= update_role_assumed_policy(connection, module, params, role) + changed |= update_role_description(connection, module, params, role) + changed |= update_role_max_session_duration(connection, module, params, role) + changed |= update_role_permissions_boundary(connection, module, params, role) + + if create_instance_profile: + changed |= create_instance_profiles(connection, module, params, role) + + changed |= update_managed_policies(connection, module, params, role, managed_policies, purge_policies) + + # Get the role again + if not role.get('MadeInCheckMode', False): + role = get_role(connection, module, params['RoleName']) + role['AttachedPolicies'] = get_attached_policy_list(connection, module, params['RoleName']) + role['tags'] = get_role_tags(connection, module) + + module.exit_json( + changed=changed, iam_role=camel_dict_to_snake_dict(role, ignore_list=['tags']), + **camel_dict_to_snake_dict(role, ignore_list=['tags'])) + + +def create_instance_profiles(connection, module, params, role): + + if role.get('MadeInCheckMode', False): + return False + + # Fetch existing Profiles + try: + instance_profiles = connection.list_instance_profiles_for_role(RoleName=params['RoleName'], aws_retry=True)['InstanceProfiles'] + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName'])) + + # Profile already exists + if any(p['InstanceProfileName'] == params['RoleName'] for p in instance_profiles): + return False + + if module.check_mode: + return True + + # Make sure an instance profile is created + try: + connection.create_instance_profile(InstanceProfileName=params['RoleName'], Path=params['Path'], aws_retry=True) + except ClientError as e: + # If the profile already exists, no problem, move on. + # Implies someone's changing things at the same time... + if e.response['Error']['Code'] == 'EntityAlreadyExists': + return False + else: + module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(params['RoleName'])) + except BotoCoreError as e: + module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(params['RoleName'])) + + # And attach the role to the profile + try: + connection.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'], aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to attach role {0} to instance profile {0}".format(params['RoleName'])) + + return True + + +def remove_instance_profiles(connection, module, role_params, role): + role_name = module.params.get('name') + delete_profiles = module.params.get("delete_instance_profile") + + try: + instance_profiles = connection.list_instance_profiles_for_role(aws_retry=True, **role_params)['InstanceProfiles'] + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) + + # Remove the role from the instance profile(s) + for profile in instance_profiles: + profile_name = profile['InstanceProfileName'] + try: + if not module.check_mode: + connection.remove_role_from_instance_profile(aws_retry=True, InstanceProfileName=profile_name, **role_params) + if profile_name == role_name: + if delete_profiles: + try: + connection.delete_instance_profile(InstanceProfileName=profile_name, aws_retry=True) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to remove instance profile {0}".format(profile_name)) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to remove role {0} from instance profile {1}".format(role_name, profile_name)) + + +def destroy_role(connection, module): + + role_name = module.params.get('name') + role = get_role(connection, module, role_name) + role_params = dict() + role_params['RoleName'] = role_name + boundary_params = dict(role_params) + boundary_params['PermissionsBoundary'] = '' + + if role is None: + module.exit_json(changed=False) + + # Before we try to delete the role we need to remove any + # - attached instance profiles + # - attached managed policies + # - permissions boundary + remove_instance_profiles(connection, module, role_params, role) + update_managed_policies(connection, module, role_params, role, [], True) + update_role_permissions_boundary(connection, module, boundary_params, role) + + try: + if not module.check_mode: + connection.delete_role(aws_retry=True, **role_params) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to delete role") + + module.exit_json(changed=True) + + +def get_role_with_backoff(connection, module, name): + try: + return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(connection.get_role)(RoleName=name)['Role'] + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) + + +def get_role(connection, module, name): + try: + return connection.get_role(RoleName=name, aws_retry=True)['Role'] + except ClientError as e: + if e.response['Error']['Code'] == 'NoSuchEntity': + return None + else: + module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) + except BotoCoreError as e: + module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) + + +def get_attached_policy_list(connection, module, name): + try: + return connection.list_attached_role_policies(RoleName=name, aws_retry=True)['AttachedPolicies'] + except (ClientError, BotoCoreError) as e: + module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) + + +def get_role_tags(connection, module): + role_name = module.params.get('name') + if not hasattr(connection, 'list_role_tags'): + return {} + try: + return boto3_tag_list_to_ansible_dict(connection.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) + except (ClientError, BotoCoreError) as e: + module.fail_json_aws(e, msg="Unable to list tags for role {0}".format(role_name)) + + +def update_role_tags(connection, module, params, role): + new_tags = params.get('Tags') + if new_tags is None: + return False + new_tags = boto3_tag_list_to_ansible_dict(new_tags) + + role_name = module.params.get('name') + purge_tags = module.params.get('purge_tags') + + try: + existing_tags = boto3_tag_list_to_ansible_dict(connection.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) + except (ClientError, KeyError): + existing_tags = {} + + tags_to_add, tags_to_remove = compare_aws_tags(existing_tags, new_tags, purge_tags=purge_tags) + + if not module.check_mode: + try: + if tags_to_remove: + connection.untag_role(RoleName=role_name, TagKeys=tags_to_remove, aws_retry=True) + if tags_to_add: + connection.tag_role(RoleName=role_name, Tags=ansible_dict_to_boto3_tag_list(tags_to_add), aws_retry=True) + except (ClientError, BotoCoreError) as e: + module.fail_json_aws(e, msg='Unable to set tags for role %s' % role_name) + + changed = bool(tags_to_add) or bool(tags_to_remove) + return changed + + +def main(): + + argument_spec = dict( + name=dict(type='str', required=True), + path=dict(type='str', default="/"), + assume_role_policy_document=dict(type='json'), + managed_policies=dict(type='list', aliases=['managed_policy']), + max_session_duration=dict(type='int'), + state=dict(type='str', choices=['present', 'absent'], default='present'), + description=dict(type='str'), + boundary=dict(type='str', aliases=['boundary_policy_arn']), + create_instance_profile=dict(type='bool', default=True), + delete_instance_profile=dict(type='bool', default=False), + purge_policies=dict(type='bool', aliases=['purge_policy', 'purge_managed_policies']), + tags=dict(type='dict'), + purge_tags=dict(type='bool', default=True), + ) + module = AnsibleAWSModule(argument_spec=argument_spec, + required_if=[('state', 'present', ['assume_role_policy_document'])], + supports_check_mode=True) + + if module.params.get('purge_policies') is None: + module.deprecate('In Ansible 2.14 the default value of purge_policies will change from true to false.' + ' To maintain the existing behaviour explicity set purge_policies=true', version='2.14') + + if module.params.get('boundary'): + if module.params.get('create_instance_profile'): + module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.") + if not module.params.get('boundary').startswith('arn:aws:iam'): + module.fail_json(msg="Boundary policy must be an ARN") + if module.params.get('tags') is not None and not module.botocore_at_least('1.12.46'): + module.fail_json(msg="When managing tags botocore must be at least v1.12.46. " + "Current versions: boto3-{boto3_version} botocore-{botocore_version}".format(**module._gather_versions())) + if module.params.get('boundary') is not None and not module.botocore_at_least('1.10.57'): + module.fail_json(msg="When using a boundary policy, botocore must be at least v1.10.57. " + "Current versions: boto3-{boto3_version} botocore-{botocore_version}".format(**module._gather_versions())) + if module.params.get('max_session_duration'): + max_session_duration = module.params.get('max_session_duration') + if max_session_duration < 3600 or max_session_duration > 43200: + module.fail_json(msg="max_session_duration must be between 1 and 12 hours (3600 and 43200 seconds)") + if module.params.get('path'): + path = module.params.get('path') + if not path.endswith('/') or not path.startswith('/'): + module.fail_json(msg="path must begin and end with /") + + connection = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + + state = module.params.get("state") + + if state == 'present': + create_or_update_role(connection, module) + else: + destroy_role(connection, module) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/iam_role_facts.py b/plugins/modules/iam_role_facts.py new file mode 120000 index 00000000000..e15c454b71c --- /dev/null +++ b/plugins/modules/iam_role_facts.py @@ -0,0 +1 @@ +iam_role_info.py \ No newline at end of file diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py new file mode 100644 index 00000000000..5a3753fd524 --- /dev/null +++ b/plugins/modules/iam_role_info.py @@ -0,0 +1,258 @@ +#!/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 + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: iam_role_info +short_description: Gather information on IAM roles +description: + - Gathers information about IAM roles. + - This module was called C(iam_role_facts) before Ansible 2.9. The usage did not change. +requirements: [ boto3 ] +author: + - "Will Thames (@willthames)" +options: + name: + description: + - Name of a role to search for. + - Mutually exclusive with I(path_prefix). + aliases: + - role_name + type: str + path_prefix: + description: + - Prefix of role to restrict IAM role search for. + - Mutually exclusive with I(name). + type: str +extends_documentation_fragment: +- ansible.amazon.aws +- ansible.amazon.ec2 + +''' + +EXAMPLES = ''' +# find all existing IAM roles +- iam_role_info: + register: result + +# describe a single role +- iam_role_info: + name: MyIAMRole + +# describe all roles matching a path prefix +- iam_role_info: + path_prefix: /application/path +''' + +RETURN = ''' +iam_roles: + description: List of IAM roles + returned: always + type: complex + contains: + arn: + description: Amazon Resource Name for IAM role. + returned: always + type: str + sample: arn:aws:iam::123456789012:role/AnsibleTestRole + assume_role_policy_document: + description: Policy Document describing what can assume the role. + returned: always + type: str + create_date: + description: Date IAM role was created. + returned: always + type: str + sample: '2017-10-23T00:05:08+00:00' + inline_policies: + description: List of names of inline policies. + returned: always + type: list + sample: [] + managed_policies: + description: List of attached managed policies. + returned: always + type: complex + contains: + policy_arn: + description: Amazon Resource Name for the policy. + returned: always + type: str + sample: arn:aws:iam::123456789012:policy/AnsibleTestEC2Policy + policy_name: + description: Name of managed policy. + returned: always + type: str + sample: AnsibleTestEC2Policy + instance_profiles: + description: List of attached instance profiles. + returned: always + type: complex + contains: + arn: + description: Amazon Resource Name for the instance profile. + returned: always + type: str + sample: arn:aws:iam::123456789012:instance-profile/AnsibleTestEC2Policy + create_date: + description: Date instance profile was created. + returned: always + type: str + sample: '2017-10-23T00:05:08+00:00' + instance_profile_id: + description: Amazon Identifier for the instance profile. + returned: always + type: str + sample: AROAII7ABCD123456EFGH + instance_profile_name: + description: Name of instance profile. + returned: always + type: str + sample: AnsibleTestEC2Policy + path: + description: Path of instance profile. + returned: always + type: str + sample: / + roles: + description: List of roles associated with this instance profile. + returned: always + type: list + sample: [] + path: + description: Path of role. + returned: always + type: str + sample: / + role_id: + description: Amazon Identifier for the role. + returned: always + type: str + sample: AROAII7ABCD123456EFGH + role_name: + description: Name of the role. + returned: always + type: str + sample: AnsibleTestRole + tags: + description: Role tags. + type: dict + returned: always + sample: '{"Env": "Prod"}' +''' + +try: + import botocore +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict, AWSRetry + + +@AWSRetry.exponential_backoff() +def list_iam_roles_with_backoff(client, **kwargs): + paginator = client.get_paginator('list_roles') + return paginator.paginate(**kwargs).build_full_result() + + +@AWSRetry.exponential_backoff() +def list_iam_role_policies_with_backoff(client, role_name): + paginator = client.get_paginator('list_role_policies') + return paginator.paginate(RoleName=role_name).build_full_result()['PolicyNames'] + + +@AWSRetry.exponential_backoff() +def list_iam_attached_role_policies_with_backoff(client, role_name): + paginator = client.get_paginator('list_attached_role_policies') + return paginator.paginate(RoleName=role_name).build_full_result()['AttachedPolicies'] + + +@AWSRetry.exponential_backoff() +def list_iam_instance_profiles_for_role_with_backoff(client, role_name): + paginator = client.get_paginator('list_instance_profiles_for_role') + return paginator.paginate(RoleName=role_name).build_full_result()['InstanceProfiles'] + + +def describe_iam_role(module, client, role): + name = role['RoleName'] + try: + role['InlinePolicies'] = list_iam_role_policies_with_backoff(client, name) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't get inline policies for role %s" % name) + try: + role['ManagedPolicies'] = list_iam_attached_role_policies_with_backoff(client, name) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't get managed policies for role %s" % name) + try: + role['InstanceProfiles'] = list_iam_instance_profiles_for_role_with_backoff(client, name) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't get instance profiles for role %s" % name) + try: + role['tags'] = boto3_tag_list_to_ansible_dict(role['Tags']) + del role['Tags'] + except KeyError: + role['tags'] = {} + return role + + +def describe_iam_roles(module, client): + name = module.params['name'] + path_prefix = module.params['path_prefix'] + if name: + try: + roles = [client.get_role(RoleName=name)['Role']] + except botocore.exceptions.ClientError as e: + if e.response['Error']['Code'] == 'NoSuchEntity': + return [] + else: + module.fail_json_aws(e, msg="Couldn't get IAM role %s" % name) + except botocore.exceptions.BotoCoreError as e: + module.fail_json_aws(e, msg="Couldn't get IAM role %s" % name) + else: + params = dict() + if path_prefix: + if not path_prefix.startswith('/'): + path_prefix = '/' + path_prefix + if not path_prefix.endswith('/'): + path_prefix = path_prefix + '/' + params['PathPrefix'] = path_prefix + try: + roles = list_iam_roles_with_backoff(client, **params)['Roles'] + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Couldn't list IAM roles") + return [camel_dict_to_snake_dict(describe_iam_role(module, client, role), ignore_list=['tags']) for role in roles] + + +def main(): + """ + Module action handler + """ + argument_spec = dict( + name=dict(aliases=['role_name']), + path_prefix=dict(), + ) + + module = AnsibleAWSModule(argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[['name', 'path_prefix']]) + if module._name == 'iam_role_facts': + module.deprecate("The 'iam_role_facts' module has been renamed to 'iam_role_info'", version='2.13') + + client = module.client('iam') + + module.exit_json(changed=False, iam_roles=describe_iam_roles(module, client)) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/iam_role/aliases b/tests/integration/targets/iam_role/aliases new file mode 100644 index 00000000000..3d7a2c9f14c --- /dev/null +++ b/tests/integration/targets/iam_role/aliases @@ -0,0 +1,3 @@ +iam_role_info +unsupported +cloud/aws diff --git a/tests/integration/targets/iam_role/defaults/main.yml b/tests/integration/targets/iam_role/defaults/main.yml new file mode 100644 index 00000000000..46db605072e --- /dev/null +++ b/tests/integration/targets/iam_role/defaults/main.yml @@ -0,0 +1,8 @@ +--- +test_role: '{{ resource_prefix }}-role' +test_path: '/{{ resource_prefix }}/' +safe_managed_policy: 'AWSDenyAll' +custom_policy_name: '{{ resource_prefix }}-denyall' +boundary_policy: 'arn:aws:iam::aws:policy/AWSDenyAll' +paranoid_pauses: no +standard_pauses: no diff --git a/tests/integration/targets/iam_role/files/deny-all-a.json b/tests/integration/targets/iam_role/files/deny-all-a.json new file mode 100644 index 00000000000..ae62fd1975d --- /dev/null +++ b/tests/integration/targets/iam_role/files/deny-all-a.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "*" + ], + "Effect": "Deny", + "Resource": "*", + "Sid": "DenyA" + } + ] +} diff --git a/tests/integration/targets/iam_role/files/deny-all-b.json b/tests/integration/targets/iam_role/files/deny-all-b.json new file mode 100644 index 00000000000..3a4704a46ab --- /dev/null +++ b/tests/integration/targets/iam_role/files/deny-all-b.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "*" + ], + "Effect": "Deny", + "Resource": "*", + "Sid": "DenyB" + } + ] +} diff --git a/tests/integration/targets/iam_role/files/deny-all.json b/tests/integration/targets/iam_role/files/deny-all.json new file mode 100644 index 00000000000..3d324b9b9c6 --- /dev/null +++ b/tests/integration/targets/iam_role/files/deny-all.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "*" + ], + "Effect": "Deny", + "Resource": "*" + } + ] +} diff --git a/tests/integration/targets/iam_role/files/deny-assume.json b/tests/integration/targets/iam_role/files/deny-assume.json new file mode 100644 index 00000000000..73e87715862 --- /dev/null +++ b/tests/integration/targets/iam_role/files/deny-assume.json @@ -0,0 +1,10 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Principal": { "Service": "ec2.amazonaws.com" }, + "Effect": "Deny" + } + ] +} diff --git a/tests/integration/targets/iam_role/meta/main.yml b/tests/integration/targets/iam_role/meta/main.yml new file mode 100644 index 00000000000..1f64f1169a9 --- /dev/null +++ b/tests/integration/targets/iam_role/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - prepare_tests + - setup_ec2 diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml new file mode 100644 index 00000000000..676179bd710 --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -0,0 +1,1519 @@ +--- +# Tests for iam_role and iam_role_info +# +# Tests: +# - Minimal Role creation +# - Role deletion +# - Fetching a specific role +# - Creating roles w/ and w/o instance profiles +# - Creating roles w/ a path +# - Updating Max Session Duration +# - Updating Description +# - Managing list of managed policies +# - Managing list of inline policies (for testing _info) +# - Managing boundary policy +# +# Notes: +# - Only tests *documented* return values ( RESULT.iam_role ) +# - There are some known timing issues with boto3 returning before actions +# complete in the case of problems with "changed" status it's worth enabling +# the standard_pauses and paranoid_pauses options as a first step in debugging +# +# Possible Bugs: +# - Fails to delete role if inline policies not removed first + +- name: 'Setup 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 }}' + iam_role: + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + block: + # =================================================================== + # Parameter Checks + - name: 'Friendly message when creating an instance profile and adding a boundary profile' + iam_role: + name: '{{ test_role }}' + boundary: '{{ boundary_policy }}' + register: iam_role + ignore_errors: yes + - assert: + that: + - iam_role is failed + - '"boundary policy" in iam_role.msg' + - '"create_instance_profile" in iam_role.msg' + - '"false" in iam_role.msg' + + - name: 'Friendly message when boundary profile is not an ARN' + iam_role: + name: '{{ test_role }}' + boundary: 'AWSDenyAll' + create_instance_profile: no + register: iam_role + ignore_errors: yes + - assert: + that: + - iam_role is failed + - '"Boundary policy" in iam_role.msg' + - '"ARN" in iam_role.msg' + + - name: 'Friendly message when "present" without assume_role_policy_document' + module_defaults: { iam_role: {} } + iam_role: + name: '{{ test_role }}' + register: iam_role + ignore_errors: yes + - assert: + that: + - iam_role is failed + - 'iam_role.msg.startswith("state is present but all of the following are missing")' + - '"assume_role_policy_document" in iam_role.msg' + + - name: 'Maximum Session Duration needs to be between 1 and 12 hours' + iam_role: + name: '{{ test_role }}' + max_session_duration: 3599 + register: iam_role + ignore_errors: yes + - assert: + that: + - iam_role is failed + - '"max_session_duration must be between" in iam_role.msg' + + - name: 'Maximum Session Duration needs to be between 1 and 12 hours' + iam_role: + name: '{{ test_role }}' + max_session_duration: 43201 + register: iam_role + ignore_errors: yes + - assert: + that: + - iam_role is failed + - '"max_session_duration must be between" in iam_role.msg' + + - name: 'Role Paths must start with /' + iam_role: + name: '{{ test_role }}' + path: 'test/' + register: iam_role + ignore_errors: yes + - assert: + that: + - iam_role is failed + - '"path must begin and end with /" in iam_role.msg' + + - name: 'Role Paths must end with /' + iam_role: + name: '{{ test_role }}' + path: '/test' + register: iam_role + ignore_errors: yes + - assert: + that: + - iam_role is failed + - '"path must begin and end with /" in iam_role.msg' + + # =================================================================== + # Supplemental resource pre-creation + - name: 'Create Safe IAM Managed Policy' + iam_managed_policy: + state: present + policy_name: '{{ custom_policy_name }}' + policy_description: "A safe (deny-all) managed policy" + policy: "{{ lookup('file', 'deny-all.json') }}" + register: create_managed_policy + - assert: + that: + - create_managed_policy is succeeded + + # =================================================================== + # Rapid Role Creation and deletion + - name: Try running some rapid fire create/delete tests + # We've previously seen issues with iam_role returning before creation's + # actually complete, if we think the issue's gone, let's try creating and + # deleting things in quick succession + when: not (standard_pauses | bool) + block: + - name: 'Minimal IAM Role without instance profile (rapid)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role + - name: 'Minimal IAM Role without instance profile (rapid)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role_again + - assert: + that: + - iam_role is changed + - iam_role_again is not changed + - name: 'Remove IAM Role (rapid)' + iam_role: + state: absent + name: '{{ test_role }}' + register: iam_role + - name: 'Remove IAM Role (rapid)' + iam_role: + state: absent + name: '{{ test_role }}' + register: iam_role_again + - assert: + that: + - iam_role is changed + - iam_role_again is not changed + + - name: 'Minimal IAM Role without instance profile (rapid)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role + - name: 'Remove IAM Role (rapid)' + iam_role: + state: absent + name: '{{ test_role }}' + register: iam_role_again + - assert: + that: + - iam_role is changed + - iam_role_again is changed + + # =================================================================== + # Role Creation + # (without Instance profile) + - name: 'iam_role_info before Role creation (no args)' + iam_role_info: + register: role_info + - assert: + that: + - role_info is succeeded + + - name: 'iam_role_info before Role creation (search for test role)' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + + - name: 'Minimal IAM Role (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + # Pause this first time, just in case we actually created something... + - name: Short pause for role creation to finish + pause: + seconds: 10 + when: standard_pauses | bool + + - name: 'iam_role_info after Role creation in check_mode' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + + - name: 'Minimal IAM Role without instance profile' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role/" + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '/' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + - name: Short pause for role creation to finish + pause: + seconds: 10 + when: standard_pauses | bool + + - name: 'Minimal IAM Role without instance profile (no change)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after Role creation' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + + - name: 'Remove IAM Role' + iam_role: + state: absent + name: '{{ test_role }}' + delete_instance_profile: yes + register: iam_role + - assert: + that: + - iam_role is changed + - name: Short pause for role removal to finish + pause: + seconds: 10 + when: paranoid_pauses | bool + + - name: 'iam_role_info after Role deletion' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + + # (with path) + - name: 'Minimal IAM Role with path (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + path: '{{ test_path }}' + register: iam_role + check_mode: yes + - assert: + that: + - iam_role is changed + + - name: 'Minimal IAM Role with path' + iam_role: + name: '{{ test_role }}' + path: '{{ test_path }}' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '{{ test_path }}' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + - name: Short pause for role creation to finish + pause: + seconds: 10 + when: standard_pauses | bool + + - name: 'Minimal IAM Role with path (no change)' + iam_role: + name: '{{ test_role }}' + path: '{{ test_path }}' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after Role creation' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '{{ test_path }}' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + + - name: 'iam_role_info after Role creation (searching a path)' + iam_role_info: + path_prefix: '{{ test_path }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].path == '{{ test_path }}' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + + - name: 'Remove IAM Role' + iam_role: + state: absent + name: '{{ test_role }}' + path: '{{ test_path }}' + # If we don't delete the existing profile it'll be reused (with the path) + # by the test below. + delete_instance_profile: yes + register: iam_role + - assert: + that: + - iam_role is changed + - name: Short pause for role removal to finish + pause: + seconds: 10 + when: paranoid_pauses | bool + + - name: 'iam_role_info after Role deletion' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + + # (with Instance profile) + - name: 'Minimal IAM Role with instance profile' + iam_role: + name: '{{ test_role }}' + create_instance_profile: yes + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role/" + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '/' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + - name: Short pause for role creation to finish + pause: + seconds: 10 + when: standard_pauses | bool + + - name: 'Minimal IAM Role wth instance profile (no change)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: yes + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after Role creation' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + + # =================================================================== + # Max Session Duration Manipulation + + - name: 'Update Max Session Duration (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + max_session_duration: 43200 + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Update Max Session Duration' + iam_role: + name: '{{ test_role }}' + max_session_duration: 43200 + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.max_session_duration == 43200 + + - name: 'Update Max Session Duration (no change)' + iam_role: + name: '{{ test_role }}' + max_session_duration: 43200 + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after updating Max Session Duration' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + + # =================================================================== + # Description Manipulation + + - name: 'Add Description (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + description: 'Ansible Test Role {{ resource_prefix }}' + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Add Description' + iam_role: + name: '{{ test_role }}' + description: 'Ansible Test Role {{ resource_prefix }}' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' + + - name: 'Add Description (no change)' + iam_role: + name: '{{ test_role }}' + description: 'Ansible Test Role {{ resource_prefix }}' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' + + - name: 'iam_role_info after adding Description' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + + - name: 'Update Description (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + description: 'Ansible Test Role (updated) {{ resource_prefix }}' + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Update Description' + iam_role: + name: '{{ test_role }}' + description: 'Ansible Test Role (updated) {{ resource_prefix }}' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' + + - name: 'Update Description (no change)' + iam_role: + name: '{{ test_role }}' + description: 'Ansible Test Role (updated) {{ resource_prefix }}' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' + + - name: 'iam_role_info after updating Description' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + + + # =================================================================== + # Tag Manipulation + + - name: 'Add Tag (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + tags: + TagA: ValueA + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Add Tag' + iam_role: + name: '{{ test_role }}' + tags: + TagA: ValueA + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.tags | length == 1 + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "ValueA" + + - name: 'Add Tag (no change)' + iam_role: + name: '{{ test_role }}' + tags: + TagA: ValueA + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "ValueA" + + - name: 'iam_role_info after adding Tags' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "ValueA" + + - name: 'Update Tag (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + tags: + TagA: AValue + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Update Tag' + iam_role: + name: '{{ test_role }}' + tags: + TagA: AValue + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "AValue" + + - name: 'Update Tag (no change)' + iam_role: + name: '{{ test_role }}' + tags: + TagA: AValue + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "AValue" + + - name: 'iam_role_info after updating Tag' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "AValue" + + - name: 'Add second Tag without purge (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + purge_tags: no + tags: + TagB: ValueB + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Add second Tag without purge' + iam_role: + name: '{{ test_role }}' + purge_tags: no + tags: + TagB: ValueB + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + + - name: 'Add second Tag without purge (no change)' + iam_role: + name: '{{ test_role }}' + purge_tags: no + tags: + TagB: ValueB + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + + - name: 'iam_role_info after adding second Tag without purge' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 2 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "AValue" + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + + - name: 'Purge first tag (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + purge_tags: yes + tags: + TagB: ValueB + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Purge first tag' + iam_role: + name: '{{ test_role }}' + purge_tags: yes + tags: + TagB: ValueB + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + + - name: 'Purge first tag (no change)' + iam_role: + name: '{{ test_role }}' + purge_tags: yes + tags: + TagB: ValueB + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + + - name: 'iam_role_info after purging first Tag' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" not in role_info.iam_roles[0].tags' + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + + + # =================================================================== + # Policy Manipulation + + - name: 'Add Managed Policy (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + purge_policies: no + managed_policy: + - '{{ safe_managed_policy }}' + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Add Managed Policy' + iam_role: + name: '{{ test_role }}' + purge_policies: no + managed_policy: + - '{{ safe_managed_policy }}' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + + - name: 'Add Managed Policy (no change)' + iam_role: + name: '{{ test_role }}' + purge_policies: no + managed_policy: + - '{{ safe_managed_policy }}' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after adding Managed Policy' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name not in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + + - name: 'Update Managed Policy without purge (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + purge_policies: no + managed_policy: + - '{{ custom_policy_name }}' + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Update Managed Policy without purge' + iam_role: + name: '{{ test_role }}' + purge_policies: no + managed_policy: + - '{{ custom_policy_name }}' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + + - name: 'Update Managed Policy without purge (no change)' + iam_role: + name: '{{ test_role }}' + purge_policies: no + managed_policy: + - '{{ custom_policy_name }}' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after updating Managed Policy without purge' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 2 + - safe_managed_policy in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + + # Managed Policies are purged by default + - name: 'Update Managed Policy with purge (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + managed_policy: + - '{{ custom_policy_name }}' + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Update Managed Policy with purge' + iam_role: + name: '{{ test_role }}' + managed_policy: + - '{{ custom_policy_name }}' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + + - name: 'Update Managed Policy with purge (no change)' + iam_role: + name: '{{ test_role }}' + managed_policy: + - '{{ custom_policy_name }}' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after updating Managed Policy with purge' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy not in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + + # =================================================================== + # Inline Policy (test _info behaviour) + + # XXX Not sure if it's a bug in Ansible or a "quirk" of AWS, but these two + # policies need to have at least different Sids or the second doesn't show + # up... + + - name: 'Attach inline policy a' + iam_policy: + state: present + iam_type: 'role' + iam_name: '{{ test_role }}' + policy_name: 'inline-policy-a' + policy_json: '{{ lookup("file", "deny-all-a.json") }}' + + - name: 'Attach inline policy b' + iam_policy: + state: present + iam_type: 'role' + iam_name: '{{ test_role }}' + policy_name: 'inline-policy-b' + policy_json: '{{ lookup("file", "deny-all-b.json") }}' + + - name: 'iam_role_info after attaching inline policies (using iam_policy)' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 2 + - '"inline-policy-a" in role_info.iam_roles[0].inline_policies' + - '"inline-policy-b" in role_info.iam_roles[0].inline_policies' + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy not in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + + # XXX iam_role fails to remove inline policies before deleting the role + - name: 'Detach inline policy a' + iam_policy: + state: absent + iam_type: 'role' + iam_name: '{{ test_role }}' + policy_name: 'inline-policy-a' + + - name: 'Detach inline policy b' + iam_policy: + state: absent + iam_type: 'role' + iam_name: '{{ test_role }}' + policy_name: 'inline-policy-b' + + # =================================================================== + # Role Removal + - name: 'Remove IAM Role (CHECK MODE)' + iam_role: + state: absent + name: '{{ test_role }}' + delete_instance_profile: yes + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + - name: 'Short pause for role removal to finish' + pause: + seconds: 10 + when: paranoid_pauses | bool + + - name: 'iam_role_info after deleting role in check mode' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + + - name: 'Remove IAM Role' + iam_role: + state: absent + name: '{{ test_role }}' + delete_instance_profile: yes + register: iam_role + - assert: + that: + - iam_role is changed + - name: 'Short pause for role removal to finish' + pause: + seconds: 10 + when: paranoid_pauses | bool + + - name: 'iam_role_info after deleting role' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + + - name: 'Remove IAM Role (should be gone already)' + iam_role: + state: absent + name: '{{ test_role }}' + delete_instance_profile: yes + register: iam_role + - assert: + that: + - iam_role is not changed + - name: 'Short pause for role removal to finish' + pause: + seconds: 10 + when: paranoid_pauses | bool + + # =================================================================== + # Boundary Policy (requires create_instance_profile: no) + - name: 'Create minimal role with no boundary policy' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + + - name: 'Configure Boundary Policy (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + boundary: '{{ boundary_policy }}' + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + + - name: 'Configure Boundary Policy' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + boundary: '{{ boundary_policy }}' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + + - name: 'Configure Boundary Policy (no change)' + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + boundary: '{{ boundary_policy }}' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after adding boundary policy' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + + - name: 'Remove IAM Role' + iam_role: + state: absent + name: '{{ test_role }}' + delete_instance_profile: yes + register: iam_role + - assert: + that: + - iam_role is changed + - name: Short pause for role removal to finish + pause: + seconds: 10 + when: paranoid_pauses | bool + + # =================================================================== + # Complex role Creation + - name: 'Complex IAM Role (CHECK MODE)' + iam_role: + name: '{{ test_role }}' + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + boundary: '{{ boundary_policy }}' + create_instance_profile: no + description: 'Ansible Test Role {{ resource_prefix }}' + managed_policy: + - '{{ safe_managed_policy }}' + - '{{ custom_policy_name }}' + max_session_duration: 43200 + path: '{{ test_path }}' + tags: + TagA: 'ValueA' + check_mode: yes + register: iam_role + - assert: + that: + - iam_role is changed + - name: Short pause for role creation to finish + pause: + seconds: 10 + when: standard_pauses | bool + + - name: 'iam_role_info after Complex Role creation in check_mode' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + + - name: 'Complex IAM Role' + iam_role: + name: '{{ test_role }}' + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + boundary: '{{ boundary_policy }}' + create_instance_profile: no + description: 'Ansible Test Role {{ resource_prefix }}' + managed_policy: + - '{{ safe_managed_policy }}' + - '{{ custom_policy_name }}' + max_session_duration: 43200 + path: '{{ test_path }}' + tags: + TagA: 'ValueA' + register: iam_role + - assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 2 + - iam_role.iam_role.max_session_duration == 43200 + - iam_role.iam_role.path == test_path + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + - name: Short pause for role creation to finish + pause: + seconds: 10 + when: standard_pauses | bool + + - name: 'Complex IAM role (no change)' + iam_role: + name: '{{ test_role }}' + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + boundary: '{{ boundary_policy }}' + create_instance_profile: no + description: 'Ansible Test Role {{ resource_prefix }}' + managed_policy: + - '{{ safe_managed_policy }}' + - '{{ custom_policy_name }}' + max_session_duration: 43200 + path: '{{ test_path }}' + tags: + TagA: 'ValueA' + register: iam_role + - assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + + - name: 'iam_role_info after Role creation' + iam_role_info: + name: '{{ test_role }}' + register: role_info + - assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 2 + - safe_managed_policy in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == test_path + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "ValueA" + + always: + # =================================================================== + # Cleanup + + # XXX iam_role fails to remove inline policies before deleting the role + - name: 'Detach inline policy a' + iam_policy: + state: absent + iam_type: 'role' + iam_name: '{{ test_role }}' + policy_name: 'inline-policy-a' + ignore_errors: true + + - name: 'Detach inline policy b' + iam_policy: + state: absent + iam_type: 'role' + iam_name: '{{ test_role }}' + policy_name: 'inline-policy-b' + ignore_errors: true + + - name: 'Remove IAM Role' + iam_role: + state: absent + name: '{{ test_role }}' + delete_instance_profile: yes + ignore_errors: true + + - name: 'Remove IAM Role (with path)' + iam_role: + state: absent + name: '{{ test_role }}' + path: '{{ test_path }}' + delete_instance_profile: yes + ignore_errors: true + + - name: 'iam_role_info after Role deletion' + iam_role_info: + name: '{{ test_role }}' + ignore_errors: true + + - name: 'Remove test managed policy' + iam_managed_policy: + state: absent + policy_name: '{{ custom_policy_name }}' From 00e04f2593af6c3ce7f6d7f33a36b20fcc10188e Mon Sep 17 00:00:00 2001 From: jillr Date: Tue, 3 Mar 2020 19:43:21 +0000 Subject: [PATCH 02/37] migration test cleanup This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/13b104b912784bb31a0bff23eed4c27b0f5e0283 --- plugins/modules/iam_role.py | 6 ++++- .../targets/iam_role/tasks/main.yml | 22 ++++++++++--------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 7b865efe896..d7da07b005c 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -198,7 +198,11 @@ from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import camel_dict_to_snake_dict, compare_policies -from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import AWSRetry, ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict, compare_aws_tags +from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import (AWSRetry, + ansible_dict_to_boto3_tag_list, + boto3_tag_list_to_ansible_dict, + compare_aws_tags, + ) try: from botocore.exceptions import ClientError, BotoCoreError diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml index 676179bd710..7a0ac006e94 100644 --- a/tests/integration/targets/iam_role/tasks/main.yml +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -31,6 +31,8 @@ region: '{{ aws_region }}' iam_role: assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + collections: + - ansible.amazon block: # =================================================================== # Parameter Checks @@ -1000,8 +1002,8 @@ - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name not in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - role_info.iam_roles[0].max_session_duration == 43200 - role_info.iam_roles[0].path == '/' - '"permissions_boundary" not in role_info.iam_roles[0]' @@ -1066,8 +1068,8 @@ - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - role_info.iam_roles[0].managed_policies | length == 2 - - safe_managed_policy in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - role_info.iam_roles[0].max_session_duration == 43200 - role_info.iam_roles[0].path == '/' - '"permissions_boundary" not in role_info.iam_roles[0]' @@ -1130,8 +1132,8 @@ - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy not in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - role_info.iam_roles[0].max_session_duration == 43200 - role_info.iam_roles[0].path == '/' - '"permissions_boundary" not in role_info.iam_roles[0]' @@ -1185,8 +1187,8 @@ - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy not in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - role_info.iam_roles[0].max_session_duration == 43200 - role_info.iam_roles[0].path == '/' - '"permissions_boundary" not in role_info.iam_roles[0]' @@ -1461,8 +1463,8 @@ - role_info.iam_roles[0].inline_policies | length == 0 - role_info.iam_roles[0].instance_profiles | length == 0 - role_info.iam_roles[0].managed_policies | length == 2 - - safe_managed_policy in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - role_info.iam_roles[0].max_session_duration == 43200 - role_info.iam_roles[0].path == test_path - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy From a0d89fdb4465a586ae6c35ce058ab2308f881fed Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Wed, 25 Mar 2020 15:39:40 -0700 Subject: [PATCH 03/37] Rename collection (#12) * Rename core collection Rename references to ansible.amazon to amazon.aws. * Rename community.amazon to community.aws Fix pep8 line lengths for rewritten amazon.aws imports * Missed a path in shippable.sh * Dependency repos moved This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/235c5db571cc45db5839476c94356c9b91e1f228 --- plugins/modules/iam_role.py | 18 +++++++++--------- plugins/modules/iam_role_info.py | 8 ++++---- .../targets/iam_role/tasks/main.yml | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index d7da07b005c..fafa00541dc 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -93,8 +93,8 @@ type: bool requirements: [ botocore, boto3 ] extends_documentation_fragment: -- ansible.amazon.aws -- ansible.amazon.ec2 +- amazon.aws.aws +- amazon.aws.ec2 ''' @@ -196,13 +196,13 @@ import json -from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule -from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import camel_dict_to_snake_dict, compare_policies -from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import (AWSRetry, - ansible_dict_to_boto3_tag_list, - boto3_tag_list_to_ansible_dict, - compare_aws_tags, - ) +from ansible_collections.amazon.aws.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict, compare_policies +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (AWSRetry, + ansible_dict_to_boto3_tag_list, + boto3_tag_list_to_ansible_dict, + compare_aws_tags, + ) try: from botocore.exceptions import ClientError, BotoCoreError diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 5a3753fd524..9912cfdaa20 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -35,8 +35,8 @@ - Mutually exclusive with I(name). type: str extends_documentation_fragment: -- ansible.amazon.aws -- ansible.amazon.ec2 +- amazon.aws.aws +- amazon.aws.ec2 ''' @@ -156,8 +156,8 @@ except ImportError: pass # caught by AnsibleAWSModule -from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule -from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict, AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict, AWSRetry @AWSRetry.exponential_backoff() diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml index 7a0ac006e94..34c17af3369 100644 --- a/tests/integration/targets/iam_role/tasks/main.yml +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -32,7 +32,7 @@ iam_role: assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' collections: - - ansible.amazon + - amazon.aws block: # =================================================================== # Parameter Checks From a36b02945ba3c72603063cc9b65947519a0204e9 Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Tue, 19 May 2020 16:06:12 -0700 Subject: [PATCH 04/37] Remove METADATA and cleanup galaxy.yml (#70) * Remove ANSIBLE_METADATA entirely, see ansible/ansible/pull/69454. Remove `license` field from galaxy.yml, in favor of `license_file`. This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/05672a64e2362cc2d865b5af6a57da6bc3cd08e3 --- plugins/modules/iam_role.py | 4 ---- plugins/modules/iam_role_info.py | 5 ----- 2 files changed, 9 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index fafa00541dc..432fcab64ad 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -4,10 +4,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - DOCUMENTATION = ''' --- diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 9912cfdaa20..bf32d32adbf 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -6,11 +6,6 @@ __metaclass__ = type -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - DOCUMENTATION = ''' --- module: iam_role_info From 5ba54ebfb216fff909c4077e3b9b851c5a4b322e Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Tue, 16 Jun 2020 11:23:52 -0700 Subject: [PATCH 05/37] Collections related fixes for CI (#96) * Update module deprecations Switch version to `removed_at_date` * Don't install amazon.aws from galaxy We've been using galaxy to install amazon.aws in shippable, but that doesn't really work if we aren't publising faster. Get that collection from git so it is most up to date. * We need to declare python test deps now * missed a python dep This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/7cd211e9383db26bc2aa4cc06e657cf60ed0acc0 --- plugins/modules/iam_role.py | 2 +- plugins/modules/iam_role_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 432fcab64ad..dc96bc93f3f 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -629,7 +629,7 @@ def main(): if module.params.get('purge_policies') is None: module.deprecate('In Ansible 2.14 the default value of purge_policies will change from true to false.' - ' To maintain the existing behaviour explicity set purge_policies=true', version='2.14') + ' To maintain the existing behaviour explicity set purge_policies=true', date='2022-06-01', collection_name='community.aws') if module.params.get('boundary'): if module.params.get('create_instance_profile'): diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index bf32d32adbf..7fdb4ac58fa 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -242,7 +242,7 @@ def main(): supports_check_mode=True, mutually_exclusive=[['name', 'path_prefix']]) if module._name == 'iam_role_facts': - module.deprecate("The 'iam_role_facts' module has been renamed to 'iam_role_info'", version='2.13') + module.deprecate("The 'iam_role_facts' module has been renamed to 'iam_role_info'", date='2021-12-01', collection_name='community.aws') client = module.client('iam') From 5d711f8a34afed9da89999296d5fd5651e74fd88 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Wed, 17 Jun 2020 01:24:54 +0530 Subject: [PATCH 06/37] Update Examples with FQCN (#67) Updated module examples with FQCN Signed-off-by: Abhijeet Kasurde This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/98173aefbbceed7fc0d9db62687b73f96a55a999 --- plugins/modules/iam_role.py | 10 +++++----- plugins/modules/iam_role_info.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index dc96bc93f3f..1ce2ceae9d0 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -45,7 +45,7 @@ description: - A list of managed policy ARNs or, since Ansible 2.4, a list of either managed policy ARNs or friendly names. - To remove all policies set I(purge_polices=true) and I(managed_policies=[None]). - - To embed an inline policy, use M(iam_policy). + - To embed an inline policy, use M(community.aws.iam_policy). aliases: ['managed_policy'] type: list max_session_duration: @@ -98,7 +98,7 @@ # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Create a role with description and tags - iam_role: + community.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file','policy.json') }}" description: This is My New Role @@ -106,20 +106,20 @@ env: dev - name: "Create a role and attach a managed policy called 'PowerUserAccess'" - iam_role: + community.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file','policy.json') }}" managed_policies: - arn:aws:iam::aws:policy/PowerUserAccess - name: Keep the role created above but remove all managed policies - iam_role: + community.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file','policy.json') }}" managed_policies: [] - name: Delete the role - iam_role: + community.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file', 'policy.json') }}" state: absent diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 7fdb4ac58fa..ac000ae8552 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -36,16 +36,16 @@ ''' EXAMPLES = ''' -# find all existing IAM roles -- iam_role_info: +- name: find all existing IAM roles + community.aws.iam_role_info: register: result -# describe a single role -- iam_role_info: +- name: describe a single role + community.aws.iam_role_info: name: MyIAMRole -# describe all roles matching a path prefix -- iam_role_info: +- name: describe all roles matching a path prefix + community.aws.iam_role_info: path_prefix: /application/path ''' From c1c75a24e0b24b09ba17a43e981063e6786dd48f Mon Sep 17 00:00:00 2001 From: flowerysong Date: Tue, 16 Jun 2020 19:30:00 -0400 Subject: [PATCH 07/37] Update module_utils paths to remove aws subdir (#23) Co-authored-by: Ezekiel Hendrickson This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/10853d9441a586ba177006dd889325cfb24a3dd6 --- plugins/modules/iam_role.py | 2 +- plugins/modules/iam_role_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 1ce2ceae9d0..09db6ed9643 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -192,7 +192,7 @@ import json -from ansible_collections.amazon.aws.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict, compare_policies from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (AWSRetry, ansible_dict_to_boto3_tag_list, diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index ac000ae8552..6b15c186360 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -151,7 +151,7 @@ except ImportError: pass # caught by AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.aws.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict, AWSRetry From 221bbfa7d4f948b409149e4b610b8eb717d5a46f Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Wed, 17 Jun 2020 09:31:32 -0700 Subject: [PATCH 08/37] Update docs (#99) * Update docs Remove .git from repo url so links in readme will generate correctly Add required ansible version Run latest version of add_docs.py Add version_added string to modules * galaxy.yml was missing authors This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/96ee268e5267f5b12c3d59892bc1279f75aa3135 --- plugins/modules/iam_role.py | 1 + plugins/modules/iam_role_info.py | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 09db6ed9643..b20c564734a 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -8,6 +8,7 @@ DOCUMENTATION = ''' --- module: iam_role +version_added: 1.0.0 short_description: Manage AWS IAM roles description: - Manage AWS IAM roles. diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 6b15c186360..95eabdb95ab 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -9,6 +9,7 @@ DOCUMENTATION = ''' --- module: iam_role_info +version_added: 1.0.0 short_description: Gather information on IAM roles description: - Gathers information about IAM roles. From 03e1336bff03464b09869e1e04c7985060224bc7 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Thu, 16 Jul 2020 01:31:41 +0530 Subject: [PATCH 09/37] Docs: sanity fixes (#133) Signed-off-by: Abhijeet Kasurde This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/059cf9efc95bb976de21ab4f8e4d9ddd001983fc --- plugins/modules/iam_role.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index b20c564734a..9a2eaca8cfe 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -5,7 +5,7 @@ __metaclass__ = type -DOCUMENTATION = ''' +DOCUMENTATION = r''' --- module: iam_role version_added: 1.0.0 @@ -49,6 +49,7 @@ - To embed an inline policy, use M(community.aws.iam_policy). aliases: ['managed_policy'] type: list + elements: str max_session_duration: description: - The maximum duration (in seconds) of a session when assuming the role. @@ -95,7 +96,7 @@ ''' -EXAMPLES = ''' +EXAMPLES = r''' # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Create a role with description and tags @@ -126,7 +127,7 @@ state: absent ''' -RETURN = ''' +RETURN = r''' iam_role: description: dictionary containing the IAM Role data returned: success @@ -613,7 +614,7 @@ def main(): name=dict(type='str', required=True), path=dict(type='str', default="/"), assume_role_policy_document=dict(type='json'), - managed_policies=dict(type='list', aliases=['managed_policy']), + managed_policies=dict(type='list', aliases=['managed_policy'], elements='str'), max_session_duration=dict(type='int'), state=dict(type='str', choices=['present', 'absent'], default='present'), description=dict(type='str'), @@ -630,7 +631,7 @@ def main(): if module.params.get('purge_policies') is None: module.deprecate('In Ansible 2.14 the default value of purge_policies will change from true to false.' - ' To maintain the existing behaviour explicity set purge_policies=true', date='2022-06-01', collection_name='community.aws') + ' To maintain the existing behaviour explicitly set purge_policies=true', date='2022-06-01', collection_name='community.aws') if module.params.get('boundary'): if module.params.get('create_instance_profile'): From fd627cd368a0928901775ca48160e409d1bbc61e Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Fri, 5 Feb 2021 09:43:09 +0100 Subject: [PATCH 10/37] Cleanup - use is_boto3_error_(message|code) (#268) * Reorder imports * Make use of is_boto3_error_message * Mass-migration over to is_boto3_error_code * Remove unused imports * unused vars in exception * Improve consistency around catching BotoCoreError and ClientError * Remove unused imports * Remove unused 'PolicyError' from iam_policy_info * Avoid catching botocore.exceptions.ClientError when we only want some error codes * Import camel_dict_to_snake_dict/snake_dict_to_camel_dict from ansible.module_utils.common.dict_transformations This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/4cf52ef67682fd4f9ed2a707adfbafffe7b88f15 --- plugins/modules/iam_role.py | 76 +++++++++++++++----------------- plugins/modules/iam_role_info.py | 15 ++++--- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 9a2eaca8cfe..ddc8ad23041 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -194,19 +194,21 @@ import json -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict, compare_policies -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (AWSRetry, - ansible_dict_to_boto3_tag_list, - boto3_tag_list_to_ansible_dict, - compare_aws_tags, - ) - try: - from botocore.exceptions import ClientError, BotoCoreError + import botocore except ImportError: pass # caught by AnsibleAWSModule +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict + +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_policies + def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc): if not compare_policies(current_policy_doc, json.loads(new_policy_doc)): @@ -242,7 +244,7 @@ def attach_policies(connection, module, policies_to_attach, params): try: if not module.check_mode: connection.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn, aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, params['RoleName'])) changed = True return changed @@ -254,7 +256,7 @@ def remove_policies(connection, module, policies_to_remove, params): try: if not module.check_mode: connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy, aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, params['RoleName'])) changed = True return changed @@ -293,7 +295,7 @@ def create_basic_role(connection, module, params): else: role = {'MadeInCheckMode': True} role['AssumeRolePolicyDocument'] = json.loads(params['AssumeRolePolicyDocument']) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to create role") return role @@ -312,7 +314,7 @@ def update_role_assumed_policy(connection, module, params, role): RoleName=params['RoleName'], PolicyDocument=json.dumps(json.loads(params['AssumeRolePolicyDocument'])), aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(params['RoleName'])) return True @@ -329,7 +331,7 @@ def update_role_description(connection, module, params, role): try: connection.update_role_description(RoleName=params['RoleName'], Description=params['Description'], aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update description for role {0}".format(params['RoleName'])) return True @@ -346,7 +348,7 @@ def update_role_max_session_duration(connection, module, params, role): try: connection.update_role(RoleName=params['RoleName'], MaxSessionDuration=params['MaxSessionDuration'], aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update maximum session duration for role {0}".format(params['RoleName'])) return True @@ -364,12 +366,12 @@ def update_role_permissions_boundary(connection, module, params, role): if params.get('PermissionsBoundary') == '': try: connection.delete_role_permissions_boundary(RoleName=params['RoleName'], aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(params['RoleName'])) else: try: connection.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'], aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(params['RoleName'])) return True @@ -457,7 +459,7 @@ def create_instance_profiles(connection, module, params, role): # Fetch existing Profiles try: instance_profiles = connection.list_instance_profiles_for_role(RoleName=params['RoleName'], aws_retry=True)['InstanceProfiles'] - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName'])) # Profile already exists @@ -470,20 +472,17 @@ def create_instance_profiles(connection, module, params, role): # Make sure an instance profile is created try: connection.create_instance_profile(InstanceProfileName=params['RoleName'], Path=params['Path'], aws_retry=True) - except ClientError as e: + except is_boto3_error_code('EntityAlreadyExists'): # If the profile already exists, no problem, move on. # Implies someone's changing things at the same time... - if e.response['Error']['Code'] == 'EntityAlreadyExists': - return False - else: - module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(params['RoleName'])) - except BotoCoreError as e: + return False + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(params['RoleName'])) # And attach the role to the profile try: connection.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'], aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to attach role {0} to instance profile {0}".format(params['RoleName'])) return True @@ -495,7 +494,7 @@ def remove_instance_profiles(connection, module, role_params, role): try: instance_profiles = connection.list_instance_profiles_for_role(aws_retry=True, **role_params)['InstanceProfiles'] - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) # Remove the role from the instance profile(s) @@ -508,9 +507,9 @@ def remove_instance_profiles(connection, module, role_params, role): if delete_profiles: try: connection.delete_instance_profile(InstanceProfileName=profile_name, aws_retry=True) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove instance profile {0}".format(profile_name)) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove role {0} from instance profile {1}".format(role_name, profile_name)) @@ -537,7 +536,7 @@ def destroy_role(connection, module): try: if not module.check_mode: connection.delete_role(aws_retry=True, **role_params) - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to delete role") module.exit_json(changed=True) @@ -546,26 +545,23 @@ def destroy_role(connection, module): def get_role_with_backoff(connection, module, name): try: return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(connection.get_role)(RoleName=name)['Role'] - except (BotoCoreError, ClientError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) def get_role(connection, module, name): try: return connection.get_role(RoleName=name, aws_retry=True)['Role'] - except ClientError as e: - if e.response['Error']['Code'] == 'NoSuchEntity': - return None - else: - module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) - except BotoCoreError as e: + except is_boto3_error_code('NoSuchEntity'): + return None + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) def get_attached_policy_list(connection, module, name): try: return connection.list_attached_role_policies(RoleName=name, aws_retry=True)['AttachedPolicies'] - except (ClientError, BotoCoreError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) @@ -575,7 +571,7 @@ def get_role_tags(connection, module): return {} try: return boto3_tag_list_to_ansible_dict(connection.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) - except (ClientError, BotoCoreError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list tags for role {0}".format(role_name)) @@ -590,7 +586,7 @@ def update_role_tags(connection, module, params, role): try: existing_tags = boto3_tag_list_to_ansible_dict(connection.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) - except (ClientError, KeyError): + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, KeyError): existing_tags = {} tags_to_add, tags_to_remove = compare_aws_tags(existing_tags, new_tags, purge_tags=purge_tags) @@ -601,7 +597,7 @@ def update_role_tags(connection, module, params, role): connection.untag_role(RoleName=role_name, TagKeys=tags_to_remove, aws_retry=True) if tags_to_add: connection.tag_role(RoleName=role_name, Tags=ansible_dict_to_boto3_tag_list(tags_to_add), aws_retry=True) - except (ClientError, BotoCoreError) as e: + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg='Unable to set tags for role %s' % role_name) changed = bool(tags_to_add) or bool(tags_to_remove) diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 95eabdb95ab..132bdeedcc9 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -152,8 +152,12 @@ except ImportError: pass # caught by AnsibleAWSModule +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict + from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict, AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict @AWSRetry.exponential_backoff() @@ -208,12 +212,9 @@ def describe_iam_roles(module, client): if name: try: roles = [client.get_role(RoleName=name)['Role']] - except botocore.exceptions.ClientError as e: - if e.response['Error']['Code'] == 'NoSuchEntity': - return [] - else: - module.fail_json_aws(e, msg="Couldn't get IAM role %s" % name) - except botocore.exceptions.BotoCoreError as e: + except is_boto3_error_code('NoSuchEntity'): + return [] + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Couldn't get IAM role %s" % name) else: params = dict() From be221069367d8554501d33eb048ac6d73438360a Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Sat, 13 Feb 2021 14:19:21 +0100 Subject: [PATCH 11/37] Add comments to the disabled/unsupported integration test aliase files (#411) Add some additional comments so we know *why* the various tests aren't running. Looks like most of them just need policy updates This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/0d245596966100516f1eeab5dcf26ee0ebca24b5 --- tests/integration/targets/iam_role/aliases | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/iam_role/aliases b/tests/integration/targets/iam_role/aliases index 3d7a2c9f14c..483c861158c 100644 --- a/tests/integration/targets/iam_role/aliases +++ b/tests/integration/targets/iam_role/aliases @@ -1,3 +1,9 @@ -iam_role_info +# reason: missing-policy +# It should be possible to test iam_role by limiting which policies can be +# attached to the roles. +# Careful review is needed prior to adding this to the main CI. unsupported + cloud/aws + +iam_role_info From dece60e4f5e288a2bdfd15c0e6c4aae85348ec75 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 6 May 2021 21:01:46 +0200 Subject: [PATCH 12/37] Update the default module requirements from python 2.6/boto to python 3.6/boto3 This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/c097c55293be0834a2b9d394733ec28965d142d7 --- plugins/modules/iam_role.py | 1 - plugins/modules/iam_role_info.py | 1 - 2 files changed, 2 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index ddc8ad23041..45551cdf188 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -89,7 +89,6 @@ - Remove tags not listed in I(tags) when tags is specified. default: true type: bool -requirements: [ botocore, boto3 ] extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 132bdeedcc9..0a627d10cc7 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -14,7 +14,6 @@ description: - Gathers information about IAM roles. - This module was called C(iam_role_facts) before Ansible 2.9. The usage did not change. -requirements: [ boto3 ] author: - "Will Thames (@willthames)" options: From d1a6f59b3cf0d3f85d46555a34dd719d0f5902f0 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Fri, 6 Aug 2021 10:43:00 +0200 Subject: [PATCH 13/37] Remove code testing for unsupported versions of boto3/botocore This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/2c7557df5e718a3dd7d5c1eadb7b1af958c702ed --- plugins/modules/iam_role.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 45551cdf188..e696d9d7417 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -34,7 +34,6 @@ - Boundaries cannot be set on Instance Profiles, as such if this option is specified then I(create_instance_profile) must be C(false). - This is intended for roles/users that have permissions to create new IAM objects. - For more information on boundaries, see U(https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html). - - Requires botocore 1.10.57 or above. aliases: [boundary_policy_arn] type: str assume_role_policy_document: @@ -82,7 +81,6 @@ tags: description: - Tag dict to apply to the queue. - - Requires botocore 1.12.46 or above. type: dict purge_tags: description: @@ -566,8 +564,6 @@ def get_attached_policy_list(connection, module, name): def get_role_tags(connection, module): role_name = module.params.get('name') - if not hasattr(connection, 'list_role_tags'): - return {} try: return boto3_tag_list_to_ansible_dict(connection.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: @@ -633,12 +629,6 @@ def main(): module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.") if not module.params.get('boundary').startswith('arn:aws:iam'): module.fail_json(msg="Boundary policy must be an ARN") - if module.params.get('tags') is not None and not module.botocore_at_least('1.12.46'): - module.fail_json(msg="When managing tags botocore must be at least v1.12.46. " - "Current versions: boto3-{boto3_version} botocore-{botocore_version}".format(**module._gather_versions())) - if module.params.get('boundary') is not None and not module.botocore_at_least('1.10.57'): - module.fail_json(msg="When using a boundary policy, botocore must be at least v1.10.57. " - "Current versions: boto3-{boto3_version} botocore-{botocore_version}".format(**module._gather_versions())) if module.params.get('max_session_duration'): max_session_duration = module.params.get('max_session_duration') if max_session_duration < 3600 or max_session_duration > 43200: From 8b6f5a871166d61f3fa14d92990d33aa597f0971 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 12 Aug 2021 21:01:04 +0200 Subject: [PATCH 14/37] use a generator rather than list comprehension when using any()/all() See also https://www.python.org/dev/peps/pep-0289/#rationale This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/87304dd3c476f8cd0b0ae0947dce2070ae87efcb --- plugins/modules/iam_role.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index e696d9d7417..e95ed0afddf 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -221,7 +221,7 @@ def _list_policies(connection): def convert_friendly_names_to_arns(connection, module, policy_names): - if not any([not policy.startswith('arn:') for policy in policy_names]): + if not any(not policy.startswith('arn:') for policy in policy_names): return policy_names allpolicies = {} policies = _list_policies(connection) From a3898c7e408badaf913cf2c1ec3628ea1e2a5951 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Mon, 23 Aug 2021 17:31:43 -0700 Subject: [PATCH 15/37] iam_role.py: update update_role_description to update_role This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/f3446fd9e50714c66bbf9372100088ee40c266c2 --- plugins/modules/iam_role.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index e95ed0afddf..f5699edf8b5 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -327,7 +327,7 @@ def update_role_description(connection, module, params, role): return True try: - connection.update_role_description(RoleName=params['RoleName'], Description=params['Description'], aws_retry=True) + connection.update_role(RoleName=params['RoleName'], Description=params['Description'], aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update description for role {0}".format(params['RoleName'])) return True From 3e08f8aac58d25d0a1cea9ba79004f4564e79b46 Mon Sep 17 00:00:00 2001 From: mark-woolley Date: Fri, 8 Oct 2021 17:55:30 +0100 Subject: [PATCH 16/37] iam_role_info jittered backoff This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/bec291e37b6483f383db62445c85c671cae1f9bf --- plugins/modules/iam_role.py | 3 +-- plugins/modules/iam_role_info.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index f5699edf8b5..62d76f46c9a 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -214,9 +214,8 @@ def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc): return False -@AWSRetry.jittered_backoff() def _list_policies(connection): - paginator = connection.get_paginator('list_policies') + paginator = connection.get_paginator('list_policies', aws_retry=True) return paginator.paginate().build_full_result()['Policies'] diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 0a627d10cc7..9c089cc35d6 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -159,27 +159,23 @@ from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict -@AWSRetry.exponential_backoff() def list_iam_roles_with_backoff(client, **kwargs): - paginator = client.get_paginator('list_roles') + paginator = client.get_paginator('list_roles', aws_retry=True) return paginator.paginate(**kwargs).build_full_result() -@AWSRetry.exponential_backoff() def list_iam_role_policies_with_backoff(client, role_name): - paginator = client.get_paginator('list_role_policies') + paginator = client.get_paginator('list_role_policies', aws_retry=True) return paginator.paginate(RoleName=role_name).build_full_result()['PolicyNames'] -@AWSRetry.exponential_backoff() def list_iam_attached_role_policies_with_backoff(client, role_name): - paginator = client.get_paginator('list_attached_role_policies') + paginator = client.get_paginator('list_attached_role_policies', aws_retry=True) return paginator.paginate(RoleName=role_name).build_full_result()['AttachedPolicies'] -@AWSRetry.exponential_backoff() def list_iam_instance_profiles_for_role_with_backoff(client, role_name): - paginator = client.get_paginator('list_instance_profiles_for_role') + paginator = client.get_paginator('list_instance_profiles_for_role', aws_retry=True) return paginator.paginate(RoleName=role_name).build_full_result()['InstanceProfiles'] @@ -210,7 +206,7 @@ def describe_iam_roles(module, client): path_prefix = module.params['path_prefix'] if name: try: - roles = [client.get_role(RoleName=name)['Role']] + roles = [client.get_role(aws_retry=True, RoleName=name)['Role']] except is_boto3_error_code('NoSuchEntity'): return [] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except @@ -245,7 +241,7 @@ def main(): if module._name == 'iam_role_facts': module.deprecate("The 'iam_role_facts' module has been renamed to 'iam_role_info'", date='2021-12-01', collection_name='community.aws') - client = module.client('iam') + client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) module.exit_json(changed=False, iam_roles=describe_iam_roles(module, client)) From 1997bd5b658e55c489e9e8a78e51ce1340df1658 Mon Sep 17 00:00:00 2001 From: mark-woolley Date: Sat, 9 Oct 2021 16:16:27 +0100 Subject: [PATCH 17/37] PR feedback This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/3ef4fa622026778806a1a139f22952e94cbfe6b7 --- plugins/modules/iam_role.py | 3 ++- plugins/modules/iam_role_info.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 62d76f46c9a..f5699edf8b5 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -214,8 +214,9 @@ def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc): return False +@AWSRetry.jittered_backoff() def _list_policies(connection): - paginator = connection.get_paginator('list_policies', aws_retry=True) + paginator = connection.get_paginator('list_policies') return paginator.paginate().build_full_result()['Policies'] diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 9c089cc35d6..acc0094cf96 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -159,23 +159,25 @@ from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict +@AWSRetry.jittered_backoff() def list_iam_roles_with_backoff(client, **kwargs): - paginator = client.get_paginator('list_roles', aws_retry=True) + paginator = client.get_paginator('list_roles') return paginator.paginate(**kwargs).build_full_result() +@AWSRetry.jittered_backoff() def list_iam_role_policies_with_backoff(client, role_name): - paginator = client.get_paginator('list_role_policies', aws_retry=True) + paginator = client.get_paginator('list_role_policies') return paginator.paginate(RoleName=role_name).build_full_result()['PolicyNames'] - +@AWSRetry.jittered_backoff() def list_iam_attached_role_policies_with_backoff(client, role_name): - paginator = client.get_paginator('list_attached_role_policies', aws_retry=True) + paginator = client.get_paginator('list_attached_role_policies') return paginator.paginate(RoleName=role_name).build_full_result()['AttachedPolicies'] - +@AWSRetry.jittered_backoff() def list_iam_instance_profiles_for_role_with_backoff(client, role_name): - paginator = client.get_paginator('list_instance_profiles_for_role', aws_retry=True) + paginator = client.get_paginator('list_instance_profiles_for_role') return paginator.paginate(RoleName=role_name).build_full_result()['InstanceProfiles'] @@ -206,7 +208,7 @@ def describe_iam_roles(module, client): path_prefix = module.params['path_prefix'] if name: try: - roles = [client.get_role(aws_retry=True, RoleName=name)['Role']] + roles = [client.get_role(RoleName=name, aws_retry=True)['Role']] except is_boto3_error_code('NoSuchEntity'): return [] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except From 16a096145108952532946f4081692cb1d1ec5d9a Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Sat, 9 Oct 2021 22:01:18 +0200 Subject: [PATCH 18/37] Whitespace linting This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/df9abcbf6bd2988c707a4e17d00955efb8bbb1a3 --- plugins/modules/iam_role_info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index acc0094cf96..a08df455fad 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -170,11 +170,13 @@ def list_iam_role_policies_with_backoff(client, role_name): paginator = client.get_paginator('list_role_policies') return paginator.paginate(RoleName=role_name).build_full_result()['PolicyNames'] + @AWSRetry.jittered_backoff() def list_iam_attached_role_policies_with_backoff(client, role_name): paginator = client.get_paginator('list_attached_role_policies') return paginator.paginate(RoleName=role_name).build_full_result()['AttachedPolicies'] + @AWSRetry.jittered_backoff() def list_iam_instance_profiles_for_role_with_backoff(client, role_name): paginator = client.get_paginator('list_instance_profiles_for_role') From 9544fba2cadae939d8c03e492e74d6859f95c49c Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 14 Oct 2021 10:04:22 +0200 Subject: [PATCH 19/37] docs - Remove references to old (unsupported) versions of Ansible This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/623dee5cee3f664fef5d3667030ad6cd0baba90d --- plugins/modules/iam_role.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index f5699edf8b5..356741e4ec5 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -43,7 +43,7 @@ type: json managed_policies: description: - - A list of managed policy ARNs or, since Ansible 2.4, a list of either managed policy ARNs or friendly names. + - A list of managed policy ARNs, managed policy ARNs or friendly names. - To remove all policies set I(purge_polices=true) and I(managed_policies=[None]). - To embed an inline policy, use M(community.aws.iam_policy). aliases: ['managed_policy'] From 1d02b8c13287e78f966cdda81e59bf482edc2dad Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 14 Oct 2021 10:14:21 +0200 Subject: [PATCH 20/37] replace Ansible version deprecations with date deprecations (in line with the module.deprecate date entries) This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/3ae2b3efa4588ac1284c565d6f73543a555a5edd --- plugins/modules/iam_role.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 356741e4ec5..948358b184e 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -57,7 +57,7 @@ purge_policies: description: - When I(purge_policies=true) any managed policies not listed in I(managed_policies) will be detatched. - - By default I(purge_policies=true). In Ansible 2.14 this will be changed to I(purge_policies=false). + - By default I(purge_policies=true). In a release after 2022-06-01 this will be changed to I(purge_policies=false). type: bool aliases: ['purge_policy', 'purge_managed_policies'] state: @@ -621,7 +621,7 @@ def main(): supports_check_mode=True) if module.params.get('purge_policies') is None: - module.deprecate('In Ansible 2.14 the default value of purge_policies will change from true to false.' + module.deprecate('After 2022-06-01 the default value of purge_policies will change from true to false.' ' To maintain the existing behaviour explicitly set purge_policies=true', date='2022-06-01', collection_name='community.aws') if module.params.get('boundary'): From b27cd33ee3fbd815c071170fe2be30c54678e540 Mon Sep 17 00:00:00 2001 From: Mark Woolley Date: Mon, 18 Oct 2021 11:14:32 +0100 Subject: [PATCH 21/37] Add waiter to the iam_role module (#767) Add waiter to the iam_role module SUMMARY This change adds the wait param used in other AWS modules, adding usage of a waiter for the iam_role creation / updates. Currently there is no waiting done to ensure the iam_role has actually created and is available before exiting. The tests have also been split up into separate files to make it a bit more manageable. Fixes: #710 ISSUE TYPE Feature Pull Request COMPONENT NAME iam_role ADDITIONAL INFORMATION Successful run completed of the iam_role integration test suite locally: ansible-test integration --docker centos8 -v iam_role --allow-unsupported PLAY RECAP ********************************************************************* testhost : ok=198 changed=46 unreachable=0 failed=0 skipped=0 rescued=0 ignored=7 AWS ACTIONS: ['iam:AddRoleToInstanceProfile', 'iam:AttachRolePolicy', 'iam:CreateInstanceProfile', 'iam:CreatePolicy', 'iam:CreateRole', 'iam:DeleteInstanceProfile', 'iam:DeletePolicy', 'iam:DeleteRole', 'iam:DeleteRolePermissionsBoundary', 'iam:DeleteRolePolicy', 'iam:DetachRolePolicy', 'iam:GetRole', 'iam:GetRolePolicy', 'iam:ListAttachedRolePolicies', 'iam:ListEntitiesForPolicy', 'iam:ListInstanceProfilesForRole', 'iam:ListPolicies', 'iam:ListPolicyVersions', 'iam:ListRolePolicies', 'iam:ListRoleTags', 'iam:ListRoles', 'iam:PutRolePermissionsBoundary', 'iam:PutRolePolicy', 'iam:RemoveRoleFromInstanceProfile', 'iam:TagRole', 'iam:UntagRole', 'iam:UpdateRole'] Run command: docker exec 56cb328c6d9af293d9e820e1f2a94fb8ca87e0769b2b9b6d46bad661f9edde65 tar czf /root/output.tgz --exclude .tmp -C /root/ansible/ansible_collections/community/aws/tests output Run command: docker exec -i 56cb328c6d9af293d9e820e1f2a94fb8ca87e0769b2b9b6d46bad661f9edde65 dd if=/root/output.tgz bs=65536 Run command: tar oxzf /tmp/ansible-result-k2lnga3v.tgz -C /mnt/c/Users/mark.woolley/Documents/GitHub/public/ansible_collections/community/aws/tests Run command: docker rm -f 56cb328c6d9af293d9e820e1f2a94fb8ca87e0769b2b9b6d46bad661f9edde65 Reviewed-by: Mark Chappell Reviewed-by: None This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/81d9abdad709752a402ed5c181c83748a91e0795 --- plugins/modules/iam_role.py | 189 +- .../targets/iam_role/defaults/main.yml | 2 - .../iam_role/tasks/boundary_policy.yml | 82 + .../iam_role/tasks/complex_role_creation.yml | 110 ++ .../iam_role/tasks/creation_deletion.yml | 353 ++++ .../iam_role/tasks/description_update.yml | 124 ++ .../iam_role/tasks/inline_policy_update.yml | 63 + .../targets/iam_role/tasks/main.yml | 1559 +---------------- .../iam_role/tasks/max_session_update.yml | 61 + .../iam_role/tasks/parameter_checks.yml | 90 + .../targets/iam_role/tasks/policy_update.yml | 208 +++ .../targets/iam_role/tasks/role_removal.yml | 53 + .../targets/iam_role/tasks/tags_update.yml | 286 +++ 13 files changed, 1637 insertions(+), 1543 deletions(-) create mode 100644 tests/integration/targets/iam_role/tasks/boundary_policy.yml create mode 100644 tests/integration/targets/iam_role/tasks/complex_role_creation.yml create mode 100644 tests/integration/targets/iam_role/tasks/creation_deletion.yml create mode 100644 tests/integration/targets/iam_role/tasks/description_update.yml create mode 100644 tests/integration/targets/iam_role/tasks/inline_policy_update.yml create mode 100644 tests/integration/targets/iam_role/tasks/max_session_update.yml create mode 100644 tests/integration/targets/iam_role/tasks/parameter_checks.yml create mode 100644 tests/integration/targets/iam_role/tasks/policy_update.yml create mode 100644 tests/integration/targets/iam_role/tasks/role_removal.yml create mode 100644 tests/integration/targets/iam_role/tasks/tags_update.yml diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 948358b184e..7ca0d8c4fbb 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -87,6 +87,17 @@ - Remove tags not listed in I(tags) when tags is specified. default: true type: bool + wait_timeout: + description: + - How long (in seconds) to wait for creation / update to complete. + default: 120 + type: int + wait: + description: + - When I(wait=True) the module will wait for up to I(wait_timeout) seconds + for IAM role creation before returning. + default: True + type: bool extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 @@ -215,16 +226,40 @@ def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc): @AWSRetry.jittered_backoff() -def _list_policies(connection): - paginator = connection.get_paginator('list_policies') +def _list_policies(): + paginator = client.get_paginator('list_policies') return paginator.paginate().build_full_result()['Policies'] -def convert_friendly_names_to_arns(connection, module, policy_names): +def wait_iam_exists(): + if module.check_mode: + return + if not module.params.get('wait'): + return + + role_name = module.params.get('name') + wait_timeout = module.params.get('wait_timeout') + + delay = min(wait_timeout, 5) + max_attempts = wait_timeout // delay + + try: + waiter = client.get_waiter('role_exists') + waiter.wait( + WaiterConfig={'Delay': delay, 'MaxAttempts': max_attempts}, + RoleName=role_name, + ) + except botocore.exceptions.WaiterError as e: + module.fail_json_aws(e, msg='Timeout while waiting on IAM role creation') + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg='Failed while waiting on IAM role creation') + + +def convert_friendly_names_to_arns(policy_names): if not any(not policy.startswith('arn:') for policy in policy_names): return policy_names allpolicies = {} - policies = _list_policies(connection) + policies = _list_policies() for policy in policies: allpolicies[policy['PolicyName']] = policy['Arn'] @@ -235,31 +270,31 @@ def convert_friendly_names_to_arns(connection, module, policy_names): module.fail_json_aws(e, msg="Couldn't find policy") -def attach_policies(connection, module, policies_to_attach, params): +def attach_policies(policies_to_attach, params): changed = False for policy_arn in policies_to_attach: try: if not module.check_mode: - connection.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn, aws_retry=True) + client.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, params['RoleName'])) changed = True return changed -def remove_policies(connection, module, policies_to_remove, params): +def remove_policies(policies_to_remove, params): changed = False for policy in policies_to_remove: try: if not module.check_mode: - connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy, aws_retry=True) + client.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, params['RoleName'])) changed = True return changed -def generate_create_params(module): +def generate_create_params(): params = dict() params['Path'] = module.params.get('path') params['RoleName'] = module.params.get('name') @@ -276,7 +311,7 @@ def generate_create_params(module): return params -def create_basic_role(connection, module, params): +def create_basic_role(params): """ Perform the Role creation. Assumes tests for the role existing have already been performed. @@ -284,11 +319,11 @@ def create_basic_role(connection, module, params): try: if not module.check_mode: - role = connection.create_role(aws_retry=True, **params) + role = client.create_role(aws_retry=True, **params) # 'Description' is documented as key of the role returned by create_role # but appears to be an AWS bug (the value is not returned using the AWS CLI either). # Get the role after creating it. - role = get_role_with_backoff(connection, module, params['RoleName']) + role = get_role_with_backoff(params['RoleName']) else: role = {'MadeInCheckMode': True} role['AssumeRolePolicyDocument'] = json.loads(params['AssumeRolePolicyDocument']) @@ -298,7 +333,7 @@ def create_basic_role(connection, module, params): return role -def update_role_assumed_policy(connection, module, params, role): +def update_role_assumed_policy(params, role): # Check Assumed Policy document if compare_assume_role_policy_doc(role['AssumeRolePolicyDocument'], params['AssumeRolePolicyDocument']): return False @@ -307,7 +342,7 @@ def update_role_assumed_policy(connection, module, params, role): return True try: - connection.update_assume_role_policy( + client.update_assume_role_policy( RoleName=params['RoleName'], PolicyDocument=json.dumps(json.loads(params['AssumeRolePolicyDocument'])), aws_retry=True) @@ -316,7 +351,7 @@ def update_role_assumed_policy(connection, module, params, role): return True -def update_role_description(connection, module, params, role): +def update_role_description(params, role): # Check Description update if params.get('Description') is None: return False @@ -327,13 +362,13 @@ def update_role_description(connection, module, params, role): return True try: - connection.update_role(RoleName=params['RoleName'], Description=params['Description'], aws_retry=True) + client.update_role(RoleName=params['RoleName'], Description=params['Description'], aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update description for role {0}".format(params['RoleName'])) return True -def update_role_max_session_duration(connection, module, params, role): +def update_role_max_session_duration(params, role): # Check MaxSessionDuration update if params.get('MaxSessionDuration') is None: return False @@ -344,13 +379,13 @@ def update_role_max_session_duration(connection, module, params, role): return True try: - connection.update_role(RoleName=params['RoleName'], MaxSessionDuration=params['MaxSessionDuration'], aws_retry=True) + client.update_role(RoleName=params['RoleName'], MaxSessionDuration=params['MaxSessionDuration'], aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update maximum session duration for role {0}".format(params['RoleName'])) return True -def update_role_permissions_boundary(connection, module, params, role): +def update_role_permissions_boundary(params, role): # Check PermissionsBoundary if params.get('PermissionsBoundary') is None: return False @@ -362,18 +397,18 @@ def update_role_permissions_boundary(connection, module, params, role): if params.get('PermissionsBoundary') == '': try: - connection.delete_role_permissions_boundary(RoleName=params['RoleName'], aws_retry=True) + client.delete_role_permissions_boundary(RoleName=params['RoleName'], aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(params['RoleName'])) else: try: - connection.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'], aws_retry=True) + client.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'], aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(params['RoleName'])) return True -def update_managed_policies(connection, module, params, role, managed_policies, purge_policies): +def update_managed_policies(params, role, managed_policies, purge_policies): # Check Managed Policies if managed_policies is None: return False @@ -384,7 +419,7 @@ def update_managed_policies(connection, module, params, role, managed_policies, return True # Get list of current attached managed policies - current_attached_policies = get_attached_policy_list(connection, module, params['RoleName']) + current_attached_policies = get_attached_policy_list(params['RoleName']) current_attached_policies_arn_list = [policy['PolicyArn'] for policy in current_attached_policies] if len(managed_policies) == 1 and managed_policies[0] is None: @@ -396,16 +431,16 @@ def update_managed_policies(connection, module, params, role, managed_policies, changed = False if purge_policies: - changed |= remove_policies(connection, module, policies_to_remove, params) + changed |= remove_policies(policies_to_remove, params) - changed |= attach_policies(connection, module, policies_to_attach, params) + changed |= attach_policies(policies_to_attach, params) return changed -def create_or_update_role(connection, module): +def create_or_update_role(): - params = generate_create_params(module) + params = generate_create_params() role_name = params['RoleName'] create_instance_profile = module.params.get('create_instance_profile') purge_policies = module.params.get('purge_policies') @@ -414,48 +449,59 @@ def create_or_update_role(connection, module): managed_policies = module.params.get('managed_policies') if managed_policies: # Attempt to list the policies early so we don't leave things behind if we can't find them. - managed_policies = convert_friendly_names_to_arns(connection, module, managed_policies) + managed_policies = convert_friendly_names_to_arns(managed_policies) changed = False # Get role - role = get_role(connection, module, role_name) + role = get_role(role_name) # If role is None, create it if role is None: - role = create_basic_role(connection, module, params) + role = create_basic_role(params) + + if not module.check_mode and module.params.get('wait'): + wait_iam_exists() + changed = True else: - changed |= update_role_tags(connection, module, params, role) - changed |= update_role_assumed_policy(connection, module, params, role) - changed |= update_role_description(connection, module, params, role) - changed |= update_role_max_session_duration(connection, module, params, role) - changed |= update_role_permissions_boundary(connection, module, params, role) + changed |= update_role_tags(params, role) + changed |= update_role_assumed_policy(params, role) + changed |= update_role_description(params, role) + changed |= update_role_max_session_duration(params, role) + changed |= update_role_permissions_boundary(params, role) + + if not module.check_mode and module.params.get('wait'): + wait_iam_exists() if create_instance_profile: - changed |= create_instance_profiles(connection, module, params, role) + changed |= create_instance_profiles(params, role) - changed |= update_managed_policies(connection, module, params, role, managed_policies, purge_policies) + if not module.check_mode and module.params.get('wait'): + wait_iam_exists() + + changed |= update_managed_policies(params, role, managed_policies, purge_policies) + wait_iam_exists() # Get the role again if not role.get('MadeInCheckMode', False): - role = get_role(connection, module, params['RoleName']) - role['AttachedPolicies'] = get_attached_policy_list(connection, module, params['RoleName']) - role['tags'] = get_role_tags(connection, module) + role = get_role(params['RoleName']) + role['AttachedPolicies'] = get_attached_policy_list(params['RoleName']) + role['tags'] = get_role_tags() module.exit_json( changed=changed, iam_role=camel_dict_to_snake_dict(role, ignore_list=['tags']), **camel_dict_to_snake_dict(role, ignore_list=['tags'])) -def create_instance_profiles(connection, module, params, role): +def create_instance_profiles(params, role): if role.get('MadeInCheckMode', False): return False # Fetch existing Profiles try: - instance_profiles = connection.list_instance_profiles_for_role(RoleName=params['RoleName'], aws_retry=True)['InstanceProfiles'] + instance_profiles = client.list_instance_profiles_for_role(RoleName=params['RoleName'], aws_retry=True)['InstanceProfiles'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName'])) @@ -468,7 +514,7 @@ def create_instance_profiles(connection, module, params, role): # Make sure an instance profile is created try: - connection.create_instance_profile(InstanceProfileName=params['RoleName'], Path=params['Path'], aws_retry=True) + client.create_instance_profile(InstanceProfileName=params['RoleName'], Path=params['Path'], aws_retry=True) except is_boto3_error_code('EntityAlreadyExists'): # If the profile already exists, no problem, move on. # Implies someone's changing things at the same time... @@ -478,19 +524,19 @@ def create_instance_profiles(connection, module, params, role): # And attach the role to the profile try: - connection.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'], aws_retry=True) + client.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'], aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to attach role {0} to instance profile {0}".format(params['RoleName'])) return True -def remove_instance_profiles(connection, module, role_params, role): +def remove_instance_profiles(role_params, role): role_name = module.params.get('name') delete_profiles = module.params.get("delete_instance_profile") try: - instance_profiles = connection.list_instance_profiles_for_role(aws_retry=True, **role_params)['InstanceProfiles'] + instance_profiles = client.list_instance_profiles_for_role(aws_retry=True, **role_params)['InstanceProfiles'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) @@ -499,21 +545,21 @@ def remove_instance_profiles(connection, module, role_params, role): profile_name = profile['InstanceProfileName'] try: if not module.check_mode: - connection.remove_role_from_instance_profile(aws_retry=True, InstanceProfileName=profile_name, **role_params) + client.remove_role_from_instance_profile(aws_retry=True, InstanceProfileName=profile_name, **role_params) if profile_name == role_name: if delete_profiles: try: - connection.delete_instance_profile(InstanceProfileName=profile_name, aws_retry=True) + client.delete_instance_profile(InstanceProfileName=profile_name, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove instance profile {0}".format(profile_name)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove role {0} from instance profile {1}".format(role_name, profile_name)) -def destroy_role(connection, module): +def destroy_role(): role_name = module.params.get('name') - role = get_role(connection, module, role_name) + role = get_role(role_name) role_params = dict() role_params['RoleName'] = role_name boundary_params = dict(role_params) @@ -526,51 +572,51 @@ def destroy_role(connection, module): # - attached instance profiles # - attached managed policies # - permissions boundary - remove_instance_profiles(connection, module, role_params, role) - update_managed_policies(connection, module, role_params, role, [], True) - update_role_permissions_boundary(connection, module, boundary_params, role) + remove_instance_profiles(role_params, role) + update_managed_policies(role_params, role, [], True) + update_role_permissions_boundary(boundary_params, role) try: if not module.check_mode: - connection.delete_role(aws_retry=True, **role_params) + client.delete_role(aws_retry=True, **role_params) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to delete role") module.exit_json(changed=True) -def get_role_with_backoff(connection, module, name): +def get_role_with_backoff(name): try: - return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(connection.get_role)(RoleName=name)['Role'] + return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(client.get_role)(RoleName=name)['Role'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) -def get_role(connection, module, name): +def get_role(name): try: - return connection.get_role(RoleName=name, aws_retry=True)['Role'] + return client.get_role(RoleName=name, aws_retry=True)['Role'] except is_boto3_error_code('NoSuchEntity'): return None except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) -def get_attached_policy_list(connection, module, name): +def get_attached_policy_list(name): try: - return connection.list_attached_role_policies(RoleName=name, aws_retry=True)['AttachedPolicies'] + return client.list_attached_role_policies(RoleName=name, aws_retry=True)['AttachedPolicies'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) -def get_role_tags(connection, module): +def get_role_tags(): role_name = module.params.get('name') try: - return boto3_tag_list_to_ansible_dict(connection.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) + return boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list tags for role {0}".format(role_name)) -def update_role_tags(connection, module, params, role): +def update_role_tags(params, role): new_tags = params.get('Tags') if new_tags is None: return False @@ -580,7 +626,7 @@ def update_role_tags(connection, module, params, role): purge_tags = module.params.get('purge_tags') try: - existing_tags = boto3_tag_list_to_ansible_dict(connection.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) + existing_tags = boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, KeyError): existing_tags = {} @@ -589,9 +635,9 @@ def update_role_tags(connection, module, params, role): if not module.check_mode: try: if tags_to_remove: - connection.untag_role(RoleName=role_name, TagKeys=tags_to_remove, aws_retry=True) + client.untag_role(RoleName=role_name, TagKeys=tags_to_remove, aws_retry=True) if tags_to_add: - connection.tag_role(RoleName=role_name, Tags=ansible_dict_to_boto3_tag_list(tags_to_add), aws_retry=True) + client.tag_role(RoleName=role_name, Tags=ansible_dict_to_boto3_tag_list(tags_to_add), aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg='Unable to set tags for role %s' % role_name) @@ -601,6 +647,9 @@ def update_role_tags(connection, module, params, role): def main(): + global module + global client + argument_spec = dict( name=dict(type='str', required=True), path=dict(type='str', default="/"), @@ -615,6 +664,8 @@ def main(): purge_policies=dict(type='bool', aliases=['purge_policy', 'purge_managed_policies']), tags=dict(type='dict'), purge_tags=dict(type='bool', default=True), + wait=dict(type='bool', default=True), + wait_timeout=dict(default=120, type='int'), ) module = AnsibleAWSModule(argument_spec=argument_spec, required_if=[('state', 'present', ['assume_role_policy_document'])], @@ -638,14 +689,14 @@ def main(): if not path.endswith('/') or not path.startswith('/'): module.fail_json(msg="path must begin and end with /") - connection = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) state = module.params.get("state") if state == 'present': - create_or_update_role(connection, module) + create_or_update_role() else: - destroy_role(connection, module) + destroy_role() if __name__ == '__main__': diff --git a/tests/integration/targets/iam_role/defaults/main.yml b/tests/integration/targets/iam_role/defaults/main.yml index 46db605072e..d496c421636 100644 --- a/tests/integration/targets/iam_role/defaults/main.yml +++ b/tests/integration/targets/iam_role/defaults/main.yml @@ -4,5 +4,3 @@ test_path: '/{{ resource_prefix }}/' safe_managed_policy: 'AWSDenyAll' custom_policy_name: '{{ resource_prefix }}-denyall' boundary_policy: 'arn:aws:iam::aws:policy/AWSDenyAll' -paranoid_pauses: no -standard_pauses: no diff --git a/tests/integration/targets/iam_role/tasks/boundary_policy.yml b/tests/integration/targets/iam_role/tasks/boundary_policy.yml new file mode 100644 index 00000000000..9f4684d150d --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/boundary_policy.yml @@ -0,0 +1,82 @@ +--- +- name: "Create minimal role with no boundary policy" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + +- name: "Configure Boundary Policy (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + boundary: "{{ boundary_policy }}" + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Configure Boundary Policy" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + boundary: "{{ boundary_policy }}" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + +- name: "Configure Boundary Policy (no change)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + boundary: "{{ boundary_policy }}" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after adding boundary policy" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + +- name: "Remove IAM Role" + iam_role: + state: absent + name: "{{ test_role }}" + delete_instance_profile: yes + register: iam_role + +- assert: + that: + - iam_role is changed \ No newline at end of file diff --git a/tests/integration/targets/iam_role/tasks/complex_role_creation.yml b/tests/integration/targets/iam_role/tasks/complex_role_creation.yml new file mode 100644 index 00000000000..93a7b7d8342 --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/complex_role_creation.yml @@ -0,0 +1,110 @@ +--- +- name: "Complex IAM Role (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + boundary: "{{ boundary_policy }}" + create_instance_profile: no + description: "Ansible Test Role {{ resource_prefix }}" + managed_policy: + - "{{ safe_managed_policy }}" + - "{{ custom_policy_name }}" + max_session_duration: 43200 + path: "{{ test_path }}" + tags: + TagA: "ValueA" + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "iam_role_info after Complex Role creation in check_mode" + iam_role_info: + name: "{{ test_role }}" + register: role_info +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + +- name: "Complex IAM Role" + iam_role: + name: "{{ test_role }}" + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + boundary: "{{ boundary_policy }}" + create_instance_profile: no + description: "Ansible Test Role {{ resource_prefix }}" + managed_policy: + - "{{ safe_managed_policy }}" + - "{{ custom_policy_name }}" + max_session_duration: 43200 + path: "{{ test_path }}" + tags: + TagA: "ValueA" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 2 + - iam_role.iam_role.max_session_duration == 43200 + - iam_role.iam_role.path == test_path + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + +- name: "Complex IAM role (no change)" + iam_role: + name: "{{ test_role }}" + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + boundary: "{{ boundary_policy }}" + create_instance_profile: no + description: "Ansible Test Role {{ resource_prefix }}" + managed_policy: + - "{{ safe_managed_policy }}" + - "{{ custom_policy_name }}" + max_session_duration: 43200 + path: "{{ test_path }}" + tags: + TagA: "ValueA" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after Role creation" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 2 + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == test_path + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "ValueA" diff --git a/tests/integration/targets/iam_role/tasks/creation_deletion.yml b/tests/integration/targets/iam_role/tasks/creation_deletion.yml new file mode 100644 index 00000000000..88fc79f977c --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/creation_deletion.yml @@ -0,0 +1,353 @@ +--- +- name: Try running some rapid fire create/delete tests + block: + - name: "Minimal IAM Role without instance profile (rapid)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + register: iam_role + + - name: "Minimal IAM Role without instance profile (rapid)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + register: iam_role_again + + - assert: + that: + - iam_role is changed + - iam_role_again is not changed + + - name: "Remove IAM Role (rapid)" + iam_role: + state: absent + name: "{{ test_role }}" + register: iam_role + + - name: "Remove IAM Role (rapid)" + iam_role: + state: absent + name: "{{ test_role }}" + register: iam_role_again + + - assert: + that: + - iam_role is changed + - iam_role_again is not changed + + - name: "Minimal IAM Role without instance profile (rapid)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + register: iam_role + + - name: "Remove IAM Role (rapid)" + iam_role: + state: absent + name: "{{ test_role }}" + + register: iam_role_again + - assert: + that: + - iam_role is changed + - iam_role_again is changed + +# =================================================================== +# Role Creation +# (without Instance profile) +- name: "iam_role_info before Role creation (no args)" + iam_role_info: + register: role_info + +- assert: + that: + - role_info is succeeded + +- name: "iam_role_info before Role creation (search for test role)" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + +- name: "Minimal IAM Role (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "iam_role_info after Role creation in check_mode" + iam_role_info: + name: "{{ test_role }}" + register: role_info +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + +- name: "Minimal IAM Role without instance profile" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role/" + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '/' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + +- name: "Minimal IAM Role without instance profile (no change)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after Role creation" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + +- name: "Remove IAM Role" + iam_role: + state: absent + name: "{{ test_role }}" + delete_instance_profile: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "iam_role_info after Role deletion" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + +# (with path) +- name: "Minimal IAM Role with path (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + path: "{{ test_path }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is changed + +- name: "Minimal IAM Role with path" + iam_role: + name: "{{ test_role }}" + path: "{{ test_path }}" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '{{ test_path }}' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + +- name: "Minimal IAM Role with path (no change)" + iam_role: + name: "{{ test_role }}" + path: "{{ test_path }}" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after Role creation" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '{{ test_path }}' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + +- name: "iam_role_info after Role creation (searching a path)" + iam_role_info: + path_prefix: "{{ test_path }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].path == '{{ test_path }}' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + +- name: "Remove IAM Role" + iam_role: + state: absent + name: "{{ test_role }}" + path: "{{ test_path }}" + # If we don't delete the existing profile it'll be reused (with the path) + # by the test below. + delete_instance_profile: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "iam_role_info after Role deletion" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + +# (with Instance profile) +- name: "Minimal IAM Role with instance profile" + iam_role: + name: "{{ test_role }}" + create_instance_profile: yes + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - 'iam_role.iam_role.arn.startswith("arn")' + - 'iam_role.iam_role.arn.endswith("role/" + test_role )' + # Would be nice to test the contents... + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '/' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + +- name: "Minimal IAM Role wth instance profile (no change)" + iam_role: + name: "{{ test_role }}" + create_instance_profile: yes + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after Role creation" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 \ No newline at end of file diff --git a/tests/integration/targets/iam_role/tasks/description_update.yml b/tests/integration/targets/iam_role/tasks/description_update.yml new file mode 100644 index 00000000000..d4ee520147f --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/description_update.yml @@ -0,0 +1,124 @@ +--- +- name: "Add Description (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role {{ resource_prefix }}" + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Add Description" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role {{ resource_prefix }}" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' + +- name: "Add Description (no change)" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role {{ resource_prefix }}" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' + +- name: "iam_role_info after adding Description" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + +- name: "Update Description (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role (updated) {{ resource_prefix }}" + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Update Description" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role (updated) {{ resource_prefix }}" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' + +- name: "Update Description (no change)" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role (updated) {{ resource_prefix }}" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' + +- name: "iam_role_info after updating Description" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 diff --git a/tests/integration/targets/iam_role/tasks/inline_policy_update.yml b/tests/integration/targets/iam_role/tasks/inline_policy_update.yml new file mode 100644 index 00000000000..7fb5eef3e37 --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/inline_policy_update.yml @@ -0,0 +1,63 @@ +--- +- name: "Attach inline policy a" + iam_policy: + state: present + iam_type: "role" + iam_name: "{{ test_role }}" + policy_name: "inline-policy-a" + policy_json: '{{ lookup("file", "deny-all-a.json") }}' + +- name: "Attach inline policy b" + iam_policy: + state: present + iam_type: "role" + iam_name: "{{ test_role }}" + policy_name: "inline-policy-b" + policy_json: '{{ lookup("file", "deny-all-b.json") }}' + +- name: "iam_role_info after attaching inline policies (using iam_policy)" + iam_role_info: + name: "{{ test_role }}" + register: role_info +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 2 + - '"inline-policy-a" in role_info.iam_roles[0].inline_policies' + - '"inline-policy-b" in role_info.iam_roles[0].inline_policies' + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + +# XXX iam_role fails to remove inline policies before deleting the role +- name: "Detach inline policy a" + iam_policy: + state: absent + iam_type: "role" + iam_name: "{{ test_role }}" + policy_name: "inline-policy-a" + +- name: "Detach inline policy b" + iam_policy: + state: absent + iam_type: "role" + iam_name: "{{ test_role }}" + policy_name: "inline-policy-b" diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml index 34c17af3369..b4132c60c2a 100644 --- a/tests/integration/targets/iam_role/tasks/main.yml +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -22,1500 +22,115 @@ # Possible Bugs: # - Fails to delete role if inline policies not removed first -- name: 'Setup AWS connection info' +- name: "Setup 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 }}' + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token | default(omit) }}" + region: "{{ aws_region }}" iam_role: assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' collections: - amazon.aws + - community.general block: - # =================================================================== - # Parameter Checks - - name: 'Friendly message when creating an instance profile and adding a boundary profile' - iam_role: - name: '{{ test_role }}' - boundary: '{{ boundary_policy }}' - register: iam_role - ignore_errors: yes - - assert: - that: - - iam_role is failed - - '"boundary policy" in iam_role.msg' - - '"create_instance_profile" in iam_role.msg' - - '"false" in iam_role.msg' - - - name: 'Friendly message when boundary profile is not an ARN' - iam_role: - name: '{{ test_role }}' - boundary: 'AWSDenyAll' - create_instance_profile: no - register: iam_role - ignore_errors: yes - - assert: - that: - - iam_role is failed - - '"Boundary policy" in iam_role.msg' - - '"ARN" in iam_role.msg' - - - name: 'Friendly message when "present" without assume_role_policy_document' - module_defaults: { iam_role: {} } - iam_role: - name: '{{ test_role }}' - register: iam_role - ignore_errors: yes - - assert: - that: - - iam_role is failed - - 'iam_role.msg.startswith("state is present but all of the following are missing")' - - '"assume_role_policy_document" in iam_role.msg' - - - name: 'Maximum Session Duration needs to be between 1 and 12 hours' - iam_role: - name: '{{ test_role }}' - max_session_duration: 3599 - register: iam_role - ignore_errors: yes - - assert: - that: - - iam_role is failed - - '"max_session_duration must be between" in iam_role.msg' - - - name: 'Maximum Session Duration needs to be between 1 and 12 hours' - iam_role: - name: '{{ test_role }}' - max_session_duration: 43201 - register: iam_role - ignore_errors: yes - - assert: - that: - - iam_role is failed - - '"max_session_duration must be between" in iam_role.msg' - - - name: 'Role Paths must start with /' - iam_role: - name: '{{ test_role }}' - path: 'test/' - register: iam_role - ignore_errors: yes - - assert: - that: - - iam_role is failed - - '"path must begin and end with /" in iam_role.msg' + # =================================================================== + # Parameter Checks + - include_tasks: parameter_checks.yml + + # =================================================================== + # Supplemental resource pre-creation + - name: "Create Safe IAM Managed Policy" + iam_managed_policy: + state: present + policy_name: "{{ custom_policy_name }}" + policy_description: "A safe (deny-all) managed policy" + policy: "{{ lookup('file', 'deny-all.json') }}" + register: create_managed_policy - - name: 'Role Paths must end with /' - iam_role: - name: '{{ test_role }}' - path: '/test' - register: iam_role - ignore_errors: yes - - assert: - that: - - iam_role is failed - - '"path must begin and end with /" in iam_role.msg' - - # =================================================================== - # Supplemental resource pre-creation - - name: 'Create Safe IAM Managed Policy' - iam_managed_policy: - state: present - policy_name: '{{ custom_policy_name }}' - policy_description: "A safe (deny-all) managed policy" - policy: "{{ lookup('file', 'deny-all.json') }}" - register: create_managed_policy - - assert: - that: - - create_managed_policy is succeeded - - # =================================================================== - # Rapid Role Creation and deletion - - name: Try running some rapid fire create/delete tests - # We've previously seen issues with iam_role returning before creation's - # actually complete, if we think the issue's gone, let's try creating and - # deleting things in quick succession - when: not (standard_pauses | bool) - block: - - name: 'Minimal IAM Role without instance profile (rapid)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - register: iam_role - - name: 'Minimal IAM Role without instance profile (rapid)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - register: iam_role_again - - assert: - that: - - iam_role is changed - - iam_role_again is not changed - - name: 'Remove IAM Role (rapid)' - iam_role: - state: absent - name: '{{ test_role }}' - register: iam_role - - name: 'Remove IAM Role (rapid)' - iam_role: - state: absent - name: '{{ test_role }}' - register: iam_role_again - assert: that: - - iam_role is changed - - iam_role_again is not changed - - - name: 'Minimal IAM Role without instance profile (rapid)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - register: iam_role - - name: 'Remove IAM Role (rapid)' - iam_role: - state: absent - name: '{{ test_role }}' - register: iam_role_again - - assert: - that: - - iam_role is changed - - iam_role_again is changed - - # =================================================================== - # Role Creation - # (without Instance profile) - - name: 'iam_role_info before Role creation (no args)' - iam_role_info: - register: role_info - - assert: - that: - - role_info is succeeded - - - name: 'iam_role_info before Role creation (search for test role)' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 0 - - - name: 'Minimal IAM Role (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - # Pause this first time, just in case we actually created something... - - name: Short pause for role creation to finish - pause: - seconds: 10 - when: standard_pauses | bool - - - name: 'iam_role_info after Role creation in check_mode' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 0 - - - name: 'Minimal IAM Role without instance profile' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role/" + test_role )' - # Would be nice to test the contents... - - '"assume_role_policy_document" in iam_role.iam_role' - - iam_role.iam_role.attached_policies | length == 0 - - iam_role.iam_role.max_session_duration == 3600 - - iam_role.iam_role.path == '/' - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' - - name: Short pause for role creation to finish - pause: - seconds: 10 - when: standard_pauses | bool - - - name: 'Minimal IAM Role without instance profile (no change)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after Role creation' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 0 - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - - - name: 'Remove IAM Role' - iam_role: - state: absent - name: '{{ test_role }}' - delete_instance_profile: yes - register: iam_role - - assert: - that: - - iam_role is changed - - name: Short pause for role removal to finish - pause: - seconds: 10 - when: paranoid_pauses | bool - - - name: 'iam_role_info after Role deletion' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 0 - - # (with path) - - name: 'Minimal IAM Role with path (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - path: '{{ test_path }}' - register: iam_role - check_mode: yes - - assert: - that: - - iam_role is changed - - - name: 'Minimal IAM Role with path' - iam_role: - name: '{{ test_role }}' - path: '{{ test_path }}' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' - # Would be nice to test the contents... - - '"assume_role_policy_document" in iam_role.iam_role' - - iam_role.iam_role.attached_policies | length == 0 - - iam_role.iam_role.max_session_duration == 3600 - - iam_role.iam_role.path == '{{ test_path }}' - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' - - name: Short pause for role creation to finish - pause: - seconds: 10 - when: standard_pauses | bool - - - name: 'Minimal IAM Role with path (no change)' - iam_role: - name: '{{ test_role }}' - path: '{{ test_path }}' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after Role creation' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '{{ test_path }}' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - - - name: 'iam_role_info after Role creation (searching a path)' - iam_role_info: - path_prefix: '{{ test_path }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].path == '{{ test_path }}' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - - - name: 'Remove IAM Role' - iam_role: - state: absent - name: '{{ test_role }}' - path: '{{ test_path }}' - # If we don't delete the existing profile it'll be reused (with the path) - # by the test below. - delete_instance_profile: yes - register: iam_role - - assert: - that: - - iam_role is changed - - name: Short pause for role removal to finish - pause: - seconds: 10 - when: paranoid_pauses | bool - - - name: 'iam_role_info after Role deletion' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 0 - - # (with Instance profile) - - name: 'Minimal IAM Role with instance profile' - iam_role: - name: '{{ test_role }}' - create_instance_profile: yes - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role/" + test_role )' - # Would be nice to test the contents... - - '"assume_role_policy_document" in iam_role.iam_role' - - iam_role.iam_role.attached_policies | length == 0 - - iam_role.iam_role.max_session_duration == 3600 - - iam_role.iam_role.path == '/' - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' - - name: Short pause for role creation to finish - pause: - seconds: 10 - when: standard_pauses | bool - - - name: 'Minimal IAM Role wth instance profile (no change)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: yes - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after Role creation' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - - # =================================================================== - # Max Session Duration Manipulation - - - name: 'Update Max Session Duration (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - max_session_duration: 43200 - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Update Max Session Duration' - iam_role: - name: '{{ test_role }}' - max_session_duration: 43200 - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.max_session_duration == 43200 - - - name: 'Update Max Session Duration (no change)' - iam_role: - name: '{{ test_role }}' - max_session_duration: 43200 - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after updating Max Session Duration' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - - # =================================================================== - # Description Manipulation - - - name: 'Add Description (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - description: 'Ansible Test Role {{ resource_prefix }}' - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Add Description' - iam_role: - name: '{{ test_role }}' - description: 'Ansible Test Role {{ resource_prefix }}' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' - - - name: 'Add Description (no change)' - iam_role: - name: '{{ test_role }}' - description: 'Ansible Test Role {{ resource_prefix }}' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' - - - name: 'iam_role_info after adding Description' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - - - name: 'Update Description (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - description: 'Ansible Test Role (updated) {{ resource_prefix }}' - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Update Description' - iam_role: - name: '{{ test_role }}' - description: 'Ansible Test Role (updated) {{ resource_prefix }}' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' - - - name: 'Update Description (no change)' - iam_role: - name: '{{ test_role }}' - description: 'Ansible Test Role (updated) {{ resource_prefix }}' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' - - - name: 'iam_role_info after updating Description' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - - - # =================================================================== - # Tag Manipulation - - - name: 'Add Tag (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - tags: - TagA: ValueA - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Add Tag' - iam_role: - name: '{{ test_role }}' - tags: - TagA: ValueA - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.tags | length == 1 - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "ValueA" + - create_managed_policy is succeeded - - name: 'Add Tag (no change)' - iam_role: - name: '{{ test_role }}' - tags: - TagA: ValueA - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "ValueA" - - - name: 'iam_role_info after adding Tags' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "ValueA" - - - name: 'Update Tag (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - tags: - TagA: AValue - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Update Tag' - iam_role: - name: '{{ test_role }}' - tags: - TagA: AValue - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "AValue" - - - name: 'Update Tag (no change)' - iam_role: - name: '{{ test_role }}' - tags: - TagA: AValue - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "AValue" - - - name: 'iam_role_info after updating Tag' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "AValue" - - - name: 'Add second Tag without purge (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - purge_tags: no - tags: - TagB: ValueB - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Add second Tag without purge' - iam_role: - name: '{{ test_role }}' - purge_tags: no - tags: - TagB: ValueB - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" - - - name: 'Add second Tag without purge (no change)' - iam_role: - name: '{{ test_role }}' - purge_tags: no - tags: - TagB: ValueB - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" - - - name: 'iam_role_info after adding second Tag without purge' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 2 - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "AValue" - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" - - - name: 'Purge first tag (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - purge_tags: yes - tags: - TagB: ValueB - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Purge first tag' - iam_role: - name: '{{ test_role }}' - purge_tags: yes - tags: - TagB: ValueB - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" - - - name: 'Purge first tag (no change)' - iam_role: - name: '{{ test_role }}' - purge_tags: yes - tags: - TagB: ValueB - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" - - - name: 'iam_role_info after purging first Tag' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagA" not in role_info.iam_roles[0].tags' - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" - - - # =================================================================== - # Policy Manipulation - - - name: 'Add Managed Policy (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - purge_policies: no - managed_policy: - - '{{ safe_managed_policy }}' - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Add Managed Policy' - iam_role: - name: '{{ test_role }}' - purge_policies: no - managed_policy: - - '{{ safe_managed_policy }}' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - - name: 'Add Managed Policy (no change)' - iam_role: - name: '{{ test_role }}' - purge_policies: no - managed_policy: - - '{{ safe_managed_policy }}' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after adding Managed Policy' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" - - - name: 'Update Managed Policy without purge (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - purge_policies: no - managed_policy: - - '{{ custom_policy_name }}' - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Update Managed Policy without purge' - iam_role: - name: '{{ test_role }}' - purge_policies: no - managed_policy: - - '{{ custom_policy_name }}' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - - name: 'Update Managed Policy without purge (no change)' - iam_role: - name: '{{ test_role }}' - purge_policies: no - managed_policy: - - '{{ custom_policy_name }}' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after updating Managed Policy without purge' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 2 - - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" - - # Managed Policies are purged by default - - name: 'Update Managed Policy with purge (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - managed_policy: - - '{{ custom_policy_name }}' - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - - name: 'Update Managed Policy with purge' - iam_role: - name: '{{ test_role }}' - managed_policy: - - '{{ custom_policy_name }}' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role + # =================================================================== + # Rapid Role Creation and deletion + - include_tasks: creation_deletion.yml - - name: 'Update Managed Policy with purge (no change)' - iam_role: - name: '{{ test_role }}' - managed_policy: - - '{{ custom_policy_name }}' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after updating Managed Policy with purge' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" - - # =================================================================== - # Inline Policy (test _info behaviour) - - # XXX Not sure if it's a bug in Ansible or a "quirk" of AWS, but these two - # policies need to have at least different Sids or the second doesn't show - # up... - - - name: 'Attach inline policy a' - iam_policy: - state: present - iam_type: 'role' - iam_name: '{{ test_role }}' - policy_name: 'inline-policy-a' - policy_json: '{{ lookup("file", "deny-all-a.json") }}' - - - name: 'Attach inline policy b' - iam_policy: - state: present - iam_type: 'role' - iam_name: '{{ test_role }}' - policy_name: 'inline-policy-b' - policy_json: '{{ lookup("file", "deny-all-b.json") }}' - - - name: 'iam_role_info after attaching inline policies (using iam_policy)' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 2 - - '"inline-policy-a" in role_info.iam_roles[0].inline_policies' - - '"inline-policy-b" in role_info.iam_roles[0].inline_policies' - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" - - # XXX iam_role fails to remove inline policies before deleting the role - - name: 'Detach inline policy a' - iam_policy: - state: absent - iam_type: 'role' - iam_name: '{{ test_role }}' - policy_name: 'inline-policy-a' - - - name: 'Detach inline policy b' - iam_policy: - state: absent - iam_type: 'role' - iam_name: '{{ test_role }}' - policy_name: 'inline-policy-b' - - # =================================================================== - # Role Removal - - name: 'Remove IAM Role (CHECK MODE)' - iam_role: - state: absent - name: '{{ test_role }}' - delete_instance_profile: yes - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - name: 'Short pause for role removal to finish' - pause: - seconds: 10 - when: paranoid_pauses | bool + # =================================================================== + # Max Session Duration Manipulation + - include_tasks: max_session_update.yml - - name: 'iam_role_info after deleting role in check mode' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 + # =================================================================== + # Description Manipulation + - include_tasks: description_update.yml - - name: 'Remove IAM Role' - iam_role: - state: absent - name: '{{ test_role }}' - delete_instance_profile: yes - register: iam_role - - assert: - that: - - iam_role is changed - - name: 'Short pause for role removal to finish' - pause: - seconds: 10 - when: paranoid_pauses | bool + # =================================================================== + # Tag Manipulation + - include_tasks: tags_update.yml - - name: 'iam_role_info after deleting role' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 0 + # =================================================================== + # Policy Manipulation + - include_tasks: policy_update.yml - - name: 'Remove IAM Role (should be gone already)' - iam_role: - state: absent - name: '{{ test_role }}' - delete_instance_profile: yes - register: iam_role - - assert: - that: - - iam_role is not changed - - name: 'Short pause for role removal to finish' - pause: - seconds: 10 - when: paranoid_pauses | bool + # =================================================================== + # Inline Policy (test _info behavior) + - include_tasks: inline_policy_update.yml - # =================================================================== - # Boundary Policy (requires create_instance_profile: no) - - name: 'Create minimal role with no boundary policy' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role + # =================================================================== + # Role Removal + - include_tasks: role_removal.yml - - name: 'Configure Boundary Policy (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - boundary: '{{ boundary_policy }}' - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed + # =================================================================== + # Boundary Policy (requires create_instance_profile: no) + - include_tasks: boundary_policy.yml - - name: 'Configure Boundary Policy' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - boundary: '{{ boundary_policy }}' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - - name: 'Configure Boundary Policy (no change)' - iam_role: - name: '{{ test_role }}' - create_instance_profile: no - boundary: '{{ boundary_policy }}' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after adding boundary policy' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 0 - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '/' - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - - name: 'Remove IAM Role' - iam_role: - state: absent - name: '{{ test_role }}' - delete_instance_profile: yes - register: iam_role - - assert: - that: - - iam_role is changed - - name: Short pause for role removal to finish - pause: - seconds: 10 - when: paranoid_pauses | bool - - # =================================================================== - # Complex role Creation - - name: 'Complex IAM Role (CHECK MODE)' - iam_role: - name: '{{ test_role }}' - assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' - boundary: '{{ boundary_policy }}' - create_instance_profile: no - description: 'Ansible Test Role {{ resource_prefix }}' - managed_policy: - - '{{ safe_managed_policy }}' - - '{{ custom_policy_name }}' - max_session_duration: 43200 - path: '{{ test_path }}' - tags: - TagA: 'ValueA' - check_mode: yes - register: iam_role - - assert: - that: - - iam_role is changed - - name: Short pause for role creation to finish - pause: - seconds: 10 - when: standard_pauses | bool - - - name: 'iam_role_info after Complex Role creation in check_mode' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 0 - - - name: 'Complex IAM Role' - iam_role: - name: '{{ test_role }}' - assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' - boundary: '{{ boundary_policy }}' - create_instance_profile: no - description: 'Ansible Test Role {{ resource_prefix }}' - managed_policy: - - '{{ safe_managed_policy }}' - - '{{ custom_policy_name }}' - max_session_duration: 43200 - path: '{{ test_path }}' - tags: - TagA: 'ValueA' - register: iam_role - - assert: - that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' - # Would be nice to test the contents... - - '"assume_role_policy_document" in iam_role.iam_role' - - iam_role.iam_role.attached_policies | length == 2 - - iam_role.iam_role.max_session_duration == 43200 - - iam_role.iam_role.path == test_path - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' - - name: Short pause for role creation to finish - pause: - seconds: 10 - when: standard_pauses | bool - - - name: 'Complex IAM role (no change)' - iam_role: - name: '{{ test_role }}' - assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' - boundary: '{{ boundary_policy }}' - create_instance_profile: no - description: 'Ansible Test Role {{ resource_prefix }}' - managed_policy: - - '{{ safe_managed_policy }}' - - '{{ custom_policy_name }}' - max_session_duration: 43200 - path: '{{ test_path }}' - tags: - TagA: 'ValueA' - register: iam_role - - assert: - that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - - name: 'iam_role_info after Role creation' - iam_role_info: - name: '{{ test_role }}' - register: role_info - - assert: - that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 0 - - role_info.iam_roles[0].managed_policies | length == 2 - - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == test_path - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "ValueA" + # =================================================================== + # Complex role Creation + - include_tasks: complex_role_creation.yml always: - # =================================================================== - # Cleanup - - # XXX iam_role fails to remove inline policies before deleting the role - - name: 'Detach inline policy a' - iam_policy: - state: absent - iam_type: 'role' - iam_name: '{{ test_role }}' - policy_name: 'inline-policy-a' - ignore_errors: true + # =================================================================== + # Cleanup - - name: 'Detach inline policy b' - iam_policy: - state: absent - iam_type: 'role' - iam_name: '{{ test_role }}' - policy_name: 'inline-policy-b' - ignore_errors: true - - - name: 'Remove IAM Role' - iam_role: - state: absent - name: '{{ test_role }}' - delete_instance_profile: yes - ignore_errors: true + # XXX iam_role fails to remove inline policies before deleting the role + - name: "Detach inline policy a" + iam_policy: + state: absent + iam_type: "role" + iam_name: "{{ test_role }}" + policy_name: "inline-policy-a" + ignore_errors: true - - name: 'Remove IAM Role (with path)' - iam_role: - state: absent - name: '{{ test_role }}' - path: '{{ test_path }}' - delete_instance_profile: yes - ignore_errors: true + - name: "Detach inline policy b" + iam_policy: + state: absent + iam_type: "role" + iam_name: "{{ test_role }}" + policy_name: "inline-policy-b" + ignore_errors: true - - name: 'iam_role_info after Role deletion' - iam_role_info: - name: '{{ test_role }}' - ignore_errors: true + - name: "Remove IAM Role" + iam_role: + state: absent + name: "{{ test_role }}" + delete_instance_profile: yes + ignore_errors: true - - name: 'Remove test managed policy' - iam_managed_policy: - state: absent - policy_name: '{{ custom_policy_name }}' + - name: "Remove IAM Role (with path)" + iam_role: + state: absent + name: "{{ test_role }}" + path: "{{ test_path }}" + delete_instance_profile: yes + ignore_errors: true + + - name: "iam_role_info after Role deletion" + iam_role_info: + name: "{{ test_role }}" + ignore_errors: true + + - name: "Remove test managed policy" + iam_managed_policy: + state: absent + policy_name: "{{ custom_policy_name }}" diff --git a/tests/integration/targets/iam_role/tasks/max_session_update.yml b/tests/integration/targets/iam_role/tasks/max_session_update.yml new file mode 100644 index 00000000000..cbe64df684e --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/max_session_update.yml @@ -0,0 +1,61 @@ +--- +- name: "Update Max Session Duration (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + max_session_duration: 43200 + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Update Max Session Duration" + iam_role: + name: "{{ test_role }}" + max_session_duration: 43200 + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.max_session_duration == 43200 + +- name: "Update Max Session Duration (no change)" + iam_role: + name: "{{ test_role }}" + max_session_duration: 43200 + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after updating Max Session Duration" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 diff --git a/tests/integration/targets/iam_role/tasks/parameter_checks.yml b/tests/integration/targets/iam_role/tasks/parameter_checks.yml new file mode 100644 index 00000000000..57df5436afc --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/parameter_checks.yml @@ -0,0 +1,90 @@ +--- +# Parameter Checks +- name: "Friendly message when creating an instance profile and adding a boundary profile" + iam_role: + name: "{{ test_role }}" + boundary: "{{ boundary_policy }}" + register: iam_role + ignore_errors: yes + +- assert: + that: + - iam_role is failed + - '"boundary policy" in iam_role.msg' + - '"create_instance_profile" in iam_role.msg' + - '"false" in iam_role.msg' + +- name: "Friendly message when boundary profile is not an ARN" + iam_role: + name: "{{ test_role }}" + boundary: "AWSDenyAll" + create_instance_profile: no + register: iam_role + ignore_errors: yes + +- assert: + that: + - iam_role is failed + - '"Boundary policy" in iam_role.msg' + - '"ARN" in iam_role.msg' + +- name: 'Friendly message when "present" without assume_role_policy_document' + module_defaults: { iam_role: {} } + iam_role: + name: "{{ test_role }}" + register: iam_role + ignore_errors: yes + +- assert: + that: + - iam_role is failed + - 'iam_role.msg.startswith("state is present but all of the following are missing")' + - '"assume_role_policy_document" in iam_role.msg' + +- name: "Maximum Session Duration needs to be between 1 and 12 hours" + iam_role: + name: "{{ test_role }}" + max_session_duration: 3599 + register: iam_role + ignore_errors: yes + +- assert: + that: + - iam_role is failed + - '"max_session_duration must be between" in iam_role.msg' + +- name: "Maximum Session Duration needs to be between 1 and 12 hours" + iam_role: + name: "{{ test_role }}" + max_session_duration: 43201 + register: iam_role + ignore_errors: yes + +- assert: + that: + - iam_role is failed + - '"max_session_duration must be between" in iam_role.msg' + +- name: "Role Paths must start with /" + iam_role: + name: "{{ test_role }}" + path: "test/" + register: iam_role + ignore_errors: yes + +- assert: + that: + - iam_role is failed + - '"path must begin and end with /" in iam_role.msg' + +- name: "Role Paths must end with /" + iam_role: + name: "{{ test_role }}" + path: "/test" + register: iam_role + ignore_errors: yes + +- assert: + that: + - iam_role is failed + - '"path must begin and end with /" in iam_role.msg' diff --git a/tests/integration/targets/iam_role/tasks/policy_update.yml b/tests/integration/targets/iam_role/tasks/policy_update.yml new file mode 100644 index 00000000000..a34f2a0ad30 --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/policy_update.yml @@ -0,0 +1,208 @@ +--- +- name: "Add Managed Policy (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ safe_managed_policy }}" + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Add Managed Policy" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ safe_managed_policy }}" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + +- name: "Add Managed Policy (no change)" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ safe_managed_policy }}" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after adding Managed Policy" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + +- name: "Update Managed Policy without purge (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ custom_policy_name }}" + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Update Managed Policy without purge" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ custom_policy_name }}" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + +- name: "Update Managed Policy without purge (no change)" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ custom_policy_name }}" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after updating Managed Policy without purge" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 2 + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + +# Managed Policies are purged by default +- name: "Update Managed Policy with purge (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + managed_policy: + - "{{ custom_policy_name }}" + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Update Managed Policy with purge" + iam_role: + name: "{{ test_role }}" + managed_policy: + - "{{ custom_policy_name }}" + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + +- name: "Update Managed Policy with purge (no change)" + iam_role: + name: "{{ test_role }}" + managed_policy: + - "{{ custom_policy_name }}" + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + +- name: "iam_role_info after updating Managed Policy with purge" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" diff --git a/tests/integration/targets/iam_role/tasks/role_removal.yml b/tests/integration/targets/iam_role/tasks/role_removal.yml new file mode 100644 index 00000000000..1b8d10710a9 --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/role_removal.yml @@ -0,0 +1,53 @@ +--- +- name: "Remove IAM Role (CHECK MODE)" + iam_role: + state: absent + name: "{{ test_role }}" + delete_instance_profile: yes + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "iam_role_info after deleting role in check mode" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + +- name: "Remove IAM Role" + iam_role: + state: absent + name: "{{ test_role }}" + delete_instance_profile: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "iam_role_info after deleting role" + iam_role_info: + name: "{{ test_role }}" + register: role_info +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 0 + +- name: "Remove IAM Role (should be gone already)" + iam_role: + state: absent + name: "{{ test_role }}" + delete_instance_profile: yes + register: iam_role + +- assert: + that: + - iam_role is not changed diff --git a/tests/integration/targets/iam_role/tasks/tags_update.yml b/tests/integration/targets/iam_role/tasks/tags_update.yml new file mode 100644 index 00000000000..617fb9a1331 --- /dev/null +++ b/tests/integration/targets/iam_role/tasks/tags_update.yml @@ -0,0 +1,286 @@ +--- +- name: "Add Tag (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + tags: + TagA: ValueA + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Add Tag" + iam_role: + name: "{{ test_role }}" + tags: + TagA: ValueA + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.tags | length == 1 + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "ValueA" + +- name: "Add Tag (no change)" + iam_role: + name: "{{ test_role }}" + tags: + TagA: ValueA + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "ValueA" + +- name: "iam_role_info after adding Tags" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "ValueA" + +- name: "Update Tag (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + tags: + TagA: AValue + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Update Tag" + iam_role: + name: "{{ test_role }}" + tags: + TagA: AValue + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "AValue" + +- name: "Update Tag (no change)" + iam_role: + name: "{{ test_role }}" + tags: + TagA: AValue + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "AValue" + +- name: "iam_role_info after updating Tag" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "AValue" + +- name: "Add second Tag without purge (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + purge_tags: no + tags: + TagB: ValueB + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Add second Tag without purge" + iam_role: + name: "{{ test_role }}" + purge_tags: no + tags: + TagB: ValueB + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + +- name: "Add second Tag without purge (no change)" + iam_role: + name: "{{ test_role }}" + purge_tags: no + tags: + TagB: ValueB + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + +- name: "iam_role_info after adding second Tag without purge" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 2 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "AValue" + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" + +- name: "Purge first tag (CHECK MODE)" + iam_role: + name: "{{ test_role }}" + purge_tags: yes + tags: + TagB: ValueB + check_mode: yes + register: iam_role + +- assert: + that: + - iam_role is changed + +- name: "Purge first tag" + iam_role: + name: "{{ test_role }}" + purge_tags: yes + tags: + TagB: ValueB + register: iam_role + +- assert: + that: + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + +- name: "Purge first tag (no change)" + iam_role: + name: "{{ test_role }}" + purge_tags: yes + tags: + TagB: ValueB + register: iam_role + +- assert: + that: + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" + +- name: "iam_role_info after purging first Tag" + iam_role_info: + name: "{{ test_role }}" + register: role_info + +- assert: + that: + - role_info is succeeded + - role_info.iam_roles | length == 1 + - 'role_info.iam_roles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' + - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" not in role_info.iam_roles[0].tags' + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" From 01786a7678292633b26ba72f570832eda75b0049 Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Thu, 2 Dec 2021 02:58:06 -0700 Subject: [PATCH 22/37] Remove deprecated "facts" aliases (#814) Remove deprecated "facts" aliases SUMMARY Modules named "facts.py" that do not return ansible_facts were renamed to "info.py" in 2.9. Remove these aliases now that the deprecation period is over. This PR should be included in 3.0.0 of the collection. ISSUE TYPE Bugfix Pull Request COMPONENT NAME *_facts.py Reviewed-by: Mark Chappell Reviewed-by: Jill R Reviewed-by: None This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/68aaa7057be46a3ab36f572fd0013d64653af909 --- plugins/modules/iam_role_facts.py | 1 - plugins/modules/iam_role_info.py | 3 --- 2 files changed, 4 deletions(-) delete mode 120000 plugins/modules/iam_role_facts.py diff --git a/plugins/modules/iam_role_facts.py b/plugins/modules/iam_role_facts.py deleted file mode 120000 index e15c454b71c..00000000000 --- a/plugins/modules/iam_role_facts.py +++ /dev/null @@ -1 +0,0 @@ -iam_role_info.py \ No newline at end of file diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index a08df455fad..561b9f92d70 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -13,7 +13,6 @@ short_description: Gather information on IAM roles description: - Gathers information about IAM roles. - - This module was called C(iam_role_facts) before Ansible 2.9. The usage did not change. author: - "Will Thames (@willthames)" options: @@ -242,8 +241,6 @@ def main(): module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True, mutually_exclusive=[['name', 'path_prefix']]) - if module._name == 'iam_role_facts': - module.deprecate("The 'iam_role_facts' module has been renamed to 'iam_role_info'", date='2021-12-01', collection_name='community.aws') client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) From bcbb160ca27a9547b6dfc07bbc9d7dc5e93f65a1 Mon Sep 17 00:00:00 2001 From: Geoffrey Hichborn <166528+phene@users.noreply.github.com> Date: Wed, 16 Mar 2022 01:59:29 -0700 Subject: [PATCH 23/37] IAM Role Removal Does Not Require Removal of Permission Boundary (#961) IAM Role Removal Does Not Require Removal of Permission Boundary SUMMARY Removes unnecessary removal of permission boundary from a role when deleting a role. Unlike inline policies, permission boundaries do not need to be removed from an IAM role before deleting the IAM role. This behavior causes issues when a permission boundary is inherited that prevents removal of the permission boundary. Fixes #959 ISSUE TYPE Bugfix Pull Request COMPONENT NAME iam_role Reviewed-by: Markus Bergholz Reviewed-by: Mark Chappell This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/e670b3488b6d61f7ca43c5cf70ce61699abef365 --- plugins/modules/iam_role.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 7ca0d8c4fbb..15683e0e060 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -571,10 +571,8 @@ def destroy_role(): # Before we try to delete the role we need to remove any # - attached instance profiles # - attached managed policies - # - permissions boundary remove_instance_profiles(role_params, role) update_managed_policies(role_params, role, [], True) - update_role_permissions_boundary(boundary_params, role) try: if not module.check_mode: From 07180a957924dc02fbc5c4a10465bfad8a744a06 Mon Sep 17 00:00:00 2001 From: Joseph Torcasso <87090265+jatorcasso@users.noreply.github.com> Date: Wed, 13 Apr 2022 15:06:43 -0400 Subject: [PATCH 24/37] iam_role - delete inline policies, stabilize for migration to amazon.aws (#1054) iam_role - delete inline policies, stabilize for migration to amazon.aws SUMMARY Stabilize for migration to amazon.aws delete inline policies before deleting the role removed global vars and refactored function definitions added some extra integration tests for check mode ISSUE TYPE Feature Pull Request COMPONENT NAME iam_role Reviewed-by: Markus Bergholz Reviewed-by: Joseph Torcasso Reviewed-by: Alina Buzachis Reviewed-by: Jill R This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/ce4186763f5fecf45642bf956588afb659640e05 --- plugins/modules/iam_role.py | 297 +++++++++--------- .../iam_role/tasks/boundary_policy.yml | 12 + .../iam_role/tasks/complex_role_creation.yml | 21 ++ .../iam_role/tasks/creation_deletion.yml | 52 ++- .../iam_role/tasks/description_update.yml | 24 ++ .../iam_role/tasks/inline_policy_update.yml | 15 - .../targets/iam_role/tasks/main.yml | 21 +- .../iam_role/tasks/max_session_update.yml | 12 +- .../targets/iam_role/tasks/policy_update.yml | 42 +++ .../targets/iam_role/tasks/role_removal.yml | 12 + .../targets/iam_role/tasks/tags_update.yml | 55 ++++ 11 files changed, 383 insertions(+), 180 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 15683e0e060..14a21fcf1f2 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -218,20 +218,13 @@ from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_policies -def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc): - if not compare_policies(current_policy_doc, json.loads(new_policy_doc)): - return True - else: - return False - - @AWSRetry.jittered_backoff() -def _list_policies(): +def _list_policies(client): paginator = client.get_paginator('list_policies') return paginator.paginate().build_full_result()['Policies'] -def wait_iam_exists(): +def wait_iam_exists(module, client): if module.check_mode: return if not module.params.get('wait'): @@ -255,11 +248,12 @@ def wait_iam_exists(): module.fail_json_aws(e, msg='Failed while waiting on IAM role creation') -def convert_friendly_names_to_arns(policy_names): +def convert_friendly_names_to_arns(module, client, policy_names): if not any(not policy.startswith('arn:') for policy in policy_names): return policy_names + allpolicies = {} - policies = _list_policies() + policies = _list_policies(client) for policy in policies: allpolicies[policy['PolicyName']] = policy['Arn'] @@ -270,31 +264,48 @@ def convert_friendly_names_to_arns(policy_names): module.fail_json_aws(e, msg="Couldn't find policy") -def attach_policies(policies_to_attach, params): +def attach_policies(module, client, policies_to_attach, role_name): + if module.check_mode and policies_to_attach: + return True + changed = False for policy_arn in policies_to_attach: try: - if not module.check_mode: - client.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn, aws_retry=True) + client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn, aws_retry=True) + changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, params['RoleName'])) - changed = True + module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, role_name)) return changed -def remove_policies(policies_to_remove, params): +def remove_policies(module, client, policies_to_remove, role_name): + if module.check_mode and policies_to_remove: + return True + changed = False for policy in policies_to_remove: try: - if not module.check_mode: - client.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy, aws_retry=True) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, params['RoleName'])) - changed = True + client.detach_role_policy(RoleName=role_name, PolicyArn=policy, aws_retry=True) + changed = True + except is_boto3_error_code('NoSuchEntityException'): + pass + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, role_name)) return changed -def generate_create_params(): +def remove_inline_policies(module, client, role_name): + current_inline_policies = get_inline_policy_list(module, client, role_name) + for policy in current_inline_policies: + try: + client.delete_role_policy(RoleName=role_name, PolicyName=policy, aws_retry=True) + except is_boto3_error_code('NoSuchEntityException'): + pass + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg="Unable to delete policy {0} embedded in {1}".format(policy, role_name)) + + +def generate_create_params(module): params = dict() params['Path'] = module.params.get('path') params['RoleName'] = module.params.get('name') @@ -311,31 +322,30 @@ def generate_create_params(): return params -def create_basic_role(params): +def create_basic_role(module, client): """ Perform the Role creation. Assumes tests for the role existing have already been performed. """ + if module.check_mode: + module.exit_json(changed=True) try: - if not module.check_mode: - role = client.create_role(aws_retry=True, **params) - # 'Description' is documented as key of the role returned by create_role - # but appears to be an AWS bug (the value is not returned using the AWS CLI either). - # Get the role after creating it. - role = get_role_with_backoff(params['RoleName']) - else: - role = {'MadeInCheckMode': True} - role['AssumeRolePolicyDocument'] = json.loads(params['AssumeRolePolicyDocument']) + params = generate_create_params(module) + role = client.create_role(aws_retry=True, **params) + # 'Description' is documented as key of the role returned by create_role + # but appears to be an AWS bug (the value is not returned using the AWS CLI either). + # Get the role after creating it. + role = get_role_with_backoff(module, client, params['RoleName']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to create role") return role -def update_role_assumed_policy(params, role): +def update_role_assumed_policy(module, client, role_name, target_assumed_policy, current_assumed_policy): # Check Assumed Policy document - if compare_assume_role_policy_doc(role['AssumeRolePolicyDocument'], params['AssumeRolePolicyDocument']): + if target_assumed_policy is None or not compare_policies(current_assumed_policy, json.loads(target_assumed_policy)): return False if module.check_mode: @@ -343,83 +353,72 @@ def update_role_assumed_policy(params, role): try: client.update_assume_role_policy( - RoleName=params['RoleName'], - PolicyDocument=json.dumps(json.loads(params['AssumeRolePolicyDocument'])), + RoleName=role_name, + PolicyDocument=target_assumed_policy, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(role_name)) return True -def update_role_description(params, role): +def update_role_description(module, client, role_name, target_description, current_description): # Check Description update - if params.get('Description') is None: - return False - if role.get('Description') == params['Description']: + if target_description is None or current_description == target_description: return False if module.check_mode: return True try: - client.update_role(RoleName=params['RoleName'], Description=params['Description'], aws_retry=True) + client.update_role(RoleName=role_name, Description=target_description, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update description for role {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to update description for role {0}".format(role_name)) return True -def update_role_max_session_duration(params, role): +def update_role_max_session_duration(module, client, role_name, target_duration, current_duration): # Check MaxSessionDuration update - if params.get('MaxSessionDuration') is None: - return False - if role.get('MaxSessionDuration') == params['MaxSessionDuration']: + if target_duration is None or current_duration == target_duration: return False if module.check_mode: return True try: - client.update_role(RoleName=params['RoleName'], MaxSessionDuration=params['MaxSessionDuration'], aws_retry=True) + client.update_role(RoleName=role_name, MaxSessionDuration=target_duration, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update maximum session duration for role {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to update maximum session duration for role {0}".format(role_name)) return True -def update_role_permissions_boundary(params, role): +def update_role_permissions_boundary(module, client, role_name, target_permissions_boundary, current_permissions_boundary): # Check PermissionsBoundary - if params.get('PermissionsBoundary') is None: - return False - if params.get('PermissionsBoundary') == role.get('PermissionsBoundary', {}).get('PermissionsBoundaryArn', ''): + if target_permissions_boundary is None or target_permissions_boundary == current_permissions_boundary: return False if module.check_mode: return True - if params.get('PermissionsBoundary') == '': + if target_permissions_boundary == '': try: - client.delete_role_permissions_boundary(RoleName=params['RoleName'], aws_retry=True) + client.delete_role_permissions_boundary(RoleName=role_name, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(role_name)) else: try: - client.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'], aws_retry=True) + client.put_role_permissions_boundary(RoleName=role_name, PermissionsBoundary=target_permissions_boundary, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(role_name)) return True -def update_managed_policies(params, role, managed_policies, purge_policies): +def update_managed_policies(module, client, role_name, managed_policies, purge_policies): # Check Managed Policies if managed_policies is None: return False - # If we're manipulating a fake role - if role.get('MadeInCheckMode', False): - role['AttachedPolicies'] = list(map(lambda x: {'PolicyArn': x, 'PolicyName': x.split(':')[5]}, managed_policies)) - return True - # Get list of current attached managed policies - current_attached_policies = get_attached_policy_list(params['RoleName']) + current_attached_policies = get_attached_policy_list(module, client, role_name) current_attached_policies_arn_list = [policy['PolicyArn'] for policy in current_attached_policies] if len(managed_policies) == 1 and managed_policies[0] is None: @@ -429,84 +428,97 @@ def update_managed_policies(params, role, managed_policies, purge_policies): policies_to_attach = set(managed_policies) - set(current_attached_policies_arn_list) changed = False + if purge_policies and policies_to_remove: + if module.check_mode: + return True + else: + changed |= remove_policies(module, client, policies_to_remove, role_name) - if purge_policies: - changed |= remove_policies(policies_to_remove, params) - - changed |= attach_policies(policies_to_attach, params) + if policies_to_attach: + if module.check_mode: + return True + else: + changed |= attach_policies(module, client, policies_to_attach, role_name) return changed -def create_or_update_role(): +def create_or_update_role(module, client): - params = generate_create_params() - role_name = params['RoleName'] + role_name = module.params.get('name') + assumed_policy = module.params.get('assume_role_policy_document') create_instance_profile = module.params.get('create_instance_profile') + description = module.params.get('description') + duration = module.params.get('max_session_duration') + path = module.params.get('path') + permissions_boundary = module.params.get('boundary') + purge_tags = module.params.get('purge_tags') + tags = ansible_dict_to_boto3_tag_list(module.params.get('tags')) if module.params.get('tags') else None purge_policies = module.params.get('purge_policies') if purge_policies is None: purge_policies = True managed_policies = module.params.get('managed_policies') if managed_policies: # Attempt to list the policies early so we don't leave things behind if we can't find them. - managed_policies = convert_friendly_names_to_arns(managed_policies) + managed_policies = convert_friendly_names_to_arns(module, client, managed_policies) changed = False # Get role - role = get_role(role_name) + role = get_role(module, client, role_name) # If role is None, create it if role is None: - role = create_basic_role(params) + role = create_basic_role(module, client) if not module.check_mode and module.params.get('wait'): - wait_iam_exists() + wait_iam_exists(module, client) changed = True else: - changed |= update_role_tags(params, role) - changed |= update_role_assumed_policy(params, role) - changed |= update_role_description(params, role) - changed |= update_role_max_session_duration(params, role) - changed |= update_role_permissions_boundary(params, role) + # Role exists - get current attributes + current_assumed_policy = role.get('AssumeRolePolicyDocument') + current_description = role.get('Description') + current_duration = role.get('MaxSessionDuration') + current_permissions_boundary = role.get('PermissionsBoundary', {}).get('PermissionsBoundaryArn', '') + + # Update attributes + changed |= update_role_tags(module, client, role_name, tags, purge_tags) + changed |= update_role_assumed_policy(module, client, role_name, assumed_policy, current_assumed_policy) + changed |= update_role_description(module, client, role_name, description, current_description) + changed |= update_role_max_session_duration(module, client, role_name, duration, current_duration) + changed |= update_role_permissions_boundary(module, client, role_name, permissions_boundary, current_permissions_boundary) if not module.check_mode and module.params.get('wait'): - wait_iam_exists() + wait_iam_exists(module, client) if create_instance_profile: - changed |= create_instance_profiles(params, role) + changed |= create_instance_profiles(module, client, role_name, path) if not module.check_mode and module.params.get('wait'): - wait_iam_exists() + wait_iam_exists(module, client) - changed |= update_managed_policies(params, role, managed_policies, purge_policies) - wait_iam_exists() + changed |= update_managed_policies(module, client, role_name, managed_policies, purge_policies) + wait_iam_exists(module, client) # Get the role again - if not role.get('MadeInCheckMode', False): - role = get_role(params['RoleName']) - role['AttachedPolicies'] = get_attached_policy_list(params['RoleName']) - role['tags'] = get_role_tags() - - module.exit_json( - changed=changed, iam_role=camel_dict_to_snake_dict(role, ignore_list=['tags']), - **camel_dict_to_snake_dict(role, ignore_list=['tags'])) + role = get_role(module, client, role_name) + role['AttachedPolicies'] = get_attached_policy_list(module, client, role_name) + role['tags'] = get_role_tags(module, client) + module.exit_json(changed=changed, iam_role=camel_dict_to_snake_dict(role, ignore_list=['tags'])) -def create_instance_profiles(params, role): - if role.get('MadeInCheckMode', False): - return False +def create_instance_profiles(module, client, role_name, path): # Fetch existing Profiles try: - instance_profiles = client.list_instance_profiles_for_role(RoleName=params['RoleName'], aws_retry=True)['InstanceProfiles'] + instance_profiles = client.list_instance_profiles_for_role(RoleName=role_name, aws_retry=True)['InstanceProfiles'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) # Profile already exists - if any(p['InstanceProfileName'] == params['RoleName'] for p in instance_profiles): + if any(p['InstanceProfileName'] == role_name for p in instance_profiles): return False if module.check_mode: @@ -514,29 +526,28 @@ def create_instance_profiles(params, role): # Make sure an instance profile is created try: - client.create_instance_profile(InstanceProfileName=params['RoleName'], Path=params['Path'], aws_retry=True) + client.create_instance_profile(InstanceProfileName=role_name, Path=path, aws_retry=True) except is_boto3_error_code('EntityAlreadyExists'): # If the profile already exists, no problem, move on. # Implies someone's changing things at the same time... return False except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(role_name)) # And attach the role to the profile try: - client.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'], aws_retry=True) + client.add_role_to_instance_profile(InstanceProfileName=role_name, RoleName=role_name, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to attach role {0} to instance profile {0}".format(params['RoleName'])) + module.fail_json_aws(e, msg="Unable to attach role {0} to instance profile {0}".format(role_name)) return True -def remove_instance_profiles(role_params, role): - role_name = module.params.get('name') +def remove_instance_profiles(module, client, role_name): delete_profiles = module.params.get("delete_instance_profile") try: - instance_profiles = client.list_instance_profiles_for_role(aws_retry=True, **role_params)['InstanceProfiles'] + instance_profiles = client.list_instance_profiles_for_role(aws_retry=True, RoleName=role_name)['InstanceProfiles'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) @@ -545,52 +556,53 @@ def remove_instance_profiles(role_params, role): profile_name = profile['InstanceProfileName'] try: if not module.check_mode: - client.remove_role_from_instance_profile(aws_retry=True, InstanceProfileName=profile_name, **role_params) + client.remove_role_from_instance_profile(aws_retry=True, InstanceProfileName=profile_name, RoleName=role_name) if profile_name == role_name: if delete_profiles: try: client.delete_instance_profile(InstanceProfileName=profile_name, aws_retry=True) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + except is_boto3_error_code('NoSuchEntityException'): + pass + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to remove instance profile {0}".format(profile_name)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove role {0} from instance profile {1}".format(role_name, profile_name)) -def destroy_role(): +def destroy_role(module, client): role_name = module.params.get('name') - role = get_role(role_name) - role_params = dict() - role_params['RoleName'] = role_name - boundary_params = dict(role_params) - boundary_params['PermissionsBoundary'] = '' + role = get_role(module, client, role_name) if role is None: module.exit_json(changed=False) - # Before we try to delete the role we need to remove any - # - attached instance profiles - # - attached managed policies - remove_instance_profiles(role_params, role) - update_managed_policies(role_params, role, [], True) - - try: - if not module.check_mode: - client.delete_role(aws_retry=True, **role_params) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to delete role") + if not module.check_mode: + # Before we try to delete the role we need to remove any + # - attached instance profiles + # - attached managed policies + # - embedded inline policies + remove_instance_profiles(module, client, role_name) + update_managed_policies(module, client, role_name, [], True) + remove_inline_policies(module, client, role_name) + try: + client.delete_role(aws_retry=True, RoleName=role_name) + except is_boto3_error_code('NoSuchEntityException'): + module.exit_json(changed=False) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg="Unable to delete role") module.exit_json(changed=True) -def get_role_with_backoff(name): +def get_role_with_backoff(module, client, name): try: return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(client.get_role)(RoleName=name)['Role'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) -def get_role(name): +def get_role(module, client, name): try: return client.get_role(RoleName=name, aws_retry=True)['Role'] except is_boto3_error_code('NoSuchEntity'): @@ -599,14 +611,21 @@ def get_role(name): module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) -def get_attached_policy_list(name): +def get_attached_policy_list(module, client, name): try: return client.list_attached_role_policies(RoleName=name, aws_retry=True)['AttachedPolicies'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) -def get_role_tags(): +def get_inline_policy_list(module, client, name): + try: + return client.list_role_policies(RoleName=name, aws_retry=True)['PolicyNames'] + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) + + +def get_role_tags(module, client): role_name = module.params.get('name') try: return boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) @@ -614,15 +633,11 @@ def get_role_tags(): module.fail_json_aws(e, msg="Unable to list tags for role {0}".format(role_name)) -def update_role_tags(params, role): - new_tags = params.get('Tags') +def update_role_tags(module, client, role_name, new_tags, purge_tags): if new_tags is None: return False new_tags = boto3_tag_list_to_ansible_dict(new_tags) - role_name = module.params.get('name') - purge_tags = module.params.get('purge_tags') - try: existing_tags = boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, KeyError): @@ -645,9 +660,6 @@ def update_role_tags(params, role): def main(): - global module - global client - argument_spec = dict( name=dict(type='str', required=True), path=dict(type='str', default="/"), @@ -665,6 +677,7 @@ def main(): wait=dict(type='bool', default=True), wait_timeout=dict(default=120, type='int'), ) + module = AnsibleAWSModule(argument_spec=argument_spec, required_if=[('state', 'present', ['assume_role_policy_document'])], supports_check_mode=True) @@ -692,9 +705,9 @@ def main(): state = module.params.get("state") if state == 'present': - create_or_update_role() - else: - destroy_role() + create_or_update_role(module, client) + elif state == 'absent': + destroy_role(module, client) if __name__ == '__main__': diff --git a/tests/integration/targets/iam_role/tasks/boundary_policy.yml b/tests/integration/targets/iam_role/tasks/boundary_policy.yml index 9f4684d150d..89a983f1564 100644 --- a/tests/integration/targets/iam_role/tasks/boundary_policy.yml +++ b/tests/integration/targets/iam_role/tasks/boundary_policy.yml @@ -34,6 +34,18 @@ - iam_role is changed - iam_role.iam_role.role_name == test_role +- name: "Configure Boundary Policy (no change) - check mode" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + boundary: "{{ boundary_policy }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Configure Boundary Policy (no change)" iam_role: name: "{{ test_role }}" diff --git a/tests/integration/targets/iam_role/tasks/complex_role_creation.yml b/tests/integration/targets/iam_role/tasks/complex_role_creation.yml index 93a7b7d8342..c23234ebf1f 100644 --- a/tests/integration/targets/iam_role/tasks/complex_role_creation.yml +++ b/tests/integration/targets/iam_role/tasks/complex_role_creation.yml @@ -60,6 +60,27 @@ - '"create_date" in iam_role.iam_role' - '"role_id" in iam_role.iam_role' +- name: "Complex IAM role (no change) - check mode" + iam_role: + name: "{{ test_role }}" + assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' + boundary: "{{ boundary_policy }}" + create_instance_profile: no + description: "Ansible Test Role {{ resource_prefix }}" + managed_policy: + - "{{ safe_managed_policy }}" + - "{{ custom_policy_name }}" + max_session_duration: 43200 + path: "{{ test_path }}" + tags: + TagA: "ValueA" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Complex IAM role (no change)" iam_role: name: "{{ test_role }}" diff --git a/tests/integration/targets/iam_role/tasks/creation_deletion.yml b/tests/integration/targets/iam_role/tasks/creation_deletion.yml index 88fc79f977c..27c48309bce 100644 --- a/tests/integration/targets/iam_role/tasks/creation_deletion.yml +++ b/tests/integration/targets/iam_role/tasks/creation_deletion.yml @@ -77,8 +77,8 @@ iam_role: name: "{{ test_role }}" create_instance_profile: no - check_mode: yes register: iam_role + check_mode: yes - assert: that: @@ -114,6 +114,17 @@ - '"create_date" in iam_role.iam_role' - '"role_id" in iam_role.iam_role' +- name: "Minimal IAM Role without instance profile (no change) - check mode" + iam_role: + name: "{{ test_role }}" + create_instance_profile: no + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Minimal IAM Role without instance profile (no change)" iam_role: name: "{{ test_role }}" @@ -170,6 +181,8 @@ - role_info is succeeded - role_info.iam_roles | length == 0 +# ------------------------------------------------------------------------------------------ + # (with path) - name: "Minimal IAM Role with path (CHECK MODE)" iam_role: @@ -203,6 +216,17 @@ - '"create_date" in iam_role.iam_role' - '"role_id" in iam_role.iam_role' +- name: "Minimal IAM Role with path (no change) - check mode" + iam_role: + name: "{{ test_role }}" + path: "{{ test_path }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Minimal IAM Role with path (no change)" iam_role: name: "{{ test_role }}" @@ -292,7 +316,20 @@ - role_info is succeeded - role_info.iam_roles | length == 0 +# ------------------------------------------------------------------------------------------ + # (with Instance profile) +- name: "Minimal IAM Role with instance profile - check mode" + iam_role: + name: "{{ test_role }}" + create_instance_profile: yes + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is changed + - name: "Minimal IAM Role with instance profile" iam_role: name: "{{ test_role }}" @@ -314,6 +351,17 @@ - '"create_date" in iam_role.iam_role' - '"role_id" in iam_role.iam_role' +- name: "Minimal IAM Role wth instance profile (no change) - check mode" + iam_role: + name: "{{ test_role }}" + create_instance_profile: yes + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Minimal IAM Role wth instance profile (no change)" iam_role: name: "{{ test_role }}" @@ -350,4 +398,4 @@ - '"permissions_boundary" not in role_info.iam_roles[0]' - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 \ No newline at end of file + - role_info.iam_roles[0].tags | length == 0 diff --git a/tests/integration/targets/iam_role/tasks/description_update.yml b/tests/integration/targets/iam_role/tasks/description_update.yml index d4ee520147f..85f5e1f56a3 100644 --- a/tests/integration/targets/iam_role/tasks/description_update.yml +++ b/tests/integration/targets/iam_role/tasks/description_update.yml @@ -22,6 +22,17 @@ - iam_role.iam_role.role_name == test_role - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' +- name: "Add Description (no change) - check mode" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role {{ resource_prefix }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Add Description (no change)" iam_role: name: "{{ test_role }}" @@ -61,6 +72,8 @@ - role_info.iam_roles[0].role_name == test_role - role_info.iam_roles[0].tags | length == 0 +# ------------------------------------------------------------------------------------------ + - name: "Update Description (CHECK MODE)" iam_role: name: "{{ test_role }}" @@ -84,6 +97,17 @@ - iam_role.iam_role.role_name == test_role - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' +- name: "Update Description (no change) - check mode" + iam_role: + name: "{{ test_role }}" + description: "Ansible Test Role (updated) {{ resource_prefix }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Update Description (no change)" iam_role: name: "{{ test_role }}" diff --git a/tests/integration/targets/iam_role/tasks/inline_policy_update.yml b/tests/integration/targets/iam_role/tasks/inline_policy_update.yml index 7fb5eef3e37..d364d87d79f 100644 --- a/tests/integration/targets/iam_role/tasks/inline_policy_update.yml +++ b/tests/integration/targets/iam_role/tasks/inline_policy_update.yml @@ -46,18 +46,3 @@ - role_info.iam_roles[0].tags | length == 1 - '"TagB" in role_info.iam_roles[0].tags' - role_info.iam_roles[0].tags.TagB == "ValueB" - -# XXX iam_role fails to remove inline policies before deleting the role -- name: "Detach inline policy a" - iam_policy: - state: absent - iam_type: "role" - iam_name: "{{ test_role }}" - policy_name: "inline-policy-a" - -- name: "Detach inline policy b" - iam_policy: - state: absent - iam_type: "role" - iam_name: "{{ test_role }}" - policy_name: "inline-policy-b" diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml index b4132c60c2a..b2db8e59c13 100644 --- a/tests/integration/targets/iam_role/tasks/main.yml +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -18,9 +18,7 @@ # - There are some known timing issues with boto3 returning before actions # complete in the case of problems with "changed" status it's worth enabling # the standard_pauses and paranoid_pauses options as a first step in debugging -# -# Possible Bugs: -# - Fails to delete role if inline policies not removed first + - name: "Setup AWS connection info" module_defaults: @@ -93,23 +91,6 @@ # =================================================================== # Cleanup - # XXX iam_role fails to remove inline policies before deleting the role - - name: "Detach inline policy a" - iam_policy: - state: absent - iam_type: "role" - iam_name: "{{ test_role }}" - policy_name: "inline-policy-a" - ignore_errors: true - - - name: "Detach inline policy b" - iam_policy: - state: absent - iam_type: "role" - iam_name: "{{ test_role }}" - policy_name: "inline-policy-b" - ignore_errors: true - - name: "Remove IAM Role" iam_role: state: absent diff --git a/tests/integration/targets/iam_role/tasks/max_session_update.yml b/tests/integration/targets/iam_role/tasks/max_session_update.yml index cbe64df684e..8ad3641be62 100644 --- a/tests/integration/targets/iam_role/tasks/max_session_update.yml +++ b/tests/integration/targets/iam_role/tasks/max_session_update.yml @@ -31,7 +31,17 @@ - assert: that: - iam_role is not changed - - iam_role.iam_role.role_name == test_role + +- name: "Update Max Session Duration (no change) - check mode" + iam_role: + name: "{{ test_role }}" + max_session_duration: 43200 + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed - name: "iam_role_info after updating Max Session Duration" iam_role_info: diff --git a/tests/integration/targets/iam_role/tasks/policy_update.yml b/tests/integration/targets/iam_role/tasks/policy_update.yml index a34f2a0ad30..a822edf74b6 100644 --- a/tests/integration/targets/iam_role/tasks/policy_update.yml +++ b/tests/integration/targets/iam_role/tasks/policy_update.yml @@ -25,6 +25,19 @@ - iam_role is changed - iam_role.iam_role.role_name == test_role +- name: "Add Managed Policy (no change) - check mode" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ safe_managed_policy }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Add Managed Policy (no change)" iam_role: name: "{{ test_role }}" @@ -69,6 +82,8 @@ - '"TagB" in role_info.iam_roles[0].tags' - role_info.iam_roles[0].tags.TagB == "ValueB" +# ------------------------------------------------------------------------------------------ + - name: "Update Managed Policy without purge (CHECK MODE)" iam_role: name: "{{ test_role }}" @@ -95,6 +110,19 @@ - iam_role is changed - iam_role.iam_role.role_name == test_role +- name: "Update Managed Policy without purge (no change) - check mode" + iam_role: + name: "{{ test_role }}" + purge_policies: no + managed_policy: + - "{{ custom_policy_name }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Update Managed Policy without purge (no change)" iam_role: name: "{{ test_role }}" @@ -139,6 +167,8 @@ - '"TagB" in role_info.iam_roles[0].tags' - role_info.iam_roles[0].tags.TagB == "ValueB" +# ------------------------------------------------------------------------------------------ + # Managed Policies are purged by default - name: "Update Managed Policy with purge (CHECK MODE)" iam_role: @@ -164,6 +194,18 @@ - iam_role is changed - iam_role.iam_role.role_name == test_role +- name: "Update Managed Policy with purge (no change) - check mode" + iam_role: + name: "{{ test_role }}" + managed_policy: + - "{{ custom_policy_name }}" + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Update Managed Policy with purge (no change)" iam_role: name: "{{ test_role }}" diff --git a/tests/integration/targets/iam_role/tasks/role_removal.yml b/tests/integration/targets/iam_role/tasks/role_removal.yml index 1b8d10710a9..ebcfd54530a 100644 --- a/tests/integration/targets/iam_role/tasks/role_removal.yml +++ b/tests/integration/targets/iam_role/tasks/role_removal.yml @@ -41,6 +41,18 @@ - role_info is succeeded - role_info.iam_roles | length == 0 +- name: "Remove IAM Role (should be gone already) - check mode" + iam_role: + state: absent + name: "{{ test_role }}" + delete_instance_profile: yes + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Remove IAM Role (should be gone already)" iam_role: state: absent diff --git a/tests/integration/targets/iam_role/tasks/tags_update.yml b/tests/integration/targets/iam_role/tasks/tags_update.yml index 617fb9a1331..5eadd9fdf7e 100644 --- a/tests/integration/targets/iam_role/tasks/tags_update.yml +++ b/tests/integration/targets/iam_role/tasks/tags_update.yml @@ -26,6 +26,18 @@ - '"TagA" in iam_role.iam_role.tags' - iam_role.iam_role.tags.TagA == "ValueA" +- name: "Add Tag (no change) - check mode" + iam_role: + name: "{{ test_role }}" + tags: + TagA: ValueA + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Add Tag (no change)" iam_role: name: "{{ test_role }}" @@ -69,6 +81,8 @@ - '"TagA" in role_info.iam_roles[0].tags' - role_info.iam_roles[0].tags.TagA == "ValueA" +# ------------------------------------------------------------------------------------------ + - name: "Update Tag (CHECK MODE)" iam_role: name: "{{ test_role }}" @@ -95,6 +109,18 @@ - '"TagA" in iam_role.iam_role.tags' - iam_role.iam_role.tags.TagA == "AValue" +- name: "Update Tag (no change) - check mode" + iam_role: + name: "{{ test_role }}" + tags: + TagA: AValue + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Update Tag (no change)" iam_role: name: "{{ test_role }}" @@ -138,6 +164,8 @@ - '"TagA" in role_info.iam_roles[0].tags' - role_info.iam_roles[0].tags.TagA == "AValue" +# ------------------------------------------------------------------------------------------ + - name: "Add second Tag without purge (CHECK MODE)" iam_role: name: "{{ test_role }}" @@ -166,6 +194,19 @@ - '"TagB" in iam_role.iam_role.tags' - iam_role.iam_role.tags.TagB == "ValueB" +- name: "Add second Tag without purge (no change) - check mode" + iam_role: + name: "{{ test_role }}" + purge_tags: no + tags: + TagB: ValueB + register: iam_role + check_mode: yes + +- assert: + that: + - iam_role is not changed + - name: "Add second Tag without purge (no change)" iam_role: name: "{{ test_role }}" @@ -212,6 +253,8 @@ - '"TagB" in role_info.iam_roles[0].tags' - role_info.iam_roles[0].tags.TagB == "ValueB" +# ------------------------------------------------------------------------------------------ + - name: "Purge first tag (CHECK MODE)" iam_role: name: "{{ test_role }}" @@ -240,6 +283,18 @@ - '"TagB" in iam_role.iam_role.tags' - iam_role.iam_role.tags.TagB == "ValueB" +- name: "Purge first tag (no change) - check mode" + iam_role: + name: "{{ test_role }}" + purge_tags: yes + tags: + TagB: ValueB + register: iam_role + +- assert: + that: + - iam_role is not changed + - name: "Purge first tag (no change)" iam_role: name: "{{ test_role }}" From c2418c100be5bc50e0ba91ba400d2d9adfc13be6 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 14 Apr 2022 16:53:58 +0200 Subject: [PATCH 25/37] Revert breaking change - iam_role return values (#1068) Revert breaking change - iam_role return values SUMMARY This hasn't been release yet, so a changelog isn't needed. While I'm generally good with cleaning up the output values here, this needs to be done as a separate breaking change, and must not be backported to stable-3. ISSUE TYPE Bugfix Pull Request COMPONENT NAME iam_role ADDITIONAL INFORMATION Breaking change silently introduced by #1054 Reviewed-by: Alina Buzachis Reviewed-by: Joseph Torcasso This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/8d80e9a062b166065c9a96fbf6d774d5d7375619 --- plugins/modules/iam_role.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 14a21fcf1f2..814dbbb8b99 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -506,7 +506,8 @@ def create_or_update_role(module, client): role['AttachedPolicies'] = get_attached_policy_list(module, client, role_name) role['tags'] = get_role_tags(module, client) - module.exit_json(changed=changed, iam_role=camel_dict_to_snake_dict(role, ignore_list=['tags'])) + camel_role = camel_dict_to_snake_dict(role, ignore_list=['tags']) + module.exit_json(changed=changed, iam_role=camel_role, **camel_role) def create_instance_profiles(module, client, role_name, path): From e285c56bbec88708836683b36148ce5d36d7c71d Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Fri, 22 Apr 2022 11:44:07 +0200 Subject: [PATCH 26/37] Integration test dependency cleanup (#1086) Integration test dependency cleanup SUMMARY remove dependencies on setup_remote_tmp_dir where it's not used (often just copy & paste from another test) remove setup_ec2 (no main.yml means it's not doing anything) remove prepare_tests (empty main.yml means it's not doing anything) ISSUE TYPE Feature Pull Request COMPONENT NAME tests/integration/targets ADDITIONAL INFORMATION By cleaning up what we have we reduce the chance of people copying things about "because that's what test XYZ did". Reviewed-by: Alina Buzachis Reviewed-by: Mark Woolley This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/dd12046a1e2d5f39692b1890ff07e06c56b3bf0e --- tests/integration/targets/iam_role/meta/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration/targets/iam_role/meta/main.yml b/tests/integration/targets/iam_role/meta/main.yml index 1f64f1169a9..32cf5dda7ed 100644 --- a/tests/integration/targets/iam_role/meta/main.yml +++ b/tests/integration/targets/iam_role/meta/main.yml @@ -1,3 +1 @@ -dependencies: - - prepare_tests - - setup_ec2 +dependencies: [] From 0348b729d9e7936987aa3ba5dba57ceee161c765 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Wed, 1 Jun 2022 15:03:38 +0200 Subject: [PATCH 27/37] Tagging fragment - Move simplest cases over to the docs fragment. (#1182) Tagging fragment - Move simplest cases over to the docs fragment. Depends-On: ansible-collections/amazon.aws#844 SUMMARY Migrate simplest cases over to the new docs fragment and add resource_tags as an alias to tags. ISSUE TYPE Docs Pull Request Feature Pull Request COMPONENT NAME changelogs/fragments/1182-tagging.yml plugins/modules/aws_glue_job.py plugins/modules/aws_msk_cluster.py plugins/modules/aws_secret.py plugins/modules/aws_step_functions_state_machine.py plugins/modules/dynamodb_table.py plugins/modules/ec2_eip.py plugins/modules/ec2_transit_gateway_vpc_attachment.py plugins/modules/ec2_vpc_peer.py plugins/modules/elb_application_lb.py plugins/modules/elb_network_lb.py plugins/modules/iam_role.py plugins/modules/iam_user.py plugins/modules/networkfirewall.py plugins/modules/networkfirewall_policy.py plugins/modules/networkfirewall_rule_group.py plugins/modules/rds_cluster.py plugins/modules/rds_instance.py plugins/modules/rds_instance_snapshot.py plugins/modules/rds_option_group.py plugins/modules/rds_subnet_group.py plugins/modules/redshift.py ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/b11ffaed2b3450f6fee9721878090da404401021 --- plugins/modules/iam_role.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 814dbbb8b99..76cd04950d3 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -12,7 +12,8 @@ short_description: Manage AWS IAM roles description: - Manage AWS IAM roles. -author: "Rob White (@wimnat)" +author: + - "Rob White (@wimnat)" options: path: description: @@ -78,15 +79,6 @@ - Only applies when I(state=absent). default: false type: bool - tags: - description: - - Tag dict to apply to the queue. - type: dict - purge_tags: - description: - - Remove tags not listed in I(tags) when tags is specified. - default: true - type: bool wait_timeout: description: - How long (in seconds) to wait for creation / update to complete. @@ -99,9 +91,9 @@ default: True type: bool extends_documentation_fragment: -- amazon.aws.aws -- amazon.aws.ec2 - + - amazon.aws.aws + - amazon.aws.ec2 + - amazon.aws.tags ''' EXAMPLES = r''' @@ -673,7 +665,7 @@ def main(): create_instance_profile=dict(type='bool', default=True), delete_instance_profile=dict(type='bool', default=False), purge_policies=dict(type='bool', aliases=['purge_policy', 'purge_managed_policies']), - tags=dict(type='dict'), + tags=dict(type='dict', aliases=['resource_tags']), purge_tags=dict(type='bool', default=True), wait=dict(type='bool', default=True), wait_timeout=dict(default=120, type='int'), From 947ff84c9d5408bb7750e6103ebcd0bb67a37c0a Mon Sep 17 00:00:00 2001 From: Alina Buzachis Date: Wed, 5 Oct 2022 17:04:40 +0200 Subject: [PATCH 28/37] Update extends_documentation_fragment with amazon.aws.boto3 (#1459) Update extends_documentation_fragment with amazon.aws.boto3 Depends-On: ansible/ansible-zuul-jobs#1654 SUMMARY As per ansible-collections/amazon.aws#985 add amazon.aws.boto3. ISSUE TYPE Docs Pull Request COMPONENT NAME several Reviewed-by: Jill R Reviewed-by: Mark Chappell Reviewed-by: Markus Bergholz This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/bd3c03fcba0848f593b86309740fa73e986a9646 --- plugins/modules/iam_role.py | 1 + plugins/modules/iam_role_info.py | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 76cd04950d3..cccc062a494 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -93,6 +93,7 @@ extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 + - amazon.aws.boto3 - amazon.aws.tags ''' diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 561b9f92d70..84e9a31718e 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -31,6 +31,7 @@ extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 +- amazon.aws.boto3 ''' From 2a9043406da80f8d16bd810909837afbb8e5579e Mon Sep 17 00:00:00 2001 From: Markus Bergholz Date: Wed, 4 Jan 2023 09:44:49 +0100 Subject: [PATCH 29/37] iam_role: drop deprecation (#1636) iam_role: drop deprecation SUMMARY The change was announced since community.aws 1.0.0 for ansible 2.14 With community.aws 2.1.0, it was changed to the date after 2022-06-01 However, in the meantime the standard value is true for purge parameters. Therefore we just drop the deprecation warning. COMPONENT NAME iam_role Reviewed-by: Mark Chappell This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/75ba63bec07d98e021fc204882c5b82c64027aeb --- plugins/modules/iam_role.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index cccc062a494..09a86a54e82 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -58,9 +58,9 @@ purge_policies: description: - When I(purge_policies=true) any managed policies not listed in I(managed_policies) will be detatched. - - By default I(purge_policies=true). In a release after 2022-06-01 this will be changed to I(purge_policies=false). type: bool aliases: ['purge_policy', 'purge_managed_policies'] + default: true state: description: - Create or remove the IAM role. @@ -448,8 +448,6 @@ def create_or_update_role(module, client): purge_tags = module.params.get('purge_tags') tags = ansible_dict_to_boto3_tag_list(module.params.get('tags')) if module.params.get('tags') else None purge_policies = module.params.get('purge_policies') - if purge_policies is None: - purge_policies = True managed_policies = module.params.get('managed_policies') if managed_policies: # Attempt to list the policies early so we don't leave things behind if we can't find them. @@ -665,7 +663,7 @@ def main(): boundary=dict(type='str', aliases=['boundary_policy_arn']), create_instance_profile=dict(type='bool', default=True), delete_instance_profile=dict(type='bool', default=False), - purge_policies=dict(type='bool', aliases=['purge_policy', 'purge_managed_policies']), + purge_policies=dict(default=True, type='bool', aliases=['purge_policy', 'purge_managed_policies']), tags=dict(type='dict', aliases=['resource_tags']), purge_tags=dict(type='bool', default=True), wait=dict(type='bool', default=True), @@ -676,10 +674,6 @@ def main(): required_if=[('state', 'present', ['assume_role_policy_document'])], supports_check_mode=True) - if module.params.get('purge_policies') is None: - module.deprecate('After 2022-06-01 the default value of purge_policies will change from true to false.' - ' To maintain the existing behaviour explicitly set purge_policies=true', date='2022-06-01', collection_name='community.aws') - if module.params.get('boundary'): if module.params.get('create_instance_profile'): module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.") From d6e643904567791bebad16876f6fdeeaf8c01912 Mon Sep 17 00:00:00 2001 From: Bikouo Aubin <79859644+abikouo@users.noreply.github.com> Date: Tue, 10 Jan 2023 19:22:13 +0100 Subject: [PATCH 30/37] Ansible User-Agent identification for community.aws (#1632) Ansible User-Agent identification for community.aws SUMMARY The value will be similar to this APN/1.0 Ansible/2.14.1 community.aws/6.0.0-dev0 ISSUE TYPE Feature Pull Request Reviewed-by: Mark Chappell Reviewed-by: Bikouo Aubin Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/a8cbce24071bcc62fe4594c38aff1baf18bd2862 --- plugins/modules/iam_role.py | 2 +- plugins/modules/iam_role_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 09a86a54e82..a1aea8a5848 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -202,7 +202,7 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 84e9a31718e..3d6e6bdc597 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -153,7 +153,7 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict From 7314bd0eeaacac110fd61388dba6cbece6de0d49 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 2 Feb 2023 15:59:36 +0100 Subject: [PATCH 31/37] iam_role - add assume_role_policy_document_raw (#1692) iam_role - add assume_role_policy_document_raw SUMMARY fixes: #551 assume_role_policy_document is an IAM policy document, and as such we shouldn't be modifying it. Running camel / snake conversion against the document breaks it. Adds assume_role_policy_document_raw and deprecates the current snake_case behaviour. ISSUE TYPE Feature Pull Request COMPONENT NAME iam_role iam_role_info ADDITIONAL INFORMATION Follows up on #1054 / #1068 Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/5bf51c0d05376434bb945c43589fd6e4dcd89bc4 --- plugins/modules/iam_role.py | 38 ++++++++++++++++++- plugins/modules/iam_role_info.py | 36 ++++++++++++++++-- .../iam_role/tasks/creation_deletion.yml | 5 ++- .../targets/iam_role/tasks/main.yml | 2 + 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index a1aea8a5848..255b4cb7964 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -160,8 +160,12 @@ returned: always sample: "2016-08-14T04:36:28+00:00" assume_role_policy_document: - description: the policy that grants an entity permission to assume the role - type: str + description: + - the policy that grants an entity permission to assume the role + - | + note: the case of keys in this dictionary are currently converted from CamelCase to + snake_case. In a release after 2023-12-01 this behaviour will change + type: dict returned: always sample: { 'statement': [ @@ -176,6 +180,25 @@ ], 'version': '2012-10-17' } + assume_role_policy_document_raw: + description: the policy that grants an entity permission to assume the role + type: dict + returned: always + version_added: 5.3.0 + sample: { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'Service': 'ec2.amazonaws.com' + }, + 'Sid': '' + } + ], + 'Version': '2012-10-17' + } + attached_policies: description: a list of dicts containing the name and ARN of the managed IAM policies attached to the role type: list @@ -498,6 +521,7 @@ def create_or_update_role(module, client): role['tags'] = get_role_tags(module, client) camel_role = camel_dict_to_snake_dict(role, ignore_list=['tags']) + camel_role["assume_role_policy_document_raw"] = role.get("AssumeRolePolicyDocument", {}) module.exit_json(changed=changed, iam_role=camel_role, **camel_role) @@ -674,6 +698,16 @@ def main(): required_if=[('state', 'present', ['assume_role_policy_document'])], supports_check_mode=True) + module.deprecate("All return values other than iam_role and changed have been deprecated and " + "will be removed in a release after 2023-12-01.", + date="2023-12-01", collection_name="community.aws") + + module.deprecate("In a release after 2023-12-01 the contents of iam_role.assume_role_policy_document " + "will no longer be converted from CamelCase to snake_case. The " + "iam_role.assume_role_policy_document_raw return value already returns the " + "policy document in this future format.", + date="2023-12-01", collection_name="community.aws") + if module.params.get('boundary'): if module.params.get('create_instance_profile'): module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.") diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 3d6e6bdc597..23da3e04097 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -61,9 +61,18 @@ type: str sample: arn:aws:iam::123456789012:role/AnsibleTestRole assume_role_policy_document: - description: Policy Document describing what can assume the role. + description: + - The policy that grants an entity permission to assume the role + - | + Note: the case of keys in this dictionary are currently converted from CamelCase to + snake_case. In a release after 2023-12-01 this behaviour will change. returned: always - type: str + type: dict + assume_role_policy_document_raw: + description: The policy document describing what can assume the role. + returned: always + type: dict + version_added: 5.3.0 create_date: description: Date IAM role was created. returned: always @@ -227,7 +236,22 @@ def describe_iam_roles(module, client): roles = list_iam_roles_with_backoff(client, **params)['Roles'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't list IAM roles") - return [camel_dict_to_snake_dict(describe_iam_role(module, client, role), ignore_list=['tags']) for role in roles] + return [normalize_role(describe_iam_role(module, client, role)) for role in roles] + + +def normalize_profile(profile): + new_profile = camel_dict_to_snake_dict(profile) + if profile.get("Roles"): + profile["roles"] = [normalize_role(role) for role in profile.get("Roles")] + return new_profile + + +def normalize_role(role): + new_role = camel_dict_to_snake_dict(role, ignore_list=['tags']) + new_role["assume_role_policy_document_raw"] = role.get("AssumeRolePolicyDocument") + if role.get("InstanceProfiles"): + role["instance_profiles"] = [normalize_profile(profile) for profile in role.get("InstanceProfiles")] + return new_role def main(): @@ -245,6 +269,12 @@ def main(): client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + module.deprecate("In a release after 2023-12-01 the contents of assume_role_policy_document " + "will no longer be converted from CamelCase to snake_case. The " + ".assume_role_policy_document_raw return value already returns the " + "policy document in this future format.", + date="2023-12-01", collection_name="community.aws") + module.exit_json(changed=False, iam_roles=describe_iam_roles(module, client)) diff --git a/tests/integration/targets/iam_role/tasks/creation_deletion.yml b/tests/integration/targets/iam_role/tasks/creation_deletion.yml index 27c48309bce..0579a6d3430 100644 --- a/tests/integration/targets/iam_role/tasks/creation_deletion.yml +++ b/tests/integration/targets/iam_role/tasks/creation_deletion.yml @@ -105,8 +105,9 @@ - iam_role.iam_role.role_name == test_role - 'iam_role.iam_role.arn.startswith("arn")' - 'iam_role.iam_role.arn.endswith("role/" + test_role )' - # Would be nice to test the contents... - '"assume_role_policy_document" in iam_role.iam_role' + - '"assume_role_policy_document_raw" in iam_role.iam_role' + - iam_role.iam_role.assume_role_policy_document_raw == assume_deny_policy - iam_role.iam_role.attached_policies | length == 0 - iam_role.iam_role.max_session_duration == 3600 - iam_role.iam_role.path == '/' @@ -148,8 +149,10 @@ - 'role_info.iam_roles[0].arn.startswith("arn")' - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"assume_role_policy_document_raw" in role_info.iam_roles[0]' - '"create_date" in role_info.iam_roles[0]' - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].assume_role_policy_document_raw == assume_deny_policy - role_info.iam_roles[0].inline_policies | length == 0 - role_info.iam_roles[0].instance_profiles | length == 0 - role_info.iam_roles[0].managed_policies | length == 0 diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml index b2db8e59c13..ae47ada1ad5 100644 --- a/tests/integration/targets/iam_role/tasks/main.yml +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -33,6 +33,8 @@ - amazon.aws - community.general block: + - set_fact: + assume_deny_policy: '{{ lookup("file", "deny-assume.json") | from_json }}' # =================================================================== # Parameter Checks - include_tasks: parameter_checks.yml From e13548043222b33c529686d2b0596d9056389123 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Wed, 8 Mar 2023 12:07:26 +0100 Subject: [PATCH 32/37] Cleanup headers and imports (#1738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup headers and imports SUMMARY Mass update of imports, docs fragments and file headers Many of the amazon.aws module_utils and docs fragments got moved about, update community.aws to reflect this. Consistently apply the comment headers as documented at https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#python-shebang-utf-8-coding ISSUE TYPE Docs Pull Request Feature Pull Request COMPONENT NAME ADDITIONAL INFORMATION Header cleanup based upon: https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#python-shebang-utf-8-coding Begin your Ansible module with #!/usr/bin/python - this “shebang” allows ansible_python_interpreter to work. Follow the shebang immediately with # -*- coding: utf-8 -*- to clarify that the file is UTF-8 encoded. and https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#copyright-and-license After the shebang and UTF-8 coding, add a copyright line with the original copyright holder and a license declaration. The license declaration should be ONLY one line, not the full GPL prefix. ... Additions to the module (for instance, rewrites) are not permitted to add additional copyright lines other than the default copyright statement if missing: Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/a4f20bf114bfab19b1c84c4ecf42efd5614ab80c --- plugins/modules/iam_role.py | 38 ++++++++++++++++---------------- plugins/modules/iam_role_info.py | 32 +++++++++++++-------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 255b4cb7964..07463cd9736 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -1,11 +1,10 @@ #!/usr/bin/python -# 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 +# -*- coding: utf-8 -*- +# Copyright: Contributors to the Ansible project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -DOCUMENTATION = r''' +DOCUMENTATION = r""" --- module: iam_role version_added: 1.0.0 @@ -91,13 +90,13 @@ default: True type: bool extends_documentation_fragment: - - amazon.aws.aws - - amazon.aws.ec2 - - amazon.aws.boto3 + - amazon.aws.common.modules + - amazon.aws.region.modules - amazon.aws.tags -''' + - amazon.aws.boto3 +""" -EXAMPLES = r''' +EXAMPLES = r""" # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Create a role with description and tags @@ -127,8 +126,8 @@ assume_role_policy_document: "{{ lookup('file', 'policy.json') }}" state: absent -''' -RETURN = r''' +""" +RETURN = r""" iam_role: description: dictionary containing the IAM Role data returned: success @@ -214,7 +213,7 @@ type: dict returned: always sample: '{"Env": "Prod"}' -''' +""" import json @@ -225,13 +224,14 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.policy import compare_policies +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list +from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags + from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_policies @AWSRetry.jittered_backoff() diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index 23da3e04097..e3bdb7695bf 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -1,12 +1,10 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- + # 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 = ''' +DOCUMENTATION = r""" --- module: iam_role_info version_added: 1.0.0 @@ -29,13 +27,12 @@ - Mutually exclusive with I(name). type: str extends_documentation_fragment: -- amazon.aws.aws -- amazon.aws.ec2 -- amazon.aws.boto3 + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" -''' - -EXAMPLES = ''' +EXAMPLES = r""" - name: find all existing IAM roles community.aws.iam_role_info: register: result @@ -47,9 +44,9 @@ - name: describe all roles matching a path prefix community.aws.iam_role_info: path_prefix: /application/path -''' +""" -RETURN = ''' +RETURN = r""" iam_roles: description: List of IAM roles returned: always @@ -153,7 +150,7 @@ type: dict returned: always sample: '{"Env": "Prod"}' -''' +""" try: import botocore @@ -162,10 +159,11 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict + from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict @AWSRetry.jittered_backoff() From 53e560ed78541fe959ff99f8894388ba09710d43 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Wed, 26 Apr 2023 19:26:07 +0200 Subject: [PATCH 33/37] Big Black PR (#1784) * Black prep * Black * changelog * Fix pylint unused-import in tests * Split SSM connection plugin changes * disable glue tests - bucket's missing * Disable s3_logging and s3_sync tests This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/2c4575c248776c65d66b06cd60fa09b0dae1cd6f --- plugins/modules/iam_role.py | 283 +++++++++++++++++-------------- plugins/modules/iam_role_info.py | 84 ++++----- 2 files changed, 206 insertions(+), 161 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 07463cd9736..3cafe85d2cb 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -236,44 +236,44 @@ @AWSRetry.jittered_backoff() def _list_policies(client): - paginator = client.get_paginator('list_policies') - return paginator.paginate().build_full_result()['Policies'] + paginator = client.get_paginator("list_policies") + return paginator.paginate().build_full_result()["Policies"] def wait_iam_exists(module, client): if module.check_mode: return - if not module.params.get('wait'): + if not module.params.get("wait"): return - role_name = module.params.get('name') - wait_timeout = module.params.get('wait_timeout') + role_name = module.params.get("name") + wait_timeout = module.params.get("wait_timeout") delay = min(wait_timeout, 5) max_attempts = wait_timeout // delay try: - waiter = client.get_waiter('role_exists') + waiter = client.get_waiter("role_exists") waiter.wait( - WaiterConfig={'Delay': delay, 'MaxAttempts': max_attempts}, + WaiterConfig={"Delay": delay, "MaxAttempts": max_attempts}, RoleName=role_name, ) except botocore.exceptions.WaiterError as e: - module.fail_json_aws(e, msg='Timeout while waiting on IAM role creation') + module.fail_json_aws(e, msg="Timeout while waiting on IAM role creation") except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg='Failed while waiting on IAM role creation') + module.fail_json_aws(e, msg="Failed while waiting on IAM role creation") def convert_friendly_names_to_arns(module, client, policy_names): - if not any(not policy.startswith('arn:') for policy in policy_names): + if not any(not policy.startswith("arn:") for policy in policy_names): return policy_names allpolicies = {} policies = _list_policies(client) for policy in policies: - allpolicies[policy['PolicyName']] = policy['Arn'] - allpolicies[policy['Arn']] = policy['Arn'] + allpolicies[policy["PolicyName"]] = policy["Arn"] + allpolicies[policy["Arn"]] = policy["Arn"] try: return [allpolicies[policy] for policy in policy_names] except KeyError as e: @@ -303,9 +303,12 @@ def remove_policies(module, client, policies_to_remove, role_name): try: client.detach_role_policy(RoleName=role_name, PolicyArn=policy, aws_retry=True) changed = True - except is_boto3_error_code('NoSuchEntityException'): + except is_boto3_error_code("NoSuchEntityException"): pass - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, role_name)) return changed @@ -315,25 +318,28 @@ def remove_inline_policies(module, client, role_name): for policy in current_inline_policies: try: client.delete_role_policy(RoleName=role_name, PolicyName=policy, aws_retry=True) - except is_boto3_error_code('NoSuchEntityException'): + except is_boto3_error_code("NoSuchEntityException"): pass - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to delete policy {0} embedded in {1}".format(policy, role_name)) def generate_create_params(module): params = dict() - params['Path'] = module.params.get('path') - params['RoleName'] = module.params.get('name') - params['AssumeRolePolicyDocument'] = module.params.get('assume_role_policy_document') - if module.params.get('description') is not None: - params['Description'] = module.params.get('description') - if module.params.get('max_session_duration') is not None: - params['MaxSessionDuration'] = module.params.get('max_session_duration') - if module.params.get('boundary') is not None: - params['PermissionsBoundary'] = module.params.get('boundary') - if module.params.get('tags') is not None: - params['Tags'] = ansible_dict_to_boto3_tag_list(module.params.get('tags')) + params["Path"] = module.params.get("path") + params["RoleName"] = module.params.get("name") + params["AssumeRolePolicyDocument"] = module.params.get("assume_role_policy_document") + if module.params.get("description") is not None: + params["Description"] = module.params.get("description") + if module.params.get("max_session_duration") is not None: + params["MaxSessionDuration"] = module.params.get("max_session_duration") + if module.params.get("boundary") is not None: + params["PermissionsBoundary"] = module.params.get("boundary") + if module.params.get("tags") is not None: + params["Tags"] = ansible_dict_to_boto3_tag_list(module.params.get("tags")) return params @@ -352,7 +358,7 @@ def create_basic_role(module, client): # 'Description' is documented as key of the role returned by create_role # but appears to be an AWS bug (the value is not returned using the AWS CLI either). # Get the role after creating it. - role = get_role_with_backoff(module, client, params['RoleName']) + role = get_role_with_backoff(module, client, params["RoleName"]) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to create role") @@ -368,10 +374,7 @@ def update_role_assumed_policy(module, client, role_name, target_assumed_policy, return True try: - client.update_assume_role_policy( - RoleName=role_name, - PolicyDocument=target_assumed_policy, - aws_retry=True) + client.update_assume_role_policy(RoleName=role_name, PolicyDocument=target_assumed_policy, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(role_name)) return True @@ -407,7 +410,9 @@ def update_role_max_session_duration(module, client, role_name, target_duration, return True -def update_role_permissions_boundary(module, client, role_name, target_permissions_boundary, current_permissions_boundary): +def update_role_permissions_boundary( + module, client, role_name, target_permissions_boundary, current_permissions_boundary +): # Check PermissionsBoundary if target_permissions_boundary is None or target_permissions_boundary == current_permissions_boundary: return False @@ -415,14 +420,16 @@ def update_role_permissions_boundary(module, client, role_name, target_permissio if module.check_mode: return True - if target_permissions_boundary == '': + if target_permissions_boundary == "": try: client.delete_role_permissions_boundary(RoleName=role_name, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(role_name)) else: try: - client.put_role_permissions_boundary(RoleName=role_name, PermissionsBoundary=target_permissions_boundary, aws_retry=True) + client.put_role_permissions_boundary( + RoleName=role_name, PermissionsBoundary=target_permissions_boundary, aws_retry=True + ) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(role_name)) return True @@ -435,7 +442,7 @@ def update_managed_policies(module, client, role_name, managed_policies, purge_p # Get list of current attached managed policies current_attached_policies = get_attached_policy_list(module, client, role_name) - current_attached_policies_arn_list = [policy['PolicyArn'] for policy in current_attached_policies] + current_attached_policies_arn_list = [policy["PolicyArn"] for policy in current_attached_policies] if len(managed_policies) == 1 and managed_policies[0] is None: managed_policies = [] @@ -460,18 +467,17 @@ def update_managed_policies(module, client, role_name, managed_policies, purge_p def create_or_update_role(module, client): - - role_name = module.params.get('name') - assumed_policy = module.params.get('assume_role_policy_document') - create_instance_profile = module.params.get('create_instance_profile') - description = module.params.get('description') - duration = module.params.get('max_session_duration') - path = module.params.get('path') - permissions_boundary = module.params.get('boundary') - purge_tags = module.params.get('purge_tags') - tags = ansible_dict_to_boto3_tag_list(module.params.get('tags')) if module.params.get('tags') else None - purge_policies = module.params.get('purge_policies') - managed_policies = module.params.get('managed_policies') + role_name = module.params.get("name") + assumed_policy = module.params.get("assume_role_policy_document") + create_instance_profile = module.params.get("create_instance_profile") + description = module.params.get("description") + duration = module.params.get("max_session_duration") + path = module.params.get("path") + permissions_boundary = module.params.get("boundary") + purge_tags = module.params.get("purge_tags") + tags = ansible_dict_to_boto3_tag_list(module.params.get("tags")) if module.params.get("tags") else None + purge_policies = module.params.get("purge_policies") + managed_policies = module.params.get("managed_policies") if managed_policies: # Attempt to list the policies early so we don't leave things behind if we can't find them. managed_policies = convert_friendly_names_to_arns(module, client, managed_policies) @@ -485,31 +491,33 @@ def create_or_update_role(module, client): if role is None: role = create_basic_role(module, client) - if not module.check_mode and module.params.get('wait'): + if not module.check_mode and module.params.get("wait"): wait_iam_exists(module, client) changed = True else: # Role exists - get current attributes - current_assumed_policy = role.get('AssumeRolePolicyDocument') - current_description = role.get('Description') - current_duration = role.get('MaxSessionDuration') - current_permissions_boundary = role.get('PermissionsBoundary', {}).get('PermissionsBoundaryArn', '') + current_assumed_policy = role.get("AssumeRolePolicyDocument") + current_description = role.get("Description") + current_duration = role.get("MaxSessionDuration") + current_permissions_boundary = role.get("PermissionsBoundary", {}).get("PermissionsBoundaryArn", "") # Update attributes changed |= update_role_tags(module, client, role_name, tags, purge_tags) changed |= update_role_assumed_policy(module, client, role_name, assumed_policy, current_assumed_policy) changed |= update_role_description(module, client, role_name, description, current_description) changed |= update_role_max_session_duration(module, client, role_name, duration, current_duration) - changed |= update_role_permissions_boundary(module, client, role_name, permissions_boundary, current_permissions_boundary) + changed |= update_role_permissions_boundary( + module, client, role_name, permissions_boundary, current_permissions_boundary + ) - if not module.check_mode and module.params.get('wait'): + if not module.check_mode and module.params.get("wait"): wait_iam_exists(module, client) if create_instance_profile: changed |= create_instance_profiles(module, client, role_name, path) - if not module.check_mode and module.params.get('wait'): + if not module.check_mode and module.params.get("wait"): wait_iam_exists(module, client) changed |= update_managed_policies(module, client, role_name, managed_policies, purge_policies) @@ -517,24 +525,25 @@ def create_or_update_role(module, client): # Get the role again role = get_role(module, client, role_name) - role['AttachedPolicies'] = get_attached_policy_list(module, client, role_name) - role['tags'] = get_role_tags(module, client) + role["AttachedPolicies"] = get_attached_policy_list(module, client, role_name) + role["tags"] = get_role_tags(module, client) - camel_role = camel_dict_to_snake_dict(role, ignore_list=['tags']) + camel_role = camel_dict_to_snake_dict(role, ignore_list=["tags"]) camel_role["assume_role_policy_document_raw"] = role.get("AssumeRolePolicyDocument", {}) module.exit_json(changed=changed, iam_role=camel_role, **camel_role) def create_instance_profiles(module, client, role_name, path): - # Fetch existing Profiles try: - instance_profiles = client.list_instance_profiles_for_role(RoleName=role_name, aws_retry=True)['InstanceProfiles'] + instance_profiles = client.list_instance_profiles_for_role(RoleName=role_name, aws_retry=True)[ + "InstanceProfiles" + ] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) # Profile already exists - if any(p['InstanceProfileName'] == role_name for p in instance_profiles): + if any(p["InstanceProfileName"] == role_name for p in instance_profiles): return False if module.check_mode: @@ -543,11 +552,14 @@ def create_instance_profiles(module, client, role_name, path): # Make sure an instance profile is created try: client.create_instance_profile(InstanceProfileName=role_name, Path=path, aws_retry=True) - except is_boto3_error_code('EntityAlreadyExists'): + except is_boto3_error_code("EntityAlreadyExists"): # If the profile already exists, no problem, move on. # Implies someone's changing things at the same time... return False - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(role_name)) # And attach the role to the profile @@ -563,31 +575,39 @@ def remove_instance_profiles(module, client, role_name): delete_profiles = module.params.get("delete_instance_profile") try: - instance_profiles = client.list_instance_profiles_for_role(aws_retry=True, RoleName=role_name)['InstanceProfiles'] + instance_profiles = client.list_instance_profiles_for_role(aws_retry=True, RoleName=role_name)[ + "InstanceProfiles" + ] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) # Remove the role from the instance profile(s) for profile in instance_profiles: - profile_name = profile['InstanceProfileName'] + profile_name = profile["InstanceProfileName"] try: if not module.check_mode: - client.remove_role_from_instance_profile(aws_retry=True, InstanceProfileName=profile_name, RoleName=role_name) + client.remove_role_from_instance_profile( + aws_retry=True, InstanceProfileName=profile_name, RoleName=role_name + ) if profile_name == role_name: if delete_profiles: try: client.delete_instance_profile(InstanceProfileName=profile_name, aws_retry=True) - except is_boto3_error_code('NoSuchEntityException'): + except is_boto3_error_code("NoSuchEntityException"): pass - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to remove instance profile {0}".format(profile_name)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to remove role {0} from instance profile {1}".format(role_name, profile_name)) + module.fail_json_aws( + e, msg="Unable to remove role {0} from instance profile {1}".format(role_name, profile_name) + ) def destroy_role(module, client): - - role_name = module.params.get('name') + role_name = module.params.get("name") role = get_role(module, client, role_name) if role is None: @@ -603,9 +623,12 @@ def destroy_role(module, client): remove_inline_policies(module, client, role_name) try: client.delete_role(aws_retry=True, RoleName=role_name) - except is_boto3_error_code('NoSuchEntityException'): + except is_boto3_error_code("NoSuchEntityException"): module.exit_json(changed=False) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to delete role") module.exit_json(changed=True) @@ -613,38 +636,43 @@ def destroy_role(module, client): def get_role_with_backoff(module, client, name): try: - return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(client.get_role)(RoleName=name)['Role'] + return AWSRetry.jittered_backoff(catch_extra_error_codes=["NoSuchEntity"])(client.get_role)(RoleName=name)[ + "Role" + ] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) def get_role(module, client, name): try: - return client.get_role(RoleName=name, aws_retry=True)['Role'] - except is_boto3_error_code('NoSuchEntity'): + return client.get_role(RoleName=name, aws_retry=True)["Role"] + except is_boto3_error_code("NoSuchEntity"): return None - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) def get_attached_policy_list(module, client, name): try: - return client.list_attached_role_policies(RoleName=name, aws_retry=True)['AttachedPolicies'] + return client.list_attached_role_policies(RoleName=name, aws_retry=True)["AttachedPolicies"] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) def get_inline_policy_list(module, client, name): try: - return client.list_role_policies(RoleName=name, aws_retry=True)['PolicyNames'] + return client.list_role_policies(RoleName=name, aws_retry=True)["PolicyNames"] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) def get_role_tags(module, client): - role_name = module.params.get('name') + role_name = module.params.get("name") try: - return boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) + return boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)["Tags"]) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to list tags for role {0}".format(role_name)) @@ -655,7 +683,9 @@ def update_role_tags(module, client, role_name, new_tags, purge_tags): new_tags = boto3_tag_list_to_ansible_dict(new_tags) try: - existing_tags = boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)['Tags']) + existing_tags = boto3_tag_list_to_ansible_dict( + client.list_role_tags(RoleName=role_name, aws_retry=True)["Tags"] + ) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, KeyError): existing_tags = {} @@ -668,69 +698,76 @@ def update_role_tags(module, client, role_name, new_tags, purge_tags): if tags_to_add: client.tag_role(RoleName=role_name, Tags=ansible_dict_to_boto3_tag_list(tags_to_add), aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg='Unable to set tags for role %s' % role_name) + module.fail_json_aws(e, msg="Unable to set tags for role %s" % role_name) changed = bool(tags_to_add) or bool(tags_to_remove) return changed def main(): - argument_spec = dict( - name=dict(type='str', required=True), - path=dict(type='str', default="/"), - assume_role_policy_document=dict(type='json'), - managed_policies=dict(type='list', aliases=['managed_policy'], elements='str'), - max_session_duration=dict(type='int'), - state=dict(type='str', choices=['present', 'absent'], default='present'), - description=dict(type='str'), - boundary=dict(type='str', aliases=['boundary_policy_arn']), - create_instance_profile=dict(type='bool', default=True), - delete_instance_profile=dict(type='bool', default=False), - purge_policies=dict(default=True, type='bool', aliases=['purge_policy', 'purge_managed_policies']), - tags=dict(type='dict', aliases=['resource_tags']), - purge_tags=dict(type='bool', default=True), - wait=dict(type='bool', default=True), - wait_timeout=dict(default=120, type='int'), + name=dict(type="str", required=True), + path=dict(type="str", default="/"), + assume_role_policy_document=dict(type="json"), + managed_policies=dict(type="list", aliases=["managed_policy"], elements="str"), + max_session_duration=dict(type="int"), + state=dict(type="str", choices=["present", "absent"], default="present"), + description=dict(type="str"), + boundary=dict(type="str", aliases=["boundary_policy_arn"]), + create_instance_profile=dict(type="bool", default=True), + delete_instance_profile=dict(type="bool", default=False), + purge_policies=dict(default=True, type="bool", aliases=["purge_policy", "purge_managed_policies"]), + tags=dict(type="dict", aliases=["resource_tags"]), + purge_tags=dict(type="bool", default=True), + wait=dict(type="bool", default=True), + wait_timeout=dict(default=120, type="int"), ) - module = AnsibleAWSModule(argument_spec=argument_spec, - required_if=[('state', 'present', ['assume_role_policy_document'])], - supports_check_mode=True) + module = AnsibleAWSModule( + argument_spec=argument_spec, + required_if=[("state", "present", ["assume_role_policy_document"])], + supports_check_mode=True, + ) - module.deprecate("All return values other than iam_role and changed have been deprecated and " - "will be removed in a release after 2023-12-01.", - date="2023-12-01", collection_name="community.aws") + module.deprecate( + "All return values other than iam_role and changed have been deprecated and " + "will be removed in a release after 2023-12-01.", + date="2023-12-01", + collection_name="community.aws", + ) - module.deprecate("In a release after 2023-12-01 the contents of iam_role.assume_role_policy_document " - "will no longer be converted from CamelCase to snake_case. The " - "iam_role.assume_role_policy_document_raw return value already returns the " - "policy document in this future format.", - date="2023-12-01", collection_name="community.aws") + module.deprecate( + "In a release after 2023-12-01 the contents of iam_role.assume_role_policy_document " + "will no longer be converted from CamelCase to snake_case. The " + "iam_role.assume_role_policy_document_raw return value already returns the " + "policy document in this future format.", + date="2023-12-01", + collection_name="community.aws", + ) - if module.params.get('boundary'): - if module.params.get('create_instance_profile'): + if module.params.get("boundary"): + if module.params.get("create_instance_profile"): module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.") - if not module.params.get('boundary').startswith('arn:aws:iam'): + if not module.params.get("boundary").startswith("arn:aws:iam"): module.fail_json(msg="Boundary policy must be an ARN") - if module.params.get('max_session_duration'): - max_session_duration = module.params.get('max_session_duration') + if module.params.get("max_session_duration"): + max_session_duration = module.params.get("max_session_duration") if max_session_duration < 3600 or max_session_duration > 43200: module.fail_json(msg="max_session_duration must be between 1 and 12 hours (3600 and 43200 seconds)") - if module.params.get('path'): - path = module.params.get('path') - if not path.endswith('/') or not path.startswith('/'): + if module.params.get("path"): + path = module.params.get("path") + if not path.endswith("/") or not path.startswith("/"): module.fail_json(msg="path must begin and end with /") - client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + client = module.client("iam", retry_decorator=AWSRetry.jittered_backoff()) state = module.params.get("state") - if state == 'present': + if state == "present": create_or_update_role(module, client) - elif state == 'absent': + elif state == "absent": destroy_role(module, client) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index e3bdb7695bf..a7576a131ec 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -168,70 +168,73 @@ @AWSRetry.jittered_backoff() def list_iam_roles_with_backoff(client, **kwargs): - paginator = client.get_paginator('list_roles') + paginator = client.get_paginator("list_roles") return paginator.paginate(**kwargs).build_full_result() @AWSRetry.jittered_backoff() def list_iam_role_policies_with_backoff(client, role_name): - paginator = client.get_paginator('list_role_policies') - return paginator.paginate(RoleName=role_name).build_full_result()['PolicyNames'] + paginator = client.get_paginator("list_role_policies") + return paginator.paginate(RoleName=role_name).build_full_result()["PolicyNames"] @AWSRetry.jittered_backoff() def list_iam_attached_role_policies_with_backoff(client, role_name): - paginator = client.get_paginator('list_attached_role_policies') - return paginator.paginate(RoleName=role_name).build_full_result()['AttachedPolicies'] + paginator = client.get_paginator("list_attached_role_policies") + return paginator.paginate(RoleName=role_name).build_full_result()["AttachedPolicies"] @AWSRetry.jittered_backoff() def list_iam_instance_profiles_for_role_with_backoff(client, role_name): - paginator = client.get_paginator('list_instance_profiles_for_role') - return paginator.paginate(RoleName=role_name).build_full_result()['InstanceProfiles'] + paginator = client.get_paginator("list_instance_profiles_for_role") + return paginator.paginate(RoleName=role_name).build_full_result()["InstanceProfiles"] def describe_iam_role(module, client, role): - name = role['RoleName'] + name = role["RoleName"] try: - role['InlinePolicies'] = list_iam_role_policies_with_backoff(client, name) + role["InlinePolicies"] = list_iam_role_policies_with_backoff(client, name) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't get inline policies for role %s" % name) try: - role['ManagedPolicies'] = list_iam_attached_role_policies_with_backoff(client, name) + role["ManagedPolicies"] = list_iam_attached_role_policies_with_backoff(client, name) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't get managed policies for role %s" % name) try: - role['InstanceProfiles'] = list_iam_instance_profiles_for_role_with_backoff(client, name) + role["InstanceProfiles"] = list_iam_instance_profiles_for_role_with_backoff(client, name) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't get instance profiles for role %s" % name) try: - role['tags'] = boto3_tag_list_to_ansible_dict(role['Tags']) - del role['Tags'] + role["tags"] = boto3_tag_list_to_ansible_dict(role["Tags"]) + del role["Tags"] except KeyError: - role['tags'] = {} + role["tags"] = {} return role def describe_iam_roles(module, client): - name = module.params['name'] - path_prefix = module.params['path_prefix'] + name = module.params["name"] + path_prefix = module.params["path_prefix"] if name: try: - roles = [client.get_role(RoleName=name, aws_retry=True)['Role']] - except is_boto3_error_code('NoSuchEntity'): + roles = [client.get_role(RoleName=name, aws_retry=True)["Role"]] + except is_boto3_error_code("NoSuchEntity"): return [] - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Couldn't get IAM role %s" % name) else: params = dict() if path_prefix: - if not path_prefix.startswith('/'): - path_prefix = '/' + path_prefix - if not path_prefix.endswith('/'): - path_prefix = path_prefix + '/' - params['PathPrefix'] = path_prefix + if not path_prefix.startswith("/"): + path_prefix = "/" + path_prefix + if not path_prefix.endswith("/"): + path_prefix = path_prefix + "/" + params["PathPrefix"] = path_prefix try: - roles = list_iam_roles_with_backoff(client, **params)['Roles'] + roles = list_iam_roles_with_backoff(client, **params)["Roles"] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't list IAM roles") return [normalize_role(describe_iam_role(module, client, role)) for role in roles] @@ -245,7 +248,7 @@ def normalize_profile(profile): def normalize_role(role): - new_role = camel_dict_to_snake_dict(role, ignore_list=['tags']) + new_role = camel_dict_to_snake_dict(role, ignore_list=["tags"]) new_role["assume_role_policy_document_raw"] = role.get("AssumeRolePolicyDocument") if role.get("InstanceProfiles"): role["instance_profiles"] = [normalize_profile(profile) for profile in role.get("InstanceProfiles")] @@ -254,27 +257,32 @@ def normalize_role(role): def main(): """ - Module action handler + Module action handler """ argument_spec = dict( - name=dict(aliases=['role_name']), + name=dict(aliases=["role_name"]), path_prefix=dict(), ) - module = AnsibleAWSModule(argument_spec=argument_spec, - supports_check_mode=True, - mutually_exclusive=[['name', 'path_prefix']]) + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[["name", "path_prefix"]], + ) - client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff()) + client = module.client("iam", retry_decorator=AWSRetry.jittered_backoff()) - module.deprecate("In a release after 2023-12-01 the contents of assume_role_policy_document " - "will no longer be converted from CamelCase to snake_case. The " - ".assume_role_policy_document_raw return value already returns the " - "policy document in this future format.", - date="2023-12-01", collection_name="community.aws") + module.deprecate( + "In a release after 2023-12-01 the contents of assume_role_policy_document " + "will no longer be converted from CamelCase to snake_case. The " + ".assume_role_policy_document_raw return value already returns the " + "policy document in this future format.", + date="2023-12-01", + collection_name="community.aws", + ) module.exit_json(changed=False, iam_roles=describe_iam_roles(module, client)) -if __name__ == '__main__': +if __name__ == "__main__": main() From 89cbb0ff6f7e283a57fd6bb82d092891ef0b890e Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Mon, 8 May 2023 19:21:22 +0200 Subject: [PATCH 34/37] Bulk migration to Python 3.6 f-strings (#1810) Bulk migration to Python 3.6 f-strings SUMMARY We've dropped support for Python <3.6, bulk migrate to fstrings and perform some general string cleanup A combination of black --preview flynt some manual cleanup ISSUE TYPE Feature Pull Request COMPONENT NAME plugins/ tests/ ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/de338210dc1b0bb2eecee1dc16e073163b2d1df7 --- plugins/modules/iam_role.py | 42 +++++++++++++++----------------- plugins/modules/iam_role_info.py | 8 +++--- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index 3cafe85d2cb..be05707238a 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -290,7 +290,7 @@ def attach_policies(module, client, policies_to_attach, role_name): client.attach_role_policy(RoleName=role_name, PolicyArn=policy_arn, aws_retry=True) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, role_name)) + module.fail_json_aws(e, msg=f"Unable to attach policy {policy_arn} to role {role_name}") return changed @@ -309,7 +309,7 @@ def remove_policies(module, client, policies_to_remove, role_name): botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, ) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, role_name)) + module.fail_json_aws(e, msg=f"Unable to detach policy {policy} from {role_name}") return changed @@ -324,7 +324,7 @@ def remove_inline_policies(module, client, role_name): botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, ) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Unable to delete policy {0} embedded in {1}".format(policy, role_name)) + module.fail_json_aws(e, msg=f"Unable to delete policy {policy} embedded in {role_name}") def generate_create_params(module): @@ -376,7 +376,7 @@ def update_role_assumed_policy(module, client, role_name, target_assumed_policy, try: client.update_assume_role_policy(RoleName=role_name, PolicyDocument=target_assumed_policy, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to update assume role policy for role {role_name}") return True @@ -391,7 +391,7 @@ def update_role_description(module, client, role_name, target_description, curre try: client.update_role(RoleName=role_name, Description=target_description, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update description for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to update description for role {role_name}") return True @@ -406,7 +406,7 @@ def update_role_max_session_duration(module, client, role_name, target_duration, try: client.update_role(RoleName=role_name, MaxSessionDuration=target_duration, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update maximum session duration for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to update maximum session duration for role {role_name}") return True @@ -424,14 +424,14 @@ def update_role_permissions_boundary( try: client.delete_role_permissions_boundary(RoleName=role_name, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to remove permission boundary for role {role_name}") else: try: client.put_role_permissions_boundary( RoleName=role_name, PermissionsBoundary=target_permissions_boundary, aws_retry=True ) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to update permission boundary for role {role_name}") return True @@ -540,7 +540,7 @@ def create_instance_profiles(module, client, role_name, path): "InstanceProfiles" ] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to list instance profiles for role {role_name}") # Profile already exists if any(p["InstanceProfileName"] == role_name for p in instance_profiles): @@ -560,13 +560,13 @@ def create_instance_profiles(module, client, role_name, path): botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, ) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to create instance profile for role {role_name}") # And attach the role to the profile try: client.add_role_to_instance_profile(InstanceProfileName=role_name, RoleName=role_name, aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to attach role {0} to instance profile {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to attach role {role_name} to instance profile {role_name}") return True @@ -579,7 +579,7 @@ def remove_instance_profiles(module, client, role_name): "InstanceProfiles" ] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to list instance profiles for role {role_name}") # Remove the role from the instance profile(s) for profile in instance_profiles: @@ -599,11 +599,9 @@ def remove_instance_profiles(module, client, role_name): botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, ) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Unable to remove instance profile {0}".format(profile_name)) + module.fail_json_aws(e, msg=f"Unable to remove instance profile {profile_name}") except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws( - e, msg="Unable to remove role {0} from instance profile {1}".format(role_name, profile_name) - ) + module.fail_json_aws(e, msg=f"Unable to remove role {role_name} from instance profile {profile_name}") def destroy_role(module, client): @@ -640,7 +638,7 @@ def get_role_with_backoff(module, client, name): "Role" ] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) + module.fail_json_aws(e, msg=f"Unable to get role {name}") def get_role(module, client, name): @@ -652,21 +650,21 @@ def get_role(module, client, name): botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, ) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Unable to get role {0}".format(name)) + module.fail_json_aws(e, msg=f"Unable to get role {name}") def get_attached_policy_list(module, client, name): try: return client.list_attached_role_policies(RoleName=name, aws_retry=True)["AttachedPolicies"] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) + module.fail_json_aws(e, msg=f"Unable to list attached policies for role {name}") def get_inline_policy_list(module, client, name): try: return client.list_role_policies(RoleName=name, aws_retry=True)["PolicyNames"] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name)) + module.fail_json_aws(e, msg=f"Unable to list attached policies for role {name}") def get_role_tags(module, client): @@ -674,7 +672,7 @@ def get_role_tags(module, client): try: return boto3_tag_list_to_ansible_dict(client.list_role_tags(RoleName=role_name, aws_retry=True)["Tags"]) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to list tags for role {0}".format(role_name)) + module.fail_json_aws(e, msg=f"Unable to list tags for role {role_name}") def update_role_tags(module, client, role_name, new_tags, purge_tags): @@ -698,7 +696,7 @@ def update_role_tags(module, client, role_name, new_tags, purge_tags): if tags_to_add: client.tag_role(RoleName=role_name, Tags=ansible_dict_to_boto3_tag_list(tags_to_add), aws_retry=True) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Unable to set tags for role %s" % role_name) + module.fail_json_aws(e, msg=f"Unable to set tags for role {role_name}") changed = bool(tags_to_add) or bool(tags_to_remove) return changed diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index a7576a131ec..d23754d90a0 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -195,15 +195,15 @@ def describe_iam_role(module, client, role): try: role["InlinePolicies"] = list_iam_role_policies_with_backoff(client, name) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Couldn't get inline policies for role %s" % name) + module.fail_json_aws(e, msg=f"Couldn't get inline policies for role {name}") try: role["ManagedPolicies"] = list_iam_attached_role_policies_with_backoff(client, name) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Couldn't get managed policies for role %s" % name) + module.fail_json_aws(e, msg=f"Couldn't get managed policies for role {name}") try: role["InstanceProfiles"] = list_iam_instance_profiles_for_role_with_backoff(client, name) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Couldn't get instance profiles for role %s" % name) + module.fail_json_aws(e, msg=f"Couldn't get instance profiles for role {name}") try: role["tags"] = boto3_tag_list_to_ansible_dict(role["Tags"]) del role["Tags"] @@ -224,7 +224,7 @@ def describe_iam_roles(module, client): botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError, ) as e: # pylint: disable=duplicate-except - module.fail_json_aws(e, msg="Couldn't get IAM role %s" % name) + module.fail_json_aws(e, msg=f"Couldn't get IAM role {name}") else: params = dict() if path_prefix: From ee71e447a48aee85be828a2250f90eac70c42c9e Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Mon, 26 Jun 2023 23:23:50 +0200 Subject: [PATCH 35/37] Various ARN handling fixes (#1848) Various ARN handling fixes Depends-On: ansible-collections/amazon.aws#1619 SUMMARY fixes: #1846 Various modules had hard-coded ARN handling which assumed the use of the main partition. This causes problems for folks using Gov Cloud (and aws-cn) ISSUE TYPE Bugfix Pull Request COMPONENT NAME plugins/modules/batch_compute_environment.py plugins/modules/ec2_launch_template.py plugins/modules/elasticache_info.py plugins/modules/iam_group.py plugins/modules/iam_role.py plugins/modules/msk_config.py plugins/modules/redshift.py plugins/modules/sns_topic.py ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/25a636cefa0defcd1022c94aa2a38bdcf4763afd --- plugins/modules/iam_role.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index be05707238a..b39281e17b9 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -224,6 +224,7 @@ from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.arn import validate_aws_arn from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.policy import compare_policies from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry @@ -265,7 +266,7 @@ def wait_iam_exists(module, client): def convert_friendly_names_to_arns(module, client, policy_names): - if not any(not policy.startswith("arn:") for policy in policy_names): + if all(validate_aws_arn(policy, service="iam") for policy in policy_names if policy is not None): return policy_names allpolicies = {} @@ -275,7 +276,7 @@ def convert_friendly_names_to_arns(module, client, policy_names): allpolicies[policy["PolicyName"]] = policy["Arn"] allpolicies[policy["Arn"]] = policy["Arn"] try: - return [allpolicies[policy] for policy in policy_names] + return [allpolicies[policy] for policy in policy_names if policy is not None] except KeyError as e: module.fail_json_aws(e, msg="Couldn't find policy") @@ -746,7 +747,7 @@ def main(): if module.params.get("boundary"): if module.params.get("create_instance_profile"): module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.") - if not module.params.get("boundary").startswith("arn:aws:iam"): + if not validate_aws_arn(module.params.get("boundary"), service="iam"): module.fail_json(msg="Boundary policy must be an ARN") if module.params.get("max_session_duration"): max_session_duration = module.params.get("max_session_duration") From db110c63584bbc127406ca666679ef477cf982bc Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 31 Aug 2023 17:58:59 +0200 Subject: [PATCH 36/37] Mass update of docs and tests (credentials/session tokens) (#1921) Mass update of docs and tests (credentials/session tokens) SUMMARY We had a cleanup of credentials/session parameters which included a batch of deprecations and renames. Ensure that all of our tests and docs are using the 'canonical' names ISSUE TYPE Docs Pull Request COMPONENT NAME plugins/modules/batch_compute_environment.py plugins/modules/cloudformation_exports_info.py plugins/modules/ec2_vpc_vpn.py plugins/modules/elasticache.py plugins/modules/elasticache_parameter_group.py plugins/modules/elasticache_snapshot.py plugins/modules/ses_rule_set.py plugins/modules/sts_assume_role.py plugins/modules/sts_session_token.py tests/integration ADDITIONAL INFORMATION See also ansible-collections/amazon.aws#1172 ansible-collections/amazon.aws#1714 Reviewed-by: Alina Buzachis This commit was initially merged in https://github.com/ansible-collections/community.aws See: https://github.com/ansible-collections/community.aws/commit/4a5b50e9b9c0d6ca1a1f802f3b03d4f503c16885 --- tests/integration/targets/iam_role/tasks/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml index ae47ada1ad5..821a683eb53 100644 --- a/tests/integration/targets/iam_role/tasks/main.yml +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -23,9 +23,9 @@ - name: "Setup 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) }}" + access_key: "{{ aws_access_key }}" + secret_key: "{{ aws_secret_key }}" + session_token: "{{ security_token | default(omit) }}" region: "{{ aws_region }}" iam_role: assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' From aadd870d603de7d7790dbc66c7dee757602fa4a1 Mon Sep 17 00:00:00 2001 From: abikouo Date: Wed, 20 Sep 2023 18:26:40 +0200 Subject: [PATCH 37/37] Migrate iam_role and iam_role_info modules from community.aws --- changelogs/fragments/migrate_iam_role.yml | 7 + meta/runtime.yml | 4 +- plugins/modules/iam_role.py | 17 +- plugins/modules/iam_role_info.py | 11 +- .../targets/iam_role/defaults/main.yml | 7 +- .../iam_role/tasks/boundary_policy.yml | 94 ++-- .../iam_role/tasks/complex_role_creation.yml | 155 +++--- .../iam_role/tasks/creation_deletion.yml | 500 +++++++++--------- .../iam_role/tasks/description_update.yml | 181 +++---- .../iam_role/tasks/inline_policy_update.yml | 77 +-- .../targets/iam_role/tasks/main.yml | 134 ++--- .../iam_role/tasks/max_session_update.yml | 77 ++- .../iam_role/tasks/parameter_checks.yml | 82 ++- .../targets/iam_role/tasks/policy_update.yml | 286 +++++----- .../targets/iam_role/tasks/role_removal.yml | 46 +- .../targets/iam_role/tasks/tags_update.yml | 365 ++++++------- 16 files changed, 969 insertions(+), 1074 deletions(-) create mode 100644 changelogs/fragments/migrate_iam_role.yml diff --git a/changelogs/fragments/migrate_iam_role.yml b/changelogs/fragments/migrate_iam_role.yml new file mode 100644 index 00000000000..9b8110ba7d8 --- /dev/null +++ b/changelogs/fragments/migrate_iam_role.yml @@ -0,0 +1,7 @@ +major_changes: +- iam_role - The module has been migrated from the ``community.aws`` collection. Playbooks + using the Fully Qualified Collection Name for this module should be updated to use + ``amazon.aws.iam_role`` (https://github.com/ansible-collections/amazon.aws/pull/1760). +- iam_role_info - The module has been migrated from the ``community.aws`` collection. + Playbooks using the Fully Qualified Collection Name for this module should be updated + to use ``amazon.aws.iam_role_info`` (https://github.com/ansible-collections/amazon.aws/pull/1760). diff --git a/meta/runtime.yml b/meta/runtime.yml index 007f42aaa72..7f75e0d5224 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -73,6 +73,8 @@ action_groups: - iam_instance_profile_info - iam_policy - iam_policy_info + - iam_role + - iam_role_info - iam_user - iam_user_info - kms_key @@ -144,4 +146,4 @@ plugin_routing: redirect: amazon.aws.ssm_parameter aws_secret: # Deprecation for this alias should not *start* prior to 2024-09-01 - redirect: amazon.aws.secretsmanager_secret + redirect: amazon.aws.secretsmanager_secret \ No newline at end of file diff --git a/plugins/modules/iam_role.py b/plugins/modules/iam_role.py index b39281e17b9..404e4aa4e5f 100644 --- a/plugins/modules/iam_role.py +++ b/plugins/modules/iam_role.py @@ -8,6 +8,7 @@ --- module: iam_role version_added: 1.0.0 +version_added_collection: community.aws short_description: Manage AWS IAM roles description: - Manage AWS IAM roles. @@ -45,7 +46,7 @@ description: - A list of managed policy ARNs, managed policy ARNs or friendly names. - To remove all policies set I(purge_polices=true) and I(managed_policies=[None]). - - To embed an inline policy, use M(community.aws.iam_policy). + - To embed an inline policy, use M(amazon.aws.iam_policy). aliases: ['managed_policy'] type: list elements: str @@ -100,7 +101,7 @@ # Note: These examples do not set authentication details, see the AWS Guide for details. - name: Create a role with description and tags - community.aws.iam_role: + amazon.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file','policy.json') }}" description: This is My New Role @@ -108,20 +109,20 @@ env: dev - name: "Create a role and attach a managed policy called 'PowerUserAccess'" - community.aws.iam_role: + amazon.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file','policy.json') }}" managed_policies: - arn:aws:iam::aws:policy/PowerUserAccess - name: Keep the role created above but remove all managed policies - community.aws.iam_role: + amazon.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file','policy.json') }}" managed_policies: [] - name: Delete the role - community.aws.iam_role: + amazon.aws.iam_role: name: mynewrole assume_role_policy_document: "{{ lookup('file', 'policy.json') }}" state: absent @@ -232,7 +233,7 @@ from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags -from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule @AWSRetry.jittered_backoff() @@ -732,7 +733,7 @@ def main(): "All return values other than iam_role and changed have been deprecated and " "will be removed in a release after 2023-12-01.", date="2023-12-01", - collection_name="community.aws", + collection_name="amazon.aws", ) module.deprecate( @@ -741,7 +742,7 @@ def main(): "iam_role.assume_role_policy_document_raw return value already returns the " "policy document in this future format.", date="2023-12-01", - collection_name="community.aws", + collection_name="amazon.aws", ) if module.params.get("boundary"): diff --git a/plugins/modules/iam_role_info.py b/plugins/modules/iam_role_info.py index d23754d90a0..b87a281287f 100644 --- a/plugins/modules/iam_role_info.py +++ b/plugins/modules/iam_role_info.py @@ -8,6 +8,7 @@ --- module: iam_role_info version_added: 1.0.0 +version_added_collection: community.aws short_description: Gather information on IAM roles description: - Gathers information about IAM roles. @@ -34,15 +35,15 @@ EXAMPLES = r""" - name: find all existing IAM roles - community.aws.iam_role_info: + amazon.aws.iam_role_info: register: result - name: describe a single role - community.aws.iam_role_info: + amazon.aws.iam_role_info: name: MyIAMRole - name: describe all roles matching a path prefix - community.aws.iam_role_info: + amazon.aws.iam_role_info: path_prefix: /application/path """ @@ -163,7 +164,7 @@ from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict -from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule @AWSRetry.jittered_backoff() @@ -278,7 +279,7 @@ def main(): ".assume_role_policy_document_raw return value already returns the " "policy document in this future format.", date="2023-12-01", - collection_name="community.aws", + collection_name="amazon.aws", ) module.exit_json(changed=False, iam_roles=describe_iam_roles(module, client)) diff --git a/tests/integration/targets/iam_role/defaults/main.yml b/tests/integration/targets/iam_role/defaults/main.yml index d496c421636..e83a563990b 100644 --- a/tests/integration/targets/iam_role/defaults/main.yml +++ b/tests/integration/targets/iam_role/defaults/main.yml @@ -1,6 +1,5 @@ ---- test_role: '{{ resource_prefix }}-role' -test_path: '/{{ resource_prefix }}/' -safe_managed_policy: 'AWSDenyAll' +test_path: /{{ resource_prefix }}/ +safe_managed_policy: AWSDenyAll custom_policy_name: '{{ resource_prefix }}-denyall' -boundary_policy: 'arn:aws:iam::aws:policy/AWSDenyAll' +boundary_policy: arn:aws:iam::aws:policy/AWSDenyAll diff --git a/tests/integration/targets/iam_role/tasks/boundary_policy.yml b/tests/integration/targets/iam_role/tasks/boundary_policy.yml index 89a983f1564..818b701f1ef 100644 --- a/tests/integration/targets/iam_role/tasks/boundary_policy.yml +++ b/tests/integration/targets/iam_role/tasks/boundary_policy.yml @@ -1,94 +1,86 @@ ---- -- name: "Create minimal role with no boundary policy" +- name: Create minimal role with no boundary policy iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role + - iam_role is changed + - iam_role.iam_role.role_name == test_role -- name: "Configure Boundary Policy (CHECK MODE)" +- name: Configure Boundary Policy (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Configure Boundary Policy" +- name: Configure Boundary Policy iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role + - iam_role is changed + - iam_role.iam_role.role_name == test_role -- name: "Configure Boundary Policy (no change) - check mode" +- name: Configure Boundary Policy (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Configure Boundary Policy (no change)" +- name: Configure Boundary Policy (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after adding boundary policy" +- name: iam_role_info after adding boundary policy iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 0 - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '/' - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role -- name: "Remove IAM Role" +- name: Remove IAM Role iam_role: state: absent - name: "{{ test_role }}" + name: '{{ test_role }}' delete_instance_profile: yes register: iam_role - - assert: that: - - iam_role is changed \ No newline at end of file + - iam_role is changed diff --git a/tests/integration/targets/iam_role/tasks/complex_role_creation.yml b/tests/integration/targets/iam_role/tasks/complex_role_creation.yml index c23234ebf1f..59db5d156fe 100644 --- a/tests/integration/targets/iam_role/tasks/complex_role_creation.yml +++ b/tests/integration/targets/iam_role/tasks/complex_role_creation.yml @@ -1,131 +1,128 @@ ---- -- name: "Complex IAM Role (CHECK MODE)" +- name: Complex IAM Role (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' create_instance_profile: no - description: "Ansible Test Role {{ resource_prefix }}" + description: Ansible Test Role {{ resource_prefix }} managed_policy: - - "{{ safe_managed_policy }}" - - "{{ custom_policy_name }}" + - '{{ safe_managed_policy }}' + - '{{ custom_policy_name }}' max_session_duration: 43200 - path: "{{ test_path }}" + path: '{{ test_path }}' tags: - TagA: "ValueA" + TagA: ValueA check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "iam_role_info after Complex Role creation in check_mode" +- name: iam_role_info after Complex Role creation in check_mode iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 0 -- name: "Complex IAM Role" +- name: Complex IAM Role iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' create_instance_profile: no - description: "Ansible Test Role {{ resource_prefix }}" + description: Ansible Test Role {{ resource_prefix }} managed_policy: - - "{{ safe_managed_policy }}" - - "{{ custom_policy_name }}" + - '{{ safe_managed_policy }}' + - '{{ custom_policy_name }}' max_session_duration: 43200 - path: "{{ test_path }}" + path: '{{ test_path }}' tags: - TagA: "ValueA" + TagA: ValueA register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.arn.startswith("arn") + - iam_role.iam_role.arn.endswith("role" + test_path + test_role ) # Would be nice to test the contents... - - '"assume_role_policy_document" in iam_role.iam_role' - - iam_role.iam_role.attached_policies | length == 2 - - iam_role.iam_role.max_session_duration == 43200 - - iam_role.iam_role.path == test_path - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 2 + - iam_role.iam_role.max_session_duration == 43200 + - iam_role.iam_role.path == test_path + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' -- name: "Complex IAM role (no change) - check mode" +- name: Complex IAM role (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' create_instance_profile: no - description: "Ansible Test Role {{ resource_prefix }}" + description: Ansible Test Role {{ resource_prefix }} managed_policy: - - "{{ safe_managed_policy }}" - - "{{ custom_policy_name }}" + - '{{ safe_managed_policy }}' + - '{{ custom_policy_name }}' max_session_duration: 43200 - path: "{{ test_path }}" + path: '{{ test_path }}' tags: - TagA: "ValueA" + TagA: ValueA register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Complex IAM role (no change)" +- name: Complex IAM role (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' - boundary: "{{ boundary_policy }}" + boundary: '{{ boundary_policy }}' create_instance_profile: no - description: "Ansible Test Role {{ resource_prefix }}" + description: Ansible Test Role {{ resource_prefix }} managed_policy: - - "{{ safe_managed_policy }}" - - "{{ custom_policy_name }}" + - '{{ safe_managed_policy }}' + - '{{ custom_policy_name }}' max_session_duration: 43200 - path: "{{ test_path }}" + path: '{{ test_path }}' tags: - TagA: "ValueA" + TagA: ValueA register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after Role creation" +- name: iam_role_info after Role creation iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 0 - - role_info.iam_roles[0].managed_policies | length == 2 - - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == test_path - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy - - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "ValueA" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role" + test_path + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 2 + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == test_path + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_arn == boundary_policy + - role_info.iam_roles[0].permissions_boundary.permissions_boundary_type == 'Policy' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "ValueA" diff --git a/tests/integration/targets/iam_role/tasks/creation_deletion.yml b/tests/integration/targets/iam_role/tasks/creation_deletion.yml index 0579a6d3430..166c7f1236e 100644 --- a/tests/integration/targets/iam_role/tasks/creation_deletion.yml +++ b/tests/integration/targets/iam_role/tasks/creation_deletion.yml @@ -1,404 +1,376 @@ ---- - name: Try running some rapid fire create/delete tests block: - - name: "Minimal IAM Role without instance profile (rapid)" - iam_role: - name: "{{ test_role }}" - create_instance_profile: no - register: iam_role - - - name: "Minimal IAM Role without instance profile (rapid)" - iam_role: - name: "{{ test_role }}" - create_instance_profile: no - register: iam_role_again - - - assert: - that: - - iam_role is changed - - iam_role_again is not changed - - - name: "Remove IAM Role (rapid)" - iam_role: - state: absent - name: "{{ test_role }}" - register: iam_role - - - name: "Remove IAM Role (rapid)" - iam_role: - state: absent - name: "{{ test_role }}" - register: iam_role_again - - - assert: - that: - - iam_role is changed - - iam_role_again is not changed - - - name: "Minimal IAM Role without instance profile (rapid)" - iam_role: - name: "{{ test_role }}" - create_instance_profile: no - register: iam_role - - - name: "Remove IAM Role (rapid)" - iam_role: - state: absent - name: "{{ test_role }}" - - register: iam_role_again - - assert: - that: - - iam_role is changed - - iam_role_again is changed + - name: Minimal IAM Role without instance profile (rapid) + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role + - name: Minimal IAM Role without instance profile (rapid) + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role_again + - assert: + that: + - iam_role is changed + - iam_role_again is not changed + + - name: Remove IAM Role (rapid) + iam_role: + state: absent + name: '{{ test_role }}' + register: iam_role + - name: Remove IAM Role (rapid) + iam_role: + state: absent + name: '{{ test_role }}' + register: iam_role_again + - assert: + that: + - iam_role is changed + - iam_role_again is not changed + + - name: Minimal IAM Role without instance profile (rapid) + iam_role: + name: '{{ test_role }}' + create_instance_profile: no + register: iam_role + - name: Remove IAM Role (rapid) + iam_role: + state: absent + name: '{{ test_role }}' + register: iam_role_again + - assert: + that: + - iam_role is changed + - iam_role_again is changed # =================================================================== # Role Creation # (without Instance profile) -- name: "iam_role_info before Role creation (no args)" +- name: iam_role_info before Role creation (no args) iam_role_info: register: role_info - - assert: that: - - role_info is succeeded + - role_info is succeeded -- name: "iam_role_info before Role creation (search for test role)" +- name: iam_role_info before Role creation (search for test role) iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 0 -- name: "Minimal IAM Role (CHECK MODE)" +- name: Minimal IAM Role (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no register: iam_role check_mode: yes - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "iam_role_info after Role creation in check_mode" +- name: iam_role_info after Role creation in check_mode iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 0 -- name: "Minimal IAM Role without instance profile" +- name: Minimal IAM Role without instance profile iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in iam_role.iam_role' - - '"assume_role_policy_document_raw" in iam_role.iam_role' - - iam_role.iam_role.assume_role_policy_document_raw == assume_deny_policy - - iam_role.iam_role.attached_policies | length == 0 - - iam_role.iam_role.max_session_duration == 3600 - - iam_role.iam_role.path == '/' - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' - -- name: "Minimal IAM Role without instance profile (no change) - check mode" + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.arn.startswith("arn") + - iam_role.iam_role.arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in iam_role.iam_role' + - '"assume_role_policy_document_raw" in iam_role.iam_role' + - iam_role.iam_role.assume_role_policy_document_raw == assume_deny_policy + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '/' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + +- name: Minimal IAM Role without instance profile (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Minimal IAM Role without instance profile (no change)" +- name: Minimal IAM Role without instance profile (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: no register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after Role creation" +- name: iam_role_info after Role creation iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"assume_role_policy_document_raw" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].assume_role_policy_document_raw == assume_deny_policy - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 0 - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - -- name: "Remove IAM Role" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"assume_role_policy_document_raw" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].assume_role_policy_document_raw == assume_deny_policy + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 0 + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + +- name: Remove IAM Role iam_role: state: absent - name: "{{ test_role }}" + name: '{{ test_role }}' delete_instance_profile: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "iam_role_info after Role deletion" +- name: iam_role_info after Role deletion iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 0 # ------------------------------------------------------------------------------------------ # (with path) -- name: "Minimal IAM Role with path (CHECK MODE)" +- name: Minimal IAM Role with path (CHECK MODE) iam_role: - name: "{{ test_role }}" - path: "{{ test_path }}" + name: '{{ test_role }}' + path: '{{ test_path }}' register: iam_role check_mode: yes - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Minimal IAM Role with path" +- name: Minimal IAM Role with path iam_role: - name: "{{ test_role }}" - path: "{{ test_path }}" + name: '{{ test_role }}' + path: '{{ test_path }}' register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role" + test_path + test_role )' + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.arn.startswith("arn") + - iam_role.iam_role.arn.endswith("role" + test_path + test_role ) # Would be nice to test the contents... - - '"assume_role_policy_document" in iam_role.iam_role' - - iam_role.iam_role.attached_policies | length == 0 - - iam_role.iam_role.max_session_duration == 3600 - - iam_role.iam_role.path == '{{ test_path }}' - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' - -- name: "Minimal IAM Role with path (no change) - check mode" + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '{{ test_path }}' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + +- name: Minimal IAM Role with path (no change) - check mode iam_role: - name: "{{ test_role }}" - path: "{{ test_path }}" + name: '{{ test_role }}' + path: '{{ test_path }}' register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Minimal IAM Role with path (no change)" +- name: Minimal IAM Role with path (no change) iam_role: - name: "{{ test_role }}" - path: "{{ test_path }}" + name: '{{ test_role }}' + path: '{{ test_path }}' register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after Role creation" +- name: iam_role_info after Role creation iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '{{ test_path }}' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - -- name: "iam_role_info after Role creation (searching a path)" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role" + test_path + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + + test_path + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '{{ test_path }}' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + +- name: iam_role_info after Role creation (searching a path) iam_role_info: - path_prefix: "{{ test_path }}" + path_prefix: '{{ test_path }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role" + test_path + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + test_path + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].path == '{{ test_path }}' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 - -- name: "Remove IAM Role" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role" + test_path + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile" + + test_path + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].path == '{{ test_path }}' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 + +- name: Remove IAM Role iam_role: state: absent - name: "{{ test_role }}" - path: "{{ test_path }}" - # If we don't delete the existing profile it'll be reused (with the path) - # by the test below. + name: '{{ test_role }}' + path: '{{ test_path }}' delete_instance_profile: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "iam_role_info after Role deletion" +- name: iam_role_info after Role deletion iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 0 # ------------------------------------------------------------------------------------------ # (with Instance profile) -- name: "Minimal IAM Role with instance profile - check mode" +- name: Minimal IAM Role with instance profile - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: yes register: iam_role check_mode: yes - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Minimal IAM Role with instance profile" +- name: Minimal IAM Role with instance profile iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: yes register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - 'iam_role.iam_role.arn.startswith("arn")' - - 'iam_role.iam_role.arn.endswith("role/" + test_role )' + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.arn.startswith("arn") + - iam_role.iam_role.arn.endswith("role/" + test_role ) # Would be nice to test the contents... - - '"assume_role_policy_document" in iam_role.iam_role' - - iam_role.iam_role.attached_policies | length == 0 - - iam_role.iam_role.max_session_duration == 3600 - - iam_role.iam_role.path == '/' - - iam_role.iam_role.role_name == test_role - - '"create_date" in iam_role.iam_role' - - '"role_id" in iam_role.iam_role' - -- name: "Minimal IAM Role wth instance profile (no change) - check mode" + - '"assume_role_policy_document" in iam_role.iam_role' + - iam_role.iam_role.attached_policies | length == 0 + - iam_role.iam_role.max_session_duration == 3600 + - iam_role.iam_role.path == '/' + - iam_role.iam_role.role_name == test_role + - '"create_date" in iam_role.iam_role' + - '"role_id" in iam_role.iam_role' + +- name: Minimal IAM Role wth instance profile (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: yes register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Minimal IAM Role wth instance profile (no change)" +- name: Minimal IAM Role wth instance profile (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' create_instance_profile: yes register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after Role creation" +- name: iam_role_info after Role creation iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 3600 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 3600 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 diff --git a/tests/integration/targets/iam_role/tasks/description_update.yml b/tests/integration/targets/iam_role/tasks/description_update.yml index 85f5e1f56a3..198104134fb 100644 --- a/tests/integration/targets/iam_role/tasks/description_update.yml +++ b/tests/integration/targets/iam_role/tasks/description_update.yml @@ -1,148 +1,143 @@ ---- -- name: "Add Description (CHECK MODE)" +- name: Add Description (CHECK MODE) iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role {{ resource_prefix }} check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Add Description" +- name: Add Description iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role {{ resource_prefix }} register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' -- name: "Add Description (no change) - check mode" +- name: Add Description (no change) - check mode iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role {{ resource_prefix }} register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Add Description (no change)" +- name: Add Description (no change) iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role {{ resource_prefix }} register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role {{ resource_prefix }}' -- name: "iam_role_info after adding Description" +- name: iam_role_info after adding Description iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 # ------------------------------------------------------------------------------------------ -- name: "Update Description (CHECK MODE)" +- name: Update Description (CHECK MODE) iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role (updated) {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role (updated) {{ resource_prefix }} check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Update Description" +- name: Update Description iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role (updated) {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role (updated) {{ resource_prefix }} register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix + }}' -- name: "Update Description (no change) - check mode" +- name: Update Description (no change) - check mode iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role (updated) {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role (updated) {{ resource_prefix }} register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Update Description (no change)" +- name: Update Description (no change) iam_role: - name: "{{ test_role }}" - description: "Ansible Test Role (updated) {{ resource_prefix }}" + name: '{{ test_role }}' + description: Ansible Test Role (updated) {{ resource_prefix }} register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix }}' + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.description == 'Ansible Test Role (updated) {{ resource_prefix + }}' -- name: "iam_role_info after updating Description" +- name: iam_role_info after updating Description iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 diff --git a/tests/integration/targets/iam_role/tasks/inline_policy_update.yml b/tests/integration/targets/iam_role/tasks/inline_policy_update.yml index d364d87d79f..3c82196dd8d 100644 --- a/tests/integration/targets/iam_role/tasks/inline_policy_update.yml +++ b/tests/integration/targets/iam_role/tasks/inline_policy_update.yml @@ -1,48 +1,49 @@ ---- -- name: "Attach inline policy a" +- name: Attach inline policy a iam_policy: state: present - iam_type: "role" - iam_name: "{{ test_role }}" - policy_name: "inline-policy-a" + iam_type: role + iam_name: '{{ test_role }}' + policy_name: inline-policy-a policy_json: '{{ lookup("file", "deny-all-a.json") }}' - -- name: "Attach inline policy b" +- name: Attach inline policy b iam_policy: state: present - iam_type: "role" - iam_name: "{{ test_role }}" - policy_name: "inline-policy-b" + iam_type: role + iam_name: '{{ test_role }}' + policy_name: inline-policy-b policy_json: '{{ lookup("file", "deny-all-b.json") }}' - -- name: "iam_role_info after attaching inline policies (using iam_policy)" +- name: iam_role_info after attaching inline policies (using iam_policy) iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 2 - - '"inline-policy-a" in role_info.iam_roles[0].inline_policies' - - '"inline-policy-b" in role_info.iam_roles[0].inline_policies' - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 2 + - '"inline-policy-a" in role_info.iam_roles[0].inline_policies' + - '"inline-policy-b" in role_info.iam_roles[0].inline_policies' + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" diff --git a/tests/integration/targets/iam_role/tasks/main.yml b/tests/integration/targets/iam_role/tasks/main.yml index 821a683eb53..a787d5cfac1 100644 --- a/tests/integration/targets/iam_role/tasks/main.yml +++ b/tests/integration/targets/iam_role/tasks/main.yml @@ -1,4 +1,3 @@ ---- # Tests for iam_role and iam_role_info # # Tests: @@ -20,100 +19,65 @@ # the standard_pauses and paranoid_pauses options as a first step in debugging -- name: "Setup AWS connection info" +- name: Setup AWS connection info module_defaults: group/aws: - access_key: "{{ aws_access_key }}" - secret_key: "{{ aws_secret_key }}" - session_token: "{{ security_token | default(omit) }}" - region: "{{ aws_region }}" + access_key: '{{ aws_access_key }}' + secret_key: '{{ aws_secret_key }}' + session_token: '{{ security_token | default(omit) }}' + region: '{{ aws_region }}' iam_role: assume_role_policy_document: '{{ lookup("file", "deny-assume.json") }}' collections: - - amazon.aws - - community.general + - community.general block: - - set_fact: - assume_deny_policy: '{{ lookup("file", "deny-assume.json") | from_json }}' - # =================================================================== - # Parameter Checks - - include_tasks: parameter_checks.yml - - # =================================================================== - # Supplemental resource pre-creation - - name: "Create Safe IAM Managed Policy" - iam_managed_policy: - state: present - policy_name: "{{ custom_policy_name }}" - policy_description: "A safe (deny-all) managed policy" - policy: "{{ lookup('file', 'deny-all.json') }}" - register: create_managed_policy - - - assert: - that: - - create_managed_policy is succeeded + - set_fact: + assume_deny_policy: '{{ lookup("file", "deny-assume.json") | from_json }}' + - include_tasks: parameter_checks.yml + - name: Create Safe IAM Managed Policy + iam_managed_policy: + state: present + policy_name: '{{ custom_policy_name }}' + policy_description: A safe (deny-all) managed policy + policy: "{{ lookup('file', 'deny-all.json') }}" + register: create_managed_policy + - assert: + that: + - create_managed_policy is succeeded # =================================================================== # Rapid Role Creation and deletion - - include_tasks: creation_deletion.yml - - # =================================================================== - # Max Session Duration Manipulation - - include_tasks: max_session_update.yml - - # =================================================================== - # Description Manipulation - - include_tasks: description_update.yml - - # =================================================================== - # Tag Manipulation - - include_tasks: tags_update.yml - - # =================================================================== - # Policy Manipulation - - include_tasks: policy_update.yml - - # =================================================================== - # Inline Policy (test _info behavior) - - include_tasks: inline_policy_update.yml - - # =================================================================== - # Role Removal - - include_tasks: role_removal.yml - - # =================================================================== - # Boundary Policy (requires create_instance_profile: no) - - include_tasks: boundary_policy.yml - - # =================================================================== - # Complex role Creation - - include_tasks: complex_role_creation.yml - + - include_tasks: creation_deletion.yml + - include_tasks: max_session_update.yml + - include_tasks: description_update.yml + - include_tasks: tags_update.yml + - include_tasks: policy_update.yml + - include_tasks: inline_policy_update.yml + - include_tasks: role_removal.yml + - include_tasks: boundary_policy.yml + - include_tasks: complex_role_creation.yml always: # =================================================================== # Cleanup - - name: "Remove IAM Role" - iam_role: - state: absent - name: "{{ test_role }}" - delete_instance_profile: yes - ignore_errors: true - - - name: "Remove IAM Role (with path)" - iam_role: - state: absent - name: "{{ test_role }}" - path: "{{ test_path }}" - delete_instance_profile: yes - ignore_errors: true - - - name: "iam_role_info after Role deletion" - iam_role_info: - name: "{{ test_role }}" - ignore_errors: true - - - name: "Remove test managed policy" - iam_managed_policy: - state: absent - policy_name: "{{ custom_policy_name }}" + - name: Remove IAM Role + iam_role: + state: absent + name: '{{ test_role }}' + delete_instance_profile: yes + ignore_errors: true + - name: Remove IAM Role (with path) + iam_role: + state: absent + name: '{{ test_role }}' + path: '{{ test_path }}' + delete_instance_profile: yes + ignore_errors: true + - name: iam_role_info after Role deletion + iam_role_info: + name: '{{ test_role }}' + ignore_errors: true + - name: Remove test managed policy + iam_managed_policy: + state: absent + policy_name: '{{ custom_policy_name }}' diff --git a/tests/integration/targets/iam_role/tasks/max_session_update.yml b/tests/integration/targets/iam_role/tasks/max_session_update.yml index 8ad3641be62..a850de70264 100644 --- a/tests/integration/targets/iam_role/tasks/max_session_update.yml +++ b/tests/integration/targets/iam_role/tasks/max_session_update.yml @@ -1,71 +1,66 @@ ---- -- name: "Update Max Session Duration (CHECK MODE)" +- name: Update Max Session Duration (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' max_session_duration: 43200 check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Update Max Session Duration" +- name: Update Max Session Duration iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' max_session_duration: 43200 register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.max_session_duration == 43200 + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.max_session_duration == 43200 -- name: "Update Max Session Duration (no change)" +- name: Update Max Session Duration (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' max_session_duration: 43200 register: iam_role - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Update Max Session Duration (no change) - check mode" +- name: Update Max Session Duration (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' max_session_duration: 43200 register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "iam_role_info after updating Max Session Duration" +- name: iam_role_info after updating Max Session Duration iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - '"description" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - '"description" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 0 diff --git a/tests/integration/targets/iam_role/tasks/parameter_checks.yml b/tests/integration/targets/iam_role/tasks/parameter_checks.yml index 57df5436afc..74d3294b1c5 100644 --- a/tests/integration/targets/iam_role/tasks/parameter_checks.yml +++ b/tests/integration/targets/iam_role/tasks/parameter_checks.yml @@ -1,90 +1,82 @@ ---- # Parameter Checks -- name: "Friendly message when creating an instance profile and adding a boundary profile" +- name: Friendly message when creating an instance profile and adding a boundary profile iam_role: - name: "{{ test_role }}" - boundary: "{{ boundary_policy }}" + name: '{{ test_role }}' + boundary: '{{ boundary_policy }}' register: iam_role ignore_errors: yes - - assert: that: - - iam_role is failed - - '"boundary policy" in iam_role.msg' - - '"create_instance_profile" in iam_role.msg' - - '"false" in iam_role.msg' + - iam_role is failed + - '"boundary policy" in iam_role.msg' + - '"create_instance_profile" in iam_role.msg' + - '"false" in iam_role.msg' -- name: "Friendly message when boundary profile is not an ARN" +- name: Friendly message when boundary profile is not an ARN iam_role: - name: "{{ test_role }}" - boundary: "AWSDenyAll" + name: '{{ test_role }}' + boundary: AWSDenyAll create_instance_profile: no register: iam_role ignore_errors: yes - - assert: that: - - iam_role is failed - - '"Boundary policy" in iam_role.msg' - - '"ARN" in iam_role.msg' + - iam_role is failed + - '"Boundary policy" in iam_role.msg' + - '"ARN" in iam_role.msg' -- name: 'Friendly message when "present" without assume_role_policy_document' - module_defaults: { iam_role: {} } +- name: Friendly message when "present" without assume_role_policy_document + module_defaults: {iam_role: {}} iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' register: iam_role ignore_errors: yes - - assert: that: - - iam_role is failed - - 'iam_role.msg.startswith("state is present but all of the following are missing")' - - '"assume_role_policy_document" in iam_role.msg' + - iam_role is failed + - iam_role.msg.startswith("state is present but all of the following are missing") + - '"assume_role_policy_document" in iam_role.msg' -- name: "Maximum Session Duration needs to be between 1 and 12 hours" +- name: Maximum Session Duration needs to be between 1 and 12 hours iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' max_session_duration: 3599 register: iam_role ignore_errors: yes - - assert: that: - - iam_role is failed - - '"max_session_duration must be between" in iam_role.msg' + - iam_role is failed + - '"max_session_duration must be between" in iam_role.msg' -- name: "Maximum Session Duration needs to be between 1 and 12 hours" +- name: Maximum Session Duration needs to be between 1 and 12 hours iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' max_session_duration: 43201 register: iam_role ignore_errors: yes - - assert: that: - - iam_role is failed - - '"max_session_duration must be between" in iam_role.msg' + - iam_role is failed + - '"max_session_duration must be between" in iam_role.msg' -- name: "Role Paths must start with /" +- name: Role Paths must start with / iam_role: - name: "{{ test_role }}" - path: "test/" + name: '{{ test_role }}' + path: test/ register: iam_role ignore_errors: yes - - assert: that: - - iam_role is failed - - '"path must begin and end with /" in iam_role.msg' + - iam_role is failed + - '"path must begin and end with /" in iam_role.msg' -- name: "Role Paths must end with /" +- name: Role Paths must end with / iam_role: - name: "{{ test_role }}" - path: "/test" + name: '{{ test_role }}' + path: /test register: iam_role ignore_errors: yes - - assert: that: - - iam_role is failed - - '"path must begin and end with /" in iam_role.msg' + - iam_role is failed + - '"path must begin and end with /" in iam_role.msg' diff --git a/tests/integration/targets/iam_role/tasks/policy_update.yml b/tests/integration/targets/iam_role/tasks/policy_update.yml index a822edf74b6..ab16ea81f39 100644 --- a/tests/integration/targets/iam_role/tasks/policy_update.yml +++ b/tests/integration/targets/iam_role/tasks/policy_update.yml @@ -1,250 +1,246 @@ ---- -- name: "Add Managed Policy (CHECK MODE)" +- name: Add Managed Policy (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ safe_managed_policy }}" + - '{{ safe_managed_policy }}' check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Add Managed Policy" +- name: Add Managed Policy iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ safe_managed_policy }}" + - '{{ safe_managed_policy }}' register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role + - iam_role is changed + - iam_role.iam_role.role_name == test_role -- name: "Add Managed Policy (no change) - check mode" +- name: Add Managed Policy (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ safe_managed_policy }}" + - '{{ safe_managed_policy }}' register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Add Managed Policy (no change)" +- name: Add Managed Policy (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ safe_managed_policy }}" + - '{{ safe_managed_policy }}' register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after adding Managed Policy" +- name: iam_role_info after adding Managed Policy iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - custom_policy_name not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" # ------------------------------------------------------------------------------------------ -- name: "Update Managed Policy without purge (CHECK MODE)" +- name: Update Managed Policy without purge (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Update Managed Policy without purge" +- name: Update Managed Policy without purge iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role + - iam_role is changed + - iam_role.iam_role.role_name == test_role -- name: "Update Managed Policy without purge (no change) - check mode" +- name: Update Managed Policy without purge (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Update Managed Policy without purge (no change)" +- name: Update Managed Policy without purge (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_policies: no managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after updating Managed Policy without purge" +- name: iam_role_info after updating Managed Policy without purge iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 2 - - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 2 + - safe_managed_policy in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" # ------------------------------------------------------------------------------------------ # Managed Policies are purged by default -- name: "Update Managed Policy with purge (CHECK MODE)" +- name: Update Managed Policy with purge (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Update Managed Policy with purge" +- name: Update Managed Policy with purge iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role + - iam_role is changed + - iam_role.iam_role.role_name == test_role -- name: "Update Managed Policy with purge (no change) - check mode" +- name: Update Managed Policy with purge (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Update Managed Policy with purge (no change)" +- name: Update Managed Policy with purge (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' managed_policy: - - "{{ custom_policy_name }}" + - '{{ custom_policy_name }}' register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role + - iam_role is not changed + - iam_role.iam_role.role_name == test_role -- name: "iam_role_info after updating Managed Policy with purge" +- name: iam_role_info after updating Managed Policy with purge iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 1 - - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") | list | flatten ) - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 1 + - safe_managed_policy not in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - custom_policy_name in ( role_info | community.general.json_query("iam_roles[*].managed_policies[*].policy_name") + | list | flatten ) + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" diff --git a/tests/integration/targets/iam_role/tasks/role_removal.yml b/tests/integration/targets/iam_role/tasks/role_removal.yml index ebcfd54530a..7450fb9685c 100644 --- a/tests/integration/targets/iam_role/tasks/role_removal.yml +++ b/tests/integration/targets/iam_role/tasks/role_removal.yml @@ -1,65 +1,59 @@ ---- -- name: "Remove IAM Role (CHECK MODE)" +- name: Remove IAM Role (CHECK MODE) iam_role: state: absent - name: "{{ test_role }}" + name: '{{ test_role }}' delete_instance_profile: yes check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "iam_role_info after deleting role in check mode" +- name: iam_role_info after deleting role in check mode iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 + - role_info is succeeded + - role_info.iam_roles | length == 1 -- name: "Remove IAM Role" +- name: Remove IAM Role iam_role: state: absent - name: "{{ test_role }}" + name: '{{ test_role }}' delete_instance_profile: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "iam_role_info after deleting role" +- name: iam_role_info after deleting role iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 0 + - role_info is succeeded + - role_info.iam_roles | length == 0 -- name: "Remove IAM Role (should be gone already) - check mode" +- name: Remove IAM Role (should be gone already) - check mode iam_role: state: absent - name: "{{ test_role }}" + name: '{{ test_role }}' delete_instance_profile: yes register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Remove IAM Role (should be gone already)" +- name: Remove IAM Role (should be gone already) iam_role: state: absent - name: "{{ test_role }}" + name: '{{ test_role }}' delete_instance_profile: yes register: iam_role - - assert: that: - - iam_role is not changed + - iam_role is not changed diff --git a/tests/integration/targets/iam_role/tasks/tags_update.yml b/tests/integration/targets/iam_role/tasks/tags_update.yml index 5eadd9fdf7e..b68013212dd 100644 --- a/tests/integration/targets/iam_role/tasks/tags_update.yml +++ b/tests/integration/targets/iam_role/tasks/tags_update.yml @@ -1,341 +1,328 @@ ---- -- name: "Add Tag (CHECK MODE)" +- name: Add Tag (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: ValueA check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Add Tag" +- name: Add Tag iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: ValueA register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - iam_role.iam_role.tags | length == 1 - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "ValueA" + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - iam_role.iam_role.tags | length == 1 + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "ValueA" -- name: "Add Tag (no change) - check mode" +- name: Add Tag (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: ValueA register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Add Tag (no change)" +- name: Add Tag (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: ValueA register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "ValueA" + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "ValueA" -- name: "iam_role_info after adding Tags" +- name: iam_role_info after adding Tags iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "ValueA" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "ValueA" # ------------------------------------------------------------------------------------------ -- name: "Update Tag (CHECK MODE)" +- name: Update Tag (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: AValue check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Update Tag" +- name: Update Tag iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: AValue register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "AValue" + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "AValue" -- name: "Update Tag (no change) - check mode" +- name: Update Tag (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: AValue register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Update Tag (no change)" +- name: Update Tag (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' tags: TagA: AValue register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagA" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagA == "AValue" + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagA" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagA == "AValue" -- name: "iam_role_info after updating Tag" +- name: iam_role_info after updating Tag iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "AValue" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "AValue" # ------------------------------------------------------------------------------------------ -- name: "Add second Tag without purge (CHECK MODE)" +- name: Add second Tag without purge (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: no tags: TagB: ValueB check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Add second Tag without purge" +- name: Add second Tag without purge iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: no tags: TagB: ValueB register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" -- name: "Add second Tag without purge (no change) - check mode" +- name: Add second Tag without purge (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: no tags: TagB: ValueB register: iam_role check_mode: yes - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Add second Tag without purge (no change)" +- name: Add second Tag without purge (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: no tags: TagB: ValueB register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" -- name: "iam_role_info after adding second Tag without purge" +- name: iam_role_info after adding second Tag without purge iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 2 - - '"TagA" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagA == "AValue" - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 2 + - '"TagA" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagA == "AValue" + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB" # ------------------------------------------------------------------------------------------ -- name: "Purge first tag (CHECK MODE)" +- name: Purge first tag (CHECK MODE) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: yes tags: TagB: ValueB check_mode: yes register: iam_role - - assert: that: - - iam_role is changed + - iam_role is changed -- name: "Purge first tag" +- name: Purge first tag iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: yes tags: TagB: ValueB register: iam_role - - assert: that: - - iam_role is changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" + - iam_role is changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" -- name: "Purge first tag (no change) - check mode" +- name: Purge first tag (no change) - check mode iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: yes tags: TagB: ValueB register: iam_role - - assert: that: - - iam_role is not changed + - iam_role is not changed -- name: "Purge first tag (no change)" +- name: Purge first tag (no change) iam_role: - name: "{{ test_role }}" + name: '{{ test_role }}' purge_tags: yes tags: TagB: ValueB register: iam_role - - assert: that: - - iam_role is not changed - - iam_role.iam_role.role_name == test_role - - '"TagB" in iam_role.iam_role.tags' - - iam_role.iam_role.tags.TagB == "ValueB" + - iam_role is not changed + - iam_role.iam_role.role_name == test_role + - '"TagB" in iam_role.iam_role.tags' + - iam_role.iam_role.tags.TagB == "ValueB" -- name: "iam_role_info after purging first Tag" +- name: iam_role_info after purging first Tag iam_role_info: - name: "{{ test_role }}" + name: '{{ test_role }}' register: role_info - - assert: that: - - role_info is succeeded - - role_info.iam_roles | length == 1 - - 'role_info.iam_roles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].arn.endswith("role/" + test_role )' - - '"assume_role_policy_document" in role_info.iam_roles[0]' - - '"create_date" in role_info.iam_roles[0]' - - 'role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix }}"' - - role_info.iam_roles[0].inline_policies | length == 0 - - role_info.iam_roles[0].instance_profiles | length == 1 - - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role - - 'role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn")' - - 'role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + test_role)' - - role_info.iam_roles[0].managed_policies | length == 0 - - role_info.iam_roles[0].max_session_duration == 43200 - - role_info.iam_roles[0].path == '/' - - '"permissions_boundary" not in role_info.iam_roles[0]' - - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id - - role_info.iam_roles[0].role_name == test_role - - role_info.iam_roles[0].tags | length == 1 - - '"TagA" not in role_info.iam_roles[0].tags' - - '"TagB" in role_info.iam_roles[0].tags' - - role_info.iam_roles[0].tags.TagB == "ValueB" + - role_info is succeeded + - role_info.iam_roles | length == 1 + - role_info.iam_roles[0].arn.startswith("arn") + - role_info.iam_roles[0].arn.endswith("role/" + test_role ) + - '"assume_role_policy_document" in role_info.iam_roles[0]' + - '"create_date" in role_info.iam_roles[0]' + - role_info.iam_roles[0].description == "Ansible Test Role (updated) {{ resource_prefix + }}" + - role_info.iam_roles[0].inline_policies | length == 0 + - role_info.iam_roles[0].instance_profiles | length == 1 + - role_info.iam_roles[0].instance_profiles[0].instance_profile_name == test_role + - role_info.iam_roles[0].instance_profiles[0].arn.startswith("arn") + - role_info.iam_roles[0].instance_profiles[0].arn.endswith("instance-profile/" + + test_role) + - role_info.iam_roles[0].managed_policies | length == 0 + - role_info.iam_roles[0].max_session_duration == 43200 + - role_info.iam_roles[0].path == '/' + - '"permissions_boundary" not in role_info.iam_roles[0]' + - role_info.iam_roles[0].role_id == iam_role.iam_role.role_id + - role_info.iam_roles[0].role_name == test_role + - role_info.iam_roles[0].tags | length == 1 + - '"TagA" not in role_info.iam_roles[0].tags' + - '"TagB" in role_info.iam_roles[0].tags' + - role_info.iam_roles[0].tags.TagB == "ValueB"