diff --git a/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py b/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py new file mode 100644 index 0000000000000..8308fb5bfa990 --- /dev/null +++ b/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py @@ -0,0 +1,167 @@ +# LocalStack Resource Provider Scaffolding v2 +from __future__ import annotations + +from pathlib import Path +from typing import Optional, TypedDict + +import localstack.services.cloudformation.provider_utils as util +from localstack.services.cloudformation.resource_provider import ( + OperationStatus, + ProgressEvent, + ResourceProvider, + ResourceRequest, +) + + +class EC2PrefixListProperties(TypedDict): + AddressFamily: Optional[str] + MaxEntries: Optional[int] + PrefixListName: Optional[str] + Arn: Optional[str] + Entries: Optional[list[Entry]] + OwnerId: Optional[str] + PrefixListId: Optional[str] + Tags: Optional[list[Tag]] + Version: Optional[int] + + +class Tag(TypedDict): + Key: Optional[str] + Value: Optional[str] + + +class Entry(TypedDict): + Cidr: Optional[str] + Description: Optional[str] + + +REPEATED_INVOCATION = "repeated_invocation" + + +class EC2PrefixListProvider(ResourceProvider[EC2PrefixListProperties]): + TYPE = "AWS::EC2::PrefixList" # Autogenerated. Don't change + SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change + + def create( + self, + request: ResourceRequest[EC2PrefixListProperties], + ) -> ProgressEvent[EC2PrefixListProperties]: + """ + Create a new resource. + + Primary identifier fields: + - /properties/PrefixListId + + Required properties: + - PrefixListName + - MaxEntries + - AddressFamily + + + + Read-only properties: + - /properties/PrefixListId + - /properties/OwnerId + - /properties/Version + - /properties/Arn + + IAM permissions required: + - EC2:CreateManagedPrefixList + - EC2:DescribeManagedPrefixLists + - EC2:CreateTags + + """ + model = request.desired_state + + if not request.custom_context.get(REPEATED_INVOCATION): + create_params = util.select_attributes( + model, ["PrefixListName", "Entries", "MaxEntries", "AddressFamily", "Tags"] + ) + + if "Tags" in create_params: + create_params["TagSpecifications"] = [ + {"ResourceType": "prefix-list", "Tags": create_params.pop("Tags")} + ] + + response = request.aws_client_factory.ec2.create_managed_prefix_list(**create_params) + model["Arn"] = response["PrefixList"]["PrefixListId"] + model["OwnerId"] = response["PrefixList"]["OwnerId"] + model["PrefixListId"] = response["PrefixList"]["PrefixListId"] + model["Version"] = response["PrefixList"]["Version"] + request.custom_context[REPEATED_INVOCATION] = True + return ProgressEvent( + status=OperationStatus.IN_PROGRESS, + resource_model=model, + custom_context=request.custom_context, + ) + + response = request.aws_client_factory.ec2.describe_managed_prefix_lists( + PrefixListIds=[model["PrefixListId"]] + ) + if not response["PrefixLists"]: + return ProgressEvent( + status=OperationStatus.FAILED, + resource_model=model, + custom_context=request.custom_context, + message="Resource not found after creation", + ) + + return ProgressEvent( + status=OperationStatus.SUCCESS, + resource_model=model, + custom_context=request.custom_context, + ) + + def read( + self, + request: ResourceRequest[EC2PrefixListProperties], + ) -> ProgressEvent[EC2PrefixListProperties]: + """ + Fetch resource information + + IAM permissions required: + - EC2:GetManagedPrefixListEntries + - EC2:DescribeManagedPrefixLists + """ + raise NotImplementedError + + def delete( + self, + request: ResourceRequest[EC2PrefixListProperties], + ) -> ProgressEvent[EC2PrefixListProperties]: + """ + Delete a resource + + IAM permissions required: + - EC2:DeleteManagedPrefixList + - EC2:DescribeManagedPrefixLists + """ + + model = request.previous_state + response = request.aws_client_factory.ec2.describe_managed_prefix_lists( + PrefixListIds=[model["PrefixListId"]] + ) + + if not response["PrefixLists"]: + return ProgressEvent(status=OperationStatus.SUCCESS, resource_model=model) + + request.aws_client_factory.ec2.delete_managed_prefix_list( + PrefixListId=request.previous_state["PrefixListId"] + ) + return ProgressEvent(status=OperationStatus.IN_PROGRESS, resource_model=model) + + def update( + self, + request: ResourceRequest[EC2PrefixListProperties], + ) -> ProgressEvent[EC2PrefixListProperties]: + """ + Update a resource + + IAM permissions required: + - EC2:DescribeManagedPrefixLists + - EC2:GetManagedPrefixListEntries + - EC2:ModifyManagedPrefixList + - EC2:CreateTags + - EC2:DeleteTags + """ + raise NotImplementedError diff --git a/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.schema.json b/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.schema.json new file mode 100644 index 0000000000000..cb27aefee2bd3 --- /dev/null +++ b/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.schema.json @@ -0,0 +1,152 @@ +{ + "typeName": "AWS::EC2::PrefixList", + "description": "Resource schema of AWS::EC2::PrefixList Type", + "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", + "definitions": { + "Tag": { + "type": "object", + "properties": { + "Key": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "Value": { + "type": "string", + "maxLength": 256 + } + }, + "required": [ + "Key" + ], + "additionalProperties": false + }, + "Entry": { + "type": "object", + "properties": { + "Cidr": { + "type": "string", + "minLength": 1, + "maxLength": 46 + }, + "Description": { + "type": "string", + "minLength": 0, + "maxLength": 255 + } + }, + "required": [ + "Cidr" + ], + "additionalProperties": false + } + }, + "properties": { + "PrefixListName": { + "description": "Name of Prefix List.", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "PrefixListId": { + "description": "Id of Prefix List.", + "type": "string" + }, + "OwnerId": { + "description": "Owner Id of Prefix List.", + "type": "string" + }, + "AddressFamily": { + "description": "Ip Version of Prefix List.", + "type": "string", + "enum": [ + "IPv4", + "IPv6" + ] + }, + "MaxEntries": { + "description": "Max Entries of Prefix List.", + "type": "integer", + "minimum": 1 + }, + "Version": { + "description": "Version of Prefix List.", + "type": "integer" + }, + "Tags": { + "description": "Tags for Prefix List", + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + } + }, + "Entries": { + "description": "Entries of Prefix List.", + "type": "array", + "items": { + "$ref": "#/definitions/Entry" + } + }, + "Arn": { + "description": "The Amazon Resource Name (ARN) of the Prefix List.", + "type": "string" + } + }, + "required": [ + "PrefixListName", + "MaxEntries", + "AddressFamily" + ], + "readOnlyProperties": [ + "/properties/PrefixListId", + "/properties/OwnerId", + "/properties/Version", + "/properties/Arn" + ], + "primaryIdentifier": [ + "/properties/PrefixListId" + ], + "tagging": { + "taggable": true, + "tagOnCreate": true, + "tagUpdatable": true, + "cloudFormationSystemTags": true + }, + "handlers": { + "create": { + "permissions": [ + "EC2:CreateManagedPrefixList", + "EC2:DescribeManagedPrefixLists", + "EC2:CreateTags" + ] + }, + "read": { + "permissions": [ + "EC2:GetManagedPrefixListEntries", + "EC2:DescribeManagedPrefixLists" + ] + }, + "update": { + "permissions": [ + "EC2:DescribeManagedPrefixLists", + "EC2:GetManagedPrefixListEntries", + "EC2:ModifyManagedPrefixList", + "EC2:CreateTags", + "EC2:DeleteTags" + ] + }, + "delete": { + "permissions": [ + "EC2:DeleteManagedPrefixList", + "EC2:DescribeManagedPrefixLists" + ] + }, + "list": { + "permissions": [ + "EC2:DescribeManagedPrefixLists", + "EC2:GetManagedPrefixListEntries" + ] + } + }, + "additionalProperties": false +} diff --git a/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py b/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py new file mode 100644 index 0000000000000..5d8b993d28409 --- /dev/null +++ b/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py @@ -0,0 +1,20 @@ +from typing import Optional, Type + +from localstack.services.cloudformation.resource_provider import ( + CloudFormationResourceProviderPlugin, + ResourceProvider, +) + + +class EC2PrefixListProviderPlugin(CloudFormationResourceProviderPlugin): + name = "AWS::EC2::PrefixList" + + def __init__(self): + self.factory: Optional[Type[ResourceProvider]] = None + + def load(self): + from localstack.services.ec2.resource_providers.aws_ec2_prefixlist import ( + EC2PrefixListProvider, + ) + + self.factory = EC2PrefixListProvider diff --git a/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py b/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py new file mode 100644 index 0000000000000..420efcb8029ee --- /dev/null +++ b/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py @@ -0,0 +1,180 @@ +# LocalStack Resource Provider Scaffolding v2 +from __future__ import annotations + +from pathlib import Path +from typing import Optional, TypedDict + +import localstack.services.cloudformation.provider_utils as util +from localstack.services.cloudformation.resource_provider import ( + OperationStatus, + ProgressEvent, + ResourceProvider, + ResourceRequest, +) + + +class EC2VPCEndpointProperties(TypedDict): + ServiceName: Optional[str] + VpcId: Optional[str] + CreationTimestamp: Optional[str] + DnsEntries: Optional[list[str]] + Id: Optional[str] + NetworkInterfaceIds: Optional[list[str]] + PolicyDocument: Optional[str | dict] + PrivateDnsEnabled: Optional[bool] + RouteTableIds: Optional[list[str]] + SecurityGroupIds: Optional[list[str]] + SubnetIds: Optional[list[str]] + VpcEndpointType: Optional[str] + + +REPEATED_INVOCATION = "repeated_invocation" + + +class EC2VPCEndpointProvider(ResourceProvider[EC2VPCEndpointProperties]): + TYPE = "AWS::EC2::VPCEndpoint" # Autogenerated. Don't change + SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change + + def create( + self, + request: ResourceRequest[EC2VPCEndpointProperties], + ) -> ProgressEvent[EC2VPCEndpointProperties]: + """ + Create a new resource. + + Primary identifier fields: + - /properties/Id + + Required properties: + - VpcId + - ServiceName + + Create-only properties: + - /properties/ServiceName + - /properties/VpcEndpointType + - /properties/VpcId + + Read-only properties: + - /properties/NetworkInterfaceIds + - /properties/CreationTimestamp + - /properties/DnsEntries + - /properties/Id + + IAM permissions required: + - ec2:CreateVpcEndpoint + - ec2:DescribeVpcEndpoints + + """ + model = request.desired_state + create_params = util.select_attributes( + model, + [ + "PolidyDocument", + "PrivateDnsEnabled", + "RouteTablesIds", + "SecurityGroupIds", + "ServiceName", + "SubnetIds", + "VpcEndpointType", + "VpcId", + ], + ) + + if not request.custom_context.get(REPEATED_INVOCATION): + response = request.aws_client_factory.ec2.create_vpc_endpoint(**create_params) + model["Id"] = response["VpcEndpoint"]["VpcEndpointId"] + model["DnsEntries"] = response["VpcEndpoint"]["DnsEntries"] + model["CreationTimestamp"] = response["VpcEndpoint"]["CreationTimestamp"] + model["NetworkInterfaceIds"] = response["VpcEndpoint"]["NetworkInterfaceIds"] + request.custom_context[REPEATED_INVOCATION] = True + return ProgressEvent( + status=OperationStatus.IN_PROGRESS, + resource_model=model, + custom_context=request.custom_context, + ) + + response = request.aws_client_factory.ec2.describe_vpc_endpoints( + VpcEndpointIds=[model["Id"]] + ) + if not response["VpcEndpoints"]: + return ProgressEvent( + status=OperationStatus.FAILED, + resource_model=model, + custom_context=request.custom_context, + message="Resource not found after creation", + ) + + state = response["VpcEndpoints"][0][ + "State" + ].lower() # API specifies capital but lowercase is returned + match state: + case "available": + return ProgressEvent(status=OperationStatus.SUCCESS, resource_model=model) + case "pending": + return ProgressEvent(status=OperationStatus.IN_PROGRESS, resource_model=model) + case "pendingacceptance": + return ProgressEvent(status=OperationStatus.IN_PROGRESS, resource_model=model) + case _: + return ProgressEvent( + status=OperationStatus.FAILED, + resource_model=model, + message=f"Invalid state '{state}' for resource", + ) + + def read( + self, + request: ResourceRequest[EC2VPCEndpointProperties], + ) -> ProgressEvent[EC2VPCEndpointProperties]: + """ + Fetch resource information + + IAM permissions required: + - ec2:DescribeVpcEndpoints + """ + raise NotImplementedError + + def delete( + self, + request: ResourceRequest[EC2VPCEndpointProperties], + ) -> ProgressEvent[EC2VPCEndpointProperties]: + """ + Delete a resource + + IAM permissions required: + - ec2:DeleteVpcEndpoints + - ec2:DescribeVpcEndpoints + """ + model = request.previous_state + response = request.aws_client_factory.ec2.describe_vpc_endpoints( + VpcEndpointIds=[model["Id"]] + ) + + if not response["VpcEndpoints"]: + return ProgressEvent( + status=OperationStatus.FAILED, + resource_model=model, + message="Resource not found for deletion", + ) + + state = response["VpcEndpoints"][0]["State"].lower() + match state: + case "deleted": + return ProgressEvent(status=OperationStatus.SUCCESS, resource_model=model) + case "deleting": + return ProgressEvent(status=OperationStatus.IN_PROGRESS, resource_model=model) + case _: + request.aws_client_factory.ec2.delete_vpc_endpoints(VpcEndpointIds=[model["Id"]]) + return ProgressEvent(status=OperationStatus.IN_PROGRESS, resource_model=model) + + def update( + self, + request: ResourceRequest[EC2VPCEndpointProperties], + ) -> ProgressEvent[EC2VPCEndpointProperties]: + """ + Update a resource + + IAM permissions required: + - ec2:ModifyVpcEndpoint + - ec2:DescribeVpcEndpoints + """ + raise NotImplementedError diff --git a/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.schema.json b/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.schema.json new file mode 100644 index 0000000000000..c8dcc84644d4c --- /dev/null +++ b/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.schema.json @@ -0,0 +1,140 @@ +{ + "typeName": "AWS::EC2::VPCEndpoint", + "description": "Resource Type definition for AWS::EC2::VPCEndpoint", + "additionalProperties": false, + "properties": { + "Id": { + "type": "string" + }, + "CreationTimestamp": { + "type": "string" + }, + "DnsEntries": { + "type": "array", + "uniqueItems": false, + "insertionOrder": false, + "items": { + "type": "string" + } + }, + "NetworkInterfaceIds": { + "type": "array", + "uniqueItems": false, + "insertionOrder": false, + "items": { + "type": "string" + } + }, + "PolicyDocument": { + "type": [ + "string", + "object" + ], + "description": "A policy to attach to the endpoint that controls access to the service." + }, + "PrivateDnsEnabled": { + "type": "boolean", + "description": "Indicate whether to associate a private hosted zone with the specified VPC." + }, + "RouteTableIds": { + "type": "array", + "description": "One or more route table IDs.", + "uniqueItems": true, + "insertionOrder": false, + "items": { + "type": "string" + } + }, + "SecurityGroupIds": { + "type": "array", + "description": "The ID of one or more security groups to associate with the endpoint network interface.", + "uniqueItems": true, + "insertionOrder": false, + "items": { + "type": "string" + } + }, + "ServiceName": { + "type": "string", + "description": "The service name." + }, + "SubnetIds": { + "type": "array", + "description": "The ID of one or more subnets in which to create an endpoint network interface.", + "uniqueItems": true, + "insertionOrder": false, + "items": { + "type": "string" + } + }, + "VpcEndpointType": { + "type": "string", + "enum": [ + "Interface", + "Gateway", + "GatewayLoadBalancer" + ] + }, + "VpcId": { + "type": "string", + "description": "The ID of the VPC in which the endpoint will be used." + } + }, + "required": [ + "VpcId", + "ServiceName" + ], + "readOnlyProperties": [ + "/properties/NetworkInterfaceIds", + "/properties/CreationTimestamp", + "/properties/DnsEntries", + "/properties/Id" + ], + "createOnlyProperties": [ + "/properties/ServiceName", + "/properties/VpcEndpointType", + "/properties/VpcId" + ], + "primaryIdentifier": [ + "/properties/Id" + ], + "tagging": { + "taggable": false, + "tagOnCreate": false, + "tagUpdatable": false, + "cloudFormationSystemTags": false + }, + "handlers": { + "create": { + "permissions": [ + "ec2:CreateVpcEndpoint", + "ec2:DescribeVpcEndpoints" + ], + "timeoutInMinutes": 210 + }, + "read": { + "permissions": [ + "ec2:DescribeVpcEndpoints" + ] + }, + "update": { + "permissions": [ + "ec2:ModifyVpcEndpoint", + "ec2:DescribeVpcEndpoints" + ], + "timeoutInMinutes": 210 + }, + "delete": { + "permissions": [ + "ec2:DeleteVpcEndpoints", + "ec2:DescribeVpcEndpoints" + ], + "timeoutInMinutes": 210 + }, + "list": { + "permissions": [ + "ec2:DescribeVpcEndpoints" + ] + } + } +} diff --git a/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py b/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py new file mode 100644 index 0000000000000..e0e1d228a95de --- /dev/null +++ b/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py @@ -0,0 +1,20 @@ +from typing import Optional, Type + +from localstack.services.cloudformation.resource_provider import ( + CloudFormationResourceProviderPlugin, + ResourceProvider, +) + + +class EC2VPCEndpointProviderPlugin(CloudFormationResourceProviderPlugin): + name = "AWS::EC2::VPCEndpoint" + + def __init__(self): + self.factory: Optional[Type[ResourceProvider]] = None + + def load(self): + from localstack.services.ec2.resource_providers.aws_ec2_vpcendpoint import ( + EC2VPCEndpointProvider, + ) + + self.factory = EC2VPCEndpointProvider diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py index 880e8f16fe4e6..0f9d251971a5d 100644 --- a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py +++ b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py @@ -2,6 +2,7 @@ import pytest from botocore.exceptions import ClientError +from localstack_snapshot.snapshots.transformer import SortingTransformer from localstack.testing.pytest import markers @@ -35,3 +36,59 @@ def test_deploy_instance_with_key_pair(deploy_cfn_template, aws_client, snapshot with pytest.raises(ClientError) as e: aws_client.ec2.describe_key_pairs(KeyNames=[key_name]) snapshot.match("key_pair_deleted", e.value.response) + + +@markers.aws.validated +def test_deploy_prefix_list(deploy_cfn_template, aws_client, snapshot): + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../../templates/ec2_prefixlist.yml" + ) + ) + + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + description = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name) + snapshot.match("resource-description", description) + + prefix_id = stack.outputs["PrefixRef"] + prefix_list = aws_client.ec2.describe_managed_prefix_lists(PrefixListIds=[prefix_id]) + snapshot.match("prefix-list", prefix_list) + snapshot.add_transformer(snapshot.transform.key_value("PrefixListId")) + + +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + "$..DnsEntries", + "$..Groups", + "$..NetworkInterfaceIds", + "$..SubnetIds", + ] +) +def test_deploy_vpc_endpoint(deploy_cfn_template, aws_client, snapshot): + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../../templates/ec2_vpc_endpoint.yml" + ) + ) + + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + + snapshot.add_transformer( + SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 + ) + description = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name) + snapshot.match("resource-description", description) + + endpoint_id = stack.outputs["EndpointRef"] + endpoint = aws_client.ec2.describe_vpc_endpoints(VpcEndpointIds=[endpoint_id]) + snapshot.match("endpoint", endpoint) + + snapshot.add_transformer(snapshot.transform.key_value("VpcEndpointId")) + snapshot.add_transformer(snapshot.transform.key_value("DnsName")) + snapshot.add_transformer(snapshot.transform.key_value("HostedZoneId")) + snapshot.add_transformer(snapshot.transform.key_value("GroupId")) + snapshot.add_transformer(snapshot.transform.key_value("GroupName")) + snapshot.add_transformer(snapshot.transform.regex(stack.outputs["VpcId"], "vpc-id")) + snapshot.add_transformer(snapshot.transform.regex(stack.outputs["SubnetBId"], "subnet-b-id")) + snapshot.add_transformer(snapshot.transform.regex(stack.outputs["SubnetAId"], "subnet-a-id")) diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json index 1132bff43ea34..54a9e40362151 100644 --- a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json +++ b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json @@ -29,5 +29,198 @@ } } } + }, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": { + "recorded-date": "30-04-2024, 19:32:40", + "recorded-content": { + "resource-description": { + "StackResources": [ + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "NewPrefixList", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::EC2::PrefixList", + "StackId": "arn:aws:cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "prefix-list": { + "PrefixLists": [ + { + "AddressFamily": "IPv4", + "MaxEntries": 10, + "OwnerId": "111111111111", + "PrefixListArn": "arn:aws:ec2::111111111111:prefix-list/", + "PrefixListId": "", + "PrefixListName": "vpc-1-servers", + "State": "create-complete", + "Tags": [ + { + "Key": "Name", + "Value": "VPC-1-Servers" + } + ], + "Version": 1 + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": { + "recorded-date": "30-04-2024, 20:01:19", + "recorded-content": { + "resource-description": { + "StackResources": [ + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "CWLInterfaceEndpoint", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::EC2::VPCEndpoint", + "StackId": "arn:aws:cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "mySecurityGroup", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::EC2::SecurityGroup", + "StackId": "arn:aws:cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "myVPC", + "PhysicalResourceId": "vpc-id", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::EC2::VPC", + "StackId": "arn:aws:cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "subnetA", + "PhysicalResourceId": "subnet-a-id", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::EC2::Subnet", + "StackId": "arn:aws:cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "DriftInformation": { + "StackResourceDriftStatus": "NOT_CHECKED" + }, + "LogicalResourceId": "subnetB", + "PhysicalResourceId": "subnet-b-id", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::EC2::Subnet", + "StackId": "arn:aws:cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "endpoint": { + "VpcEndpoints": [ + { + "CreationTimestamp": "timestamp", + "DnsEntries": [ + { + "DnsName": "-g5hws96k.logs..vpce.amazonaws.com", + "HostedZoneId": "" + }, + { + "DnsName": "-g5hws96k-b.logs..vpce.amazonaws.com", + "HostedZoneId": "" + }, + { + "DnsName": "-g5hws96k-a.logs..vpce.amazonaws.com", + "HostedZoneId": "" + }, + { + "DnsName": "", + "HostedZoneId": "" + }, + { + "DnsName": "", + "HostedZoneId": "" + } + ], + "DnsOptions": { + "DnsRecordIpType": "ipv4" + }, + "Groups": [ + { + "GroupId": "", + "GroupName": "-mySecurityGroup-RWU3KD7UZFAy" + } + ], + "IpAddressType": "ipv4", + "NetworkInterfaceIds": [ + "eni-0b89833f2bf9a89c0", + "eni-05151d42b885fbd35" + ], + "OwnerId": "111111111111", + "PolicyDocument": { + "Statement": [ + { + "Action": "*", + "Effect": "Allow", + "Principal": "*", + "Resource": "*" + } + ] + }, + "PrivateDnsEnabled": true, + "RequesterManaged": false, + "RouteTableIds": [], + "ServiceName": "com.amazonaws..logs", + "State": "available", + "SubnetIds": [ + "subnet-a-id", + "subnet-b-id" + ], + "Tags": [], + "VpcEndpointId": "", + "VpcEndpointType": "Interface", + "VpcId": "vpc-id" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json index e0cdb63e68937..320d8da5177d9 100644 --- a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json +++ b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json @@ -1,5 +1,11 @@ { "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_instance_with_key_pair": { "last_validated_date": "2024-01-30T21:09:52+00:00" + }, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": { + "last_validated_date": "2024-04-26T16:18:18+00:00" + }, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": { + "last_validated_date": "2024-04-30T20:01:19+00:00" } -} \ No newline at end of file +} diff --git a/tests/aws/templates/ec2_prefixlist.yml b/tests/aws/templates/ec2_prefixlist.yml new file mode 100644 index 0000000000000..1cb2e7dac7ed8 --- /dev/null +++ b/tests/aws/templates/ec2_prefixlist.yml @@ -0,0 +1,23 @@ +Resources: + NewPrefixList: + Type: AWS::EC2::PrefixList + Properties: + PrefixListName: "vpc-1-servers" + AddressFamily: "IPv4" + MaxEntries: 10 + Entries: + - Cidr: "10.0.0.5/32" + Description: "Server 1" + - Cidr: "10.0.0.10/32" + Description: "Server 2" + Tags: + - Key: "Name" + Value: "VPC-1-Servers" + +Outputs: + PrefixRef: + Value: !Ref NewPrefixList + PrefixArn: + Value: !GetAtt NewPrefixList.Arn + PrefixId: + Value: !GetAtt NewPrefixList.PrefixListId diff --git a/tests/aws/templates/ec2_vpc_endpoint.yml b/tests/aws/templates/ec2_vpc_endpoint.yml new file mode 100644 index 0000000000000..901eed3e229d7 --- /dev/null +++ b/tests/aws/templates/ec2_vpc_endpoint.yml @@ -0,0 +1,58 @@ +Resources: + CWLInterfaceEndpoint: + Type: 'AWS::EC2::VPCEndpoint' + Properties: + VpcEndpointType: 'Interface' + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.logs' + VpcId: !Ref myVPC + PrivateDnsEnabled: true + SubnetIds: + - !Ref subnetA + - !Ref subnetB + SecurityGroupIds: + - !Ref mySecurityGroup + myVPC: + Type: 'AWS::EC2::VPC' + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: 'Name' + Value: 'myVPC' + subnetA: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref myVPC + CidrBlock: '10.0.1.0/24' + AvailabilityZone: !Select [ 0, !GetAZs ] + subnetB: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref myVPC + CidrBlock: '10.0.2.0/24' + AvailabilityZone: !Select [ 1, !GetAZs ] + mySecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: 'Allow HTTPS traffic from the VPC' + VpcId: !Ref myVPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: !GetAtt myVPC.CidrBlock + +Outputs: + EndpointRef: + Value: !Ref CWLInterfaceEndpoint + EndpointCreationTimestamp: + Value: !GetAtt CWLInterfaceEndpoint.CreationTimestamp + Id: + Value: !GetAtt CWLInterfaceEndpoint.Id + VpcId: + Value: !Ref myVPC + SubnetAId: + Value: !Ref subnetA + SubnetBId: + Value: !Ref subnetB