From f1311d3e98118e98449b77637476aa14f6653bdf Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Thu, 21 Nov 2019 00:59:02 +0100 Subject: [PATCH] Rewrite iam_policy using boto3 (#63924) * reworked iam_policy * Deprecate policy_document option * deprecate defaulting skip_duplicates to true * No longer explicitly catch ParamValidationError. ParamValidationErrror is already caught by ClientError * Work with complex policy objects rather than json documents comparisons can better cope with the special cases (eg True vs "True" ) * Enable check_mode tests and fix related 'changed' bug * changelog * doc cleanup based on review --- changelogs/fragments/63924-boto3.yml | 5 + .../rst/porting_guides/porting_guide_2.10.rst | 4 + .../modules/cloud/amazon/iam_policy.py | 404 ++++++------- .../targets/iam_policy/tasks/object.yml | 565 +++++++++--------- 4 files changed, 501 insertions(+), 477 deletions(-) create mode 100644 changelogs/fragments/63924-boto3.yml diff --git a/changelogs/fragments/63924-boto3.yml b/changelogs/fragments/63924-boto3.yml new file mode 100644 index 00000000000..4b4553f4e5a --- /dev/null +++ b/changelogs/fragments/63924-boto3.yml @@ -0,0 +1,5 @@ +minor_changes: +- "iam_policy - The iam_policy module has been migrated from boto to boto3." +deprecated_features: +- "iam_policy - The ``policy_document`` will be removed in Ansible 2.14. To maintain the existing behavior use the ``policy_json`` option and read the file with the ``lookup`` plugin." +- "iam_policy - The default value of ``skip_duplicates`` will change in Ansible 2.14 from ``true`` to ``false``." diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.10.rst b/docs/docsite/rst/porting_guides/porting_guide_2.10.rst index 62adf60b708..29542a4b224 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.10.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.10.rst @@ -68,11 +68,15 @@ The following functionality will be removed in Ansible 2.14. Please update updat * :ref:`ec2_key `: the ``wait`` option will be removed. It has had no effect since Ansible 2.5. * :ref:`ec2_key `: the ``wait_timeout`` option will be removed. It has had no effect since Ansible 2.5. * :ref:`ec2_lc `: the ``associate_public_ip_address`` option will be removed. It has always been ignored by the module. +* :ref:`iam_policy `: the ``policy_document`` option will be removed. To maintain the existing behavior use the ``policy_json`` option and read the file with the ``lookup`` plugin. + The following functionality will change in Ansible 2.14. Please update update your playbooks accordingly. * The :ref:`docker_container ` module has a new option, ``container_default_behavior``, whose default value will change from ``compatibility`` to ``no_defaults``. Set to an explicit value to avoid deprecation warnings. +* :ref:`iam_policy `: the default value for the ``skip_duplicates`` option will change from ``true`` to ``false``. To maintain the existing behavior explicitly set it to ``true``. + The following modules will be removed in Ansible 2.14. Please update your playbooks accordingly. diff --git a/lib/ansible/modules/cloud/amazon/iam_policy.py b/lib/ansible/modules/cloud/amazon/iam_policy.py index b2498e3f49d..999affcafa3 100644 --- a/lib/ansible/modules/cloud/amazon/iam_policy.py +++ b/lib/ansible/modules/cloud/amazon/iam_policy.py @@ -5,7 +5,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type - ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['stableinterface'], 'supported_by': 'community'} @@ -40,6 +39,8 @@ options: description: - The path to the properly json formatted policy file. - Mutually exclusive with I(policy_json). + - This option has been deprecated and will be removed in 2.14. The existing behavior can be + reproduced by using the I(policy_json) option and reading the file using the lookup plugin. type: str policy_json: description: @@ -50,19 +51,20 @@ options: state: description: - Whether to create or delete the IAM policy. - required: true choices: [ "present", "absent"] default: present type: str skip_duplicates: description: - - By default the module looks for any policies that match the document you pass in, if there is a match it will not make a new policy object with - the same rules. You can override this by specifying false which would allow for two policy objects with different names but same rules. - default: True + - When I(skip_duplicates=true) the module looks for any policies that match the document you pass in. If there is a match it will not make + a new policy object with the same rules. + - The current default is C(true). However, this behavior can be confusing and as such the default will change to C(false) in 2.14. To maintain + the existing behavior explicitly set I(skip_duplicates=true). type: bool author: - - Jonathan I. Davila (@defionscode) + - "Jonathan I. Davila (@defionscode)" + - "Dennis Podkovyrin (@sbj-ss)" extends_documentation_fragment: - aws - ec2 @@ -115,238 +117,230 @@ EXAMPLES = ''' import json try: - import boto - import boto.iam - import boto.ec2 - HAS_BOTO = True + from botocore.exceptions import BotoCoreError, ClientError except ImportError: - HAS_BOTO = False + pass -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.ec2 import connect_to_aws, ec2_argument_spec, get_aws_connection_info, boto_exception +from ansible.module_utils.aws.core import AnsibleAWSModule +from ansible.module_utils.ec2 import compare_policies from ansible.module_utils.six import string_types -from ansible.module_utils.six.moves import urllib -def user_action(module, iam, name, policy_name, skip, pdoc, state): - policy_match = False - changed = False - try: - current_policies = [cp for cp in iam.get_all_user_policies(name). - list_user_policies_result. - policy_names] - matching_policies = [] - for pol in current_policies: - ''' - urllib is needed here because boto returns url encoded strings instead - ''' - if urllib.parse.unquote(iam.get_user_policy(name, pol). - get_user_policy_result.policy_document) == pdoc: - policy_match = True - matching_policies.append(pol) - - if state == 'present': - # If policy document does not already exist (either it's changed - # or the policy is not present) or if we're not skipping dupes then - # make the put call. Note that the put call does a create or update. - if not policy_match or (not skip and policy_name not in matching_policies): - changed = True - iam.put_user_policy(name, policy_name, pdoc) - elif state == 'absent': - try: - iam.delete_user_policy(name, policy_name) - changed = True - except boto.exception.BotoServerError as err: - error_msg = boto_exception(err) - if 'cannot be found.' in error_msg: - changed = False - module.exit_json(changed=changed, msg="%s policy is already absent" % policy_name) - - updated_policies = [cp for cp in iam.get_all_user_policies(name). - list_user_policies_result. - policy_names] - except boto.exception.BotoServerError as err: - error_msg = boto_exception(err) - module.fail_json(changed=changed, msg=error_msg) - - return changed, name, updated_policies +class PolicyError(Exception): + pass -def role_action(module, iam, name, policy_name, skip, pdoc, state): - policy_match = False - changed = False - try: - current_policies = [cp for cp in iam.list_role_policies(name). - list_role_policies_result. - policy_names] - except boto.exception.BotoServerError as e: - if e.error_code == "NoSuchEntity": - # Role doesn't exist so it's safe to assume the policy doesn't either - module.exit_json(changed=False, msg="No such role, policy will be skipped.") +class Policy: + + def __init__(self, client, name, policy_name, policy_document, policy_json, skip_duplicates, state, check_mode): + self.client = client + self.name = name + self.policy_name = policy_name + self.policy_document = policy_document + self.policy_json = policy_json + self.skip_duplicates = skip_duplicates + self.state = state + self.check_mode = check_mode + self.changed = False + + @staticmethod + def _iam_type(): + return '' + + def _list(self, name): + return {} + + def list(self): + return self._list(self.name).get('PolicyNames', []) + + def _get(self, name, policy_name): + return '{}' + + def get(self, policy_name): + return self._get(self.name, policy_name)['PolicyDocument'] + + def _put(self, name, policy_name, policy_doc): + pass + + def put(self, policy_doc): + if not self.check_mode: + self._put(self.name, self.policy_name, json.dumps(policy_doc, sort_keys=True)) + self.changed = True + + def _delete(self, name, policy_name): + pass + + def delete(self): + if self.policy_name not in self.list(): + self.changed = False + return + + self.changed = True + if not self.check_mode: + self._delete(self.name, self.policy_name) + + def get_policy_text(self): + try: + if self.policy_document is not None: + return self.get_policy_from_document() + if self.policy_json is not None: + return self.get_policy_from_json() + except json.JSONDecodeError as e: + raise PolicyError('Failed to decode the policy as valid JSON: %s' % str(e)) + return None + + def get_policy_from_document(self): + try: + with open(self.policy_document, 'r') as json_data: + pdoc = json.load(json_data) + json_data.close() + except IOError as e: + if e.errno == 2: + raise PolicyError('policy_document {0:!r} does not exist'.format(self.policy_document)) + raise + return pdoc + + def get_policy_from_json(self): + if isinstance(self.policy_json, string_types): + pdoc = json.loads(self.policy_json) else: - module.fail_json(msg=e.message) + pdoc = self.policy_json + return pdoc - try: + def create(self): matching_policies = [] - for pol in current_policies: - if urllib.parse.unquote(iam.get_role_policy(name, pol). - get_role_policy_result.policy_document) == pdoc: - policy_match = True + policy_doc = self.get_policy_text() + policy_match = False + for pol in self.list(): + if not compare_policies(self.get(pol), policy_doc): matching_policies.append(pol) - - if state == 'present': - # If policy document does not already exist (either it's changed - # or the policy is not present) or if we're not skipping dupes then - # make the put call. Note that the put call does a create or update. - if not policy_match or (not skip and policy_name not in matching_policies): - changed = True - iam.put_role_policy(name, policy_name, pdoc) - elif state == 'absent': - try: - iam.delete_role_policy(name, policy_name) - changed = True - except boto.exception.BotoServerError as err: - error_msg = boto_exception(err) - if 'cannot be found.' in error_msg: - changed = False - module.exit_json(changed=changed, - msg="%s policy is already absent" % policy_name) - else: - module.fail_json(msg=err.message) - - updated_policies = [cp for cp in iam.list_role_policies(name). - list_role_policies_result. - policy_names] - except boto.exception.BotoServerError as err: - error_msg = boto_exception(err) - module.fail_json(changed=changed, msg=error_msg) - - return changed, name, updated_policies - - -def group_action(module, iam, name, policy_name, skip, pdoc, state): - policy_match = False - changed = False - msg = '' - try: - current_policies = [cp for cp in iam.get_all_group_policies(name). - list_group_policies_result. - policy_names] - matching_policies = [] - for pol in current_policies: - if urllib.parse.unquote(iam.get_group_policy(name, pol). - get_group_policy_result.policy_document) == pdoc: policy_match = True - matching_policies.append(pol) - msg = ("The policy document you specified already exists " - "under the name %s." % pol) - if state == 'present': - # If policy document does not already exist (either it's changed - # or the policy is not present) or if we're not skipping dupes then - # make the put call. Note that the put call does a create or update. - if not policy_match or (not skip and policy_name not in matching_policies): - changed = True - iam.put_group_policy(name, policy_name, pdoc) - elif state == 'absent': - try: - iam.delete_group_policy(name, policy_name) - changed = True - except boto.exception.BotoServerError as err: - error_msg = boto_exception(err) - if 'cannot be found.' in error_msg: - changed = False - module.exit_json(changed=changed, - msg="%s policy is already absent" % policy_name) - updated_policies = [cp for cp in iam.get_all_group_policies(name). - list_group_policies_result. - policy_names] - except boto.exception.BotoServerError as err: - error_msg = boto_exception(err) - module.fail_json(changed=changed, msg=error_msg) + if (self.policy_name not in matching_policies) and not (self.skip_duplicates and policy_match): + self.put(policy_doc) - return changed, name, updated_policies, msg + def run(self): + if self.state == 'present': + self.create() + elif self.state == 'absent': + self.delete() + return { + 'changed': self.changed, + self._iam_type() + '_name': self.name, + 'policies': self.list() + } + + +class UserPolicy(Policy): + + @staticmethod + def _iam_type(): + return 'user' + + def _list(self, name): + return self.client.list_user_policies(UserName=name) + + def _get(self, name, policy_name): + return self.client.get_user_policy(UserName=name, PolicyName=policy_name) + + def _put(self, name, policy_name, policy_doc): + return self.client.put_user_policy(UserName=name, PolicyName=policy_name, PolicyDocument=policy_doc) + + def _delete(self, name, policy_name): + return self.client.delete_user_policy(UserName=name, PolicyName=policy_name) + + +class RolePolicy(Policy): + + @staticmethod + def _iam_type(): + return 'role' + + def _list(self, name): + return self.client.list_role_policies(RoleName=name) + + def _get(self, name, policy_name): + return self.client.get_role_policy(RoleName=name, PolicyName=policy_name) + + def _put(self, name, policy_name, policy_doc): + return self.client.put_role_policy(RoleName=name, PolicyName=policy_name, PolicyDocument=policy_doc) + + def _delete(self, name, policy_name): + return self.client.delete_role_policy(RoleName=name, PolicyName=policy_name) + + +class GroupPolicy(Policy): + + @staticmethod + def _iam_type(): + return 'group' + + def _list(self, name): + return self.client.list_group_policies(GroupName=name) + + def _get(self, name, policy_name): + return self.client.get_group_policy(GroupName=name, PolicyName=policy_name) + + def _put(self, name, policy_name, policy_doc): + return self.client.put_group_policy(GroupName=name, PolicyName=policy_name, PolicyDocument=policy_doc) + + def _delete(self, name, policy_name): + return self.client.delete_group_policy(GroupName=name, PolicyName=policy_name) def main(): - argument_spec = ec2_argument_spec() - argument_spec.update(dict( + argument_spec = dict( iam_type=dict(required=True, choices=['user', 'group', 'role']), state=dict(default='present', choices=['present', 'absent']), iam_name=dict(default=None, required=False), policy_name=dict(required=True), policy_document=dict(default=None, required=False), policy_json=dict(type='json', default=None, required=False), - skip_duplicates=dict(type='bool', default=True, required=False) - )) - - module = AnsibleModule( - argument_spec=argument_spec, + skip_duplicates=dict(type='bool', default=None, required=False) ) + mutually_exclusive = [['policy_document', 'policy_json']] - if not HAS_BOTO: - module.fail_json(msg='boto required for this module') + module = AnsibleAWSModule(argument_spec=argument_spec, mutually_exclusive=mutually_exclusive, supports_check_mode=True) - iam_type = module.params.get('iam_type').lower() - state = module.params.get('state') - name = module.params.get('iam_name') - policy_name = module.params.get('policy_name') - skip = module.params.get('skip_duplicates') + skip_duplicates = module.params.get('skip_duplicates') - policy_document = module.params.get('policy_document') - if policy_document is not None and module.params.get('policy_json') is not None: - module.fail_json(msg='Only one of "policy_document" or "policy_json" may be set') + if (skip_duplicates is None): + module.deprecate('The skip_duplicates behaviour has caused confusion and' + ' will be disabled by default in Ansible 2.14', + version='2.14') + skip_duplicates = True - if policy_document is not None: - try: - with open(policy_document, 'r') as json_data: - pdoc = json.dumps(json.load(json_data)) - json_data.close() - except IOError as e: - if e.errno == 2: - module.fail_json( - msg='policy_document {0:!r} does not exist'.format(policy_document)) - else: - raise - elif module.params.get('policy_json') is not None: - pdoc = module.params.get('policy_json') - # if its a string, assume it is already JSON - if not isinstance(pdoc, string_types): - try: - pdoc = json.dumps(pdoc) - except Exception as e: - module.fail_json(msg='Failed to convert the policy into valid JSON: %s' % str(e)) - else: - pdoc = None + if module.params.get('policy_document'): + module.deprecate('The policy_document option has been deprecated and' + ' will be removed in Ansible 2.14', + version='2.14') - region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) + args = dict( + client=module.client('iam'), + name=module.params.get('iam_name'), + policy_name=module.params.get('policy_name'), + policy_document=module.params.get('policy_document'), + policy_json=module.params.get('policy_json'), + skip_duplicates=skip_duplicates, + state=module.params.get('state'), + check_mode=module.check_mode, + ) + iam_type = module.params.get('iam_type') try: - if region: - iam = connect_to_aws(boto.iam, region, **aws_connect_kwargs) - else: - iam = boto.iam.connection.IAMConnection(**aws_connect_kwargs) - except boto.exception.NoAuthHandlerFound as e: + if iam_type == 'user': + policy = UserPolicy(**args) + elif iam_type == 'role': + policy = RolePolicy(**args) + elif iam_type == 'group': + policy = GroupPolicy(**args) + + module.exit_json(**(policy.run())) + except (BotoCoreError, ClientError) as e: + module.fail_json_aws(e) + except PolicyError as e: module.fail_json(msg=str(e)) - changed = False - - if iam_type == 'user': - changed, user_name, current_policies = user_action(module, iam, name, - policy_name, skip, pdoc, - state) - module.exit_json(changed=changed, user_name=name, policies=current_policies) - elif iam_type == 'role': - changed, role_name, current_policies = role_action(module, iam, name, - policy_name, skip, pdoc, - state) - module.exit_json(changed=changed, role_name=name, policies=current_policies) - elif iam_type == 'group': - changed, group_name, current_policies, msg = group_action(module, iam, name, - policy_name, skip, pdoc, - state) - module.exit_json(changed=changed, group_name=name, policies=current_policies, msg=msg) - if __name__ == '__main__': main() diff --git a/test/integration/targets/iam_policy/tasks/object.yml b/test/integration/targets/iam_policy/tasks/object.yml index f0d0190ddc6..79fcda42caf 100644 --- a/test/integration/targets/iam_policy/tasks/object.yml +++ b/test/integration/targets/iam_policy/tasks/object.yml @@ -30,21 +30,21 @@ - iam_policy_info is succeeded # ============================================================ - #- name: 'Create policy using document for {{ iam_type }} (check mode)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_a }}' - # policy_document: '{{ tmpdir.path }}/no_access.json' - # skip_duplicates: yes - # register: result + - name: 'Create policy using document for {{ iam_type }} (check mode)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_a }}' + policy_document: '{{ tmpdir.path }}/no_access.json' + skip_duplicates: yes + register: result - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result is changed + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result is changed - name: 'Create policy using document for {{ iam_type }}' iam_policy: @@ -103,29 +103,29 @@ - '"Id" not in iam_policy_info.policies[0].policy_document' # ============================================================ - #- name: 'Create policy using document for {{ iam_type }} (check mode) (skip_duplicates)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_b }}' - # policy_document: '{{ tmpdir.path }}/no_access.json' - # skip_duplicates: yes - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_b }}' - # register: iam_policy_info + - name: 'Create policy using document for {{ iam_type }} (check mode) (skip_duplicates)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_b }}' + policy_document: '{{ tmpdir.path }}/no_access.json' + skip_duplicates: yes + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_b }}' + register: iam_policy_info - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result is not changed - # - iam_policy_info.all_policy_names | length == 1 - # - '"policies" not in iam_policy_info' - # - iam_policy_name_b not in iam_policy_info.all_policy_names + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result is not changed + - iam_policy_info.all_policy_names | length == 1 + - '"policies" not in iam_policy_info' + - iam_policy_name_b not in iam_policy_info.all_policy_names - name: 'Create policy using document for {{ iam_type }} (skip_duplicates)' iam_policy: @@ -154,30 +154,30 @@ - iam_policy_info.all_policy_names | length == 1 - iam_policy_name_b not in iam_policy_info.all_policy_names - #- name: 'Create policy using document for {{ iam_type }} (check mode) (skip_duplicates = no)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_b }}' - # policy_document: '{{ tmpdir.path }}/no_access.json' - # skip_duplicates: no - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_b }}' - # register: iam_policy_info + - name: 'Create policy using document for {{ iam_type }} (check mode) (skip_duplicates = no)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_b }}' + policy_document: '{{ tmpdir.path }}/no_access.json' + skip_duplicates: no + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_b }}' + register: iam_policy_info - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result.changed == True - # - '"policies" not in iam_policy_info' - # - iam_policy_info.all_policy_names | length == 1 - # - iam_policy_name_a in iam_policy_info.all_policy_names - # - iam_policy_name_b not in iam_policy_info.all_policy_names + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result.changed == True + - '"policies" not in iam_policy_info' + - iam_policy_info.all_policy_names | length == 1 + - iam_policy_name_a in iam_policy_info.all_policy_names + - iam_policy_name_b not in iam_policy_info.all_policy_names - name: 'Create policy using document for {{ iam_type }} (skip_duplicates = no)' iam_policy: @@ -238,31 +238,31 @@ - '"Id" not in iam_policy_info.policies[0].policy_document' # ============================================================ - #- name: 'Create policy using json for {{ iam_type }} (check mode)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_c }}' - # policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_id.json") }}' - # skip_duplicates: yes - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_c }}' - # register: iam_policy_info + - name: 'Create policy using json for {{ iam_type }} (check mode)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_c }}' + policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_id.json") }}' + skip_duplicates: yes + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_c }}' + register: iam_policy_info - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result is changed - # - '"policies" not in iam_policy_info' - # - iam_policy_info.all_policy_names | length == 2 - # - iam_policy_name_c not in iam_policy_info.all_policy_names - # - iam_policy_name_a in iam_policy_info.all_policy_names - # - iam_policy_name_b in iam_policy_info.all_policy_names + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result is changed + - '"policies" not in iam_policy_info' + - iam_policy_info.all_policy_names | length == 2 + - iam_policy_name_c not in iam_policy_info.all_policy_names + - iam_policy_name_a in iam_policy_info.all_policy_names + - iam_policy_name_b in iam_policy_info.all_policy_names - name: 'Create policy using json for {{ iam_type }}' iam_policy: @@ -324,32 +324,32 @@ - iam_policy_info.policies[0].policy_document.Id == 'MyId' # ============================================================ - #- name: 'Create policy using json for {{ iam_type }} (check mode) (skip_duplicates)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_id.json") }}' - # skip_duplicates: yes - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # register: iam_policy_info + - name: 'Create policy using json for {{ iam_type }} (check mode) (skip_duplicates)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_id.json") }}' + skip_duplicates: yes + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + register: iam_policy_info - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result is not changed - # - iam_policy_name_a in iam_policy_info.all_policy_names - # - iam_policy_name_b in iam_policy_info.all_policy_names - # - iam_policy_name_c in iam_policy_info.all_policy_names - # - iam_policy_name_d not in iam_policy_info.all_policy_names - # - iam_policy_info.all_policy_names | length == 3 - # - '"policies" not in iam_policy_info' + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result is not changed + - iam_policy_name_a in iam_policy_info.all_policy_names + - iam_policy_name_b in iam_policy_info.all_policy_names + - iam_policy_name_c in iam_policy_info.all_policy_names + - iam_policy_name_d not in iam_policy_info.all_policy_names + - iam_policy_info.all_policy_names | length == 3 + - '"policies" not in iam_policy_info' - name: 'Create policy using json for {{ iam_type }} (skip_duplicates)' iam_policy: @@ -380,26 +380,26 @@ - iam_policy_info.all_policy_names | length == 3 - '"policies" not in iam_policy_info' - #- name: 'Create policy using json for {{ iam_type }} (check mode) (skip_duplicates = no)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_id.json") }}' - # skip_duplicates: no - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # register: iam_policy_info + - name: 'Create policy using json for {{ iam_type }} (check mode) (skip_duplicates = no)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_id.json") }}' + skip_duplicates: no + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + register: iam_policy_info - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result.changed == True + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result.changed == True - name: 'Create policy using json for {{ iam_type }} (skip_duplicates = no)' iam_policy: @@ -490,28 +490,28 @@ - iam_policy_name_d in (iam_policy_info.policies | json_query('[?policy_document.Id == `MyId`].policy_name') | list) # ============================================================ - #- name: 'Update policy using document for {{ iam_type }} (check mode) (skip_duplicates)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_a }}' - # policy_document: '{{ tmpdir.path }}/no_access_with_id.json' - # skip_duplicates: yes - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_a }}' - # register: iam_policy_info + - name: 'Update policy using document for {{ iam_type }} (check mode) (skip_duplicates)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_a }}' + policy_document: '{{ tmpdir.path }}/no_access_with_id.json' + skip_duplicates: yes + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_a }}' + register: iam_policy_info - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result is not changed - # - iam_policy_info.policies[0].policy_name == iam_policy_name_a - # - '"Id" not in iam_policy_info.policies[0].policy_document' + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result is not changed + - iam_policy_info.policies[0].policy_name == iam_policy_name_a + - '"Id" not in iam_policy_info.policies[0].policy_document' - name: 'Update policy using document for {{ iam_type }} (skip_duplicates)' iam_policy: @@ -539,29 +539,29 @@ - iam_policy_info.policies[0].policy_name == iam_policy_name_a - '"Id" not in iam_policy_info.policies[0].policy_document' - #- name: 'Update policy using document for {{ iam_type }} (check mode) (skip_duplicates = no)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_a }}' - # policy_document: '{{ tmpdir.path }}/no_access_with_id.json' - # skip_duplicates: no - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_a }}' - # register: iam_policy_info + - name: 'Update policy using document for {{ iam_type }} (check mode) (skip_duplicates = no)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_a }}' + policy_document: '{{ tmpdir.path }}/no_access_with_id.json' + skip_duplicates: no + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_a }}' + register: iam_policy_info - #- name: 'Assert policy would be updated for {{ iam_type }}' - # assert: - # that: - # - result.changed == True - # - iam_policy_info.all_policy_names | length == 4 - # - iam_policy_info.policies[0].policy_name == iam_policy_name_a - # - '"Id" not in iam_policy_info.policies[0].policy_document' + - name: 'Assert policy would be updated for {{ iam_type }}' + assert: + that: + - result.changed == True + - iam_policy_info.all_policy_names | length == 4 + - iam_policy_info.policies[0].policy_name == iam_policy_name_a + - '"Id" not in iam_policy_info.policies[0].policy_document' - name: 'Update policy using document for {{ iam_type }} (skip_duplicates = no)' iam_policy: @@ -638,28 +638,28 @@ # ============================================================ # Update C with no_access.json # Delete C - # - #- name: 'Update policy using json for {{ iam_type }} (check mode) (skip_duplicates)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_c }}' - # policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access.json") }}' - # skip_duplicates: yes - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_c }}' - # register: iam_policy_info - #- name: 'Assert policy would be added for {{ iam_type }}' - # assert: - # that: - # - result is not changed - # - iam_policy_info.policies[0].policy_document.Id == 'MyId' + - name: 'Update policy using json for {{ iam_type }} (check mode) (skip_duplicates)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_c }}' + policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access.json") }}' + skip_duplicates: yes + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_c }}' + register: iam_policy_info + + - name: 'Assert policy would be added for {{ iam_type }}' + assert: + that: + - result is not changed + - iam_policy_info.policies[0].policy_document.Id == 'MyId' - name: 'Update policy using json for {{ iam_type }} (skip_duplicates)' iam_policy: @@ -685,27 +685,27 @@ - result[iam_object_key] == iam_name - iam_policy_info.policies[0].policy_document.Id == 'MyId' - #- name: 'Update policy using json for {{ iam_type }} (check mode) (skip_duplicates = no)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_c }}' - # policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access.json") }}' - # skip_duplicates: no - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_c }}' - # register: iam_policy_info + - name: 'Update policy using json for {{ iam_type }} (check mode) (skip_duplicates = no)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_c }}' + policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access.json") }}' + skip_duplicates: no + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_c }}' + register: iam_policy_info - #- name: 'Assert policy would be updated for {{ iam_type }}' - # assert: - # that: - # - result.changed == True - # - iam_policy_info.policies[0].policy_document.Id == 'MyId' + - name: 'Assert policy would be updated for {{ iam_type }}' + assert: + that: + - result.changed == True + - iam_policy_info.policies[0].policy_document.Id == 'MyId' - name: 'Update policy using json for {{ iam_type }} (skip_duplicates = no)' iam_policy: @@ -780,26 +780,26 @@ - iam_policy_name_c not in iam_policy_info.all_policy_names # ============================================================ - #- name: 'Update policy using document for {{ iam_type }} (check mode)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_b }}' - # policy_document: '{{ tmpdir.path }}/no_access_with_second_id.json' - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_b }}' - # register: iam_policy_info + - name: 'Update policy using document for {{ iam_type }} (check mode)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_b }}' + policy_document: '{{ tmpdir.path }}/no_access_with_second_id.json' + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_b }}' + register: iam_policy_info - #- name: 'Assert policy would be updated for {{ iam_type }}' - # assert: - # that: - # - result.changed == True - # - '"Id" not in iam_policy_info.policies[0].policy_document' + - name: 'Assert policy would be updated for {{ iam_type }}' + assert: + that: + - result.changed == True + - '"Id" not in iam_policy_info.policies[0].policy_document' - name: 'Update policy using document for {{ iam_type }}' iam_policy: @@ -872,26 +872,26 @@ - iam_policy_name_b not in iam_policy_info.all_policy_names # ============================================================ - #- name: 'Update policy using json for {{ iam_type }} (check mode)' - # check_mode: yes - # iam_policy: - # state: present - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_second_id.json") }}' - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # register: iam_policy_info + - name: 'Update policy using json for {{ iam_type }} (check mode)' + check_mode: yes + iam_policy: + state: present + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + policy_json: '{{ lookup("file", "{{ tmpdir.path }}/no_access_with_second_id.json") }}' + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + register: iam_policy_info - #- name: 'Assert policy would be updated for {{ iam_type }}' - # assert: - # that: - # - result.changed == True - # - iam_policy_info.policies[0].policy_document.Id == 'MyId' + - name: 'Assert policy would be updated for {{ iam_type }}' + assert: + that: + - result.changed == True + - iam_policy_info.policies[0].policy_document.Id == 'MyId' - name: 'Update policy using json for {{ iam_type }}' iam_policy: @@ -941,30 +941,30 @@ - iam_policy_info.policies[0].policy_document.Id == 'MyOtherId' # ============================================================ - #- name: 'Delete policy D (check_mode)' - # check_mode: yes - # iam_policy: - # state: absent - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # register: result - #- iam_policy_info: - # iam_type: '{{ iam_type }}' - # iam_name: '{{ iam_name }}' - # policy_name: '{{ iam_policy_name_d }}' - # register: iam_policy_info + - name: 'Delete policy D (check_mode)' + check_mode: yes + iam_policy: + state: absent + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + register: iam_policy_info - #- name: 'Assert not deleted' - # assert: - # that: - # - result is changed - # - result.policies | length == 1 - # - iam_policy_name_d in result.policies - # - result[iam_object_key] == iam_name - # - iam_policy_info.all_policy_names | length == 1 - # - iam_policy_name_d in iam_policy_info.all_policy_names - # - iam_policy_info.policies[0].policy_document.Id == 'MyOtherId' + - name: 'Assert not deleted' + assert: + that: + - result is changed + - result.policies | length == 1 + - iam_policy_name_d in result.policies + - result[iam_object_key] == iam_name + - iam_policy_info.all_policy_names | length == 1 + - iam_policy_name_d in iam_policy_info.all_policy_names + - iam_policy_info.policies[0].policy_document.Id == 'MyOtherId' - name: 'Delete policy D' iam_policy: @@ -1009,6 +1009,27 @@ - '"policies" not in iam_policy_info' - iam_policy_info.all_policy_names | length == 0 + - name: 'Delete policy D (check_mode) (test idempotency)' + check_mode: yes + iam_policy: + state: absent + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + register: result + - iam_policy_info: + iam_type: '{{ iam_type }}' + iam_name: '{{ iam_name }}' + policy_name: '{{ iam_policy_name_d }}' + register: iam_policy_info + + - name: 'Assert deleted' + assert: + that: + - result is not changed + - '"policies" not in iam_policy_info' + - iam_policy_info.all_policy_names | length == 0 + always: # ============================================================ - name: 'Delete policy A for {{ iam_type }}'