From c43c82477f9208b2804dd298e115e5b6fac356bc Mon Sep 17 00:00:00 2001 From: Audrey Lin Date: Sun, 15 Mar 2026 20:08:40 -0400 Subject: [PATCH 1/5] test(ec2): add lifecycle test cases for subnet, internet gateway, and route table --- .../cli/ec2/internet-gateway-lifecycle.rst | 127 ++++++++++++++++ .../tests/cli/ec2/lifecyclePRmapping.md | 38 +++++ .../tests/cli/ec2/route-table-lifecycle.rst | 125 ++++++++++++++++ .../tests/cli/ec2/subnet-lifecycle.rst | 135 ++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 emulators/aws-ec2/tests/cli/ec2/internet-gateway-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/lifecyclePRmapping.md create mode 100644 emulators/aws-ec2/tests/cli/ec2/route-table-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/subnet-lifecycle.rst diff --git a/emulators/aws-ec2/tests/cli/ec2/internet-gateway-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/internet-gateway-lifecycle.rst new file mode 100644 index 0000000..3501c20 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/internet-gateway-lifecycle.rst @@ -0,0 +1,127 @@ +**Example 1: To create a VPC for the internet gateway workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-vpc \ + --cidr-block 10.1.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-igw-workflow-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.1.0.0/16", + "DhcpOptionsId": "dopt-5EXAMPLE", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "InstanceTenancy": "default", + "Ipv6CidrBlockAssociationSet": [], + "CidrBlockAssociationSet": [ + { + "AssociationId": "vpc-cidr-assoc-07501b79ecEXAMPLE", + "CidrBlock": "10.1.0.0/16", + "CidrBlockState": { + "State": "associated" + } + } + ], + "IsDefault": false, + "Tags": [ + { + "Key": "Name", + "Value": "my-igw-workflow-vpc" + } + ] + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create an internet gateway** + +The following ``create-internet-gateway`` example creates an internet gateway and applies a Name tag. :: + + aws ec2 create-internet-gateway \ + --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=my-igw}]' + +Output:: + + { + "InternetGateway": { + "Attachments": [], + "InternetGatewayId": "igw-0d0fb496b3EXAMPLE", + "OwnerId": "123456789012", + "Tags": [ + { + "Key": "Name", + "Value": "my-igw" + } + ] + } + } + +**Example 4: To attach the internet gateway to the VPC** + +The following ``attach-internet-gateway`` example attaches the specified internet gateway to the specified VPC. If the command succeeds, no output is returned. :: + + aws ec2 attach-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +**Example 5: To describe the internet gateway and confirm the attachment** + +The following ``describe-internet-gateways`` example retrieves details about the internet gateway to confirm that it is attached to the specified VPC and that the attachment is in the ``available`` state. :: + + aws ec2 describe-internet-gateways \ + --internet-gateway-ids igw-0d0fb496b3EXAMPLE + +Output:: + + { + "InternetGateways": [ + { + "Attachments": [ + { + "State": "available", + "VpcId": "vpc-0a60eb65b4EXAMPLE" + } + ], + "InternetGatewayId": "igw-0d0fb496b3EXAMPLE", + "OwnerId": "123456789012", + "Tags": [ + { + "Key": "Name", + "Value": "my-igw" + } + ] + } + ] + } + +**Example 6: To detach the internet gateway from the VPC** + +The following ``detach-internet-gateway`` example detaches the specified internet gateway from the specified VPC. If the command succeeds, no output is returned. :: + + aws ec2 detach-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +**Example 7: To delete the internet gateway** + +The following ``delete-internet-gateway`` example deletes the specified internet gateway after it has been detached from the VPC. If the command succeeds, no output is returned. :: + + aws ec2 delete-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE + +**Example 8: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the internet gateway has been detached and deleted. If the command succeeds, no output is returned. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/lifecyclePRmapping.md b/emulators/aws-ec2/tests/cli/ec2/lifecyclePRmapping.md new file mode 100644 index 0000000..6c49d64 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/lifecyclePRmapping.md @@ -0,0 +1,38 @@ +## Summary + +This PR adds three EC2 lifecycle test cases under `emulators/aws-ec2/tests/cli/ec2/`: + +- `subnet-lifecycle.rst` +- `internet-gateway-lifecycle.rst` +- `route-table-lifecycle.rst` + +These cases extend the existing lifecycle-style coverage already present in files such as: + +- `vpc-lifecycle.rst` +- `volume-lifecycle.rst` +- `vpc-endpoint-lifecycle.rst` + +## Why + +The current EC2 CLI corpus already contains the atomic building blocks for these workflows, but the lifecycle validation is still fragmented across separate create / describe / delete examples. + +This PR groups those existing assets into complete lifecycle-oriented flows so that the test corpus can validate: + +- parent resource setup +- resource creation +- state and relationship verification via describe calls +- cleanup ordering + +## Mapping from existing cases + +| Existing `.rst` set | Lifecycle-related? | Missing steps / gaps | Resulting lifecycle flow | +|---|---|---|---| +| `create-subnet.rst` + `describe-subnets.rst` + `delete-subnet.rst` | Yes, partial | missing parent VPC setup, wait step, and grouped cleanup | create-vpc → wait → create-subnet → describe-subnets → delete-subnet → delete-vpc | +| `create-internet-gateway.rst` + `attach-internet-gateway.rst` + `describe-internet-gateways.rst` + `detach-internet-gateway.rst` + `delete-internet-gateway.rst` | Yes, partial | missing parent VPC setup, wait step, and grouped attach/detach cleanup flow | create-vpc → wait → create-internet-gateway → attach → describe-internet-gateways → detach → delete-internet-gateway → delete-vpc | +| `create-route-table.rst` + `describe-route-tables.rst` + `delete-route-table.rst` | Yes, partial | missing parent VPC setup, wait step, and grouped cleanup | create-vpc → wait → create-route-table → describe-route-tables → delete-route-table → delete-vpc | + +## Notes + +- The new files follow the existing lifecycle file style already used in `vpc-lifecycle.rst`. +- These cases intentionally use a minimal workflow slice and do not yet add extra mutation steps such as subnet attribute modification, route association flows, or route creation via internet gateway. +- The goal of this PR is to establish the smallest clear lifecycle assets first, then extend coverage incrementally if needed. \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/route-table-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/route-table-lifecycle.rst new file mode 100644 index 0000000..ce2f3e0 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/route-table-lifecycle.rst @@ -0,0 +1,125 @@ +**Example 1: To create a VPC for the route table workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-vpc \ + --cidr-block 10.2.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-route-table-workflow-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.2.0.0/16", + "DhcpOptionsId": "dopt-5EXAMPLE", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "InstanceTenancy": "default", + "Ipv6CidrBlockAssociationSet": [], + "CidrBlockAssociationSet": [ + { + "AssociationId": "vpc-cidr-assoc-07501b79ecEXAMPLE", + "CidrBlock": "10.2.0.0/16", + "CidrBlockState": { + "State": "associated" + } + } + ], + "IsDefault": false, + "Tags": [ + { + "Key": "Name", + "Value": "my-route-table-workflow-vpc" + } + ] + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a route table for the VPC** + +The following ``create-route-table`` example creates a route table for the specified VPC and applies a Name tag. :: + + aws ec2 create-route-table \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=my-route-table}]' + +Output:: + + { + "RouteTable": { + "Associations": [], + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "PropagatingVgws": [], + "Tags": [ + { + "Key": "Name", + "Value": "my-route-table" + } + ], + "Routes": [ + { + "GatewayId": "local", + "DestinationCidrBlock": "10.2.0.0/16", + "State": "active" + } + ] + } + } + +**Example 4: To describe the route table and confirm its settings** + +The following ``describe-route-tables`` example retrieves details about the route table to confirm that it belongs to the specified VPC and that the local route is in the ``active`` state. :: + + aws ec2 describe-route-tables \ + --route-table-ids rtb-22574640 + +Output:: + + { + "RouteTables": [ + { + "Associations": [], + "PropagatingVgws": [], + "RouteTableId": "rtb-22574640", + "Routes": [ + { + "DestinationCidrBlock": "10.2.0.0/16", + "GatewayId": "local", + "Origin": "CreateRouteTable", + "State": "active" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "my-route-table" + } + ], + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012" + } + ] + } + +**Example 5: To delete the route table** + +The following ``delete-route-table`` example deletes the specified route table. If the command succeeds, no output is returned. :: + + aws ec2 delete-route-table \ + --route-table-id rtb-22574640 + +**Example 6: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the route table has been removed. If the command succeeds, no output is returned. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/subnet-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/subnet-lifecycle.rst new file mode 100644 index 0000000..1334d93 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/subnet-lifecycle.rst @@ -0,0 +1,135 @@ +**Example 1: To create a VPC for the subnet workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-vpc \ + --cidr-block 10.0.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-subnet-workflow-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.0.0.0/16", + "DhcpOptionsId": "dopt-5EXAMPLE", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "InstanceTenancy": "default", + "Ipv6CidrBlockAssociationSet": [], + "CidrBlockAssociationSet": [ + { + "AssociationId": "vpc-cidr-assoc-07501b79ecEXAMPLE", + "CidrBlock": "10.0.0.0/16", + "CidrBlockState": { + "State": "associated" + } + } + ], + "IsDefault": false, + "Tags": [ + { + "Key": "Name", + "Value": "my-subnet-workflow-vpc" + } + ] + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a subnet in the VPC** + +The following ``create-subnet`` example creates a subnet in the specified VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-subnet \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --cidr-block 10.0.1.0/24 \ + --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-subnet}]' + +Output:: + + { + "Subnet": { + "AvailabilityZone": "us-east-1a", + "AvailabilityZoneId": "use1-az1", + "AvailableIpAddressCount": 251, + "CidrBlock": "10.0.1.0/24", + "DefaultForAz": false, + "MapPublicIpOnLaunch": false, + "State": "available", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "AssignIpv6AddressOnCreation": false, + "Ipv6CidrBlockAssociationSet": [], + "Tags": [ + { + "Key": "Name", + "Value": "my-subnet" + } + ], + "SubnetArn": "arn:aws:ec2:us-east-1:123456789012:subnet/subnet-0e99b93155EXAMPLE" + } + } + +**Example 4: To describe the subnet and confirm it is available** + +The following ``describe-subnets`` example retrieves details about the subnet to confirm that it belongs to the specified VPC, that the CIDR block matches the requested value, and that the subnet is in the ``available`` state. :: + + aws ec2 describe-subnets \ + --subnet-ids subnet-0e99b93155EXAMPLE + +Output:: + + { + "Subnets": [ + { + "AvailabilityZone": "us-east-1a", + "AvailabilityZoneId": "use1-az1", + "AvailableIpAddressCount": 251, + "CidrBlock": "10.0.1.0/24", + "DefaultForAz": false, + "MapPublicIpOnLaunch": false, + "State": "available", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "AssignIpv6AddressOnCreation": false, + "Ipv6CidrBlockAssociationSet": [], + "Tags": [ + { + "Key": "Name", + "Value": "my-subnet" + } + ], + "SubnetArn": "arn:aws:ec2:us-east-1:123456789012:subnet/subnet-0e99b93155EXAMPLE", + "EnableDns64": false, + "Ipv6Native": false, + "PrivateDnsNameOptionsOnLaunch": { + "HostnameType": "ip-name", + "EnableResourceNameDnsARecord": false, + "EnableResourceNameDnsAAAARecord": false + } + } + ] + } + +**Example 5: To delete the subnet** + +The following ``delete-subnet`` example deletes the specified subnet. If the command succeeds, no output is returned. :: + + aws ec2 delete-subnet \ + --subnet-id subnet-0e99b93155EXAMPLE + +**Example 6: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the subnet has been removed. If the command succeeds, no output is returned. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file From 525c0cc320e22e063c0776093b4b5a595736a475 Mon Sep 17 00:00:00 2001 From: Audrey Lin Date: Fri, 20 Mar 2026 09:48:05 -0400 Subject: [PATCH 2/5] test: add EC2 networking dependency lifecycle rst examples --- ...ork-interface-security-group-lifecycle.rst | 139 ++++++++++ .../route-to-internet-gateway-lifecycle.rst | 189 ++++++++++++++ .../ec2/security-group-instance-lifecycle.rst | 208 +++++++++++++++ .../cli/ec2/subnet-network-acl-lifecycle.rst | 184 +++++++++++++ .../subnet-route-association-lifecycle.rst | 185 +++++++++++++ .../cli/ec2/subnet-route-to-igw-lifecycle.rst | 247 ++++++++++++++++++ .../vpc-endpoint-route-table-lifecycle.rst | 135 ++++++++++ 7 files changed, 1287 insertions(+) create mode 100644 emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/route-to-internet-gateway-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/subnet-network-acl-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/subnet-route-association-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/subnet-route-to-igw-lifecycle.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/vpc-endpoint-route-table-lifecycle.rst diff --git a/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst new file mode 100644 index 0000000..3fad401 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst @@ -0,0 +1,139 @@ +**Example 1: To create a VPC for the network-interface security-group workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block. :: + + aws ec2 create-vpc \ + --cidr-block 10.8.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-eni-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.8.0.0/16", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE" + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a subnet in the VPC** + +The following ``create-subnet`` example creates a subnet in the specified VPC. :: + + aws ec2 create-subnet \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --cidr-block 10.8.1.0/24 \ + --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-eni-subnet}]' + +Output:: + + { + "Subnet": { + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "CidrBlock": "10.8.1.0/24", + "State": "available" + } + } + +**Example 4: To create a security group in the VPC** + +The following ``create-security-group`` example creates a security group in the specified VPC. :: + + aws ec2 create-security-group \ + --group-name my-eni-sg \ + --description "Security group for ENI workflow" \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +Output:: + + { + "GroupId": "sg-0abc1234def567890" + } + +**Example 5: To create a network interface in the subnet with the security group** + +The following ``create-network-interface`` example creates a network interface in the specified subnet and attaches the specified security group. :: + + aws ec2 create-network-interface \ + --subnet-id subnet-0e99b93155EXAMPLE \ + --groups sg-0abc1234def567890 \ + --description "ENI for workflow validation" + +Output:: + + { + "NetworkInterface": { + "NetworkInterfaceId": "eni-0abc1234def567890", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "Status": "available", + "Groups": [ + { + "GroupId": "sg-0abc1234def567890", + "GroupName": "my-eni-sg" + } + ] + } + } + +**Example 6: To describe the network interface and confirm subnet and security group relationships** + +The following ``describe-network-interfaces`` example retrieves details about the network interface to confirm that it belongs to the specified subnet and VPC and references the specified security group. :: + + aws ec2 describe-network-interfaces \ + --network-interface-ids eni-0abc1234def567890 + +Output:: + + { + "NetworkInterfaces": [ + { + "NetworkInterfaceId": "eni-0abc1234def567890", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "Status": "available", + "Groups": [ + { + "GroupId": "sg-0abc1234def567890", + "GroupName": "my-eni-sg" + } + ] + } + ] + } + +**Example 7: To delete the network interface** + +The following ``delete-network-interface`` example deletes the specified network interface. :: + + aws ec2 delete-network-interface \ + --network-interface-id eni-0abc1234def567890 + +**Example 8: To delete the security group** + +The following ``delete-security-group`` example deletes the specified security group after the network interface has been removed. :: + + aws ec2 delete-security-group \ + --group-id sg-0abc1234def567890 + +**Example 9: To delete the subnet** + +The following ``delete-subnet`` example deletes the specified subnet. :: + + aws ec2 delete-subnet \ + --subnet-id subnet-0e99b93155EXAMPLE + +**Example 10: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the dependent resources have been removed. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/route-to-internet-gateway-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/route-to-internet-gateway-lifecycle.rst new file mode 100644 index 0000000..c775e5d --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/route-to-internet-gateway-lifecycle.rst @@ -0,0 +1,189 @@ +**Example 1: To create a VPC for the internet-routed workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-vpc \ + --cidr-block 10.4.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-igw-route-workflow-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.4.0.0/16", + "DhcpOptionsId": "dopt-5EXAMPLE", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "InstanceTenancy": "default", + "Ipv6CidrBlockAssociationSet": [], + "CidrBlockAssociationSet": [ + { + "AssociationId": "vpc-cidr-assoc-07501b79ecEXAMPLE", + "CidrBlock": "10.4.0.0/16", + "CidrBlockState": { + "State": "associated" + } + } + ], + "IsDefault": false, + "Tags": [ + { + "Key": "Name", + "Value": "my-igw-route-workflow-vpc" + } + ] + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create an internet gateway** + +The following ``create-internet-gateway`` example creates an internet gateway and applies a Name tag. :: + + aws ec2 create-internet-gateway \ + --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=my-route-igw}]' + +Output:: + + { + "InternetGateway": { + "Attachments": [], + "InternetGatewayId": "igw-0d0fb496b3EXAMPLE", + "OwnerId": "123456789012", + "Tags": [ + { + "Key": "Name", + "Value": "my-route-igw" + } + ] + } + } + +**Example 4: To attach the internet gateway to the VPC** + +The following ``attach-internet-gateway`` example attaches the specified internet gateway to the specified VPC. :: + + aws ec2 attach-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +**Example 5: To create a route table** + +The following ``create-route-table`` example creates a route table for the specified VPC. :: + + aws ec2 create-route-table \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=my-igw-route-table}]' + +Output:: + + { + "RouteTable": { + "Associations": [], + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "PropagatingVgws": [], + "Tags": [ + { + "Key": "Name", + "Value": "my-igw-route-table" + } + ], + "Routes": [ + { + "GatewayId": "local", + "DestinationCidrBlock": "10.4.0.0/16", + "State": "active" + } + ] + } + } + +**Example 6: To create a default route through the internet gateway** + +The following ``create-route`` example adds a default route that targets the attached internet gateway. :: + + aws ec2 create-route \ + --route-table-id rtb-22574640 \ + --destination-cidr-block 0.0.0.0/0 \ + --gateway-id igw-0d0fb496b3EXAMPLE + +Output:: + + { + "Return": true + } + +**Example 7: To describe the route table and confirm the internet route** + +The following ``describe-route-tables`` example retrieves details about the route table to confirm that the default route through the internet gateway is active. :: + + aws ec2 describe-route-tables \ + --route-table-ids rtb-22574640 + +Output:: + + { + "RouteTables": [ + { + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "Routes": [ + { + "DestinationCidrBlock": "10.4.0.0/16", + "GatewayId": "local", + "Origin": "CreateRouteTable", + "State": "active" + }, + { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": "igw-0d0fb496b3EXAMPLE", + "Origin": "CreateRoute", + "State": "active" + } + ] + } + ] + } + +**Example 8: To delete the default route** + +The following ``delete-route`` example removes the default route from the route table. :: + + aws ec2 delete-route \ + --route-table-id rtb-22574640 \ + --destination-cidr-block 0.0.0.0/0 + +**Example 9: To delete the route table** + +The following ``delete-route-table`` example deletes the specified route table. :: + + aws ec2 delete-route-table \ + --route-table-id rtb-22574640 + +**Example 10: To detach and delete the internet gateway** + +The following ``detach-internet-gateway`` example detaches the internet gateway from the VPC. :: + + aws ec2 detach-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +The following ``delete-internet-gateway`` example deletes the detached internet gateway. :: + + aws ec2 delete-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE + +**Example 11: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the dependent resources have been removed. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst new file mode 100644 index 0000000..f8cf238 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst @@ -0,0 +1,208 @@ +**Example 1: To create a VPC for the security-group and instance workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-vpc \ + --cidr-block 10.7.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-sg-instance-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.7.0.0/16", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE" + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a subnet in the VPC** + +The following ``create-subnet`` example creates a subnet in the specified VPC. :: + + aws ec2 create-subnet \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --cidr-block 10.7.1.0/24 \ + --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-instance-subnet}]' + +Output:: + + { + "Subnet": { + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "CidrBlock": "10.7.1.0/24", + "State": "available" + } + } + +**Example 4: To create a security group in the VPC** + +The following ``create-security-group`` example creates a security group in the specified VPC. :: + + aws ec2 create-security-group \ + --group-name my-instance-sg \ + --description "Security group for instance workflow" \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=my-instance-sg}]' + +Output:: + + { + "GroupId": "sg-0abc1234def567890", + "Tags": [ + { + "Key": "Name", + "Value": "my-instance-sg" + } + ] + } + +**Example 5: To authorize inbound SSH access** + +The following ``authorize-security-group-ingress`` example adds an inbound SSH rule to the security group. :: + + aws ec2 authorize-security-group-ingress \ + --group-id sg-0abc1234def567890 \ + --ip-permissions IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges='[{CidrIp=0.0.0.0/0,Description=SSH}]' + +**Example 6: To run an instance in the subnet with the security group** + +The following ``run-instances`` example launches an instance in the specified subnet with the specified security group. :: + + aws ec2 run-instances \ + --image-id ami-12345678 \ + --instance-type t2.micro \ + --subnet-id subnet-0e99b93155EXAMPLE \ + --security-group-ids sg-0abc1234def567890 \ + --min-count 1 \ + --max-count 1 \ + --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=my-workflow-instance}]' + +Output:: + + { + "Instances": [ + { + "InstanceId": "i-0123456789abcdef0", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "State": { + "Name": "pending" + }, + "SecurityGroups": [ + { + "GroupId": "sg-0abc1234def567890", + "GroupName": "my-instance-sg" + } + ] + } + ] + } + +**Example 7: To wait for the instance to enter the running state** + +The following ``wait instance-running`` example pauses and resumes running only after it confirms that the specified instance is in the ``running`` state. :: + + aws ec2 wait instance-running \ + --instance-ids i-0123456789abcdef0 + +**Example 8: To describe the instance and confirm subnet and security group attachment** + +The following ``describe-instances`` example retrieves details about the instance to confirm that it belongs to the specified subnet and uses the specified security group. :: + + aws ec2 describe-instances \ + --instance-ids i-0123456789abcdef0 + +Output:: + + { + "Reservations": [ + { + "Instances": [ + { + "InstanceId": "i-0123456789abcdef0", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "State": { + "Name": "running" + }, + "SecurityGroups": [ + { + "GroupId": "sg-0abc1234def567890", + "GroupName": "my-instance-sg" + } + ] + } + ] + } + ] + } + +**Example 9: To describe the security group and confirm the ingress rule configuration** + +The following ``describe-security-groups`` example retrieves details about the security group to confirm that the expected ingress rule is present. :: + + aws ec2 describe-security-groups \ + --group-ids sg-0abc1234def567890 + +Output:: + + { + "SecurityGroups": [ + { + "GroupId": "sg-0abc1234def567890", + "GroupName": "my-instance-sg", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "IpPermissions": [ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 22 + } + ] + } + ] + } + +**Example 10: To terminate the instance** + +The following ``terminate-instances`` example terminates the specified instance. :: + + aws ec2 terminate-instances \ + --instance-ids i-0123456789abcdef0 + +**Example 11: To wait for the instance to terminate** + +The following ``wait instance-terminated`` example pauses and resumes running only after it confirms that the specified instance is in the ``terminated`` state. :: + + aws ec2 wait instance-terminated \ + --instance-ids i-0123456789abcdef0 + +**Example 12: To delete the security group** + +The following ``delete-security-group`` example deletes the specified security group after the instance has been terminated. :: + + aws ec2 delete-security-group \ + --group-id sg-0abc1234def567890 + +**Example 13: To delete the subnet** + +The following ``delete-subnet`` example deletes the specified subnet. :: + + aws ec2 delete-subnet \ + --subnet-id subnet-0e99b93155EXAMPLE + +**Example 14: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the dependent resources have been removed. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/subnet-network-acl-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/subnet-network-acl-lifecycle.rst new file mode 100644 index 0000000..951b706 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/subnet-network-acl-lifecycle.rst @@ -0,0 +1,184 @@ +**Example 1: To create a VPC for the subnet network ACL workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block. :: + + aws ec2 create-vpc \ + --cidr-block 10.11.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-acl-vpc}]' + +Output:: + + { + "Vpc": { + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "CidrBlock": "10.11.0.0/16", + "State": "pending" + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a subnet in the VPC** + +The following ``create-subnet`` example creates a subnet in the specified VPC. :: + + aws ec2 create-subnet \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --cidr-block 10.11.1.0/24 + +Output:: + + { + "Subnet": { + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "CidrBlock": "10.11.1.0/24", + "State": "available" + } + } + +**Example 4: To identify the subnet's current default network ACL association** + +The following ``describe-network-acls`` example retrieves the current network ACL association for the subnet so that it can later be replaced. :: + + aws ec2 describe-network-acls \ + --filters Name=association.subnet-id,Values=subnet-0e99b93155EXAMPLE + +Output:: + + { + "NetworkAcls": [ + { + "NetworkAclId": "acl-0default1234567890", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "IsDefault": true, + "Associations": [ + { + "NetworkAclAssociationId": "aclassoc-0abcdef1234567890", + "NetworkAclId": "acl-0default1234567890", + "SubnetId": "subnet-0e99b93155EXAMPLE" + } + ] + } + ] + } + +**Example 5: To create a network ACL in the VPC** + +The following ``create-network-acl`` example creates a non-default network ACL in the specified VPC. :: + + aws ec2 create-network-acl \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +Output:: + + { + "NetworkAcl": { + "NetworkAclId": "acl-0abc1234def567890", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "IsDefault": false, + "Entries": [], + "Associations": [] + } + } + +**Example 6: To associate the new network ACL with the subnet** + +The following ``replace-network-acl-association`` example replaces the subnet's current default ACL association with the new network ACL. :: + + aws ec2 replace-network-acl-association \ + --association-id aclassoc-0abcdef1234567890 \ + --network-acl-id acl-0abc1234def567890 + +Output:: + + { + "NewAssociationId": "aclassoc-0fedcba9876543210" + } + +**Example 7: To create an inbound rule in the network ACL** + +The following ``create-network-acl-entry`` example creates an inbound allow rule for TCP port 80. :: + + aws ec2 create-network-acl-entry \ + --network-acl-id acl-0abc1234def567890 \ + --rule-number 100 \ + --protocol tcp \ + --port-range From=80,To=80 \ + --cidr-block 0.0.0.0/0 \ + --rule-action allow \ + --ingress + +**Example 8: To describe the custom network ACL and confirm both the subnet association and the rule** + +The following ``describe-network-acls`` example retrieves details about the custom network ACL to confirm that it is associated with the subnet and contains the expected rule. :: + + aws ec2 describe-network-acls \ + --network-acl-ids acl-0abc1234def567890 + +Output:: + + { + "NetworkAcls": [ + { + "NetworkAclId": "acl-0abc1234def567890", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "Associations": [ + { + "NetworkAclAssociationId": "aclassoc-0fedcba9876543210", + "NetworkAclId": "acl-0abc1234def567890", + "SubnetId": "subnet-0e99b93155EXAMPLE" + } + ], + "Entries": [ + { + "RuleNumber": 100, + "Protocol": "6", + "RuleAction": "allow", + "Egress": false, + "CidrBlock": "0.0.0.0/0" + } + ] + } + ] + } + +**Example 9: To restore the subnet's default network ACL association** + +The following ``replace-network-acl-association`` example re-associates the subnet with its default network ACL before the custom ACL is deleted. :: + + aws ec2 replace-network-acl-association \ + --association-id aclassoc-0fedcba9876543210 \ + --network-acl-id acl-0default1234567890 + +Output:: + + { + "NewAssociationId": "aclassoc-0123456789abcdef0" + } + +**Example 10: To delete the custom network ACL** + +The following ``delete-network-acl`` example deletes the specified custom network ACL after the subnet has been moved back to the default ACL. :: + + aws ec2 delete-network-acl \ + --network-acl-id acl-0abc1234def567890 + +**Example 11: To delete the subnet** + +The following ``delete-subnet`` example deletes the specified subnet. :: + + aws ec2 delete-subnet \ + --subnet-id subnet-0e99b93155EXAMPLE + +**Example 12: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after all dependent resources have been removed. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/subnet-route-association-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/subnet-route-association-lifecycle.rst new file mode 100644 index 0000000..344cd4c --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/subnet-route-association-lifecycle.rst @@ -0,0 +1,185 @@ +**Example 1: To create a VPC for the subnet-route-table association workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-vpc \ + --cidr-block 10.3.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-subnet-association-workflow-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.3.0.0/16", + "DhcpOptionsId": "dopt-5EXAMPLE", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "InstanceTenancy": "default", + "Ipv6CidrBlockAssociationSet": [], + "CidrBlockAssociationSet": [ + { + "AssociationId": "vpc-cidr-assoc-07501b79ecEXAMPLE", + "CidrBlock": "10.3.0.0/16", + "CidrBlockState": { + "State": "associated" + } + } + ], + "IsDefault": false, + "Tags": [ + { + "Key": "Name", + "Value": "my-subnet-association-workflow-vpc" + } + ] + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a subnet in the VPC** + +The following ``create-subnet`` example creates a subnet in the specified VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-subnet \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --cidr-block 10.3.1.0/24 \ + --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-associated-subnet}]' + +Output:: + + { + "Subnet": { + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "CidrBlock": "10.3.1.0/24", + "State": "available", + "Tags": [ + { + "Key": "Name", + "Value": "my-associated-subnet" + } + ] + } + } + +**Example 4: To create a route table in the VPC** + +The following ``create-route-table`` example creates a route table for the specified VPC and applies a Name tag. :: + + aws ec2 create-route-table \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=my-associated-route-table}]' + +Output:: + + { + "RouteTable": { + "Associations": [], + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "PropagatingVgws": [], + "Tags": [ + { + "Key": "Name", + "Value": "my-associated-route-table" + } + ], + "Routes": [ + { + "GatewayId": "local", + "DestinationCidrBlock": "10.3.0.0/16", + "State": "active" + } + ] + } + } + +**Example 5: To associate the route table with the subnet** + +The following ``associate-route-table`` example associates the specified route table with the specified subnet. :: + + aws ec2 associate-route-table \ + --route-table-id rtb-22574640 \ + --subnet-id subnet-0e99b93155EXAMPLE + +Output:: + + { + "AssociationId": "rtbassoc-0abcdef1234567890", + "AssociationState": { + "State": "associated" + } + } + +**Example 6: To describe the route table and confirm the subnet association** + +The following ``describe-route-tables`` example retrieves details about the route table to confirm that the subnet association was created successfully. :: + + aws ec2 describe-route-tables \ + --route-table-ids rtb-22574640 + +Output:: + + { + "RouteTables": [ + { + "Associations": [ + { + "Main": false, + "RouteTableAssociationId": "rtbassoc-0abcdef1234567890", + "RouteTableId": "rtb-22574640", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "AssociationState": { + "State": "associated" + } + } + ], + "PropagatingVgws": [], + "RouteTableId": "rtb-22574640", + "Routes": [ + { + "DestinationCidrBlock": "10.3.0.0/16", + "GatewayId": "local", + "Origin": "CreateRouteTable", + "State": "active" + } + ], + "VpcId": "vpc-0a60eb65b4EXAMPLE" + } + ] + } + +**Example 7: To disassociate the route table from the subnet** + +The following ``disassociate-route-table`` example removes the subnet association from the route table. :: + + aws ec2 disassociate-route-table \ + --association-id rtbassoc-0abcdef1234567890 + +**Example 8: To delete the subnet** + +The following ``delete-subnet`` example deletes the specified subnet. :: + + aws ec2 delete-subnet \ + --subnet-id subnet-0e99b93155EXAMPLE + +**Example 9: To delete the route table** + +The following ``delete-route-table`` example deletes the specified route table. :: + + aws ec2 delete-route-table \ + --route-table-id rtb-22574640 + +**Example 10: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the dependent resources have been removed. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/subnet-route-to-igw-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/subnet-route-to-igw-lifecycle.rst new file mode 100644 index 0000000..bbc0fa8 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/subnet-route-to-igw-lifecycle.rst @@ -0,0 +1,247 @@ +**Example 1: To create a VPC for the public-subnet route workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: + + aws ec2 create-vpc \ + --cidr-block 10.6.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-public-route-vpc}]' + +Output:: + + { + "Vpc": { + "CidrBlock": "10.6.0.0/16", + "DhcpOptionsId": "dopt-5EXAMPLE", + "State": "pending", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "OwnerId": "123456789012", + "InstanceTenancy": "default", + "Ipv6CidrBlockAssociationSet": [], + "CidrBlockAssociationSet": [ + { + "AssociationId": "vpc-cidr-assoc-07501b79ecEXAMPLE", + "CidrBlock": "10.6.0.0/16", + "CidrBlockState": { + "State": "associated" + } + } + ], + "IsDefault": false, + "Tags": [ + { + "Key": "Name", + "Value": "my-public-route-vpc" + } + ] + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a subnet in the VPC** + +The following ``create-subnet`` example creates a subnet in the specified VPC and applies a Name tag. :: + + aws ec2 create-subnet \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --cidr-block 10.6.1.0/24 \ + --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-public-subnet}]' + +Output:: + + { + "Subnet": { + "SubnetId": "subnet-0e99b93155EXAMPLE", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "CidrBlock": "10.6.1.0/24", + "State": "available", + "Tags": [ + { + "Key": "Name", + "Value": "my-public-subnet" + } + ] + } + } + +**Example 4: To create an internet gateway** + +The following ``create-internet-gateway`` example creates an internet gateway and applies a Name tag. :: + + aws ec2 create-internet-gateway \ + --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=my-public-igw}]' + +Output:: + + { + "InternetGateway": { + "Attachments": [], + "InternetGatewayId": "igw-0d0fb496b3EXAMPLE", + "OwnerId": "123456789012", + "Tags": [ + { + "Key": "Name", + "Value": "my-public-igw" + } + ] + } + } + +**Example 5: To attach the internet gateway to the VPC** + +The following ``attach-internet-gateway`` example attaches the specified internet gateway to the specified VPC. :: + + aws ec2 attach-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +**Example 6: To create a route table for the VPC** + +The following ``create-route-table`` example creates a route table for the specified VPC and applies a Name tag. :: + + aws ec2 create-route-table \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=my-public-route-table}]' + +Output:: + + { + "RouteTable": { + "Associations": [], + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "PropagatingVgws": [], + "Tags": [ + { + "Key": "Name", + "Value": "my-public-route-table" + } + ], + "Routes": [ + { + "GatewayId": "local", + "DestinationCidrBlock": "10.6.0.0/16", + "State": "active" + } + ] + } + } + +**Example 7: To create a default route through the internet gateway** + +The following ``create-route`` example adds a default route that targets the attached internet gateway. :: + + aws ec2 create-route \ + --route-table-id rtb-22574640 \ + --destination-cidr-block 0.0.0.0/0 \ + --gateway-id igw-0d0fb496b3EXAMPLE + +Output:: + + { + "Return": true + } + +**Example 8: To associate the route table with the subnet** + +The following ``associate-route-table`` example associates the route table with the specified subnet. :: + + aws ec2 associate-route-table \ + --route-table-id rtb-22574640 \ + --subnet-id subnet-0e99b93155EXAMPLE + +Output:: + + { + "AssociationId": "rtbassoc-0abcdef1234567890", + "AssociationState": { + "State": "associated" + } + } + +**Example 9: To describe the route table and confirm both the default route and the subnet association** + +The following ``describe-route-tables`` example retrieves details about the route table to confirm that the internet route is active and that the subnet association exists. :: + + aws ec2 describe-route-tables \ + --route-table-ids rtb-22574640 + +Output:: + + { + "RouteTables": [ + { + "Associations": [ + { + "Main": false, + "RouteTableAssociationId": "rtbassoc-0abcdef1234567890", + "RouteTableId": "rtb-22574640", + "SubnetId": "subnet-0e99b93155EXAMPLE", + "AssociationState": { + "State": "associated" + } + } + ], + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "Routes": [ + { + "DestinationCidrBlock": "10.6.0.0/16", + "GatewayId": "local", + "Origin": "CreateRouteTable", + "State": "active" + }, + { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": "igw-0d0fb496b3EXAMPLE", + "Origin": "CreateRoute", + "State": "active" + } + ] + } + ] + } + +**Example 10: To clean up the public-subnet route workflow** + +The following ``disassociate-route-table`` example removes the subnet association. :: + + aws ec2 disassociate-route-table \ + --association-id rtbassoc-0abcdef1234567890 + +The following ``delete-route`` example removes the default route. :: + + aws ec2 delete-route \ + --route-table-id rtb-22574640 \ + --destination-cidr-block 0.0.0.0/0 + +The following ``delete-route-table`` example deletes the route table. :: + + aws ec2 delete-route-table \ + --route-table-id rtb-22574640 + +The following ``delete-subnet`` example deletes the subnet. :: + + aws ec2 delete-subnet \ + --subnet-id subnet-0e99b93155EXAMPLE + +The following ``detach-internet-gateway`` example detaches the internet gateway from the VPC. :: + + aws ec2 detach-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE \ + --vpc-id vpc-0a60eb65b4EXAMPLE + +The following ``delete-internet-gateway`` example deletes the detached internet gateway. :: + + aws ec2 delete-internet-gateway \ + --internet-gateway-id igw-0d0fb496b3EXAMPLE + +The following ``delete-vpc`` example deletes the VPC after all dependent resources have been removed. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/vpc-endpoint-route-table-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/vpc-endpoint-route-table-lifecycle.rst new file mode 100644 index 0000000..2fb2d95 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/vpc-endpoint-route-table-lifecycle.rst @@ -0,0 +1,135 @@ +**Example 1: To create a VPC for the VPC endpoint route-table workflow** + +The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block. :: + + aws ec2 create-vpc \ + --cidr-block 10.9.0.0/16 \ + --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-endpoint-vpc}]' + +Output:: + + { + "Vpc": { + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "CidrBlock": "10.9.0.0/16", + "State": "pending" + } + } + +**Example 2: To wait for the VPC to become available** + +The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + + aws ec2 wait vpc-available \ + --vpc-ids vpc-0a60eb65b4EXAMPLE + +**Example 3: To create a route table in the VPC** + +The following ``create-route-table`` example creates a route table for the specified VPC. :: + + aws ec2 create-route-table \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=my-endpoint-route-table}]' + +Output:: + + { + "RouteTable": { + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE" + } + } + +**Example 4: To create a gateway VPC endpoint associated with the route table** + +The following ``create-vpc-endpoint`` example creates a gateway VPC endpoint for the specified service and associates it with the specified route table. :: + + aws ec2 create-vpc-endpoint \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ + --service-name com.amazonaws.us-east-1.s3 \ + --vpc-endpoint-type Gateway \ + --route-table-ids rtb-22574640 + +Output:: + + { + "VpcEndpoint": { + "VpcEndpointId": "vpce-0abc1234def567890", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "ServiceName": "com.amazonaws.us-east-1.s3", + "VpcEndpointType": "Gateway", + "State": "available", + "RouteTableIds": [ + "rtb-22574640" + ] + } + } + +**Example 5: To describe the VPC endpoint and confirm route-table association** + +The following ``describe-vpc-endpoints`` example retrieves details about the VPC endpoint to confirm that it is associated with the expected route table. :: + + aws ec2 describe-vpc-endpoints \ + --vpc-endpoint-ids vpce-0abc1234def567890 + +Output:: + + { + "VpcEndpoints": [ + { + "VpcEndpointId": "vpce-0abc1234def567890", + "VpcId": "vpc-0a60eb65b4EXAMPLE", + "ServiceName": "com.amazonaws.us-east-1.s3", + "VpcEndpointType": "Gateway", + "State": "available", + "RouteTableIds": [ + "rtb-22574640" + ] + } + ] + } + +**Example 6: To describe the route table after endpoint association** + +The following ``describe-route-tables`` example retrieves details about the route table after the endpoint has been associated with it. :: + + aws ec2 describe-route-tables \ + --route-table-ids rtb-22574640 + +Output:: + + { + "RouteTables": [ + { + "RouteTableId": "rtb-22574640", + "VpcId": "vpc-0a60eb65b4EXAMPLE" + } + ] + } + +**Example 7: To delete the VPC endpoint** + +The following ``delete-vpc-endpoints`` example deletes the specified VPC endpoint. :: + + aws ec2 delete-vpc-endpoints \ + --vpc-endpoint-ids vpce-0abc1234def567890 + +Output:: + + { + "Unsuccessful": [] + } + +**Example 8: To delete the route table** + +The following ``delete-route-table`` example deletes the specified route table. :: + + aws ec2 delete-route-table \ + --route-table-id rtb-22574640 + +**Example 9: To delete the VPC** + +The following ``delete-vpc`` example deletes the specified VPC after the dependent resources have been removed. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file From 9401a964c8def0de44210a4599f30f2eefbb8433 Mon Sep 17 00:00:00 2001 From: Audrey Lin Date: Fri, 27 Mar 2026 01:55:51 -0400 Subject: [PATCH 3/5] test: add security group probe script and output log --- emulators/aws-ec2/tests/cli/ec2/sg_probe.sh | 91 +++++++++++++++++++ .../aws-ec2/tests/cli/ec2/sg_probe_output.txt | 55 +++++++++++ 2 files changed, 146 insertions(+) create mode 100755 emulators/aws-ec2/tests/cli/ec2/sg_probe.sh create mode 100644 emulators/aws-ec2/tests/cli/ec2/sg_probe_output.txt diff --git a/emulators/aws-ec2/tests/cli/ec2/sg_probe.sh b/emulators/aws-ec2/tests/cli/ec2/sg_probe.sh new file mode 100755 index 0000000..3c6841d --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/sg_probe.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -u + +echo "== create vpc ==" +VPC_ID=$(awscli ec2 create-vpc --cidr-block 10.20.0.0/16 | python3 -c "import sys,json; print(json.load(sys.stdin)['Vpc']['VpcId'])") +echo "VPC_ID=$VPC_ID" + +awscli ec2 wait vpc-available --vpc-ids "$VPC_ID" + +echo +echo "== create security group ==" +SG_ID=$(awscli ec2 create-security-group \ + --group-name sg-probe-group \ + --description "probe security group parsing" \ + --vpc-id "$VPC_ID" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['GroupId'])") +echo "SG_ID=$SG_ID" + +echo +echo "== probe 1: simple ingress args ==" +awscli ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 +echo "probe 1 exit code: $?" + +echo +echo "== describe security groups after probe 1 ==" +awscli ec2 describe-security-groups --group-ids "$SG_ID" + +echo +echo "== probe 2: shorthand ip-permissions ingress ==" +awscli ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --ip-permissions "IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=0.0.0.0/0}]" +echo "probe 2 exit code: $?" + +echo +echo "== describe security groups after probe 2 ==" +awscli ec2 describe-security-groups --group-ids "$SG_ID" + +echo +echo "== write ip_permissions.json ==" +cat > ip_permissions.json <<'EOF' +[ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 22, + "IpRanges": [ + { + "CidrIp": "0.0.0.0/0" + } + ] + } +] +EOF + +cat ip_permissions.json + +echo +echo "== probe 3: file:// ip-permissions ingress ==" +awscli ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --ip-permissions file://ip_permissions.json +echo "probe 3 exit code: $?" + +echo +echo "== describe security groups after probe 3 ==" +awscli ec2 describe-security-groups --group-ids "$SG_ID" + +echo +echo "== probe 4: shorthand ip-permissions egress ==" +awscli ec2 authorize-security-group-egress \ + --group-id "$SG_ID" \ + --ip-permissions "IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0}]" +echo "probe 4 exit code: $?" + +echo +echo "== describe security groups after probe 4 ==" +awscli ec2 describe-security-groups --group-ids "$SG_ID" + +echo +echo "== cleanup ==" +awscli ec2 delete-security-group --group-id "$SG_ID" +awscli ec2 delete-vpc --vpc-id "$VPC_ID" +rm -f ip_permissions.json + +echo +echo "done" \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/sg_probe_output.txt b/emulators/aws-ec2/tests/cli/ec2/sg_probe_output.txt new file mode 100644 index 0000000..44f049a --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/sg_probe_output.txt @@ -0,0 +1,55 @@ +== create vpc == +VPC_ID=vpc-c0ec0d5143414152a + +== create security group == +SG_ID=sg-de7f70a7aa134297b + +== probe 1: simple ingress args == + +An error occurred (MissingParameter) when calling the AuthorizeSecurityGroupIngress operation: Missing required parameter: IpPermissions +probe 1 exit code: 255 + +== describe security groups after probe 1 == + +== probe 2: shorthand ip-permissions ingress == + +An error occurred (MissingParameter) when calling the AuthorizeSecurityGroupIngress operation: Missing required parameter: IpPermissions +probe 2 exit code: 255 + +== describe security groups after probe 2 == + +== write ip_permissions.json == +[ + { + "IpProtocol": "tcp", + "FromPort": 22, + "ToPort": 22, + "IpRanges": [ + { + "CidrIp": "0.0.0.0/0" + } + ] + } +] + +== probe 3: file:// ip-permissions ingress == + +An error occurred (MissingParameter) when calling the AuthorizeSecurityGroupIngress operation: Missing required parameter: IpPermissions +probe 3 exit code: 255 + +== describe security groups after probe 3 == + +== probe 4: shorthand ip-permissions egress == + +An error occurred (MissingParameter) when calling the AuthorizeSecurityGroupEgress operation: Missing required parameter: IpPermissions +probe 4 exit code: 255 + +== describe security groups after probe 4 == + +== cleanup == + +An error occurred (DependencyViolation) when calling the DeleteSecurityGroup operation: SecurityGroup has VPC associations and cannot be deleted. + +An error occurred (DependencyViolation) when calling the DeleteVpc operation: Vpc has dependent SecurityGroup(s) and cannot be deleted. + +done From 0c7c142fbf71b0c2e62ed291b0d97c17aa014015 Mon Sep 17 00:00:00 2001 From: audreyll <90126440+audreyll2@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:40:26 -0400 Subject: [PATCH 4/5] Added SG probe/readback rst --- ...ork-interface-security-group-lifecycle.rst | 171 +++++------- .../ec2/security-group-instance-lifecycle.rst | 249 ++++++------------ .../ec2/security-group-parameter-probe.rst | 104 ++++++++ .../ec2/security-group-readback-lifecycle.rst | 95 +++++++ emulators/aws-ec2/tests/cli/ec2/sg_probe.sh | 26 +- 5 files changed, 366 insertions(+), 279 deletions(-) create mode 100644 emulators/aws-ec2/tests/cli/ec2/security-group-parameter-probe.rst create mode 100644 emulators/aws-ec2/tests/cli/ec2/security-group-readback-lifecycle.rst diff --git a/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst index 3fad401..f536184 100644 --- a/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst +++ b/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst @@ -1,139 +1,104 @@ -**Example 1: To create a VPC for the network-interface security-group workflow** +Network Interface Security Group Lifecycle +========================================== -The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block. :: +The following examples validate the lifecycle of attaching a security group to a network +interface and reading that relationship back through ``describe-network-interfaces``. + +**Example 1: To create a VPC** + +The following ``create-vpc`` example creates the VPC used by this lifecycle. :: aws ec2 create-vpc \ - --cidr-block 10.8.0.0/16 \ - --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-eni-vpc}]' + --cidr-block 10.33.0.0/16 -Output:: +Expected behavior: +- A VPC is created successfully. - { - "Vpc": { - "CidrBlock": "10.8.0.0/16", - "State": "pending", - "VpcId": "vpc-0a60eb65b4EXAMPLE" - } - } +**Example 2: To create a subnet in the VPC** -**Example 2: To wait for the VPC to become available** +The following ``create-subnet`` example creates a subnet for the network interface. :: -The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: + aws ec2 create-subnet \ + --vpc-id vpc-1234567890abcdef0 \ + --cidr-block 10.33.1.0/24 - aws ec2 wait vpc-available \ - --vpc-ids vpc-0a60eb65b4EXAMPLE +Expected behavior: +- A subnet is created successfully. -**Example 3: To create a subnet in the VPC** +**Example 3: To create a security group in the VPC** -The following ``create-subnet`` example creates a subnet in the specified VPC. :: +The following ``create-security-group`` example creates a security group for later ENI +association. :: - aws ec2 create-subnet \ - --vpc-id vpc-0a60eb65b4EXAMPLE \ - --cidr-block 10.8.1.0/24 \ - --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-eni-subnet}]' + aws ec2 create-security-group \ + --group-name sg-eni-lifecycle \ + --description "network interface security group lifecycle" \ + --vpc-id vpc-1234567890abcdef0 -Output:: +Expected behavior: +- A security group is created successfully. - { - "Subnet": { - "SubnetId": "subnet-0e99b93155EXAMPLE", - "VpcId": "vpc-0a60eb65b4EXAMPLE", - "CidrBlock": "10.8.1.0/24", - "State": "available" - } - } +**Example 4: To create a network interface with the security group** -**Example 4: To create a security group in the VPC** +The following ``create-network-interface`` example creates an ENI and attaches the security +group at creation time. :: -The following ``create-security-group`` example creates a security group in the specified VPC. :: + aws ec2 create-network-interface \ + --subnet-id subnet-1234567890abcdef0 \ + --groups sg-1234567890abcdef0 - aws ec2 create-security-group \ - --group-name my-eni-sg \ - --description "Security group for ENI workflow" \ - --vpc-id vpc-0a60eb65b4EXAMPLE +Expected behavior: +- A network interface is created successfully. +- The network interface stores the security group association. -Output:: +**Example 5: To describe the network interface and verify the security group** - { - "GroupId": "sg-0abc1234def567890" - } +The following ``describe-network-interfaces`` example reads back the ENI and verifies that +the security group relationship is present. :: -**Example 5: To create a network interface in the subnet with the security group** + aws ec2 describe-network-interfaces \ + --network-interface-ids eni-1234567890abcdef0 -The following ``create-network-interface`` example creates a network interface in the specified subnet and attaches the specified security group. :: +Expected behavior: +- The response includes the target ENI. +- The response includes the associated security group in the ENI group list. - aws ec2 create-network-interface \ - --subnet-id subnet-0e99b93155EXAMPLE \ - --groups sg-0abc1234def567890 \ - --description "ENI for workflow validation" - -Output:: - - { - "NetworkInterface": { - "NetworkInterfaceId": "eni-0abc1234def567890", - "SubnetId": "subnet-0e99b93155EXAMPLE", - "VpcId": "vpc-0a60eb65b4EXAMPLE", - "Status": "available", - "Groups": [ - { - "GroupId": "sg-0abc1234def567890", - "GroupName": "my-eni-sg" - } - ] - } - } - -**Example 6: To describe the network interface and confirm subnet and security group relationships** - -The following ``describe-network-interfaces`` example retrieves details about the network interface to confirm that it belongs to the specified subnet and VPC and references the specified security group. :: +**Example 6: To delete the network interface after validation** - aws ec2 describe-network-interfaces \ - --network-interface-ids eni-0abc1234def567890 - -Output:: - - { - "NetworkInterfaces": [ - { - "NetworkInterfaceId": "eni-0abc1234def567890", - "SubnetId": "subnet-0e99b93155EXAMPLE", - "VpcId": "vpc-0a60eb65b4EXAMPLE", - "Status": "available", - "Groups": [ - { - "GroupId": "sg-0abc1234def567890", - "GroupName": "my-eni-sg" - } - ] - } - ] - } - -**Example 7: To delete the network interface** - -The following ``delete-network-interface`` example deletes the specified network interface. :: +The following ``delete-network-interface`` example removes the ENI. :: aws ec2 delete-network-interface \ - --network-interface-id eni-0abc1234def567890 + --network-interface-id eni-1234567890abcdef0 + +Expected behavior: +- The network interface is deleted successfully. -**Example 8: To delete the security group** +**Example 7: To delete the security group after validation** -The following ``delete-security-group`` example deletes the specified security group after the network interface has been removed. :: +The following ``delete-security-group`` example removes the security group. :: aws ec2 delete-security-group \ - --group-id sg-0abc1234def567890 + --group-id sg-1234567890abcdef0 -**Example 9: To delete the subnet** +Expected behavior: +- The security group is deleted successfully. -The following ``delete-subnet`` example deletes the specified subnet. :: +**Example 8: To delete the subnet after validation** + +The following ``delete-subnet`` example removes the subnet. :: aws ec2 delete-subnet \ - --subnet-id subnet-0e99b93155EXAMPLE + --subnet-id subnet-1234567890abcdef0 + +Expected behavior: +- The subnet is deleted successfully. -**Example 10: To delete the VPC** +**Example 9: To delete the VPC after validation** -The following ``delete-vpc`` example deletes the specified VPC after the dependent resources have been removed. :: +The following ``delete-vpc`` example removes the VPC. :: aws ec2 delete-vpc \ - --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file + --vpc-id vpc-1234567890abcdef0 + +Expected behavior: +- The VPC is deleted successfully. \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst index f8cf238..917bf35 100644 --- a/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst +++ b/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst @@ -1,208 +1,131 @@ -**Example 1: To create a VPC for the security-group and instance workflow** +Security Group Instance Lifecycle +================================= -The following ``create-vpc`` example creates a VPC with the specified IPv4 CIDR block and applies a Name tag. :: +The following examples validate the lifecycle of creating a security group rule and then +launching an instance in a subnet with that security group attached. - aws ec2 create-vpc \ - --cidr-block 10.7.0.0/16 \ - --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-sg-instance-vpc}]' +**Example 1: To create a VPC** -Output:: +The following ``create-vpc`` example creates the VPC used by this lifecycle. :: - { - "Vpc": { - "CidrBlock": "10.7.0.0/16", - "State": "pending", - "VpcId": "vpc-0a60eb65b4EXAMPLE" - } - } + aws ec2 create-vpc \ + --cidr-block 10.34.0.0/16 -**Example 2: To wait for the VPC to become available** +Expected behavior: +- A VPC is created successfully. -The following ``wait vpc-available`` example pauses and resumes running only after it confirms that the specified VPC is available. :: +**Example 2: To create a subnet in the VPC** - aws ec2 wait vpc-available \ - --vpc-ids vpc-0a60eb65b4EXAMPLE +The following ``create-subnet`` example creates a subnet for the instance launch. :: -**Example 3: To create a subnet in the VPC** + aws ec2 create-subnet \ + --vpc-id vpc-1234567890abcdef0 \ + --cidr-block 10.34.1.0/24 -The following ``create-subnet`` example creates a subnet in the specified VPC. :: +Expected behavior: +- A subnet is created successfully. - aws ec2 create-subnet \ - --vpc-id vpc-0a60eb65b4EXAMPLE \ - --cidr-block 10.7.1.0/24 \ - --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-instance-subnet}]' +**Example 3: To create a security group in the VPC** + +The following ``create-security-group`` example creates a security group for the instance. :: -Output:: + aws ec2 create-security-group \ + --group-name sg-instance-lifecycle \ + --description "security group instance lifecycle" \ + --vpc-id vpc-1234567890abcdef0 - { - "Subnet": { - "SubnetId": "subnet-0e99b93155EXAMPLE", - "VpcId": "vpc-0a60eb65b4EXAMPLE", - "CidrBlock": "10.7.1.0/24", - "State": "available" - } - } +Expected behavior: +- A security group is created successfully. -**Example 4: To create a security group in the VPC** +**Example 4: To authorize an ingress rule for the security group** -The following ``create-security-group`` example creates a security group in the specified VPC. :: +The following ``authorize-security-group-ingress`` example adds a TCP/22 rule to the +security group. :: - aws ec2 create-security-group \ - --group-name my-instance-sg \ - --description "Security group for instance workflow" \ - --vpc-id vpc-0a60eb65b4EXAMPLE \ - --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=my-instance-sg}]' + aws ec2 authorize-security-group-ingress \ + --group-id sg-1234567890abcdef0 \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 -Output:: +Expected behavior: +- The request succeeds. +- The ingress rule is stored on the security group. - { - "GroupId": "sg-0abc1234def567890", - "Tags": [ - { - "Key": "Name", - "Value": "my-instance-sg" - } - ] - } +**Example 5: To describe the security group and verify the rule** -**Example 5: To authorize inbound SSH access** +The following ``describe-security-groups`` example verifies the rule was persisted. :: -The following ``authorize-security-group-ingress`` example adds an inbound SSH rule to the security group. :: + aws ec2 describe-security-groups \ + --group-ids sg-1234567890abcdef0 - aws ec2 authorize-security-group-ingress \ - --group-id sg-0abc1234def567890 \ - --ip-permissions IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges='[{CidrIp=0.0.0.0/0,Description=SSH}]' +Expected behavior: +- The response includes the target security group. +- The response includes the TCP/22 ingress rule. **Example 6: To run an instance in the subnet with the security group** -The following ``run-instances`` example launches an instance in the specified subnet with the specified security group. :: +The following ``run-instances`` example launches an instance into the subnet with the +security group attached. :: aws ec2 run-instances \ - --image-id ami-12345678 \ + --image-id ami-1234567890abcdef0 \ --instance-type t2.micro \ - --subnet-id subnet-0e99b93155EXAMPLE \ - --security-group-ids sg-0abc1234def567890 \ - --min-count 1 \ - --max-count 1 \ - --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=my-workflow-instance}]' - -Output:: - - { - "Instances": [ - { - "InstanceId": "i-0123456789abcdef0", - "SubnetId": "subnet-0e99b93155EXAMPLE", - "VpcId": "vpc-0a60eb65b4EXAMPLE", - "State": { - "Name": "pending" - }, - "SecurityGroups": [ - { - "GroupId": "sg-0abc1234def567890", - "GroupName": "my-instance-sg" - } - ] - } - ] - } - -**Example 7: To wait for the instance to enter the running state** - -The following ``wait instance-running`` example pauses and resumes running only after it confirms that the specified instance is in the ``running`` state. :: - - aws ec2 wait instance-running \ - --instance-ids i-0123456789abcdef0 - -**Example 8: To describe the instance and confirm subnet and security group attachment** - -The following ``describe-instances`` example retrieves details about the instance to confirm that it belongs to the specified subnet and uses the specified security group. :: + --subnet-id subnet-1234567890abcdef0 \ + --security-group-ids sg-1234567890abcdef0 \ + --count 1 + +Expected behavior: +- The instance is launched successfully. +- The instance records the target security group association. + +**Example 7: To describe the instance and verify the security group** + +The following ``describe-instances`` example verifies the instance security group attachment. :: aws ec2 describe-instances \ - --instance-ids i-0123456789abcdef0 - -Output:: - - { - "Reservations": [ - { - "Instances": [ - { - "InstanceId": "i-0123456789abcdef0", - "SubnetId": "subnet-0e99b93155EXAMPLE", - "VpcId": "vpc-0a60eb65b4EXAMPLE", - "State": { - "Name": "running" - }, - "SecurityGroups": [ - { - "GroupId": "sg-0abc1234def567890", - "GroupName": "my-instance-sg" - } - ] - } - ] - } - ] - } - -**Example 9: To describe the security group and confirm the ingress rule configuration** - -The following ``describe-security-groups`` example retrieves details about the security group to confirm that the expected ingress rule is present. :: + --instance-ids i-1234567890abcdef0 - aws ec2 describe-security-groups \ - --group-ids sg-0abc1234def567890 - -Output:: - - { - "SecurityGroups": [ - { - "GroupId": "sg-0abc1234def567890", - "GroupName": "my-instance-sg", - "VpcId": "vpc-0a60eb65b4EXAMPLE", - "IpPermissions": [ - { - "IpProtocol": "tcp", - "FromPort": 22, - "ToPort": 22 - } - ] - } - ] - } - -**Example 10: To terminate the instance** - -The following ``terminate-instances`` example terminates the specified instance. :: +Expected behavior: +- The response includes the target instance. +- The response includes the target security group in the instance security group list. - aws ec2 terminate-instances \ - --instance-ids i-0123456789abcdef0 +**Example 8: To terminate the instance after validation** -**Example 11: To wait for the instance to terminate** +The following ``terminate-instances`` example terminates the instance. :: -The following ``wait instance-terminated`` example pauses and resumes running only after it confirms that the specified instance is in the ``terminated`` state. :: + aws ec2 terminate-instances \ + --instance-ids i-1234567890abcdef0 - aws ec2 wait instance-terminated \ - --instance-ids i-0123456789abcdef0 +Expected behavior: +- The instance is terminated successfully. -**Example 12: To delete the security group** +**Example 9: To delete the security group after validation** -The following ``delete-security-group`` example deletes the specified security group after the instance has been terminated. :: +The following ``delete-security-group`` example removes the security group. :: aws ec2 delete-security-group \ - --group-id sg-0abc1234def567890 + --group-id sg-1234567890abcdef0 + +Expected behavior: +- The security group is deleted successfully. -**Example 13: To delete the subnet** +**Example 10: To delete the subnet after validation** -The following ``delete-subnet`` example deletes the specified subnet. :: +The following ``delete-subnet`` example removes the subnet. :: aws ec2 delete-subnet \ - --subnet-id subnet-0e99b93155EXAMPLE + --subnet-id subnet-1234567890abcdef0 -**Example 14: To delete the VPC** +Expected behavior: +- The subnet is deleted successfully. -The following ``delete-vpc`` example deletes the specified VPC after the dependent resources have been removed. :: +**Example 11: To delete the VPC after validation** + +The following ``delete-vpc`` example removes the VPC. :: aws ec2 delete-vpc \ - --vpc-id vpc-0a60eb65b4EXAMPLE \ No newline at end of file + --vpc-id vpc-1234567890abcdef0 + +Expected behavior: +- The VPC is deleted successfully. \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/security-group-parameter-probe.rst b/emulators/aws-ec2/tests/cli/ec2/security-group-parameter-probe.rst new file mode 100644 index 0000000..71931e8 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/security-group-parameter-probe.rst @@ -0,0 +1,104 @@ +Security Group Parameter Probe +============================= + +The following examples probe the input parsing paths for security group rule APIs, covering +simple-form parameters, shorthand ``--ip-permissions``, file-based ``--ip-permissions``, +and revoke flows. These examples are intended to verify that multiple valid AWS CLI input +forms are accepted and converted into usable rule objects. + +**Example 1: To authorize an ingress rule using simple-form parameters** + +The following ``authorize-security-group-ingress`` example adds a TCP/22 ingress rule using +``--protocol``, ``--port``, and ``--cidr``. :: + + aws ec2 authorize-security-group-ingress \ + --group-id sg-1234567890abcdef0 \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 + +Expected behavior: +- The request succeeds. +- A security group rule is created for TCP port 22 from ``0.0.0.0/0``. + +**Example 2: To authorize an ingress rule using shorthand ip-permissions** + +The following ``authorize-security-group-ingress`` example adds a TCP/80 ingress rule using +shorthand ``--ip-permissions`` syntax. :: + + aws ec2 authorize-security-group-ingress \ + --group-id sg-1234567890abcdef0 \ + --ip-permissions IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges=[{CidrIp=0.0.0.0/0}] + +Expected behavior: +- The request succeeds. +- A security group rule is created for TCP port 80 from ``0.0.0.0/0``. + +**Example 3: To authorize an ingress rule using file-based ip-permissions** + +The following ``authorize-security-group-ingress`` example adds a TCP/443 ingress rule using +a JSON file passed through ``file://``. Assume the file ``ip-permissions.json`` contains: :: + + [ + { + "IpProtocol": "tcp", + "FromPort": 443, + "ToPort": 443, + "IpRanges": [ + { "CidrIp": "0.0.0.0/0" } + ] + } + ] + +Run command: :: + + aws ec2 authorize-security-group-ingress \ + --group-id sg-1234567890abcdef0 \ + --ip-permissions file://ip-permissions.json + +Expected behavior: +- The request succeeds. +- A security group rule is created for TCP port 443 from ``0.0.0.0/0``. + +**Example 4: To authorize an egress rule using shorthand ip-permissions** + +The following ``authorize-security-group-egress`` example adds a TCP/443 egress rule using +shorthand ``--ip-permissions`` syntax. :: + + aws ec2 authorize-security-group-egress \ + --group-id sg-1234567890abcdef0 \ + --ip-permissions IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0}] + +Expected behavior: +- The request succeeds. +- An egress rule is created for TCP port 443 to ``0.0.0.0/0``. + +**Example 5: To revoke an ingress rule using basic-form parameters** + +The following ``revoke-security-group-ingress`` example removes a previously added TCP/22 +ingress rule. :: + + aws ec2 revoke-security-group-ingress \ + --group-id sg-1234567890abcdef0 \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 + +Expected behavior: +- The request succeeds. +- The matching TCP/22 ingress rule is removed. + +**Example 6: To revoke an egress rule using basic-form parameters** + +The following ``revoke-security-group-egress`` example removes a previously added TCP/443 +egress rule. :: + + aws ec2 revoke-security-group-egress \ + --group-id sg-1234567890abcdef0 \ + --protocol tcp \ + --port 443 \ + --cidr 0.0.0.0/0 + +Expected behavior: +- The request succeeds. +- The matching TCP/443 egress rule is removed. \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/security-group-readback-lifecycle.rst b/emulators/aws-ec2/tests/cli/ec2/security-group-readback-lifecycle.rst new file mode 100644 index 0000000..4cfc9c6 --- /dev/null +++ b/emulators/aws-ec2/tests/cli/ec2/security-group-readback-lifecycle.rst @@ -0,0 +1,95 @@ +Security Group Readback Lifecycle +================================= + +The following examples validate that security group rules can be written and then read back +through describe APIs. This lifecycle focuses on the path: + +create VPC -> create security group -> authorize rule -> describe rule state -> cleanup + +**Example 1: To create a VPC for security group readback checks** + +The following ``create-vpc`` example creates a VPC for subsequent security group operations. :: + + aws ec2 create-vpc \ + --cidr-block 10.31.0.0/16 + +Expected behavior: +- A VPC is created and returns a VPC ID. + +**Example 2: To create a security group inside the VPC** + +The following ``create-security-group`` example creates a security group in the VPC. :: + + aws ec2 create-security-group \ + --group-name sg-readback \ + --description "security group readback lifecycle" \ + --vpc-id vpc-1234567890abcdef0 + +Expected behavior: +- A security group is created and returns a group ID. + +**Example 3: To authorize an ingress rule in the security group** + +The following ``authorize-security-group-ingress`` example adds a TCP/22 rule. :: + + aws ec2 authorize-security-group-ingress \ + --group-id sg-1234567890abcdef0 \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 + +Expected behavior: +- The request succeeds. +- The ingress rule is stored on the security group. + +**Example 4: To describe the security group and verify the rule** + +The following ``describe-security-groups`` example reads back the security group definition. :: + + aws ec2 describe-security-groups \ + --group-ids sg-1234567890abcdef0 + +Expected behavior: +- The response includes the security group. +- The response includes an ingress permission with: + - protocol ``tcp`` + - from port ``22`` + - to port ``22`` + - CIDR ``0.0.0.0/0`` + +**Example 5: To describe the security group rules directly** + +The following ``describe-security-group-rules`` example reads back the individual rule objects. :: + + aws ec2 describe-security-group-rules + +Expected behavior: +- The response includes a security group rule associated with the target group ID. +- The response includes: + - ``GroupId = sg-1234567890abcdef0`` + - ``IsEgress = false`` + - ``IpProtocol = tcp`` + - ``FromPort = 22`` + - ``ToPort = 22`` + - ``CidrIpv4 = 0.0.0.0/0`` + +**Example 6: To delete the security group after validation** + +The following ``delete-security-group`` example removes the security group after readback +checks complete. :: + + aws ec2 delete-security-group \ + --group-id sg-1234567890abcdef0 + +Expected behavior: +- The security group is deleted successfully. + +**Example 7: To delete the VPC after validation** + +The following ``delete-vpc`` example removes the VPC after the security group is deleted. :: + + aws ec2 delete-vpc \ + --vpc-id vpc-1234567890abcdef0 + +Expected behavior: +- The VPC is deleted successfully. \ No newline at end of file diff --git a/emulators/aws-ec2/tests/cli/ec2/sg_probe.sh b/emulators/aws-ec2/tests/cli/ec2/sg_probe.sh index 3c6841d..d9cb532 100755 --- a/emulators/aws-ec2/tests/cli/ec2/sg_probe.sh +++ b/emulators/aws-ec2/tests/cli/ec2/sg_probe.sh @@ -2,14 +2,14 @@ set -u echo "== create vpc ==" -VPC_ID=$(awscli ec2 create-vpc --cidr-block 10.20.0.0/16 | python3 -c "import sys,json; print(json.load(sys.stdin)['Vpc']['VpcId'])") +VPC_ID=$(uv run awscli ec2 create-vpc --cidr-block 10.20.0.0/16 | python3 -c "import sys,json; print(json.load(sys.stdin)['Vpc']['VpcId'])") echo "VPC_ID=$VPC_ID" -awscli ec2 wait vpc-available --vpc-ids "$VPC_ID" +uv run awscli ec2 wait vpc-available --vpc-ids "$VPC_ID" echo echo "== create security group ==" -SG_ID=$(awscli ec2 create-security-group \ +SG_ID=$(uv run awscli ec2 create-security-group \ --group-name sg-probe-group \ --description "probe security group parsing" \ --vpc-id "$VPC_ID" \ @@ -18,7 +18,7 @@ echo "SG_ID=$SG_ID" echo echo "== probe 1: simple ingress args ==" -awscli ec2 authorize-security-group-ingress \ +uv run awscli ec2 authorize-security-group-ingress \ --group-id "$SG_ID" \ --protocol tcp \ --port 22 \ @@ -27,18 +27,18 @@ echo "probe 1 exit code: $?" echo echo "== describe security groups after probe 1 ==" -awscli ec2 describe-security-groups --group-ids "$SG_ID" +uv run awscli ec2 describe-security-groups --group-ids "$SG_ID" echo echo "== probe 2: shorthand ip-permissions ingress ==" -awscli ec2 authorize-security-group-ingress \ +uv run awscli ec2 authorize-security-group-ingress \ --group-id "$SG_ID" \ --ip-permissions "IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=0.0.0.0/0}]" echo "probe 2 exit code: $?" echo echo "== describe security groups after probe 2 ==" -awscli ec2 describe-security-groups --group-ids "$SG_ID" +uv run awscli ec2 describe-security-groups --group-ids "$SG_ID" echo echo "== write ip_permissions.json ==" @@ -61,30 +61,30 @@ cat ip_permissions.json echo echo "== probe 3: file:// ip-permissions ingress ==" -awscli ec2 authorize-security-group-ingress \ +uv run awscli ec2 authorize-security-group-ingress \ --group-id "$SG_ID" \ --ip-permissions file://ip_permissions.json echo "probe 3 exit code: $?" echo echo "== describe security groups after probe 3 ==" -awscli ec2 describe-security-groups --group-ids "$SG_ID" +uv run awscli ec2 describe-security-groups --group-ids "$SG_ID" echo echo "== probe 4: shorthand ip-permissions egress ==" -awscli ec2 authorize-security-group-egress \ +uv run awscli ec2 authorize-security-group-egress \ --group-id "$SG_ID" \ --ip-permissions "IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0}]" echo "probe 4 exit code: $?" echo echo "== describe security groups after probe 4 ==" -awscli ec2 describe-security-groups --group-ids "$SG_ID" +uv run awscli ec2 describe-security-groups --group-ids "$SG_ID" echo echo "== cleanup ==" -awscli ec2 delete-security-group --group-id "$SG_ID" -awscli ec2 delete-vpc --vpc-id "$VPC_ID" +uv run awscli ec2 delete-security-group --group-id "$SG_ID" +uv run awscli ec2 delete-vpc --vpc-id "$VPC_ID" rm -f ip_permissions.json echo From cef7f4c71e5527c70aea8f9581244c72e8b68af5 Mon Sep 17 00:00:00 2001 From: Audrey Lin Date: Fri, 3 Apr 2026 19:25:47 -0400 Subject: [PATCH 5/5] Fix ENI groupSet serialization and keep local SG patches --- .../services/elasticnetworkinterface.py | 144 +++++++- .../emulator_core/services/securitygroup.py | 341 ++++++++++++++++-- 2 files changed, 445 insertions(+), 40 deletions(-) diff --git a/emulators/aws-ec2/emulator_core/services/elasticnetworkinterface.py b/emulators/aws-ec2/emulator_core/services/elasticnetworkinterface.py index 721be02..882658a 100644 --- a/emulators/aws-ec2/emulator_core/services/elasticnetworkinterface.py +++ b/emulators/aws-ec2/emulator_core/services/elasticnetworkinterface.py @@ -368,8 +368,8 @@ def CreateNetworkInterface(self, params: Dict[str, Any]): if not sg: return create_error_response("InvalidGroup.NotFound", f"The ID '{group_id}' does not exist") group_set.append({ - "GroupId": group_id, - "GroupName": getattr(sg, "group_name", ""), + "groupId": group_id, + "groupName": getattr(sg, "group_name", ""), }) tag_set: List[Dict[str, Any]] = [] @@ -996,8 +996,8 @@ def ModifyNetworkInterfaceAttribute(self, params: Dict[str, Any]): if not sg: return create_error_response("InvalidGroup.NotFound", f"The ID '{group_id}' does not exist") group_set.append({ - "GroupId": group_id, - "GroupName": getattr(sg, "group_name", ""), + "groupId": group_id, + "groupName": getattr(sg, "group_name", ""), }) network_interface.group_set = group_set @@ -1378,6 +1378,27 @@ def _serialize_nested_fields(d: Dict[str, Any], indent_level: int) -> List[str]: xml_parts.append(f'{indent}<{key}>{esc(str(value))}') return xml_parts + + @staticmethod + def _serialize_group_set(group_set: List[Dict[str, Any]], indent_level: int) -> List[str]: + xml_parts = [] + indent = ' ' * indent_level + if group_set: + xml_parts.append(f'{indent}') + for item in group_set: + xml_parts.append(f'{indent} ') + group_id = item.get("groupId") or item.get("GroupId") or "" + group_name = item.get("groupName") or item.get("GroupName") or "" + if group_id != "": + xml_parts.append(f'{indent} {esc(str(group_id))}') + if group_name != "": + xml_parts.append(f'{indent} {esc(str(group_name))}') + xml_parts.append(f'{indent} ') + xml_parts.append(f'{indent}') + else: + xml_parts.append(f'{indent}') + return xml_parts + @staticmethod def serialize_assign_ipv6_addresses_response(data: Dict[str, Any], request_id: str) -> str: xml_parts = [] @@ -1516,6 +1537,26 @@ def serialize_attach_network_interface_response(data: Dict[str, Any], request_id xml_parts.append(f'') return "\n".join(xml_parts) + @staticmethod + def _serialize_group_set(group_set: List[Dict[str, Any]], indent_level: int) -> List[str]: + xml_parts = [] + indent = ' ' * indent_level + if group_set: + xml_parts.append(f'{indent}') + for item in group_set: + xml_parts.append(f'{indent} ') + group_id = item.get("groupId") or item.get("GroupId") or "" + group_name = item.get("groupName") or item.get("GroupName") or "" + if group_id != "": + xml_parts.append(f'{indent} {esc(str(group_id))}') + if group_name != "": + xml_parts.append(f'{indent} {esc(str(group_name))}') + xml_parts.append(f'{indent} ') + xml_parts.append(f'{indent}') + else: + xml_parts.append(f'{indent}') + return xml_parts + @staticmethod def serialize_create_network_interface_response(data: Dict[str, Any], request_id: str) -> str: xml_parts = [] @@ -1541,10 +1582,44 @@ def serialize_create_network_interface_response(data: Dict[str, Any], request_id param_data = data[_networkInterface_key] indent_str = " " * 1 xml_parts.append(f'{indent_str}') - xml_parts.extend(elasticnetworkinterface_ResponseSerializer._serialize_nested_fields(param_data, 2)) + for key, value in param_data.items(): + if key == "groupSet": + xml_parts.extend( + elasticnetworkinterface_ResponseSerializer._serialize_group_set( + value, 2 + ) + ) + elif value is None: + continue + elif isinstance(value, dict): + xml_parts.append(f' <{key}>') + xml_parts.extend( + elasticnetworkinterface_ResponseSerializer._serialize_nested_fields( + value, 3 + ) + ) + xml_parts.append(f' ') + elif isinstance(value, list): + xml_parts.append(f' <{key}>') + for item in value: + if isinstance(item, dict): + xml_parts.append(f' ') + xml_parts.extend( + elasticnetworkinterface_ResponseSerializer._serialize_nested_fields( + item, 4 + ) + ) + xml_parts.append(f' ') + else: + xml_parts.append(f' {esc(str(item))}') + xml_parts.append(f' ') + elif isinstance(value, bool): + xml_parts.append(f' <{key}>{str(value).lower()}') + else: + xml_parts.append(f' <{key}>{esc(str(value))}') xml_parts.append(f'{indent_str}') xml_parts.append(f'') - return "\n".join(xml_parts) + return "".join(xml_parts) @staticmethod def serialize_create_network_interface_permission_response(data: Dict[str, Any], request_id: str) -> str: @@ -1657,16 +1732,11 @@ def serialize_describe_network_interface_attribute_response(data: Dict[str, Any] _groupSet_key = "Groups" if _groupSet_key: param_data = data[_groupSet_key] - indent_str = " " * 1 - if param_data: - xml_parts.append(f'{indent_str}') - for item in param_data: - xml_parts.append(f'{indent_str} ') - xml_parts.extend(elasticnetworkinterface_ResponseSerializer._serialize_nested_fields(item, 2)) - xml_parts.append(f'{indent_str} ') - xml_parts.append(f'{indent_str}') - else: - xml_parts.append(f'{indent_str}') + xml_parts.extend( + elasticnetworkinterface_ResponseSerializer._serialize_group_set( + param_data, 1 + ) + ) # Serialize networkInterfaceId _networkInterfaceId_key = None if "networkInterfaceId" in data: @@ -1690,7 +1760,7 @@ def serialize_describe_network_interface_attribute_response(data: Dict[str, Any] xml_parts.extend(elasticnetworkinterface_ResponseSerializer._serialize_nested_fields(param_data, 2)) xml_parts.append(f'{indent_str}') xml_parts.append(f'') - return "\n".join(xml_parts) + return "".join(xml_parts) @staticmethod def serialize_describe_network_interface_permissions_response(data: Dict[str, Any], request_id: str) -> str: @@ -1748,7 +1818,43 @@ def serialize_describe_network_interfaces_response(data: Dict[str, Any], request xml_parts.append(f'{indent_str}') for item in param_data: xml_parts.append(f'{indent_str} ') - xml_parts.extend(elasticnetworkinterface_ResponseSerializer._serialize_nested_fields(item, 2)) + for key, value in item.items(): + if key == "groupSet": + xml_parts.extend( + elasticnetworkinterface_ResponseSerializer._serialize_group_set( + value, 3 + ) + ) + elif value is None: + continue + elif isinstance(value, dict): + xml_parts.append(f' <{key}>') + xml_parts.extend( + elasticnetworkinterface_ResponseSerializer._serialize_nested_fields( + value, 4 + ) + ) + xml_parts.append(f' ') + elif isinstance(value, list): + xml_parts.append(f' <{key}>') + for sub_item in value: + if isinstance(sub_item, dict): + xml_parts.append(f' ') + xml_parts.extend( + elasticnetworkinterface_ResponseSerializer._serialize_nested_fields( + sub_item, 5 + ) + ) + xml_parts.append(f' ') + else: + xml_parts.append( + f' {esc(str(sub_item))}' + ) + xml_parts.append(f' ') + elif isinstance(value, bool): + xml_parts.append(f' <{key}>{str(value).lower()}') + else: + xml_parts.append(f' <{key}>{esc(str(value))}') xml_parts.append(f'{indent_str} ') xml_parts.append(f'{indent_str}') else: @@ -1764,7 +1870,7 @@ def serialize_describe_network_interfaces_response(data: Dict[str, Any], request indent_str = " " * 1 xml_parts.append(f'{indent_str}{esc(str(param_data))}') xml_parts.append(f'') - return "\n".join(xml_parts) + return "".join(xml_parts) @staticmethod def serialize_detach_network_interface_response(data: Dict[str, Any], request_id: str) -> str: diff --git a/emulators/aws-ec2/emulator_core/services/securitygroup.py b/emulators/aws-ec2/emulator_core/services/securitygroup.py index 6710f81..0bad0c5 100644 --- a/emulators/aws-ec2/emulator_core/services/securitygroup.py +++ b/emulators/aws-ec2/emulator_core/services/securitygroup.py @@ -115,20 +115,241 @@ def _get_security_group_by_id_or_name(self, group_id: Optional[str], group_name: ) return None, create_error_response("MissingParameter", "GroupId is required.") + # def _normalize_ip_permissions(self, params: Dict[str, Any]) -> List[Dict[str, Any]]: + # ip_permissions = params.get("IpPermissions.N", []) or [] + # if not ip_permissions and params.get("IpProtocol"): + # ip_permissions = [ + # { + # "IpProtocol": params.get("IpProtocol", "-1"), + # "FromPort": int(params.get("FromPort") or 0), + # "ToPort": int(params.get("ToPort") or 0), + # "IpRanges": [ + # {"CidrIp": params.get("CidrIp", "0.0.0.0/0")} + # ], + # } + # ] + # return ip_permissions + + def _to_int_or_none(self, value: Any) -> Optional[int]: + if value is None or value == "": + return None + try: + return int(value) + except Exception: + return None + + def _permission_template(self) -> Dict[str, Any]: + return { + "IpProtocol": None, + "FromPort": None, + "ToPort": None, + "IpRanges": [], + "Ipv6Ranges": [], + "PrefixListIds": [], + "UserIdGroupPairs": [], + } + + def _normalize_permission_shape(self, perm: Dict[str, Any]) -> Dict[str, Any]: + """ + Ensure every permission has the canonical fields expected by downstream code. + """ + normalized = self._permission_template() + normalized["IpProtocol"] = perm.get("IpProtocol", perm.get("ipProtocol")) + normalized["FromPort"] = self._to_int_or_none(perm.get("FromPort", perm.get("fromPort"))) + normalized["ToPort"] = self._to_int_or_none(perm.get("ToPort", perm.get("toPort"))) + + ip_ranges = perm.get("IpRanges", []) or [] + ipv6_ranges = perm.get("Ipv6Ranges", []) or [] + prefix_lists = perm.get("PrefixListIds", []) or [] + user_groups = perm.get("UserIdGroupPairs", []) or [] + + normalized["IpRanges"] = ip_ranges if isinstance(ip_ranges, list) else [ip_ranges] + normalized["Ipv6Ranges"] = ipv6_ranges if isinstance(ipv6_ranges, list) else [ipv6_ranges] + normalized["PrefixListIds"] = prefix_lists if isinstance(prefix_lists, list) else [prefix_lists] + normalized["UserIdGroupPairs"] = user_groups if isinstance(user_groups, list) else [user_groups] + + if perm.get("Description") is not None: + normalized["Description"] = perm.get("Description") + + return normalized + + def _build_permission_from_simple_fields(self, params: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + Fallback for simple-form inputs such as: + --protocol tcp --port 22 --cidr 0.0.0.0/0 + + Depending on how the request reached the service, these may appear as top-level + IpProtocol / FromPort / ToPort / CidrIp fields. + """ + ip_protocol = params.get("IpProtocol") + from_port = params.get("FromPort") + to_port = params.get("ToPort") + cidr_ip = params.get("CidrIp") + cidr_ipv6 = params.get("CidrIpv6") + + if ip_protocol is None and cidr_ip is None and cidr_ipv6 is None: + return [] + + perm = self._permission_template() + perm["IpProtocol"] = ip_protocol if ip_protocol is not None else "-1" + perm["FromPort"] = self._to_int_or_none(from_port) + perm["ToPort"] = self._to_int_or_none(to_port) + + if cidr_ip is not None: + perm["IpRanges"].append({"CidrIp": cidr_ip}) + if cidr_ipv6 is not None: + perm["Ipv6Ranges"].append({"CidrIpv6": cidr_ipv6}) + + if params.get("Description") is not None: + perm["Description"] = params.get("Description") + + return [perm] + + def _parse_ip_permissions_raw(self, raw_params: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + Parse flattened AWS Query parameters such as: + IpPermissions.1.IpProtocol=tcp + IpPermissions.1.FromPort=22 + IpPermissions.1.ToPort=22 + IpPermissions.1.IpRanges.1.CidrIp=0.0.0.0/0 + + This is the critical fallback when get_indexed_list(md, "IpPermissions") + returns [] even though the CLI sent valid flattened parameters. + """ + if not raw_params: + return [] + + perms: Dict[int, Dict[str, Any]] = {} + ip_ranges_map: Dict[int, Dict[int, Dict[str, Any]]] = {} + ipv6_ranges_map: Dict[int, Dict[int, Dict[str, Any]]] = {} + prefix_lists_map: Dict[int, Dict[int, Dict[str, Any]]] = {} + user_groups_map: Dict[int, Dict[int, Dict[str, Any]]] = {} + + for raw_key, raw_value in raw_params.items(): + if not isinstance(raw_key, str): + continue + if not raw_key.startswith("IpPermissions."): + continue + + m = re.match(r"^IpPermissions\.(\d+)\.(.+)$", raw_key) + if not m: + continue + + perm_idx = int(m.group(1)) + suffix = m.group(2) + + perm = perms.setdefault(perm_idx, self._permission_template()) + + if suffix == "IpProtocol": + perm["IpProtocol"] = raw_value + continue + if suffix == "FromPort": + perm["FromPort"] = self._to_int_or_none(raw_value) + continue + if suffix == "ToPort": + perm["ToPort"] = self._to_int_or_none(raw_value) + continue + if suffix == "Description": + perm["Description"] = raw_value + continue + + m_v4 = re.match(r"^IpRanges\.(\d+)\.CidrIp$", suffix) + if m_v4: + item_idx = int(m_v4.group(1)) + ip_ranges_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["CidrIp"] = raw_value + continue + + m_v4_desc = re.match(r"^IpRanges\.(\d+)\.Description$", suffix) + if m_v4_desc: + item_idx = int(m_v4_desc.group(1)) + ip_ranges_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["Description"] = raw_value + continue + + m_v6 = re.match(r"^Ipv6Ranges\.(\d+)\.CidrIpv6$", suffix) + if m_v6: + item_idx = int(m_v6.group(1)) + ipv6_ranges_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["CidrIpv6"] = raw_value + continue + + m_v6_desc = re.match(r"^Ipv6Ranges\.(\d+)\.Description$", suffix) + if m_v6_desc: + item_idx = int(m_v6_desc.group(1)) + ipv6_ranges_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["Description"] = raw_value + continue + + m_pl = re.match(r"^PrefixListIds\.(\d+)\.PrefixListId$", suffix) + if m_pl: + item_idx = int(m_pl.group(1)) + prefix_lists_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["PrefixListId"] = raw_value + continue + + m_pl_desc = re.match(r"^PrefixListIds\.(\d+)\.Description$", suffix) + if m_pl_desc: + item_idx = int(m_pl_desc.group(1)) + prefix_lists_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["Description"] = raw_value + continue + + m_ug_gid = re.match(r"^UserIdGroupPairs\.(\d+)\.GroupId$", suffix) + if m_ug_gid: + item_idx = int(m_ug_gid.group(1)) + user_groups_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["GroupId"] = raw_value + continue + + m_ug_uid = re.match(r"^UserIdGroupPairs\.(\d+)\.UserId$", suffix) + if m_ug_uid: + item_idx = int(m_ug_uid.group(1)) + user_groups_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["UserId"] = raw_value + continue + + m_ug_vpc = re.match(r"^UserIdGroupPairs\.(\d+)\.VpcId$", suffix) + if m_ug_vpc: + item_idx = int(m_ug_vpc.group(1)) + user_groups_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["VpcId"] = raw_value + continue + + m_ug_desc = re.match(r"^UserIdGroupPairs\.(\d+)\.Description$", suffix) + if m_ug_desc: + item_idx = int(m_ug_desc.group(1)) + user_groups_map.setdefault(perm_idx, {}).setdefault(item_idx, {})["Description"] = raw_value + continue + + if not perms: + return [] + + for perm_idx, perm in perms.items(): + if perm_idx in ip_ranges_map: + perm["IpRanges"] = [ip_ranges_map[perm_idx][i] for i in sorted(ip_ranges_map[perm_idx].keys())] + if perm_idx in ipv6_ranges_map: + perm["Ipv6Ranges"] = [ipv6_ranges_map[perm_idx][i] for i in sorted(ipv6_ranges_map[perm_idx].keys())] + if perm_idx in prefix_lists_map: + perm["PrefixListIds"] = [prefix_lists_map[perm_idx][i] for i in sorted(prefix_lists_map[perm_idx].keys())] + if perm_idx in user_groups_map: + perm["UserIdGroupPairs"] = [user_groups_map[perm_idx][i] for i in sorted(user_groups_map[perm_idx].keys())] + + ordered = [self._normalize_permission_shape(perms[i]) for i in sorted(perms.keys())] + return ordered + def _normalize_ip_permissions(self, params: Dict[str, Any]) -> List[Dict[str, Any]]: - ip_permissions = params.get("IpPermissions.N", []) or [] - if not ip_permissions and params.get("IpProtocol"): - ip_permissions = [ - { - "IpProtocol": params.get("IpProtocol", "-1"), - "FromPort": int(params.get("FromPort") or 0), - "ToPort": int(params.get("ToPort") or 0), - "IpRanges": [ - {"CidrIp": params.get("CidrIp", "0.0.0.0/0")} - ], - } - ] - return ip_permissions + """ + Priority: + 1. already parsed IpPermissions.N list + 2. raw flattened IpPermissions.* query parameters + 3. simple top-level IpProtocol / FromPort / ToPort / CidrIp fallback + """ + existing = params.get("IpPermissions.N", []) or [] + if existing: + return [self._normalize_permission_shape(p) for p in existing] + + raw_params = params.get("IpPermissionsRaw", {}) or {} + parsed_raw = self._parse_ip_permissions_raw(raw_params) + if parsed_raw: + return parsed_raw + + simple = self._build_permission_from_simple_fields(params) + if simple: + return [self._normalize_permission_shape(p) for p in simple] + + return [] def _extract_tags(self, tag_specs: List[Dict[str, Any]], resource_type: str = "security-group") -> List[Dict[str, Any]]: tags: List[Dict[str, Any]] = [] @@ -163,6 +384,55 @@ def _generate_rule_id(self) -> str: def _list_rules(self, group: SecurityGroup) -> List[Dict[str, Any]]: return list(group.security_group_rules.values()) + def _permission_to_describe_sg_shape(self, perm: Dict[str, Any]) -> Dict[str, Any]: + return { + "ipProtocol": perm.get("IpProtocol"), + "fromPort": perm.get("FromPort"), + "toPort": perm.get("ToPort"), + "groups": [ + { + "groupId": pair.get("GroupId"), + "userId": pair.get("UserId"), + "vpcId": pair.get("VpcId"), + "description": pair.get("Description"), + } + for pair in (perm.get("UserIdGroupPairs", []) or []) + ], + "ipRanges": [ + { + "cidrIp": r.get("CidrIp"), + "description": r.get("Description"), + } + for r in (perm.get("IpRanges", []) or []) + ], + "ipv6Ranges": [ + { + "cidrIpv6": r.get("CidrIpv6"), + "description": r.get("Description"), + } + for r in (perm.get("Ipv6Ranges", []) or []) + ], + "prefixListIds": [ + { + "prefixListId": p.get("PrefixListId"), + "description": p.get("Description"), + } + for p in (perm.get("PrefixListIds", []) or []) + ], + } + + def _security_group_to_describe_shape(self, group: SecurityGroup) -> Dict[str, Any]: + return { + "groupDescription": group.group_description, + "groupId": group.group_id, + "groupName": group.group_name, + "ipPermissions": [self._permission_to_describe_sg_shape(p) for p in (group.ip_permissions or [])], + "ipPermissionsEgress": [self._permission_to_describe_sg_shape(p) for p in (group.ip_permissions_egress or [])], + "ownerId": group.owner_id, + "securityGroupArn": group.security_group_arn, + "tagSet": group.tag_set, + "vpcId": group.vpc_id, + } # - State management: _update_state(resource, new_state: str) # - Complex operations: _process_associations(params: Dict) -> Dict @@ -187,6 +457,7 @@ def AuthorizeSecurityGroupEgress(self, params: Dict[str, Any]): ) ip_permissions = self._normalize_ip_permissions(params) + print("[sg-debug] normalized ip_permissions =", ip_permissions) if not ip_permissions: return create_error_response("MissingParameter", "Missing required parameter: IpPermissions") @@ -318,6 +589,7 @@ def AuthorizeSecurityGroupIngress(self, params: Dict[str, Any]): return error ip_permissions = self._normalize_ip_permissions(params) + print("[sg-debug] normalized ip_permissions =", ip_permissions) if not ip_permissions: return create_error_response("MissingParameter", "Missing required parameter: IpPermissions") @@ -460,6 +732,7 @@ def CreateSecurityGroup(self, params: Dict[str, Any]): tags = self._extract_tags(params.get("TagSpecification.N", [])) security_group_arn = f"arn:aws:ec2:::security-group/{group_id}" owner_id = getattr(vpc, "owner_id", "") if vpc else "" + resource = SecurityGroup( group_description=params.get("GroupDescription") or "", group_id=group_id, @@ -469,8 +742,6 @@ def CreateSecurityGroup(self, params: Dict[str, Any]): tag_set=tags, vpc_id=vpc_id or "", ) - if vpc_id: - self._register_vpc_association(resource, vpc_id, state="associated") self.resources[group_id] = resource @@ -501,6 +772,11 @@ def DeleteSecurityGroup(self, params: Dict[str, Any]): "SecurityGroup has dependent AuthorizationRule(s) and cannot be deleted.", ) + # Backward-compat cleanup: + # older objects may have had the primary VPC incorrectly stored as an association. + if group.vpc_id and group.associated_vpc_ids == [group.vpc_id]: + self._deregister_vpc_association(group, group.vpc_id) + if group.associated_vpc_ids: return create_error_response( "DependencyViolation", @@ -602,8 +878,8 @@ def DescribeSecurityGroups(self, params: Dict[str, Any]): return { 'nextToken': None, - 'securityGroupInfo': [resource.to_dict() for resource in resources], - } + 'securityGroupInfo': [self._security_group_to_describe_shape(resource) for resource in resources], + } def ModifySecurityGroupRules(self, params: Dict[str, Any]): """Modifies the rules of a security group.""" @@ -697,6 +973,7 @@ def RevokeSecurityGroupEgress(self, params: Dict[str, Any]): group.security_group_rules.pop(rule_id, None) ip_permissions = self._normalize_ip_permissions(params) + print("[sg-debug] normalized ip_permissions =", ip_permissions) if ip_permissions: for perm in ip_permissions: matched = False @@ -812,6 +1089,7 @@ def RevokeSecurityGroupIngress(self, params: Dict[str, Any]): group.security_group_rules.pop(rule_id, None) ip_permissions = self._normalize_ip_permissions(params) + print("[sg-debug] normalized ip_permissions =", ip_permissions) if ip_permissions: for perm in ip_permissions: matched = False @@ -1263,14 +1541,22 @@ def _generate_id(self, prefix: str = 'sg') -> str: from ..utils import is_error_response, serialize_error_response class securitygroup_RequestParser: + + @staticmethod + def _extract_prefixed_params(md: Dict[str, Any], prefix: str) -> Dict[str, Any]: + return {k: v for k, v in md.items() if isinstance(k, str) and k.startswith(prefix)} + @staticmethod def parse_authorize_security_group_egress_request(md: Dict[str, Any]) -> Dict[str, Any]: return { "CidrIp": get_scalar(md, "CidrIp"), + "CidrIpv6": get_scalar(md, "CidrIpv6"), + "Description": get_scalar(md, "Description"), "DryRun": str2bool(get_scalar(md, "DryRun")), "FromPort": get_int(md, "FromPort"), "GroupId": get_scalar(md, "GroupId"), "IpPermissions.N": get_indexed_list(md, "IpPermissions"), + "IpPermissionsRaw": securitygroup_RequestParser._extract_prefixed_params(md, "IpPermissions."), "IpProtocol": get_scalar(md, "IpProtocol"), "SourceSecurityGroupName": get_scalar(md, "SourceSecurityGroupName"), "SourceSecurityGroupOwnerId": get_scalar(md, "SourceSecurityGroupOwnerId"), @@ -1282,11 +1568,14 @@ def parse_authorize_security_group_egress_request(md: Dict[str, Any]) -> Dict[st def parse_authorize_security_group_ingress_request(md: Dict[str, Any]) -> Dict[str, Any]: return { "CidrIp": get_scalar(md, "CidrIp"), + "CidrIpv6": get_scalar(md, "CidrIpv6"), + "Description": get_scalar(md, "Description"), "DryRun": str2bool(get_scalar(md, "DryRun")), "FromPort": get_int(md, "FromPort"), "GroupId": get_scalar(md, "GroupId"), "GroupName": get_scalar(md, "GroupName"), "IpPermissions.N": get_indexed_list(md, "IpPermissions"), + "IpPermissionsRaw": securitygroup_RequestParser._extract_prefixed_params(md, "IpPermissions."), "IpProtocol": get_scalar(md, "IpProtocol"), "SourceSecurityGroupName": get_scalar(md, "SourceSecurityGroupName"), "SourceSecurityGroupOwnerId": get_scalar(md, "SourceSecurityGroupOwnerId"), @@ -1345,10 +1634,12 @@ def parse_modify_security_group_rules_request(md: Dict[str, Any]) -> Dict[str, A def parse_revoke_security_group_egress_request(md: Dict[str, Any]) -> Dict[str, Any]: return { "CidrIp": get_scalar(md, "CidrIp"), + "CidrIpv6": get_scalar(md, "CidrIpv6"), "DryRun": str2bool(get_scalar(md, "DryRun")), "FromPort": get_int(md, "FromPort"), "GroupId": get_scalar(md, "GroupId"), "IpPermissions.N": get_indexed_list(md, "IpPermissions"), + "IpPermissionsRaw": securitygroup_RequestParser._extract_prefixed_params(md, "IpPermissions."), "IpProtocol": get_scalar(md, "IpProtocol"), "SecurityGroupRuleId.N": get_indexed_list(md, "SecurityGroupRuleId"), "SourceSecurityGroupName": get_scalar(md, "SourceSecurityGroupName"), @@ -1360,11 +1651,13 @@ def parse_revoke_security_group_egress_request(md: Dict[str, Any]) -> Dict[str, def parse_revoke_security_group_ingress_request(md: Dict[str, Any]) -> Dict[str, Any]: return { "CidrIp": get_scalar(md, "CidrIp"), + "CidrIpv6": get_scalar(md, "CidrIpv6"), "DryRun": str2bool(get_scalar(md, "DryRun")), "FromPort": get_int(md, "FromPort"), "GroupId": get_scalar(md, "GroupId"), "GroupName": get_scalar(md, "GroupName"), "IpPermissions.N": get_indexed_list(md, "IpPermissions"), + "IpPermissionsRaw": securitygroup_RequestParser._extract_prefixed_params(md, "IpPermissions."), "IpProtocol": get_scalar(md, "IpProtocol"), "SecurityGroupRuleId.N": get_indexed_list(md, "SecurityGroupRuleId"), "SourceSecurityGroupName": get_scalar(md, "SourceSecurityGroupName"), @@ -1379,6 +1672,7 @@ def parse_update_security_group_rule_descriptions_egress_request(md: Dict[str, A "GroupId": get_scalar(md, "GroupId"), "GroupName": get_scalar(md, "GroupName"), "IpPermissions.N": get_indexed_list(md, "IpPermissions"), + "IpPermissionsRaw": securitygroup_RequestParser._extract_prefixed_params(md, "IpPermissions."), "SecurityGroupRuleDescription.N": get_indexed_list(md, "SecurityGroupRuleDescription"), } @@ -1389,6 +1683,7 @@ def parse_update_security_group_rule_descriptions_ingress_request(md: Dict[str, "GroupId": get_scalar(md, "GroupId"), "GroupName": get_scalar(md, "GroupName"), "IpPermissions.N": get_indexed_list(md, "IpPermissions"), + "IpPermissionsRaw": securitygroup_RequestParser._extract_prefixed_params(md, "IpPermissions."), "SecurityGroupRuleDescription.N": get_indexed_list(md, "SecurityGroupRuleDescription"), } @@ -1746,20 +2041,24 @@ def serialize_describe_security_groups_response(data: Dict[str, Any], request_id _securityGroupInfo_key = "securityGroupInfo" elif "SecurityGroupInfo" in data: _securityGroupInfo_key = "SecurityGroupInfo" + if _securityGroupInfo_key: param_data = data[_securityGroupInfo_key] indent_str = " " * 1 if param_data: - xml_parts.append(f'{indent_str}') + xml_parts.append(f'{indent_str}') for item in param_data: xml_parts.append(f'{indent_str} ') xml_parts.extend(securitygroup_ResponseSerializer._serialize_nested_fields(item, 2)) xml_parts.append(f'{indent_str} ') - xml_parts.append(f'{indent_str}') + xml_parts.append(f'{indent_str}') else: - xml_parts.append(f'{indent_str}') + xml_parts.append(f'{indent_str}') + xml_parts.append(f'') - return "\n".join(xml_parts) + xml = "\n".join(xml_parts) + print("[sg-describe-xml]", xml) + return xml @staticmethod def serialize_modify_security_group_rules_response(data: Dict[str, Any], request_id: str) -> str: