Repository: getmoto/moto

Issue:
Issue with VPCGatewayAttachment in mock_cloudformation
As soon as add "AWS::EC2::VPCGatewayAttachment", I got exception, with "KeyError: u'InternetGatewayId'"

```
    "DEVLAB1DCGateway": {
      "Type": "AWS::EC2::VPNGateway",
      "Properties": {
        "Type": "ipsec.1",
        "Tags": [
          {
            "Value": "DEVLAB1_VPG",
            "Key": "Name"
          }
        ]
      }
    },
    "DEVLAB1VPGAttaching": {
      "Type": "AWS::EC2::VPCGatewayAttachment",
      "Properties": {
        "VpcId": {
          "Ref": "DEVLAB1"
        },
        "VpnGatewayId": {
          "Ref": "DEVLAB1DCGateway"
        }
      }
    },
```



Discussion:
It looks like we don't currently support creating VPCGatewayAttachments with VpnGatewayIds so InternetGatewayId is required. We will need to add support for VpnGatewayIds to fix this.


Resolving patch:
diff --git a/moto/ec2/models.py b/moto/ec2/models.py
--- a/moto/ec2/models.py
+++ b/moto/ec2/models.py
@@ -5750,7 +5750,7 @@ def attach_internet_gateway(self, internet_gateway_id, vpc_id):
             raise ResourceAlreadyAssociatedError(internet_gateway_id)
         vpc = self.get_vpc(vpc_id)
         igw.vpc = vpc
-        return True
+        return VPCGatewayAttachment(gateway_id=internet_gateway_id, vpc_id=vpc_id)
 
     def get_internet_gateway(self, internet_gateway_id):
         igw_ids = [internet_gateway_id]
