From 911d29cc338a5a325b1eb69a273c6359e7bfc67a Mon Sep 17 00:00:00 2001 From: Mohit Alonja Date: Tue, 3 Aug 2021 12:28:25 +0530 Subject: [PATCH] Added support for tgw peering attachment (#4115) * Added required params to run the terraform test --- .github/workflows/build.yml | 2 +- moto/ec2/models.py | 120 +++++++++++++++- .../responses/transit_gateway_attachments.py | 133 +++++++++++++++++- moto/ec2/responses/vpn_connections.py | 3 + moto/ec2/utils.py | 23 +++ tests/terraform-tests.failures.txt | 2 - tests/terraform-tests.success.txt | 2 + 7 files changed, 278 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46500aefb..40b093ee4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -230,7 +230,7 @@ jobs: - name: Run Terraform Tests run: | cd moto-terraform-tests - bin/run-tests -i ../tests/terraform-tests.success.txt -e ../tests/terraform-tests.failures.txt + AWS_DEFAULT_REGION=us-east-1 AWS_ALTERNATE_REGION=eu-west-1 bin/run-tests -i ../tests/terraform-tests.success.txt -e ../tests/terraform-tests.failures.txt cd .. - name: "Create report" run: | diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 88be042d9..920825bc1 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -170,6 +170,7 @@ from .utils import ( tag_filter_matches, rsa_public_key_parse, rsa_public_key_fingerprint, + describe_tag_filter, ) @@ -3355,7 +3356,7 @@ class VPCBackend(object): } -class VPCPeeringConnectionStatus(object): +class PeeringConnectionStatus(object): def __init__(self, code="initiating-request", message=""): self.code = code self.message = message @@ -3386,7 +3387,7 @@ class VPCPeeringConnection(TaggedEC2Resource, CloudFormationModel): self.id = vpc_pcx_id self.vpc = vpc self.peer_vpc = peer_vpc - self._status = VPCPeeringConnectionStatus() + self._status = PeeringConnectionStatus() @staticmethod def cloudformation_name_type(): @@ -6332,7 +6333,7 @@ class TransitGatewayAttachment(TaggedEC2Resource): self.add_tags(tags or {}) self._created_at = datetime.utcnow() - self.owner_id = ACCOUNT_ID + self.owner_id = self.resource_owner_id @property def create_time(self): @@ -6372,6 +6373,43 @@ class TransitGatewayVpcAttachment(TransitGatewayAttachment): self.options = merge_multiple_dicts(self.DEFAULT_OPTIONS, options or {}) +class TransitGatewayPeeringAttachment(TransitGatewayAttachment): + def __init__( + self, + backend, + transit_gateway_id=None, + peer_transit_gateway_id=None, + peer_region=None, + peer_account_id=None, + tags=None, + region_name=None, + ): + + super().__init__( + backend=backend, + transit_gateway_id=transit_gateway_id, + resource_id=peer_transit_gateway_id, + resource_type="peering", + tags=tags, + ) + + self.accepter_tgw_info = { + "ownerId": peer_account_id, + "region": peer_region, + "transitGatewayId": peer_transit_gateway_id, + } + self.requester_tgw_info = { + "ownerId": self.owner_id, + "region": region_name, + "transitGatewayId": transit_gateway_id, + } + self.status = PeeringConnectionStatus() + + @property + def resource_owner_id(self): + return ACCOUNT_ID + + class TransitGatewayAttachmentBackend(object): def __init__(self): self.transit_gateway_attachments = {} @@ -6512,6 +6550,82 @@ class TransitGatewayAttachmentBackend(object): "state" ] = "disabled" + def create_transit_gateway_peering_attachment( + self, + transit_gateway_id, + peer_transit_gateway_id, + peer_region, + peer_account_id, + tags, + ): + transit_gateway_peering_attachment = TransitGatewayPeeringAttachment( + self, + transit_gateway_id=transit_gateway_id, + peer_transit_gateway_id=peer_transit_gateway_id, + peer_region=peer_region, + peer_account_id=peer_account_id, + tags=tags, + region_name=self.region_name, + ) + transit_gateway_peering_attachment.status.accept() + transit_gateway_peering_attachment.state = "available" + self.transit_gateway_attachments[ + transit_gateway_peering_attachment.id + ] = transit_gateway_peering_attachment + return transit_gateway_peering_attachment + + def describe_transit_gateway_peering_attachments( + self, transit_gateways_attachment_ids=None, filters=None, max_results=0 + ): + transit_gateway_attachments = list(self.transit_gateway_attachments.values()) + + attr_pairs = ( + ("state", "state"), + ("transit-gateway-attachment-id", "id"), + ("local-owner-id", "requester_tgw_info", "ownerId"), + ("remote-owner-id", "accepter_tgw_info", "ownerId"), + ) + + if transit_gateways_attachment_ids: + transit_gateway_attachments = [ + transit_gateways_attachment + for transit_gateways_attachment in transit_gateway_attachments + if transit_gateways_attachment.id in transit_gateways_attachment_ids + ] + + if filters: + transit_gateway_attachments = filter_resources( + transit_gateway_attachments, filters, attr_pairs + ) + transit_gateway_attachments = describe_tag_filter( + filters, transit_gateway_attachments + ) + return transit_gateway_attachments + + def accept_transit_gateway_peering_attachment(self, transit_gateway_attachment_id): + transit_gateway_attachment = self.transit_gateway_attachments[ + transit_gateway_attachment_id + ] + transit_gateway_attachment.state = "available" + transit_gateway_attachment.status.accept() + return transit_gateway_attachment + + def reject_transit_gateway_peering_attachment(self, transit_gateway_attachment_id): + transit_gateway_attachment = self.transit_gateway_attachments[ + transit_gateway_attachment_id + ] + transit_gateway_attachment.state = "rejected" + transit_gateway_attachment.status.reject() + return transit_gateway_attachment + + def delete_transit_gateway_peering_attachment(self, transit_gateway_attachment_id): + transit_gateway_attachment = self.transit_gateway_attachments[ + transit_gateway_attachment_id + ] + transit_gateway_attachment.state = "deleted" + transit_gateway_attachment.status.deleted() + return transit_gateway_attachment + class TransitGatewayRelations(object): # this class is for TransitGatewayAssociation and TransitGatewayPropagation diff --git a/moto/ec2/responses/transit_gateway_attachments.py b/moto/ec2/responses/transit_gateway_attachments.py index c323499f0..0b877c8c9 100644 --- a/moto/ec2/responses/transit_gateway_attachments.py +++ b/moto/ec2/responses/transit_gateway_attachments.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.ec2.utils import filters_from_querystring +from moto.ec2.utils import filters_from_querystring, add_tag_specification class TransitGatewayAttachment(BaseResponse): @@ -108,6 +108,74 @@ class TransitGatewayAttachment(BaseResponse): template = self.response_template(TRANSIT_GATEWAY_PROPAGATION) return template.render(transit_gateway_propagation=transit_gateway_propagation) + def create_transit_gateway_peering_attachment(self): + peer_account_id = self._get_param("PeerAccountId") + peer_region = self._get_param("PeerRegion") + peer_transit_gateway_id = self._get_param("PeerTransitGatewayId") + transit_gateway_id = self._get_param("TransitGatewayId") + tags = add_tag_specification(self._get_multi_param("TagSpecification")) + transit_gateway_peering_attachment = self.ec2_backend.create_transit_gateway_peering_attachment( + transit_gateway_id, + peer_transit_gateway_id, + peer_region, + peer_account_id, + tags, + ) + template = self.response_template(TRANSIT_GATEWAY_PEERING_ATTACHMENT) + return template.render( + method_name="CreateTransitGatewayPeeringAttachment", + transit_gateway_peering_attachment=transit_gateway_peering_attachment, + ) + + def describe_transit_gateway_peering_attachments(self): + transit_gateways_attachment_ids = self._get_multi_param( + "TransitGatewayAttachmentIds" + ) + filters = filters_from_querystring(self.querystring) + max_results = self._get_param("MaxResults") + transit_gateway_peering_attachments = self.ec2_backend.describe_transit_gateway_peering_attachments( + transit_gateways_attachment_ids=transit_gateways_attachment_ids, + filters=filters, + max_results=max_results, + ) + template = self.response_template(DESCRIBE_TRANSIT_GATEWAY_PEERING_ATTACHMENTS) + return template.render( + transit_gateway_peering_attachments=transit_gateway_peering_attachments + ) + + def accept_transit_gateway_peering_attachment(self): + transit_gateway_attachment_id = self._get_param("TransitGatewayAttachmentId") + transit_gateway_peering_attachment = self.ec2_backend.accept_transit_gateway_peering_attachment( + transit_gateway_attachment_id=transit_gateway_attachment_id + ) + template = self.response_template(TRANSIT_GATEWAY_PEERING_ATTACHMENT) + return template.render( + method_name="AcceptTransitGatewayPeeringAttachment", + transit_gateway_peering_attachment=transit_gateway_peering_attachment, + ) + + def delete_transit_gateway_peering_attachment(self): + transit_gateway_attachment_id = self._get_param("TransitGatewayAttachmentId") + transit_gateway_peering_attachment = self.ec2_backend.delete_transit_gateway_peering_attachment( + transit_gateway_attachment_id=transit_gateway_attachment_id + ) + template = self.response_template(TRANSIT_GATEWAY_PEERING_ATTACHMENT) + return template.render( + method_name="DeleteTransitGatewayPeeringAttachment", + transit_gateway_peering_attachment=transit_gateway_peering_attachment, + ) + + def reject_transit_gateway_peering_attachment(self): + transit_gateway_attachment_id = self._get_param("TransitGatewayAttachmentId") + transit_gateway_peering_attachment = self.ec2_backend.reject_transit_gateway_peering_attachment( + transit_gateway_attachment_id=transit_gateway_attachment_id + ) + template = self.response_template(TRANSIT_GATEWAY_PEERING_ATTACHMENT) + return template.render( + method_name="RejectTransitGatewayPeeringAttachment", + transit_gateway_peering_attachment=transit_gateway_peering_attachment, + ) + CREATE_TRANSIT_GATEWAY_VPC_ATTACHMENT = """ 9b5766ac-2af6-4b92-9a8a-4d74ae46ae79 @@ -294,3 +362,66 @@ TRANSIT_GATEWAY_PROPAGATION = """ """ + + +TRANSIT_GATEWAY_PEERING_ATTACHMENT = """<{{ method_name }} xmlns="http://ec2.amazonaws.com/doc/2016-11-15/"> + 9b5766ac-2af6-4b92-9a8a-4d74ae46ae79 + + {{ transit_gateway_peering_attachment.create_time }} + {{ transit_gateway_peering_attachment.state }} + + {{ transit_gateway_peering_attachment.accepter_tgw_info.ownerId or '' }} + {{ transit_gateway_peering_attachment.accepter_tgw_info.region or '' }} + {{ transit_gateway_peering_attachment.accepter_tgw_info.transitGatewayId or '' }} + + + {{ transit_gateway_peering_attachment.requester_tgw_info.ownerId or '' }} + {{ transit_gateway_peering_attachment.requester_tgw_info.region or '' }} + {{ transit_gateway_peering_attachment.requester_tgw_info.transitGatewayId or '' }} + + {{ transit_gateway_peering_attachment.status.code }} + + {% for tag in transit_gateway_peering_attachment.get_tags() %} + + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + {{ transit_gateway_peering_attachment.id }} + +""" + + +DESCRIBE_TRANSIT_GATEWAY_PEERING_ATTACHMENTS = """ + bebc9670-0205-4f28-ad89-049c97e46633 + + {% for transit_gateway_peering_attachment in transit_gateway_peering_attachments %} + + {{ transit_gateway_peering_attachment.create_time }} + {{ transit_gateway_peering_attachment.state }} + + {{ transit_gateway_peering_attachment.accepter_tgw_info.ownerId or '' }} + {{ transit_gateway_peering_attachment.accepter_tgw_info.region or '' }} + {{ transit_gateway_peering_attachment.accepter_tgw_info.transitGatewayId or '' }} + + + {{ transit_gateway_peering_attachment.requester_tgw_info.ownerId or '' }} + {{ transit_gateway_peering_attachment.requester_tgw_info.region or '' }} + {{ transit_gateway_peering_attachment.requester_tgw_info.transitGatewayId or '' }} + + {{ transit_gateway_peering_attachment.status.code }} + + {% for tag in transit_gateway_peering_attachment.get_tags() %} + + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + {{ transit_gateway_peering_attachment.id }} + + {% endfor %} + + +""" diff --git a/moto/ec2/responses/vpn_connections.py b/moto/ec2/responses/vpn_connections.py index 5cc3a5e85..a5187de6a 100644 --- a/moto/ec2/responses/vpn_connections.py +++ b/moto/ec2/responses/vpn_connections.py @@ -201,18 +201,21 @@ CREATE_VPN_CONNECTION_ROUTE_RESPONSE = """ true """ + DELETE_VPN_CONNECTION_RESPONSE = """ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE true """ + DELETE_VPN_CONNECTION_ROUTE_RESPONSE = """ 4f35a1b2-c2c3-4093-b51f-abb9d7311990 true """ + DESCRIBE_VPN_CONNECTION_RESPONSE = ( """ diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 284220e05..62d70f1d2 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -678,3 +678,26 @@ def filter_iam_instance_profiles(iam_instance_profile_arn, iam_instance_profile_ instance_profile = None return instance_profile + + +def describe_tag_filter(filters, instances): + result = instances.copy() + for instance in instances: + for key in filters: + if key.startswith("tag:"): + match = re.match(r"tag:(.*)", key) + if match: + tag_key_name = match.group(1) + need_delete = True + for tag in instance.get_tags(): + if tag.get("key") == tag_key_name and tag.get( + "value" + ) in filters.get(key): + need_delete = False + elif tag.get("key") == tag_key_name and tag.get( + "value" + ) not in filters.get(key): + need_delete = True + if need_delete: + result.remove(instance) + return result diff --git a/tests/terraform-tests.failures.txt b/tests/terraform-tests.failures.txt index 73a4783f7..e784b8ceb 100644 --- a/tests/terraform-tests.failures.txt +++ b/tests/terraform-tests.failures.txt @@ -1,7 +1,5 @@ TestAccAWSEc2TransitGatewayDxGatewayAttachmentDataSource -TestAccAWSEc2TransitGatewayPeeringAttachment TestAccAWSEc2TransitGatewayPeeringAttachmentAccepter -TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource TestAccAWSEc2TransitGatewayRouteTableAssociation TestAccAWSEc2TransitGatewayVpcAttachment TestAccAWSFms diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt index f32629ca2..83bfd5c7b 100644 --- a/tests/terraform-tests.success.txt +++ b/tests/terraform-tests.success.txt @@ -39,6 +39,8 @@ TestAccAWSEc2TransitGatewayVpcAttachment_SharedTransitGateway TestAccAWSEc2TransitGatewayVpcAttachmentAccepter TestAccAWSEc2TransitGatewayVpcAttachmentDataSource TestAccAWSEc2TransitGatewayVpnAttachmentDataSource +TestAccAWSEc2TransitGatewayPeeringAttachment +TestAccAWSEc2TransitGatewayPeeringAttachmentDataSource TestAccAWSElasticBeanstalkSolutionStackDataSource TestAccAWSElbHostedZoneId TestAccAWSElbServiceAccount