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))}{key}>')
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' {key}>')
+ 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' {key}>')
+ elif isinstance(value, bool):
+ xml_parts.append(f' <{key}>{str(value).lower()}{key}>')
+ else:
+ xml_parts.append(f' <{key}>{esc(str(value))}{key}>')
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' {key}>')
+ 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' {key}>')
+ elif isinstance(value, bool):
+ xml_parts.append(f' <{key}>{str(value).lower()}{key}>')
+ else:
+ xml_parts.append(f' <{key}>{esc(str(value))}{key}>')
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:
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/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..f536184
--- /dev/null
+++ b/emulators/aws-ec2/tests/cli/ec2/network-interface-security-group-lifecycle.rst
@@ -0,0 +1,104 @@
+Network Interface Security Group Lifecycle
+==========================================
+
+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.33.0.0/16
+
+Expected behavior:
+- A VPC is created successfully.
+
+**Example 2: To create a subnet in the VPC**
+
+The following ``create-subnet`` example creates a subnet for the network interface. ::
+
+ aws ec2 create-subnet \
+ --vpc-id vpc-1234567890abcdef0 \
+ --cidr-block 10.33.1.0/24
+
+Expected behavior:
+- A subnet is created successfully.
+
+**Example 3: To create a security group in the VPC**
+
+The following ``create-security-group`` example creates a security group for later ENI
+association. ::
+
+ aws ec2 create-security-group \
+ --group-name sg-eni-lifecycle \
+ --description "network interface security group lifecycle" \
+ --vpc-id vpc-1234567890abcdef0
+
+Expected behavior:
+- A security group is created successfully.
+
+**Example 4: To create a network interface with the security group**
+
+The following ``create-network-interface`` example creates an ENI and attaches the security
+group at creation time. ::
+
+ aws ec2 create-network-interface \
+ --subnet-id subnet-1234567890abcdef0 \
+ --groups sg-1234567890abcdef0
+
+Expected behavior:
+- A network interface is created successfully.
+- The network interface stores the security group association.
+
+**Example 5: To describe the network interface and verify the security group**
+
+The following ``describe-network-interfaces`` example reads back the ENI and verifies that
+the security group relationship is present. ::
+
+ aws ec2 describe-network-interfaces \
+ --network-interface-ids eni-1234567890abcdef0
+
+Expected behavior:
+- The response includes the target ENI.
+- The response includes the associated security group in the ENI group list.
+
+**Example 6: To delete the network interface after validation**
+
+The following ``delete-network-interface`` example removes the ENI. ::
+
+ aws ec2 delete-network-interface \
+ --network-interface-id eni-1234567890abcdef0
+
+Expected behavior:
+- The network interface is deleted successfully.
+
+**Example 7: To delete the security group after validation**
+
+The following ``delete-security-group`` example removes the security group. ::
+
+ aws ec2 delete-security-group \
+ --group-id sg-1234567890abcdef0
+
+Expected behavior:
+- The security group is deleted successfully.
+
+**Example 8: To delete the subnet after validation**
+
+The following ``delete-subnet`` example removes the subnet. ::
+
+ aws ec2 delete-subnet \
+ --subnet-id subnet-1234567890abcdef0
+
+Expected behavior:
+- The subnet is deleted successfully.
+
+**Example 9: To delete the VPC after validation**
+
+The following ``delete-vpc`` example removes the VPC. ::
+
+ 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/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/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..917bf35
--- /dev/null
+++ b/emulators/aws-ec2/tests/cli/ec2/security-group-instance-lifecycle.rst
@@ -0,0 +1,131 @@
+Security Group Instance Lifecycle
+=================================
+
+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.
+
+**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.34.0.0/16
+
+Expected behavior:
+- A VPC is created successfully.
+
+**Example 2: To create a subnet in the VPC**
+
+The following ``create-subnet`` example creates a subnet for the instance launch. ::
+
+ aws ec2 create-subnet \
+ --vpc-id vpc-1234567890abcdef0 \
+ --cidr-block 10.34.1.0/24
+
+Expected behavior:
+- A subnet is created successfully.
+
+**Example 3: To create a security group in the VPC**
+
+The following ``create-security-group`` example creates a security group for the instance. ::
+
+ aws ec2 create-security-group \
+ --group-name sg-instance-lifecycle \
+ --description "security group instance lifecycle" \
+ --vpc-id vpc-1234567890abcdef0
+
+Expected behavior:
+- A security group is created successfully.
+
+**Example 4: To authorize an ingress rule for the security group**
+
+The following ``authorize-security-group-ingress`` example adds a TCP/22 rule to the
+security group. ::
+
+ 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 5: To describe the security group and verify the rule**
+
+The following ``describe-security-groups`` example verifies the rule was persisted. ::
+
+ aws ec2 describe-security-groups \
+ --group-ids sg-1234567890abcdef0
+
+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 into the subnet with the
+security group attached. ::
+
+ aws ec2 run-instances \
+ --image-id ami-1234567890abcdef0 \
+ --instance-type t2.micro \
+ --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-1234567890abcdef0
+
+Expected behavior:
+- The response includes the target instance.
+- The response includes the target security group in the instance security group list.
+
+**Example 8: To terminate the instance after validation**
+
+The following ``terminate-instances`` example terminates the instance. ::
+
+ aws ec2 terminate-instances \
+ --instance-ids i-1234567890abcdef0
+
+Expected behavior:
+- The instance is terminated successfully.
+
+**Example 9: To delete the security group after validation**
+
+The following ``delete-security-group`` example removes the security group. ::
+
+ aws ec2 delete-security-group \
+ --group-id sg-1234567890abcdef0
+
+Expected behavior:
+- The security group is deleted successfully.
+
+**Example 10: To delete the subnet after validation**
+
+The following ``delete-subnet`` example removes the subnet. ::
+
+ aws ec2 delete-subnet \
+ --subnet-id subnet-1234567890abcdef0
+
+Expected behavior:
+- The subnet is deleted successfully.
+
+**Example 11: To delete the VPC after validation**
+
+The following ``delete-vpc`` example removes the VPC. ::
+
+ 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/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
new file mode 100755
index 0000000..d9cb532
--- /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=$(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"
+
+uv run awscli ec2 wait vpc-available --vpc-ids "$VPC_ID"
+
+echo
+echo "== 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" \
+ | python3 -c "import sys,json; print(json.load(sys.stdin)['GroupId'])")
+echo "SG_ID=$SG_ID"
+
+echo
+echo "== probe 1: simple ingress args =="
+uv run 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 =="
+uv run awscli ec2 describe-security-groups --group-ids "$SG_ID"
+
+echo
+echo "== probe 2: shorthand ip-permissions 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 =="
+uv run 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 =="
+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 =="
+uv run awscli ec2 describe-security-groups --group-ids "$SG_ID"
+
+echo
+echo "== probe 4: shorthand ip-permissions 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 =="
+uv run awscli ec2 describe-security-groups --group-ids "$SG_ID"
+
+echo
+echo "== cleanup =="
+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
+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
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
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