From d6ff62371666d82dfdd1772587c305dee10497df Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Wed, 9 Dec 2020 20:23:53 +0100 Subject: [PATCH] Cleanup IGW modules (#318) * import order * Add retry decorators * Switch tests to using module_defaults * module_defaults * Add initial _info tests * Handle Boto Errors with fail_json_aws * Test state=absent when IGW missing * Support not purging tags * Support converting Tags from boto to dict * Add tagging tests * Use random CIDR for VPC * Add check_mode tests * changelog --- changelogs/fragments/318-cleanup-vpc_igw.yml | 7 + plugins/modules/ec2_vpc_igw.py | 65 +-- plugins/modules/ec2_vpc_igw_info.py | 45 +- tests/integration/targets/ec2_vpc_igw/aliases | 1 + .../targets/ec2_vpc_igw/defaults/main.yml | 4 + .../targets/ec2_vpc_igw/tasks/main.yml | 387 ++++++++++++++++-- 6 files changed, 451 insertions(+), 58 deletions(-) create mode 100644 changelogs/fragments/318-cleanup-vpc_igw.yml create mode 100644 tests/integration/targets/ec2_vpc_igw/defaults/main.yml diff --git a/changelogs/fragments/318-cleanup-vpc_igw.yml b/changelogs/fragments/318-cleanup-vpc_igw.yml new file mode 100644 index 00000000000..58dc4f50ea0 --- /dev/null +++ b/changelogs/fragments/318-cleanup-vpc_igw.yml @@ -0,0 +1,7 @@ +minor_changes: +- ec2_vpc_igw - Add AWSRetry decorators to improve reliability (https://github.com/ansible-collections/community.aws/pull/318). +- ec2_vpc_igw_info - Add AWSRetry decorators to improve reliability (https://github.com/ansible-collections/community.aws/pull/318). +- ec2_vpc_igw - Add ``purge_tags`` parameter so that tags can be added without purging existing tags to match the collection standard tagging behaviour (https://github.com/ansible-collections/community.aws/pull/318). +- ec2_vpc_igw_info - Add ``convert_tags`` parameter so that tags can be returned in standard dict format rather than the both list of dict format (https://github.com/ansible-collections/community.aws/pull/318). +deprecated_features: +- ec2_vpc_igw_info - After 2022-06-22 the ``convert_tags`` parameter default value will change from ``False`` to ``True`` to match the collection standard behavior (https://github.com/ansible-collections/community.aws/pull/318). diff --git a/plugins/modules/ec2_vpc_igw.py b/plugins/modules/ec2_vpc_igw.py index b920682b76c..3d8d9f3bf25 100644 --- a/plugins/modules/ec2_vpc_igw.py +++ b/plugins/modules/ec2_vpc_igw.py @@ -22,9 +22,16 @@ type: str tags: description: - - "A dict of tags to apply to the internet gateway. Any tags currently applied to the internet gateway and not present here will be removed." + - A dict of tags to apply to the internet gateway. + - To remove all tags set I(tags={}) and I(purge_tags=true). aliases: [ 'resource_tags' ] type: dict + purge_tags: + description: + - Remove tags not listed in I(tags). + type: bool + default: true + version_added: 1.3.0 state: description: - Create or terminate the IGW @@ -85,17 +92,16 @@ except ImportError: pass # caught by AnsibleAWSModule +from ansible.module_utils.six import string_types + from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.waiters import get_waiter -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ( - AWSRetry, - camel_dict_to_snake_dict, - boto3_tag_list_to_ansible_dict, - ansible_dict_to_boto3_filter_list, - ansible_dict_to_boto3_tag_list, - compare_aws_tags -) -from ansible.module_utils.six import string_types +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_filter_list +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 camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags class AnsibleEc2Igw(object): @@ -103,16 +109,17 @@ class AnsibleEc2Igw(object): def __init__(self, module, results): self._module = module self._results = results - self._connection = self._module.client('ec2') + self._connection = self._module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) self._check_mode = self._module.check_mode def process(self): vpc_id = self._module.params.get('vpc_id') state = self._module.params.get('state', 'present') tags = self._module.params.get('tags') + purge_tags = self._module.params.get('purge_tags') if state == 'present': - self.ensure_igw_present(vpc_id, tags) + self.ensure_igw_present(vpc_id, tags, purge_tags) elif state == 'absent': self.ensure_igw_absent(vpc_id) @@ -120,7 +127,7 @@ def get_matching_igw(self, vpc_id): filters = ansible_dict_to_boto3_filter_list({'attachment.vpc-id': vpc_id}) igws = [] try: - response = self._connection.describe_internet_gateways(Filters=filters) + response = self._connection.describe_internet_gateways(aws_retry=True, Filters=filters) igws = response.get('InternetGateways', []) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self._module.fail_json_aws(e) @@ -135,21 +142,25 @@ def get_matching_igw(self, vpc_id): return igw def check_input_tags(self, tags): + if tags is None: + return nonstring_tags = [k for k, v in tags.items() if not isinstance(v, string_types)] if nonstring_tags: self._module.fail_json(msg='One or more tags contain non-string values: {0}'.format(nonstring_tags)) - def ensure_tags(self, igw_id, tags, add_only): + def ensure_tags(self, igw_id, tags, purge_tags): final_tags = [] filters = ansible_dict_to_boto3_filter_list({'resource-id': igw_id, 'resource-type': 'internet-gateway'}) cur_tags = None try: - cur_tags = self._connection.describe_tags(Filters=filters) + cur_tags = self._connection.describe_tags(aws_retry=True, Filters=filters) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self._module.fail_json_aws(e, msg="Couldn't describe tags") - purge_tags = bool(not add_only) + if tags is None: + return boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')) + to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags) final_tags = boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')) @@ -159,7 +170,8 @@ def ensure_tags(self, igw_id, tags, add_only): # update tags final_tags.update(to_update) else: - AWSRetry.exponential_backoff()(self._connection.create_tags)( + self._connection.create_tags( + aws_retry=True, Resources=[igw_id], Tags=ansible_dict_to_boto3_tag_list(to_update) ) @@ -179,7 +191,7 @@ def ensure_tags(self, igw_id, tags, add_only): for key in to_delete: tags_list.append({'Key': key}) - AWSRetry.exponential_backoff()(self._connection.delete_tags)(Resources=[igw_id], Tags=tags_list) + self._connection.delete_tags(aws_retry=True, Resources=[igw_id], Tags=tags_list) self._results['changed'] = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: @@ -187,7 +199,7 @@ def ensure_tags(self, igw_id, tags, add_only): if not self._check_mode and (to_update or to_delete): try: - response = self._connection.describe_tags(Filters=filters) + response = self._connection.describe_tags(aws_retry=True, Filters=filters) final_tags = boto3_tag_list_to_ansible_dict(response.get('Tags')) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self._module.fail_json_aws(e, msg="Couldn't describe tags") @@ -213,14 +225,14 @@ def ensure_igw_absent(self, vpc_id): try: self._results['changed'] = True - self._connection.detach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id) - self._connection.delete_internet_gateway(InternetGatewayId=igw['internet_gateway_id']) + self._connection.detach_internet_gateway(aws_retry=True, InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id) + self._connection.delete_internet_gateway(aws_retry=True, InternetGatewayId=igw['internet_gateway_id']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self._module.fail_json_aws(e, msg="Unable to delete Internet Gateway") return self._results - def ensure_igw_present(self, vpc_id, tags): + def ensure_igw_present(self, vpc_id, tags, purge_tags): self.check_input_tags(tags) igw = self.get_matching_igw(vpc_id) @@ -232,21 +244,21 @@ def ensure_igw_present(self, vpc_id, tags): return self._results try: - response = self._connection.create_internet_gateway() + response = self._connection.create_internet_gateway(aws_retry=True) # Ensure the gateway exists before trying to attach it or add tags waiter = get_waiter(self._connection, 'internet_gateway_exists') waiter.wait(InternetGatewayIds=[response['InternetGateway']['InternetGatewayId']]) igw = camel_dict_to_snake_dict(response['InternetGateway']) - self._connection.attach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id) + self._connection.attach_internet_gateway(aws_retry=True, InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id) self._results['changed'] = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self._module.fail_json_aws(e, msg='Unable to create Internet Gateway') igw['vpc_id'] = vpc_id - igw['tags'] = self.ensure_tags(igw_id=igw['internet_gateway_id'], tags=tags, add_only=False) + igw['tags'] = self.ensure_tags(igw_id=igw['internet_gateway_id'], tags=tags, purge_tags=purge_tags) igw_info = self.get_igw_info(igw) self._results.update(igw_info) @@ -258,7 +270,8 @@ def main(): argument_spec = dict( vpc_id=dict(required=True), state=dict(default='present', choices=['present', 'absent']), - tags=dict(default=dict(), required=False, type='dict', aliases=['resource_tags']) + tags=dict(required=False, type='dict', aliases=['resource_tags']), + purge_tags=dict(default=True, type='bool'), ) module = AnsibleAWSModule( diff --git a/plugins/modules/ec2_vpc_igw_info.py b/plugins/modules/ec2_vpc_igw_info.py index 4719d495fd8..ab7d26a80b4 100644 --- a/plugins/modules/ec2_vpc_igw_info.py +++ b/plugins/modules/ec2_vpc_igw_info.py @@ -27,6 +27,12 @@ - Get details of specific Internet Gateway ID. Provide this value as a list. type: list elements: str + convert_tags: + description: + - Convert tags from boto3 format (list of dictionaries) to the standard dictionary format. + - This currently defaults to C(False). The default will be changed to C(True) after 2022-06-22. + type: bool + version_added: 1.3.0 extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 @@ -94,31 +100,45 @@ pass # Handled by 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 +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 ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict -def get_internet_gateway_info(internet_gateway): +def get_internet_gateway_info(internet_gateway, convert_tags): + if convert_tags: + tags = boto3_tag_list_to_ansible_dict(internet_gateway['Tags']) + ignore_list = ["Tags"] + else: + tags = internet_gateway['Tags'] + ignore_list = [] internet_gateway_info = {'InternetGatewayId': internet_gateway['InternetGatewayId'], 'Attachments': internet_gateway['Attachments'], - 'Tags': internet_gateway['Tags']} + 'Tags': tags} + + internet_gateway_info = camel_dict_to_snake_dict(internet_gateway_info, ignore_list=ignore_list) return internet_gateway_info -def list_internet_gateways(client, module): +def list_internet_gateways(connection, module): params = dict() params['Filters'] = ansible_dict_to_boto3_filter_list(module.params.get('filters')) + convert_tags = module.params.get('convert_tags') if module.params.get("internet_gateway_ids"): params['InternetGatewayIds'] = module.params.get("internet_gateway_ids") try: - all_internet_gateways = client.describe_internet_gateways(**params) - except botocore.exceptions.ClientError as e: - module.fail_json(msg=str(e)) + all_internet_gateways = connection.describe_internet_gateways(aws_retry=True, **params) + except is_boto3_error_code('InvalidInternetGatewayID.NotFound'): + module.fail_json('InternetGateway not found') + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, 'Unable to describe internet gateways') - return [camel_dict_to_snake_dict(get_internet_gateway_info(igw)) + return [get_internet_gateway_info(igw, convert_tags) for igw in all_internet_gateways['InternetGateways']] @@ -126,15 +146,22 @@ def main(): argument_spec = dict( filters=dict(type='dict', default=dict()), internet_gateway_ids=dict(type='list', default=None, elements='str'), + convert_tags=dict(type='bool'), ) module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) if module._name == 'ec2_vpc_igw_facts': module.deprecate("The 'ec2_vpc_igw_facts' module has been renamed to 'ec2_vpc_igw_info'", date='2021-12-01', collection_name='community.aws') + if module.params.get('convert_tags') is None: + module.deprecate('This module currently returns boto3 style tags by default. ' + 'This default has been deprecated and the module will return a simple dictionary in future. ' + 'This behaviour can be controlled through the convert_tags parameter.', + date='2021-12-01', collection_name='community.aws') + # Validate Requirements try: - connection = module.client('ec2') + connection = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg='Failed to connect to AWS') diff --git a/tests/integration/targets/ec2_vpc_igw/aliases b/tests/integration/targets/ec2_vpc_igw/aliases index 6e3860bee23..6b8a2ae5af7 100644 --- a/tests/integration/targets/ec2_vpc_igw/aliases +++ b/tests/integration/targets/ec2_vpc_igw/aliases @@ -1,2 +1,3 @@ cloud/aws shippable/aws/group2 +ec2_vpc_igw_info diff --git a/tests/integration/targets/ec2_vpc_igw/defaults/main.yml b/tests/integration/targets/ec2_vpc_igw/defaults/main.yml new file mode 100644 index 00000000000..eeda091c81c --- /dev/null +++ b/tests/integration/targets/ec2_vpc_igw/defaults/main.yml @@ -0,0 +1,4 @@ +--- +vpc_name: '{{ resource_prefix }}-vpc' +vpc_seed: '{{ resource_prefix }}' +vpc_cidr: '10.{{ 256 | random(seed=vpc_seed) }}.0.0/16' diff --git a/tests/integration/targets/ec2_vpc_igw/tasks/main.yml b/tests/integration/targets/ec2_vpc_igw/tasks/main.yml index 56da84772a2..634438c0875 100644 --- a/tests/integration/targets/ec2_vpc_igw/tasks/main.yml +++ b/tests/integration/targets/ec2_vpc_igw/tasks/main.yml @@ -3,72 +3,415 @@ collections: - amazon.aws + module_defaults: + group/aws: + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token | default(omit) }}" + region: "{{ aws_region }}" block: - - name: set up aws connection info - set_fact: - aws_connection_info: &aws_connection_info - aws_access_key: "{{ aws_access_key }}" - aws_secret_key: "{{ aws_secret_key }}" - security_token: "{{ security_token }}" - region: "{{ aws_region }}" - no_log: yes + # ============================================================ + - name: Fetch IGWs in check_mode + ec2_vpc_igw_info: + register: igw_info + check_mode: True + + - name: Assert success + assert: + that: + - igw_info is successful + - '"internet_gateways" in igw_info' # ============================================================ - name: create a VPC ec2_vpc_net: - name: "{{ resource_prefix }}-vpc" + name: "{{ vpc_name }}" state: present - cidr_block: "10.232.232.128/26" - <<: *aws_connection_info + cidr_block: "{{ vpc_cidr }}" tags: Name: "{{ resource_prefix }}-vpc" Description: "Created by ansible-test" register: vpc_result + - name: Assert success + assert: + that: + - vpc_result is successful + # ============================================================ + - name: Search for internet gateway by VPC - no matches + ec2_vpc_igw_info: + filters: + attachment.vpc-id: '{{ vpc_result.vpc.id }}' + register: igw_info + + - name: Assert success + assert: + that: + - igw_info is successful + - '"internet_gateways" in igw_info' + + # ============================================================ + - name: create internet gateway (expected changed=true) - CHECK_MODE + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' + register: vpc_igw_create + check_mode: yes + + - name: assert creation would happen (expected changed=true) - CHECK_MODE + assert: + that: + - vpc_igw_create is changed + - name: create internet gateway (expected changed=true) ec2_vpc_igw: state: present vpc_id: "{{ vpc_result.vpc.id }}" - <<: *aws_connection_info + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' register: vpc_igw_create - name: assert creation happened (expected changed=true) assert: that: - - 'vpc_igw_create' + - vpc_igw_create is changed - 'vpc_igw_create.gateway_id.startswith("igw-")' - 'vpc_igw_create.vpc_id == vpc_result.vpc.id' - '"tags" in vpc_igw_create' + - vpc_igw_create.tags | length == 2 + - vpc_igw_create.tags["tag_one"] == '{{ resource_prefix }} One' + - vpc_igw_create.tags["Tag Two"] == 'two {{ resource_prefix }}' - '"gateway_id" in vpc_igw_create' # ============================================================ + - name: Save IDs for later + set_fact: + igw_id: '{{ vpc_igw_create.gateway_id }}' + vpc_id: '{{ vpc_result.vpc.id }}' + + # ============================================================ + - name: Search for internet gateway by VPC + ec2_vpc_igw_info: + filters: + attachment.vpc-id: '{{ vpc_id }}' + register: igw_info + + - name: 'Check standard IGW details' + assert: + that: + - '"internet_gateways" in igw_info' + - igw_info.internet_gateways | length == 1 + - '"attachments" in current_igw' + - current_igw.attachments | length == 1 + - '"state" in current_igw.attachments[0]' + - current_igw.attachments[0].state == "available" + - '"vpc_id" in current_igw.attachments[0]' + - current_igw.attachments[0].vpc_id == vpc_id + - '"internet_gateway_id" in current_igw' + - current_igw.internet_gateway_id == igw_id + - '"tags" in current_igw' + - current_igw.tags | length == 2 + - '"key" in current_igw.tags[0]' + - '"value" in current_igw.tags[0]' + - '"key" in current_igw.tags[1]' + - '"value" in current_igw.tags[1]' + # Order isn't guaranteed in boto3 style, so just check the keys and + # values we expect are in there. + - current_igw.tags[0].key in ["tag_one", "Tag Two"] + - current_igw.tags[1].key in ["tag_one", "Tag Two"] + - current_igw.tags[0].value in [resource_prefix + " One", "two " + resource_prefix] + - current_igw.tags[1].value in [resource_prefix + " One", "two " + resource_prefix] + vars: + current_igw: '{{ igw_info.internet_gateways[0] }}' + + # ============================================================ + - name: Fetch IGW by ID + ec2_vpc_igw_info: + internet_gateway_ids: '{{ igw_id }}' + convert_tags: yes + register: igw_info + + - name: 'Check standard IGW details' + assert: + that: + - '"internet_gateways" in igw_info' + - igw_info.internet_gateways | length == 1 + - '"attachments" in current_igw' + - current_igw.attachments | length == 1 + - '"state" in current_igw.attachments[0]' + - current_igw.attachments[0].state == "available" + - '"vpc_id" in current_igw.attachments[0]' + - current_igw.attachments[0].vpc_id == vpc_id + - '"internet_gateway_id" in current_igw' + - current_igw.internet_gateway_id == igw_id + - '"tags" in current_igw' + - current_igw.tags | length == 2 + - '"tag_one" in current_igw.tags' + - '"Tag Two" in current_igw.tags' + - current_igw.tags["tag_one"] == '{{ resource_prefix }} One' + - current_igw.tags["Tag Two"] == 'two {{ resource_prefix }}' + vars: + current_igw: '{{ igw_info.internet_gateways[0] }}' + + # ============================================================ + - name: Fetch IGW by ID (list) + ec2_vpc_igw_info: + internet_gateway_ids: + - '{{ igw_id }}' + register: igw_info + + - name: 'Check standard IGW details' + assert: + that: + - '"internet_gateways" in igw_info' + - igw_info.internet_gateways | length == 1 + - '"attachments" in current_igw' + - current_igw.attachments | length == 1 + - '"state" in current_igw.attachments[0]' + - current_igw.attachments[0].state == "available" + - '"vpc_id" in current_igw.attachments[0]' + - current_igw.attachments[0].vpc_id == vpc_id + - '"internet_gateway_id" in current_igw' + - current_igw.internet_gateway_id == igw_id + - '"tags" in current_igw' + vars: + current_igw: '{{ igw_info.internet_gateways[0] }}' + + # ============================================================ + - name: attempt to recreate internet gateway on VPC (expected changed=false) - CHECK_MODE + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + register: vpc_igw_recreate + check_mode: yes + + - name: assert recreation would do nothing (expected changed=false) - CHECK_MODE + assert: + that: + - vpc_igw_recreate is not changed + - name: attempt to recreate internet gateway on VPC (expected changed=false) ec2_vpc_igw: state: present vpc_id: "{{ vpc_result.vpc.id }}" - <<: *aws_connection_info register: vpc_igw_recreate - name: assert recreation did nothing (expected changed=false) assert: that: - - 'vpc_igw_recreate.changed == False' - - 'vpc_igw_recreate.gateway_id == vpc_igw_create.gateway_id' - - 'vpc_igw_recreate.vpc_id == vpc_igw_create.vpc_id' + - vpc_igw_recreate is not changed + - vpc_igw_recreate.gateway_id == igw_id + - vpc_igw_recreate.vpc_id == vpc_id + - '"tags" in vpc_igw_create' + - vpc_igw_create.tags | length == 2 + - vpc_igw_create.tags["tag_one"] == '{{ resource_prefix }} One' + - vpc_igw_create.tags["Tag Two"] == 'two {{ resource_prefix }}' + + # ============================================================ + - name: Update the tags (no change) - CHECK_MODE + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' + register: vpc_igw_recreate + check_mode: yes + + - name: assert tag update would do nothing (expected changed=false) - CHECK_MODE + assert: + that: + - vpc_igw_recreate is not changed + + - name: Update the tags (no change) + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + tags: + tag_one: '{{ resource_prefix }} One' + "Tag Two": 'two {{ resource_prefix }}' + register: vpc_igw_recreate + + - name: assert tag update did nothing (expected changed=false) + assert: + that: + - vpc_igw_recreate is not changed + - vpc_igw_recreate.gateway_id == igw_id + - vpc_igw_recreate.vpc_id == vpc_id + - '"tags" in vpc_igw_recreate' + - vpc_igw_recreate.tags | length == 2 + - vpc_igw_recreate.tags["tag_one"] == '{{ resource_prefix }} One' + - vpc_igw_recreate.tags["Tag Two"] == 'two {{ resource_prefix }}' + + # ============================================================ + - name: Update the tags - remove and add - CHECK_MODE + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + tags: + tag_three: '{{ resource_prefix }} Three' + "Tag Two": 'two {{ resource_prefix }}' + register: vpc_igw_update + check_mode: yes + + - name: assert tag update would happen (expected changed=true) - CHECK_MODE + assert: + that: + - vpc_igw_update is changed + + - name: Update the tags - remove and add + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + tags: + tag_three: '{{ resource_prefix }} Three' + "Tag Two": 'two {{ resource_prefix }}' + register: vpc_igw_update + + - name: assert tags are updated (expected changed=true) + assert: + that: + - vpc_igw_update is changed + - vpc_igw_update.gateway_id == igw_id + - vpc_igw_update.vpc_id == vpc_id + - '"tags" in vpc_igw_update' + - vpc_igw_update.tags | length == 2 + - vpc_igw_update.tags["tag_three"] == '{{ resource_prefix }} Three' + - vpc_igw_update.tags["Tag Two"] == 'two {{ resource_prefix }}' + + # ============================================================ + - name: Update the tags add without purge - CHECK_MODE + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + purge_tags: no + tags: + tag_one: '{{ resource_prefix }} One' + register: vpc_igw_update + check_mode: yes + + - name: assert tags would be added - CHECK_MODE + assert: + that: + - vpc_igw_update is changed + + - name: Update the tags add without purge + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + purge_tags: no + tags: + tag_one: '{{ resource_prefix }} One' + register: vpc_igw_update + + - name: assert tags added + assert: + that: + - vpc_igw_update is changed + - vpc_igw_update.gateway_id == igw_id + - vpc_igw_update.vpc_id == vpc_id + - '"tags" in vpc_igw_update' + - vpc_igw_update.tags | length == 3 + - vpc_igw_update.tags["tag_one"] == '{{ resource_prefix }} One' + - vpc_igw_update.tags["tag_three"] == '{{ resource_prefix }} Three' + - vpc_igw_update.tags["Tag Two"] == 'two {{ resource_prefix }}' + + # ============================================================ + - name: Remove all tags - CHECK_MODE + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + tags: {} + register: vpc_igw_update + check_mode: yes + + - name: assert tags would be removed - CHECK_MODE + assert: + that: + - vpc_igw_update is changed + + - name: Remove all tags + ec2_vpc_igw: + state: present + vpc_id: "{{ vpc_result.vpc.id }}" + tags: {} + register: vpc_igw_update + + - name: assert tags removed + assert: + that: + - vpc_igw_update is changed + - vpc_igw_update.gateway_id == igw_id + - vpc_igw_update.vpc_id == vpc_id + - '"tags" in vpc_igw_update' + - vpc_igw_update.tags | length == 0 # ============================================================ + - name: test state=absent (expected changed=true) - CHECK_MODE + ec2_vpc_igw: + state: absent + vpc_id: "{{ vpc_result.vpc.id }}" + register: vpc_igw_delete + check_mode: yes + + - name: assert state=absent (expected changed=true) - CHECK_MODE + assert: + that: + - vpc_igw_delete is changed + - name: test state=absent (expected changed=true) ec2_vpc_igw: state: absent vpc_id: "{{ vpc_result.vpc.id }}" - <<: *aws_connection_info register: vpc_igw_delete - name: assert state=absent (expected changed=true) assert: that: - - 'vpc_igw_delete.changed' + - vpc_igw_delete is changed + + # ============================================================ + - name: Fetch IGW by ID (list) + ec2_vpc_igw_info: + internet_gateway_ids: + - '{{ igw_id }}' + register: igw_info + ignore_errors: True + + - name: 'Check IGW does not exist' + assert: + that: + # Deliberate choice not to change bevahiour when searching by ID + - igw_info is failed + + # ============================================================ + - name: test state=absent when already deleted (expected changed=false) - CHECK_MODE + ec2_vpc_igw: + state: absent + vpc_id: "{{ vpc_result.vpc.id }}" + register: vpc_igw_delete + check_mode: yes + + - name: assert state=absent (expected changed=false) - CHECK_MODE + assert: + that: + - vpc_igw_delete is not changed + + - name: test state=absent when already deleted (expected changed=false) + ec2_vpc_igw: + state: absent + vpc_id: "{{ vpc_result.vpc.id }}" + register: vpc_igw_delete + + - name: assert state=absent (expected changed=false) + assert: + that: + - vpc_igw_delete is not changed always: # ============================================================ @@ -76,13 +419,11 @@ ec2_vpc_igw: state: absent vpc_id: "{{ vpc_result.vpc.id }}" - <<: *aws_connection_info ignore_errors: true - name: tidy up VPC ec2_vpc_net: - name: "{{ resource_prefix }}-vpc" + name: "{{ vpc_name }}" state: absent - cidr_block: "10.232.232.128/26" - <<: *aws_connection_info + cidr_block: "{{ vpc_cidr }}" ignore_errors: true