From 04a34a4c0a79e62f0d1568e339196ca86424ef16 Mon Sep 17 00:00:00 2001 From: Marco Braga Date: Tue, 12 Jul 2022 19:56:22 -0300 Subject: [PATCH] feat(module/vpc-cagw): Add Carrier Gateway modules ``` $ ansible localhost -m local_ec2_vpc_cagw_info localhost | SUCCESS => { "carrier_gateways": [ { "carrier_gateway_id": "cagw-037df45cae5362d59", "tags": { "Name": "test1-54dsl-vpc-cagw" }, "vpc_id": "vpc-069cabb60c7e7fc6d" } ], "changed": false } $ ansible localhost -m local_ec2_vpc_cagw -a "state=absent vpc_id=vpc-069cabb60c7e7fc6d carrier_gateway_id=cagw-037df45cae5362d59" localhost | CHANGED => { "changed": true } $ ansible localhost -m local_ec2_vpc_cagw_info localhost | SUCCESS => { "carrier_gateways": [], "changed": false } $ ansible localhost -m local_ec2_vpc_cagw -a "vpc_id=vpc-069cabb60c7e7fc6d" localhost | CHANGED => { "carrier_gateway_id": "cagw-095f998ebdcb5ef86", "changed": true, "tags": {}, "vpc_id": "vpc-069cabb60c7e7fc6d" } $ ansible localhost -m local_ec2_vpc_cagw_info localhost | SUCCESS => { "carrier_gateways": [ { "carrier_gateway_id": "cagw-095f998ebdcb5ef86", "tags": {}, "vpc_id": "vpc-069cabb60c7e7fc6d" } ], "changed": false } ``` --- plugins/modules/ec2_vpc_cagw.py | 243 +++++++++++++++++++++++++++ plugins/modules/ec2_vpc_cagw_info.py | 166 ++++++++++++++++++ 2 files changed, 409 insertions(+) create mode 100644 plugins/modules/ec2_vpc_cagw.py create mode 100644 plugins/modules/ec2_vpc_cagw_info.py diff --git a/plugins/modules/ec2_vpc_cagw.py b/plugins/modules/ec2_vpc_cagw.py new file mode 100644 index 00000000000..56d70a41a5e --- /dev/null +++ b/plugins/modules/ec2_vpc_cagw.py @@ -0,0 +1,243 @@ +#!/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 + + +DOCUMENTATION = ''' +--- +module: ec2_vpc_cagw +version_added: 1.0.0 +short_description: Manage an AWS VPC Carrier gateway +description: + - Manage an AWS VPC Carrier gateway +author: "Marco Braga (@mtulio)" +options: + vpc_id: + description: + - The VPC ID for the VPC in which to manage the Carrier Gateway. + required: true + type: str + state: + description: + - Create or terminate the CAGW + default: present + choices: [ 'present', 'absent' ] + type: str +notes: +- Support for I(purge_tags) was added in release 1.3.0. +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 +- amazon.aws.tags +''' + +EXAMPLES = ''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +# Ensure that the VPC has an Carrier Gateway. +# The Carrier Gateway ID is can be accessed via {{cagw.carrier_gateway_id}} for use in setting up NATs etc. +- name: Create Carrier gateway + community.aws.ec2_vpc_cagw: + vpc_id: vpc-abcdefgh + state: present + register: cagw + +- name: Create Carrier gateway with tags + community.aws.ec2_vpc_cagw: + vpc_id: vpc-abcdefgh + state: present + tags: + Tag1: tag1 + Tag2: tag2 + register: cagw + +- name: Delete Carrier gateway + community.aws.ec2_vpc_cagw: + state: absent + vpc_id: vpc-abcdefgh + register: vpc_cagw_delete +''' + +RETURN = ''' +changed: + description: If any changes have been made to the Carrier Gateway. + type: bool + returned: always + sample: + changed: false +carrier_gateway_id: + description: The unique identifier for the Carrier Gateway. + type: str + returned: I(state=present) + sample: + carrier_gateway_id: "cagw-XXXXXXXX" +tags: + description: The tags associated the Carrier Gateway. + type: dict + returned: I(state=present) + sample: + tags: + "Ansible": "Test" +vpc_id: + description: The VPC ID associated with the Carrier Gateway. + type: str + returned: I(state=present) + sample: + vpc_id: "vpc-XXXXXXXX" +''' + +try: + import botocore +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry +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 ensure_ec2_tags +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list +from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict + +@AWSRetry.jittered_backoff(retries=10, delay=10) +def describe_cagws_with_backoff(connection, **params): + paginator = connection.get_paginator('describe_carrier_gateways') + return paginator.paginate(**params).build_full_result()['CarrierGateways'] + + +class AnsibleEc2Cagw(): + + def __init__(self, module, results): + self._module = module + self._results = results + 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_cagw_present(vpc_id, tags, purge_tags) + elif state == 'absent': + self.ensure_cagw_absent(vpc_id) + + def get_matching_cagw(self, vpc_id, carrier_gateway_id=None): + ''' + Returns the carrier gateway found. + Parameters: + vpc_id (str): VPC ID + carrier_gateway_id (str): Carrier Gateway ID, if specified + Returns: + cagw (dict): dict of cagw found, None if none found + ''' + filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id}) + try: + if not carrier_gateway_id: + cagws = describe_cagws_with_backoff(self._connection, Filters=filters) + else: + cagws = describe_cagws_with_backoff(self._connection, CarrierGatewayIds=[carrier_gateway_id]) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + self._module.fail_json_aws(e) + + cagw = None + if len(cagws) > 1: + self._module.fail_json( + msg='EC2 returned more than one Carrier Gateway for VPC {0}, aborting' + .format(vpc_id)) + elif cagws: + cagw = camel_dict_to_snake_dict(cagws[0]) + + return cagw + + @staticmethod + def get_cagw_info(cagw, vpc_id): + return { + 'carrier_gateway_id': cagw['carrier_gateway_id'], + 'tags': boto3_tag_list_to_ansible_dict(cagw['tags']), + 'vpc_id': vpc_id + } + + def ensure_cagw_absent(self, vpc_id): + cagw = self.get_matching_cagw(vpc_id) + if cagw is None: + return self._results + + if self._check_mode: + self._results['changed'] = True + return self._results + + try: + self._results['changed'] = True + self._connection.delete_carrier_gateway( + aws_retry=True, + CarrierGatewayId=cagw['carrier_gateway_id'] + ) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + self._module.fail_json_aws(e, msg="Unable to delete Carrier Gateway") + + return self._results + + def ensure_cagw_present(self, vpc_id, tags, purge_tags): + cagw = self.get_matching_cagw(vpc_id) + + if cagw is None: + if self._check_mode: + self._results['changed'] = True + self._results['carrier_gateway_id'] = None + return self._results + + try: + response = self._connection.create_carrier_gateway(VpcId=vpc_id, aws_retry=True) + cagw = camel_dict_to_snake_dict(response['CarrierGateway']) + self._results['changed'] = True + except botocore.exceptions.WaiterError as e: + self._module.fail_json_aws(e, msg="No Carrier Gateway exists.") + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + self._module.fail_json_aws(e, msg='Unable to create Carrier Gateway') + + # Modify tags + self._results['changed'] |= ensure_ec2_tags( + self._connection, self._module, cagw['carrier_gateway_id'], + resource_type='carrier-gateway', tags=tags, purge_tags=purge_tags, + retry_codes='InvalidCarrierGatewayID.NotFound' + ) + + # Update cagw + cagw = self.get_matching_cagw(vpc_id, carrier_gateway_id=cagw['carrier_gateway_id']) + cagw_info = self.get_cagw_info(cagw, vpc_id) + self._results.update(cagw_info) + + return self._results + + +def main(): + argument_spec = dict( + carrier_gateway_id=dict(required=False), + vpc_id=dict(required=False), + state=dict(default='present', choices=['present', 'absent']), + tags=dict(required=False, type='dict', aliases=['resource_tags']), + purge_tags=dict(default=True, type='bool'), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + results = dict( + changed=False + ) + cagw_manager = AnsibleEc2Cagw(module=module, results=results) + cagw_manager.process() + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/ec2_vpc_cagw_info.py b/plugins/modules/ec2_vpc_cagw_info.py new file mode 100644 index 00000000000..4ad4feba0f1 --- /dev/null +++ b/plugins/modules/ec2_vpc_cagw_info.py @@ -0,0 +1,166 @@ +#!/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 + + +DOCUMENTATION = r''' +--- +module: ec2_vpc_cagw_info +version_added: 1.0.0 +short_description: Gather information about carrier gateways in AWS +description: + - Gather information about carrier gateways in AWS. +author: "Marco Braga (@mtulio)" +options: + 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_DescribeCarrierGateways.html) for possible filters. + type: dict + carrier_gateway_ids: + description: + - Get details of specific Carrier 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. + - Prior to release 4.0.0 this defaulted to C(False). + default: True + type: bool + version_added: 1.3.0 +extends_documentation_fragment: +- amazon.aws.aws +- amazon.aws.ec2 + +''' + +EXAMPLES = r''' +# # Note: These examples do not set authentication details, see the AWS Guide for details. + +- name: Gather information about all Carrier Gateways for an account or profile + amazon.aws.ec2_vpc_cagw_info: + region: ap-southeast-2 + profile: production + register: cagw_info + +- name: Gather information about a filtered list of Carrier Gateways + amazon.aws.ec2_vpc_cagw_info: + region: ap-southeast-2 + profile: production + filters: + "tag:Name": "cagw-123" + register: cagw_info + +- name: Gather information about a specific carrier gateway by CarrierGatewayId + amazon.aws.ec2_vpc_cagw_info: + region: ap-southeast-2 + profile: production + carrier_gateway_ids: cagw-c1231234 + register: cagw_info +''' + +RETURN = r''' +changed: + description: True if listing the carrier gateways succeeds. + type: bool + returned: always + sample: "false" +carrier_gateways: + description: The carrier gateways for the account. + returned: always + type: complex + contains: + vpc_id: + description: The ID of the VPC. + returned: I(state=present) + type: str + sample: vpc-02123b67 + carrier_gateway_id: + description: The ID of the carrier gateway. + returned: I(state=present) + type: str + sample: cagw-2123634d + tags: + description: Any tags assigned to the carrier gateway. + returned: I(state=present) + type: dict + sample: + tags: + "Ansible": "Test" +''' + +try: + import botocore +except ImportError: + pass # Handled by AnsibleAWSModule + +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 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_carrier_gateway_info(carrier_gateway, convert_tags): + if convert_tags: + tags = boto3_tag_list_to_ansible_dict(carrier_gateway['Tags']) + ignore_list = ["Tags"] + else: + tags = carrier_gateway['Tags'] + ignore_list = [] + carrier_gateway_info = {'CarrierGatewayId': carrier_gateway['CarrierGatewayId'], + 'VpcId': carrier_gateway['VpcId'], + 'Tags': tags} + + carrier_gateway_info = camel_dict_to_snake_dict(carrier_gateway_info, ignore_list=ignore_list) + return carrier_gateway_info + + +def list_carrier_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("carrier_gateway_ids"): + params['CarrierGatewayIds'] = module.params.get("carrier_gateway_ids") + + try: + all_carrier_gateways = connection.describe_carrier_gateways(aws_retry=True, **params) + except is_boto3_error_code('InvalidCarrierGatewayID.NotFound'): + module.fail_json('CarrierGateway not found') + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, 'Unable to describe carrier gateways') + + return [get_carrier_gateway_info(cagw, convert_tags) + for cagw in all_carrier_gateways['CarrierGateways']] + + +def main(): + argument_spec = dict( + filters=dict(type='dict', default=dict()), + carrier_gateway_ids=dict(type='list', default=None, elements='str'), + convert_tags=dict(type='bool', default=True), + ) + + module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) + + # Validate Requirements + try: + 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') + + # call your function here + results = list_carrier_gateways(connection, module) + + module.exit_json(carrier_gateways=results) + + +if __name__ == '__main__': + main()