From 9ddde6b27f4f58f75207748e36615a4f157eb0f3 Mon Sep 17 00:00:00 2001 From: Bob Boldin Date: Fri, 12 Apr 2019 11:35:39 -0400 Subject: [PATCH] AWS: add ec2_transit_gateway_info module (#54460) * AWS: add ec2_transit_gateway_info module * move info module test to main module and ensure unique description for parallel tests * add type designators to module options in documentation * assign results and return instead of exit. Add elements directive available with ansible 2.8 * assign results and return instead of exit * get() method superfluous for module.params * correct return type in documentation for ASN and format the sample for Tag correctly * added random uid to test description - removing unstable alias * change random uuid to resource_prefix to improve source of object identification in testing --- .../cloud/amazon/ec2_transit_gateway_info.py | 266 ++++++++++++++++++ .../targets/ec2_transit_gateway/aliases | 2 +- .../ec2_transit_gateway/tasks/main.yml | 76 ++++- 3 files changed, 336 insertions(+), 8 deletions(-) create mode 100644 lib/ansible/modules/cloud/amazon/ec2_transit_gateway_info.py diff --git a/lib/ansible/modules/cloud/amazon/ec2_transit_gateway_info.py b/lib/ansible/modules/cloud/amazon/ec2_transit_gateway_info.py new file mode 100644 index 0000000000..1022598b16 --- /dev/null +++ b/lib/ansible/modules/cloud/amazon/ec2_transit_gateway_info.py @@ -0,0 +1,266 @@ +#!/usr/bin/python +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['preview'] +} + +DOCUMENTATION = ''' +module: ec2_transit_gateway_info +short_description: Gather information about ec2 transit gateways in AWS +description: + - Gather information about ec2 transit gateways in AWS +version_added: "2.8" +author: "Bob Boldin (@BobBoldin)" +requirements: + - botocore + - boto3 +options: + transit_gateway_ids: + description: + - A list of transit gateway IDs to gather information for. + version_added: "2.8" + aliases: [transit_gateway_id] + type: list + filters: + description: + - A dict of filters to apply. Each dict item consists of a filter key and a filter value. + See U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeTransitGateways.html) for filters. + type: dict +extends_documentation_fragment: + - aws + - ec2 +''' + +EXAMPLES = ''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +# Gather info about all transit gateways +- ec2_transit_gateway_info: + +# Gather info about a particular transit gateway using filter transit gateway ID +- ec2_transit_gateway_info: + filters: + transit-gateway-id: tgw-02c42332e6b7da829 + +# Gather info about a particular transit gateway using multiple option filters +- ec2_transit_gateway_info: + filters: + options.dns-support: enable + options.vpn-ecmp-support: enable + +# Gather info about multiple transit gateways using module param +- ec2_transit_gateway_info: + transit_gateway_ids: + - tgw-02c42332e6b7da829 + - tgw-03c53443d5a8cb716 +''' + +RETURN = ''' +transit_gateways: + description: > + Transit gateways that match the provided filters. Each element consists of a dict with all the information + related to that transit gateway. + returned: on success + type: complex + contains: + creation_time: + description: The creation time. + returned: always + type: str + sample: "2019-02-05T16:19:58+00:00" + description: + description: The description of the transit gateway. + returned: always + type: str + sample: "A transit gateway" + options: + description: A dictionary of the transit gateway options. + returned: always + type: complex + contains: + amazon_side_asn: + description: + - A private Autonomous System Number (ASN) for the Amazon + side of a BGP session. The range is 64512 to 65534 for + 16-bit ASNs and 4200000000 to 4294967294 for 32-bit ASNs. + returned: always + type: int + sample: 64512 + auto_accept_shared_attachments: + description: + - Indicates whether attachment requests are automatically accepted. + returned: always + type: str + sample: "enable" + default_route_table_association: + description: + - Indicates whether resource attachments are automatically + associated with the default association route table. + returned: always + type: str + sample: "disable" + association_default_route_table_id: + description: + - The ID of the default association route table. + returned: when present + type: str + sample: "rtb-11223344" + default_route_table_propagation: + description: + - Indicates whether resource attachments automatically + propagate routes to the default propagation route table. + returned: always + type: str + sample: "disable" + dns_support: + description: + - Indicates whether DNS support is enabled. + returned: always + type: str + sample: "enable" + propagation_default_route_table_id: + description: + - The ID of the default propagation route table. + returned: when present + type: str + sample: "rtb-11223344" + vpn_ecmp_support: + description: + - Indicates whether Equal Cost Multipath Protocol support + is enabled. + returned: always + type: str + sample: "enable" + owner_id: + description: The AWS account number ID which owns the transit gateway. + returned: always + type: str + sample: "1234567654323" + state: + description: The state of the transit gateway. + returned: always + type: str + sample: "available" + tags: + description: A dict of tags associated with the transit gateway. + returned: always + type: dict + sample: '{ + "Name": "A sample TGW" + }' + transit_gateway_arn: + description: The Amazon Resource Name (ARN) of the transit gateway. + returned: always + type: str + sample: "arn:aws:ec2:us-west-2:1234567654323:transit-gateway/tgw-02c42332e6b7da829" + transit_gateway_id: + description: The ID of the transit gateway. + returned: always + type: str + sample: "tgw-02c42332e6b7da829" +''' + +try: + from botocore.exceptions import BotoCoreError, ClientError +except Exception: + pass + # handled by imported AnsibleAWSModule + +from ansible.module_utils.aws.core import AnsibleAWSModule +from ansible.module_utils.ec2 import ( + AWSRetry, + boto3_tag_list_to_ansible_dict, + camel_dict_to_snake_dict, + ansible_dict_to_boto3_filter_list +) + + +class AnsibleEc2TgwInfo(object): + + def __init__(self, module, results): + self._module = module + self._results = results + self._connection = self._module.client('ec2') + self._check_mode = self._module.check_mode + + if not hasattr(self._connection, 'describe_transit_gateways'): + self._module.fail_json(msg='transit gateway module requires boto3 >= 1.9.52') + + @AWSRetry.exponential_backoff() + def describe_transit_gateways(self): + """ + Describe transit gateways. + + module : AnsibleAWSModule object + connection : boto3 client connection object + """ + # collect parameters + filters = ansible_dict_to_boto3_filter_list(self._module.params['filters']) + transit_gateway_ids = self._module.params['transit_gateway_ids'] + + # init empty list for return vars + transit_gateway_info = list() + + # Get the basic transit gateway info + try: + response = self._connection.describe_transit_gateways( + TransitGatewayIds=transit_gateway_ids, Filters=filters) + except (BotoCoreError, ClientError) as e: + if e.response['Error']['Code'] == 'InvalidTransitGatewayID.NotFound': + self._results['transit_gateways'] = [] + return + else: + self._module.fail_json_aws(e) + + for transit_gateway in response['TransitGateways']: + transit_gateway_info.append(camel_dict_to_snake_dict(transit_gateway, ignore_list=['Tags'])) + # convert tag list to ansible dict + transit_gateway_info[-1]['tags'] = boto3_tag_list_to_ansible_dict(transit_gateway.get('Tags', [])) + + self._results['transit_gateways'] = transit_gateway_info + return + + +def setup_module_object(): + """ + merge argument spec and create Ansible module object + :return: Ansible module object + """ + + argument_spec = dict( + transit_gateway_ids=dict(type='list', default=[], elements='str', aliases=['transit_gateway_id']), + filters=dict(type='dict', default={}) + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + return module + + +def main(): + + module = setup_module_object() + + results = dict( + changed=False + ) + + tgwf_manager = AnsibleEc2TgwInfo(module=module, results=results) + tgwf_manager.describe_transit_gateways() + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/ec2_transit_gateway/aliases b/test/integration/targets/ec2_transit_gateway/aliases index 75ec93f9f2..ce6c077119 100644 --- a/test/integration/targets/ec2_transit_gateway/aliases +++ b/test/integration/targets/ec2_transit_gateway/aliases @@ -1,3 +1,3 @@ cloud/aws shippable/aws/group2 -unstable # sometimes tries to delete while in state pending +ec2_transit_gateway_info diff --git a/test/integration/targets/ec2_transit_gateway/tasks/main.yml b/test/integration/targets/ec2_transit_gateway/tasks/main.yml index 51b35745e6..b70db39302 100644 --- a/test/integration/targets/ec2_transit_gateway/tasks/main.yml +++ b/test/integration/targets/ec2_transit_gateway/tasks/main.yml @@ -10,10 +10,14 @@ region: "{{ aws_region }}" no_log: yes +- name: generate unique value for testing + set_fact: + tgw_description: "{{ resource_prefix }}-tgw" + - block: - name: test create transit gateway without permissions ec2_transit_gateway: - description: integration-testing + description: "{{ tgw_description }}" region: "{{ aws_region }}" register: result ignore_errors: yes @@ -26,11 +30,11 @@ - name: test create transit gateway without region ec2_transit_gateway: - description: integration-testing + description: "{{ tgw_description }}" register: result ignore_errors: yes - - name: assert failure when called with with minimal parameters but no region + - name: assert failure when called with minimal parameters but no region assert: that: - 'result.failed' @@ -38,7 +42,7 @@ - name: test create transit gateway without tags ec2_transit_gateway: - description: integration-testing + description: "{{ tgw_description }}" <<: *aws_connection_info register: create_result - name: assert changed is True @@ -48,7 +52,7 @@ - name: test update transit gateway with tags by description ec2_transit_gateway: - description: integration-testing + description: "{{ tgw_description }}" tags: Name: Ansible Test TGW <<: *aws_connection_info @@ -92,7 +96,7 @@ - name: test idempotence ec2_transit_gateway: - description: integration-testing + description: "{{ tgw_description }}" purge_tags: True tags: status: ok to delete @@ -103,11 +107,69 @@ that: - result.changed == False + # ==== Combine ec2_transit_gateway_info ====================== + - name: test success with no parameters + ec2_transit_gateway_info: + <<: *aws_connection_info + register: result + - name: assert success with no parameters + assert: + that: + - 'result.changed == false' + - 'result.transit_gateways != []' + + - name: test success with single filter + ec2_transit_gateway_info: + filters: + transit-gateway-id: "{{ create_result.transit_gateway.transit_gateway_id }}" + <<: *aws_connection_info + register: result + - name: assert success with transit_gateway_id filter + assert: + that: + - 'result.changed == false' + - 'result.transit_gateways != []' + + - name: test empty result set for non-existent tgw id via filter + ec2_transit_gateway_info: + filters: + transit-gateway-id: tgw-00000011111111122 + <<: *aws_connection_info + register: result + - name: assert success with transit_gateway_id filter + assert: + that: + - 'result.changed == false' + - 'result.transit_gateways == []' + + - name: test NotFound exception caught and returned empty result set + ec2_transit_gateway_info: + transit_gateway_id: tgw-00000011111111122 + <<: *aws_connection_info + register: result + - name: assert success with transit_gateway_id filter + assert: + that: + - 'result.changed == false' + - 'result.transit_gateways == []' + + - name: test success with multiple filters + ec2_transit_gateway_info: + filters: + options.dns-support: enable + options.vpn-ecmp-support: enable + <<: *aws_connection_info + register: result + - name: assert success with transit_gateway_id filter + assert: + that: + - 'result.changed == false' + - 'result.transit_gateways != []' always: ###### TEARDOWN STARTS HERE ###### - name: delete transit gateway ec2_transit_gateway: - description: integration-testing + description: "{{ tgw_description }}" state: absent <<: *aws_connection_info ignore_errors: yes