@@ -5890,12 +5890,16 @@ def create_from_cloudformation_json(
         properties = cloudformation_json["Properties"]
 
         ec2_backend = ec2_backends[region_name]
-        attachment = ec2_backend.create_vpc_gateway_attachment(
-            gateway_id=properties["InternetGatewayId"], vpc_id=properties["VpcId"]
-        )
-        ec2_backend.attach_internet_gateway(
-            properties["InternetGatewayId"], properties["VpcId"]
-        )
+        vpn_gateway_id = properties.get("VpnGatewayId", None)
+        internet_gateway_id = properties.get("InternetGatewayId", None)
+        if vpn_gateway_id:
+            attachment = ec2_backend.attach_vpn_gateway(
+                vpc_id=properties["VpcId"], vpn_gateway_id=vpn_gateway_id
+            )
+        elif internet_gateway_id:
+            attachment = ec2_backend.attach_internet_gateway(
+                internet_gateway_id=internet_gateway_id, vpc_id=properties["VpcId"]
+            )
         return attachment
 
     @property
@@ -7106,7 +7110,7 @@ def __init__(
         self.port_range_to = port_range_to
 
 
-class VpnGateway(TaggedEC2Resource):
+class VpnGateway(CloudFormationModel, TaggedEC2Resource):
     def __init__(
         self,
         ec2_backend,
@@ -7127,6 +7131,30 @@ def __init__(
         self.attachments = {}
         super().__init__()
 
+    @staticmethod
+    def cloudformation_name_type():
+        return None
+
+    @staticmethod
+    def cloudformation_type():
+        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcgatewayattachment.html
+        return "AWS::EC2::VPNGateway"
+
+    @classmethod
+    def create_from_cloudformation_json(
+        cls, resource_name, cloudformation_json, region_name
+    ):
+        properties = cloudformation_json["Properties"]
+        _type = properties["Type"]
+        asn = properties.get("AmazonSideAsn", None)
+        ec2_backend = ec2_backends[region_name]
+
+        return ec2_backend.create_vpn_gateway(type=_type, amazon_side_asn=asn)
+
+    @property
+    def physical_resource_id(self):
+        return self.id
+
     def get_filter_value(self, filter_name):
         if filter_name == "attachment.vpc-id":
             return self.attachments.keys()
@@ -8444,7 +8472,6 @@ class EC2Backend(
     RouteBackend,
     InternetGatewayBackend,
     EgressOnlyInternetGatewayBackend,
-    VPCGatewayAttachmentBackend,
     SpotFleetBackend,
     SpotRequestBackend,
     SpotPriceBackend,


Test patch:
diff --git a/tests/test_ec2/test_ec2_cloudformation.py b/tests/test_ec2/test_ec2_cloudformation.py
--- a/tests/test_ec2/test_ec2_cloudformation.py
+++ b/tests/test_ec2/test_ec2_cloudformation.py
@@ -215,37 +215,96 @@ def test_volume_size_through_cloudformation():
     volumes["Volumes"][0]["Size"].should.equal(50)
 
 
-# Has boto3 equivalent
-@mock_ec2_deprecated
-@mock_cloudformation_deprecated
-def test_subnet_tags_through_cloudformation():
-    vpc_conn = boto.vpc.connect_to_region("us-west-1")
-    vpc = vpc_conn.create_vpc("10.0.0.0/16")
+@mock_ec2
+@mock_cloudformation
+def test_attach_internet_gateway():
+    ec2 = boto3.client("ec2", region_name="us-east-1")
+    cf = boto3.client("cloudformation", region_name="us-east-1")
 
-    subnet_template = {
+    volume_template = {
         "AWSTemplateFormatVersion": "2010-09-09",
         "Resources": {
-            "testSubnet": {
-                "Type": "AWS::EC2::Subnet",
+            "DEVLAB1": {
+                "Type": "AWS::EC2::VPC",
+                "Properties": {"CidrBlock": "10.0.0.0/16"},
+            },
+            "internetgateway": {"Type": "AWS::EC2::InternetGateway"},
+            "DEVLAB1VPGAttaching": {
+                "Type": "AWS::EC2::VPCGatewayAttachment",
                 "Properties": {
-                    "VpcId": vpc.id,
-                    "CidrBlock": "10.0.0.0/24",
-                    "AvailabilityZone": "us-west-1b",
-                    "Tags": [
-                        {"Key": "foo", "Value": "bar"},
-                        {"Key": "blah", "Value": "baz"},
-                    ],
+                    "VpcId": {"Ref": "DEVLAB1"},
+                    "InternetGatewayId": {"Ref": "internetgateway"},
                 },
-            }
+            },
         },
     }
-    cf_conn = boto.cloudformation.connect_to_region("us-west-1")
-    template_json = json.dumps(subnet_template)
-    cf_conn.create_stack("test_stack", template_body=template_json)
+    template_json = json.dumps(volume_template)
+    stack_name = str(uuid4())[0:6]
+    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
+    stack_resources = cf.list_stack_resources(StackName=stack_name)[
+        "StackResourceSummaries"
+    ]
+
+    # Verify VPC is created
+    vpc = [r for r in stack_resources if r["ResourceType"] == "AWS::EC2::VPC"][0]
+    vpc["LogicalResourceId"].should.equal("DEVLAB1")
+    vpc_id = vpc["PhysicalResourceId"]
+
+    # Verify Internet Gateway is created
+    gateway_id = get_resource_id("AWS::EC2::InternetGateway", stack_resources)
+    gateway = ec2.describe_internet_gateways(InternetGatewayIds=[gateway_id])[
+        "InternetGateways"
+    ][0]
+    gateway["Attachments"].should.contain({"State": "available", "VpcId": vpc_id})
+    gateway["Tags"].should.contain(
+        {"Key": "aws:cloudformation:logical-id", "Value": "internetgateway"}
+    )
+
+
+@mock_ec2
+@mock_cloudformation
+def test_attach_vpn_gateway():
+    ec2 = boto3.client("ec2", region_name="us-east-1")
+    cf = boto3.client("cloudformation", region_name="us-east-1")
+
+    vpn_gateway_template = {
+        "AWSTemplateFormatVersion": "2010-09-09",
+        "Resources": {
+            "DEVLAB1": {
+                "Type": "AWS::EC2::VPC",
+                "Properties": {"CidrBlock": "10.0.0.0/16"},
+            },
+            "DEVLAB1DCGateway": {
+                "Type": "AWS::EC2::VPNGateway",
+                "Properties": {"Type": "ipsec.1",},
+            },
+            "DEVLAB1VPGAttaching": {
+                "Type": "AWS::EC2::VPCGatewayAttachment",
+                "Properties": {
+                    "VpcId": {"Ref": "DEVLAB1"},
+                    "VpnGatewayId": {"Ref": "DEVLAB1DCGateway"},
+                },
+                "DependsOn": ["DEVLAB1DCGateway"],
+            },
+        },
+    }
+    template_json = json.dumps(vpn_gateway_template)
+    stack_name = str(uuid4())[0:6]
+    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
+    stack_resources = cf.list_stack_resources(StackName=stack_name)[
+        "StackResourceSummaries"
+    ]
+
+    gateway_id = get_resource_id("AWS::EC2::VPNGateway", stack_resources)
+    vpc_id = get_resource_id("AWS::EC2::VPC", stack_resources)
+
+    gateway = ec2.describe_vpn_gateways(VpnGatewayIds=[gateway_id])["VpnGateways"][0]
+    gateway["VpcAttachments"].should.contain({"State": "attached", "VpcId": vpc_id})
+
 
-    subnet = vpc_conn.get_all_subnets(filters={"cidrBlock": "10.0.0.0/24"})[0]
-    subnet.tags["foo"].should.equal("bar")
-    subnet.tags["blah"].should.equal("baz")
+def get_resource_id(resource_type, stack_resources):
+    r = [r for r in stack_resources if r["ResourceType"] == resource_type][0]
+    return r["PhysicalResourceId"]
 
 
 @mock_ec2