1use std::collections::{HashMap, HashSet};
2
3use crate::types::*;
4
5#[derive(Debug, Default)]
6pub struct Ec2State {
7 pub vpcs: HashMap<String, Vpc>,
8 pub subnets: HashMap<String, Subnet>,
9 pub igws: HashMap<String, InternetGateway>,
10 pub security_groups: HashMap<String, SecurityGroup>,
11 pub route_tables: HashMap<String, RouteTable>,
12 pub key_pairs: HashMap<String, KeyPair>,
13 pub network_acls: HashMap<String, NetworkAcl>,
14 pub elastic_ips: HashMap<String, ElasticIp>,
15 pub nat_gateways: HashMap<String, NatGateway>,
16 pub dhcp_options: HashMap<String, DhcpOptions>,
17 pub egress_only_igws: HashMap<String, EgressOnlyInternetGateway>,
18 pub flow_logs: HashMap<String, FlowLog>,
19 pub vpc_peering_connections: HashMap<String, VpcPeeringConnection>,
20 pub vpc_endpoints: HashMap<String, VpcEndpoint>,
21 pub managed_prefix_lists: HashMap<String, ManagedPrefixList>,
22 pub customer_gateways: HashMap<String, CustomerGateway>,
23 pub vpn_gateways: HashMap<String, VpnGateway>,
24 pub vpn_connections: HashMap<String, VpnConnection>,
25 pub carrier_gateways: HashMap<String, CarrierGateway>,
26 pub network_interfaces: HashMap<String, NetworkInterface>,
27 pub vpc_cidr_associations: HashMap<String, String>, pub ebs_encryption_by_default: bool,
29 pub ebs_default_kms_key_id: Option<String>,
33 pub serial_console_access_enabled: bool,
35 pub allowed_images_settings_state: Option<String>,
39 pub image_block_public_access_state: Option<String>,
42 pub aws_network_performance_subscriptions:
45 HashMap<(String, String, String, String), AwsNetworkPerformanceSubscription>,
46 pub transit_gateways: HashMap<String, TransitGateway>,
47 pub tgw_vpc_attachments: HashMap<String, TransitGatewayVpcAttachment>,
48 pub tgw_peering_attachments: HashMap<String, TransitGatewayPeeringAttachment>,
49 pub tgw_route_tables: HashMap<String, TransitGatewayRouteTable>,
50 pub tgw_routes: HashMap<String, Vec<TransitGatewayRoute>>,
51 pub instances: HashMap<String, Instance>,
52 pub volumes: HashMap<String, Volume>,
53 pub snapshots: HashMap<String, Snapshot>,
54 pub images: HashMap<String, Image>,
55 pub deregistered_images: HashSet<String>,
58 pub launch_templates: HashMap<String, LaunchTemplate>,
59 pub launch_template_versions: HashMap<String, Vec<LaunchTemplateVersion>>,
60 pub spot_requests: HashMap<String, SpotInstanceRequest>,
61 pub spot_datafeed_subscription: Option<SpotDatafeedSubscription>,
66 pub iam_instance_profile_associations: HashMap<String, IamInstanceProfileAssociation>,
67 pub dedicated_hosts: HashMap<String, DedicatedHost>,
68 pub ec2_fleets: HashMap<String, Ec2Fleet>,
69 pub vpc_endpoint_service_configs: HashMap<String, VpcEndpointServiceConfiguration>,
70 pub spot_fleet_requests: HashMap<String, SpotFleetRequest>,
71 pub subnet_cidr_reservations: HashMap<String, SubnetCidrReservationEntry>,
72 pub placement_groups: HashMap<String, PlacementGroup>,
73 pub network_interface_permissions: HashMap<String, NetworkInterfacePermission>,
74 pub instance_connect_endpoints: HashMap<String, InstanceConnectEndpoint>,
75 pub capacity_reservations: HashMap<String, CapacityReservation>,
76 pub capacity_reservation_fleets: HashMap<String, CapacityReservationFleet>,
77 pub coip_pools: HashMap<String, CoipPool>,
78 pub byoip_cidrs: HashMap<String, ByoipCidr>,
80 pub public_ipv4_pools: HashMap<String, PublicIpv4Pool>,
82 pub coip_cidrs: HashMap<(String, String), CoipCidr>,
84 pub address_transfers: HashMap<String, AddressTransfer>,
86 pub security_group_vpc_associations: HashMap<(String, String), SecurityGroupVpcAssociation>,
88 pub enclave_certificate_iam_role_associations:
90 HashMap<(String, String), EnclaveCertificateIamRoleAssociation>,
91 pub mac_sip_modification_tasks: HashMap<String, MacSipModificationTask>,
92 pub declarative_policies_reports: HashMap<String, DeclarativePoliciesReport>,
93 pub vpn_concentrators: HashMap<String, VpnConcentrator>,
95 pub vpc_endpoint_connections: HashMap<(String, String), VpcEndpointConnection>,
98 pub vpc_endpoint_connection_notifications: HashMap<String, VpcEndpointConnectionNotification>,
100 pub vpc_block_public_access_exclusions: HashMap<String, VpcBlockPublicAccessExclusion>,
102 pub vpc_block_public_access_options: Option<VpcBlockPublicAccessOptions>,
106 pub vpc_encryption_controls: HashMap<String, VpcEncryptionControl>,
108 pub mac_volume_ownership_tasks: HashMap<String, MacVolumeOwnershipTask>,
111 pub replace_root_volume_tasks: HashMap<String, ReplaceRootVolumeTask>,
113 pub snapshot_import_tasks: HashMap<String, SnapshotImportTask>,
115 pub conversion_tasks: HashMap<String, ConversionTask>,
117 pub export_tasks: HashMap<String, ExportTask>,
119 pub import_tasks: HashMap<String, (String, Option<String>)>,
123 pub trunk_interface_associations: HashMap<String, TrunkInterfaceAssociation>,
125 pub secondary_networks: HashMap<String, SecondaryNetwork>,
127 pub secondary_subnets: HashMap<String, SecondarySubnet>,
129 pub deleted_volumes_recycle_bin: HashMap<String, Volume>,
133 pub deleted_snapshots_recycle_bin: HashMap<String, Snapshot>,
136 pub reserved_instances_exchanges: HashMap<String, ReservedInstancesExchange>,
139 pub reserved_instances_listings: HashMap<String, ReservedInstancesListing>,
141 pub queued_reserved_instances_purchases: HashMap<String, ReservedInstancesPurchase>,
143 pub reserved_instances_modifications: HashMap<String, ReservedInstancesModification>,
145 pub reserved_instances_purchases: HashMap<String, ReservedInstancesPurchase>,
147 pub reserved_instances: HashMap<String, ReservedInstances>,
149 pub fpga_images: HashMap<String, FpgaImage>,
151 pub image_usage_reports: HashMap<String, ImageUsageReport>,
153 pub restore_image_tasks: HashMap<String, RestoreImageTask>,
155 pub store_image_tasks: HashMap<String, StoreImageTask>,
157 pub import_image_tasks: HashMap<String, ImportImageTask>,
159 pub allowed_image_criteria: Vec<AllowedImageCriterion>,
161 pub default_credit_specifications: HashMap<String, String>,
163 pub instance_metadata_defaults: Option<InstanceMetadataDefaults>,
165 pub instance_event_windows: HashMap<String, InstanceEventWindow>,
167 pub instance_event_notification_attributes: Option<InstanceTagNotificationAttributes>,
169 pub instance_events: HashMap<String, InstanceEvent>,
171 pub host_reservations: HashMap<String, HostReservation>,
173 pub scheduled_instances: HashMap<String, ScheduledInstance>,
175 pub az_group_opt_in: HashMap<String, String>,
177 pub instance_status_reports: Vec<InstanceStatusReport>,
179 pub network_insights_access_scopes: HashMap<String, NetworkInsightsAccessScope>,
182 pub network_insights_access_scope_analyses: HashMap<String, NetworkInsightsAccessScopeAnalysis>,
184 pub network_insights_paths: HashMap<String, NetworkInsightsPath>,
186 pub network_insights_analyses: HashMap<String, NetworkInsightsAnalysis>,
188 pub traffic_mirror_filters: HashMap<String, TrafficMirrorFilter>,
190 pub traffic_mirror_sessions: HashMap<String, TrafficMirrorSession>,
192 pub traffic_mirror_targets: HashMap<String, TrafficMirrorTarget>,
194 pub client_vpn_endpoints: HashMap<String, ClientVpnEndpoint>,
197 pub client_vpn_target_network_associations: HashMap<String, ClientVpnTargetNetworkAssociation>,
199 pub client_vpn_authorization_rules:
201 HashMap<(String, String, String), ClientVpnAuthorizationRule>,
202 pub client_vpn_routes: HashMap<(String, String, String), ClientVpnRoute>,
204 pub client_vpn_connections: HashMap<String, ClientVpnConnection>,
206 pub local_gateways: HashMap<String, LocalGateway>,
208 pub local_gateway_route_tables: HashMap<String, LocalGatewayRouteTable>,
210 pub local_gateway_routes: HashMap<(String, String), LocalGatewayRoute>,
212 pub local_gateway_route_table_virtual_interface_group_associations:
214 HashMap<String, LocalGatewayRouteTableVirtualInterfaceGroupAssociation>,
215 pub local_gateway_route_table_vpc_associations:
217 HashMap<String, LocalGatewayRouteTableVpcAssociation>,
218 pub local_gateway_virtual_interfaces: HashMap<String, LocalGatewayVirtualInterface>,
220 pub local_gateway_virtual_interface_groups: HashMap<String, LocalGatewayVirtualInterfaceGroup>,
222 pub route_servers: HashMap<String, RouteServer>,
225 pub route_server_endpoints: HashMap<String, RouteServerEndpoint>,
227 pub route_server_peers: HashMap<String, RouteServerPeer>,
229 pub route_server_associations: HashMap<(String, String), RouteServerAssociation>,
231 pub verified_access_instances: HashMap<String, VerifiedAccessInstance>,
234 pub verified_access_trust_providers: HashMap<String, VerifiedAccessTrustProvider>,
236 pub verified_access_groups: HashMap<String, VerifiedAccessGroup>,
238 pub verified_access_endpoints: HashMap<String, VerifiedAccessEndpoint>,
240 pub verified_access_trust_provider_attachments:
242 HashMap<(String, String), VerifiedAccessTrustProviderAttachment>,
243 pub verified_access_instance_logging_configurations: HashMap<String, VerifiedAccessLogs>,
245 pub billing_ownership_offers: HashMap<(String, String), BillingOwnershipOffer>,
248 pub capacity_manager_data_exports: HashMap<String, CapacityManagerDataExport>,
250 pub interruptible_capacity_reservation_allocations:
252 HashMap<String, InterruptibleCapacityReservationAllocation>,
253 pub capacity_blocks: HashMap<String, CapacityBlock>,
255 pub capacity_block_extensions: HashMap<String, CapacityBlockExtension>,
257 pub capacity_manager_organizations_access: Option<CapacityManagerOrganizationsAccess>,
259 pub tgw_multicast_domains: HashMap<String, TransitGatewayMulticastDomain>,
262 pub tgw_multicast_domain_associations:
264 HashMap<(String, String), TransitGatewayMulticastDomainAssociation>,
265 pub tgw_multicast_group_members:
267 HashMap<(String, String, String), TransitGatewayMulticastGroupMember>,
268 pub tgw_multicast_group_sources:
270 HashMap<(String, String, String), TransitGatewayMulticastGroupSource>,
271 pub tgw_connects: HashMap<String, TransitGatewayConnect>,
273 pub tgw_connect_peers: HashMap<String, TransitGatewayConnectPeer>,
275 pub tgw_metering_policies: HashMap<String, TransitGatewayMeteringPolicy>,
277 pub tgw_metering_policy_entries: HashMap<(String, String), TransitGatewayMeteringPolicyEntry>,
279 pub tgw_policy_tables: HashMap<String, TransitGatewayPolicyTable>,
281 pub tgw_policy_table_associations:
283 HashMap<(String, String), TransitGatewayPolicyTableAssociation>,
284 pub tgw_prefix_list_references: HashMap<(String, String), TransitGatewayPrefixListReference>,
286 pub tgw_route_table_announcements: HashMap<String, TransitGatewayRouteTableAnnouncement>,
288 pub ipams: HashMap<String, Ipam>,
291 pub ipam_scopes: HashMap<String, IpamScope>,
293 pub ipam_pools: HashMap<String, IpamPool>,
295 pub ipam_pool_cidrs: HashMap<(String, String), IpamPoolCidr>,
297 pub ipam_pool_allocations: HashMap<(String, String), IpamPoolAllocation>,
299 pub ipam_resource_discoveries: HashMap<String, IpamResourceDiscovery>,
301 pub ipam_resource_discovery_associations: HashMap<String, IpamResourceDiscoveryAssociation>,
303 pub ipam_byoasns: HashMap<(String, String), IpamByoasn>,
305 pub ipam_external_resource_verification_tokens:
307 HashMap<String, IpamExternalResourceVerificationToken>,
308 pub ipam_policies: HashMap<String, IpamPolicy>,
310 pub ipam_prefix_list_resolvers: HashMap<String, IpamPrefixListResolver>,
312 pub ipam_prefix_list_resolver_targets: HashMap<(String, String), IpamPrefixListResolverTarget>,
314 pub volume_modifications: HashMap<String, VolumeModification>,
317 pub import_volume_tasks: HashMap<String, ImportVolumeTask>,
319 pub bundle_tasks: HashMap<String, BundleTask>,
321 pub id_format: HashMap<String, IdFormatEntry>,
324 pub outpost_lags: HashMap<String, OutpostLag>,
326 pub export_image_tasks: HashMap<String, ExportImageTask>,
328 pub counters: Ec2Counters,
329}
330
331#[derive(Debug, Default)]
332pub struct Ec2Counters {
333 pub vpc: u32,
334 pub subnet: u32,
335 pub igw: u32,
336 pub sg: u32,
337 pub sgr: u32,
338 pub rtb: u32,
339 pub rtbassoc: u32,
340 pub keypair: u32,
341 pub nacl: u32,
342 pub nacl_assoc: u32,
343 pub eip: u32,
344 pub eip_assoc: u32,
345 pub nat: u32,
346 pub dopt: u32,
347 pub eigw: u32,
348 pub flow_log: u32,
349 pub vpc_peering: u32,
350 pub vpc_endpoint: u32,
351 pub prefix_list: u32,
352 pub cgw: u32,
353 pub vgw: u32,
354 pub vpn: u32,
355 pub cgw_carrier: u32,
356 pub eni: u32,
357 pub eni_attach: u32,
358 pub vpc_cidr_assoc: u32,
359 pub tgw: u32,
360 pub tgw_attach: u32,
361 pub tgw_rtb: u32,
362 pub instance: u32,
363 pub vol: u32,
364 pub snapshot: u32,
365 pub ami: u32,
366 pub lt: u32,
367 pub spot: u32,
368 pub iam_assoc: u32,
369 pub host: u32,
370 pub fleet: u32,
371 pub vpce_svc: u32,
372 pub spot_fleet: u32,
373 pub subnet_cidr_res: u32,
374 pub subnet_ipv6_assoc: u32,
375 pub placement_group: u32,
376 pub eni_permission: u32,
377 pub instance_connect_endpoint: u32,
378 pub capacity_reservation: u32,
379 pub capacity_reservation_fleet: u32,
380 pub coip_pool: u32,
381 pub mac_sip_task: u32,
382 pub declarative_policies_report: u32,
383 pub public_ipv4_pool: u32,
384 pub address_transfer: u32,
385 pub nat_gateway_address_assoc: u32,
386 pub vpn_concentrator: u32,
387 pub vpc_endpoint_connection_notification: u32,
388 pub vpc_block_public_access_exclusion: u32,
389 pub vpc_encryption_control: u32,
390 pub mac_volume_ownership_task: u32,
392 pub replace_root_volume_task: u32,
393 pub snapshot_import_task: u32,
394 pub conversion_task: u32,
395 pub export_task: u32,
396 pub import_task: u32,
397 pub trunk_interface_assoc: u32,
398 pub secondary_network: u32,
399 pub secondary_subnet: u32,
400 pub reserved_instances_exchange: u32,
402 pub reserved_instances_listing: u32,
403 pub reserved_instances_purchase: u32,
404 pub reserved_instances: u32,
405 pub reserved_instances_modification: u32,
406 pub fpga_image: u32,
407 pub image_usage_report: u32,
408 pub import_image_task: u32,
409 pub instance_event_window: u32,
410 pub instance_event: u32,
411 pub host_reservation: u32,
412 pub scheduled_instance: u32,
413 pub network_insights_access_scope: u32,
415 pub network_insights_access_scope_analysis: u32,
416 pub network_insights_path: u32,
417 pub network_insights_analysis: u32,
418 pub traffic_mirror_filter: u32,
419 pub traffic_mirror_filter_rule: u32,
420 pub traffic_mirror_session: u32,
421 pub traffic_mirror_target: u32,
422 pub client_vpn_endpoint: u32,
424 pub client_vpn_target_network_association: u32,
425 pub client_vpn_connection: u32,
426 pub local_gateway: u32,
427 pub local_gateway_route_table: u32,
428 pub local_gateway_route_table_virtual_interface_group_association: u32,
429 pub local_gateway_route_table_vpc_association: u32,
430 pub local_gateway_virtual_interface: u32,
431 pub local_gateway_virtual_interface_group: u32,
432 pub route_server: u32,
434 pub route_server_endpoint: u32,
435 pub route_server_peer: u32,
436 pub verified_access_instance: u32,
438 pub verified_access_trust_provider: u32,
439 pub verified_access_group: u32,
440 pub verified_access_endpoint: u32,
441 pub capacity_manager_data_export: u32,
443 pub interruptible_capacity_reservation_allocation: u32,
444 pub capacity_block: u32,
445 pub capacity_block_extension: u32,
446 pub tgw_multicast_domain: u32,
448 pub tgw_connect: u32,
449 pub tgw_connect_peer: u32,
450 pub tgw_metering_policy: u32,
451 pub tgw_metering_policy_entry: u32,
452 pub tgw_policy_table: u32,
453 pub tgw_route_table_announcement: u32,
454 pub ipam: u32,
456 pub ipam_scope: u32,
457 pub ipam_pool: u32,
458 pub ipam_pool_cidr: u32,
459 pub ipam_pool_allocation: u32,
460 pub ipam_resource_discovery: u32,
461 pub ipam_resource_discovery_association: u32,
462 pub ipam_external_resource_verification_token: u32,
463 pub ipam_policy: u32,
464 pub ipam_prefix_list_resolver: u32,
465 pub ipam_prefix_list_resolver_target: u32,
466 pub bundle_task: u32,
468 pub volume_modification: u32,
469 pub import_volume_task: u32,
470 pub export_image_task: u32,
471 pub outpost_lag: u32,
472}
473
474#[derive(Debug, thiserror::Error)]
475pub enum Ec2Error {
476 #[error("The vpc ID '{0}' does not exist")]
477 VpcNotFound(String),
478
479 #[error("The network ACL '{0}' does not exist")]
480 NetworkAclNotFound(String),
481
482 #[error("Cannot delete the default network ACL")]
483 CannotDeleteDefaultNetworkAcl,
484
485 #[error("DHCP options set is associated with a VPC")]
486 DhcpOptionsAssociatedWithVpc,
487
488 #[error("The association '{0}' does not exist")]
489 AssociationNotFound(String),
490
491 #[error("The association ID '{0}' does not exist")]
492 AssociationIdNotFound(String),
493
494 #[error("The allocation ID '{0}' does not exist")]
495 AllocationNotFound(String),
496
497 #[error("The subnet ID '{0}' does not exist")]
498 SubnetNotFound(String),
499
500 #[error("The NAT gateway '{0}' does not exist")]
501 NatGatewayNotFound(String),
502
503 #[error("The DHCP options ID '{0}' does not exist")]
504 DhcpOptionsNotFound(String),
505
506 #[error("The internetGateway ID '{0}' does not exist")]
507 InternetGatewayNotFound(String),
508
509 #[error("The security group '{0}' does not exist")]
510 SecurityGroupNotFound(String),
511
512 #[error("The routeTable ID '{0}' does not exist")]
513 RouteTableNotFound(String),
514
515 #[error("The TGW route table '{0}' does not exist")]
516 TgwRouteTableNotFound(String),
517
518 #[error("The route with destination '{0}' does not exist")]
519 RouteNotFound(String),
520
521 #[error("The egress-only internet gateway '{0}' does not exist")]
522 EgressOnlyIgwNotFound(String),
523
524 #[error("The VPC peering connection '{0}' does not exist")]
525 VpcPeeringConnectionNotFound(String),
526
527 #[error("The prefix list '{0}' does not exist")]
528 PrefixListNotFound(String),
529
530 #[error("The customer gateway '{0}' does not exist")]
531 CustomerGatewayNotFound(String),
532
533 #[error("The VPN gateway '{0}' does not exist")]
534 VpnGatewayNotFound(String),
535
536 #[error("The VPN connection '{0}' does not exist")]
537 VpnConnectionNotFound(String),
538
539 #[error("The carrier gateway '{0}' does not exist")]
540 CarrierGatewayNotFound(String),
541
542 #[error("The network interface '{0}' does not exist")]
543 NetworkInterfaceNotFound(String),
544
545 #[error("The attachment '{0}' does not exist")]
546 AttachmentNotFound(String),
547
548 #[error("The CIDR block association '{0}' does not exist")]
549 VpcCidrBlockAssociationNotFound(String),
550
551 #[error("The volume '{0}' does not exist")]
552 VolumeNotFound(String),
553
554 #[error("The instance '{0}' does not exist")]
555 InstanceNotFound(String),
556
557 #[error("Volume is not attached to the specified instance/device")]
558 VolumeNotAttached,
559
560 #[error("The snapshot '{0}' does not exist")]
561 SnapshotNotFound(String),
562
563 #[error("The AMI '{0}' does not exist")]
564 AmiNotFound(String),
565
566 #[error("A spot datafeed subscription already exists for this account")]
567 SpotDatafeedAlreadyExists,
568
569 #[error("The spot datafeed subscription for this account was not found")]
570 SpotDatafeedNotFound,
571
572 #[error("A launch template with name '{0}' already exists")]
573 LaunchTemplateAlreadyExists(String),
574
575 #[error("Launch template '{0}' does not exist")]
576 LaunchTemplateNotFound(String),
577
578 #[error("The transit gateway '{0}' does not exist")]
579 TransitGatewayNotFound(String),
580
581 #[error("The TGW attachment '{0}' does not exist")]
582 TgwVpcAttachmentNotFound(String),
583
584 #[error("The peering attachment '{0}' does not exist")]
585 TgwPeeringAttachmentNotFound(String),
586
587 #[error("The endpoint '{0}' does not exist")]
588 VpcEndpointNotFound(String),
589
590 #[error("The VPC endpoint service ID '{0}' does not exist")]
591 VpcEndpointServiceNotFound(String),
592
593 #[error("The CIDR reservation '{0}' does not exist")]
594 SubnetCidrReservationNotFound(String),
595
596 #[error("A Default VPC already exists for this user in this region.")]
597 DefaultVpcAlreadyExists,
598
599 #[error("No default VPC found")]
600 DefaultVpcNotFound,
601
602 #[error("The placement group '{0}' does not exist")]
603 PlacementGroupNotFound(String),
604
605 #[error("The placement group '{0}' already exists")]
606 PlacementGroupAlreadyExists(String),
607
608 #[error("{0}")]
609 InvalidParameterValue(String),
610
611 #[error("The network interface permission '{0}' does not exist")]
612 NetworkInterfacePermissionNotFound(String),
613
614 #[error("The instance connect endpoint '{0}' does not exist")]
615 InstanceConnectEndpointNotFound(String),
616
617 #[error("The capacity reservation '{0}' does not exist")]
618 CapacityReservationNotFound(String),
619
620 #[error("The capacity reservation fleet '{0}' does not exist")]
621 CapacityReservationFleetNotFound(String),
622
623 #[error("The COIP pool '{0}' does not exist")]
624 CoipPoolNotFound(String),
625
626 #[error("The security group VPC association for group '{0}' and VPC '{1}' does not exist")]
627 SecurityGroupVpcAssociationNotFound(String, String),
628
629 #[error(
630 "The enclave certificate IAM role association for certificate '{0}' and role '{1}' does not exist"
631 )]
632 EnclaveCertificateIamRoleAssociationNotFound(String, String),
633
634 #[error("The Mac SIP modification task '{0}' does not exist")]
635 MacSipModificationTaskNotFound(String),
636
637 #[error("The declarative policies report '{0}' does not exist")]
638 DeclarativePoliciesReportNotFound(String),
639
640 #[error("The declarative policies report '{0}' is not in a cancellable state")]
641 DeclarativePoliciesReportNotCancellable(String),
642
643 #[error("The BYOIP CIDR '{0}' does not exist")]
644 InvalidByoipCidrNotFound(String),
645
646 #[error("The BYOIP CIDR '{0}' already exists")]
647 ByoipCidrAlreadyExists(String),
648
649 #[error("The BYOIP CIDR '{0}' is not in a state that allows this operation")]
650 ByoipCidrInvalidState(String),
651
652 #[error("The public IPv4 pool '{0}' does not exist")]
653 InvalidPublicIpv4PoolNotFound(String),
654
655 #[error("The public IPv4 pool '{0}' is not empty")]
656 PublicIpv4PoolNotEmpty(String),
657
658 #[error("The CIDR '{0}' does not belong to the public IPv4 pool '{1}'")]
659 PublicIpv4PoolCidrNotFound(String, String),
660
661 #[error("The COIP CIDR '{0}' in pool '{1}' does not exist")]
662 CoipCidrNotFound(String, String),
663
664 #[error("The COIP CIDR '{0}' in pool '{1}' already exists")]
665 CoipCidrAlreadyExists(String, String),
666
667 #[error("The address transfer for allocation '{0}' does not exist")]
668 InvalidAddressTransferNotFound(String),
669
670 #[error(
671 "The NAT gateway secondary address (allocation '{0}' / private IP '{1}') does not exist"
672 )]
673 InvalidNatGatewaySecondaryAddressNotFound(String, String),
674
675 #[error("The VPN concentrator '{0}' does not exist")]
676 InvalidVpnConcentratorNotFound(String),
677
678 #[error("The VPC endpoint connection (service '{0}', endpoint '{1}') does not exist")]
679 InvalidVpcEndpointConnectionNotFound(String, String),
680
681 #[error("The VPC endpoint connection notification '{0}' does not exist")]
682 InvalidVpcEndpointConnectionNotificationNotFound(String),
683
684 #[error("The VPC block public access exclusion '{0}' does not exist")]
685 InvalidVpcBlockPublicAccessExclusionNotFound(String),
686
687 #[error("VPC block public access options have not been configured for this account/region")]
688 InvalidVpcBlockPublicAccessOptionsNotFound,
689
690 #[error("The VPC encryption control '{0}' does not exist")]
691 InvalidVpcEncryptionControlNotFound(String),
692
693 #[error(
694 "The VPN connection static route ({vpn_connection_id} -> {destination_cidr}) does not exist"
695 )]
696 InvalidVpnConnectionRouteNotFound {
697 vpn_connection_id: String,
698 destination_cidr: String,
699 },
700
701 #[error(
702 "The VPN tunnel ({vpn_connection_id} / {outside_ip_address}) does not exist on this VPN connection"
703 )]
704 InvalidVpnTunnelNotFound {
705 vpn_connection_id: String,
706 outside_ip_address: String,
707 },
708
709 #[error("The conversion task '{0}' does not exist")]
710 InvalidConversionTaskNotFound(String),
711
712 #[error("The export task '{0}' does not exist")]
713 InvalidExportTaskNotFound(String),
714
715 #[error("The import task '{0}' does not exist")]
716 InvalidImportTaskNotFound(String),
717
718 #[error("The snapshot import task '{0}' does not exist")]
719 InvalidSnapshotImportTaskNotFound(String),
720
721 #[error("The Mac volume ownership delegation task '{0}' does not exist")]
722 InvalidMacVolumeOwnershipTaskNotFound(String),
723
724 #[error("The replace-root-volume task '{0}' does not exist")]
725 InvalidReplaceRootVolumeTaskNotFound(String),
726
727 #[error("The secondary network '{0}' does not exist")]
728 InvalidSecondaryNetworkNotFound(String),
729
730 #[error("The secondary subnet '{0}' does not exist")]
731 InvalidSecondarySubnetNotFound(String),
732
733 #[error("The trunk interface association '{0}' does not exist")]
734 InvalidTrunkInterfaceAssociationNotFound(String),
735
736 #[error("The secondary network '{0}' has dependent secondary subnets")]
737 SecondaryNetworkHasSubnets(String),
738
739 #[error("The snapshot '{0}' is locked and cannot be modified or deleted")]
740 SnapshotIsLocked(String),
741
742 #[error("The snapshot '{0}' is not in the recycle bin")]
743 SnapshotNotInRecycleBin(String),
744
745 #[error("The volume '{0}' is not in the recycle bin")]
746 VolumeNotInRecycleBin(String),
747
748 #[error("The FPGA image '{0}' does not exist")]
749 InvalidFpgaImageNotFound(String),
750
751 #[error("The import image task '{0}' does not exist")]
752 InvalidImportImageTaskNotFound(String),
753
754 #[error("The store image task for AMI '{0}' does not exist")]
755 InvalidStoreImageTaskNotFound(String),
756
757 #[error("The restore image task for image '{0}' does not exist")]
758 InvalidRestoreImageTaskNotFound(String),
759
760 #[error("The image usage report '{0}' does not exist")]
761 InvalidImageUsageReportNotFound(String),
762
763 #[error("The instance event window '{0}' does not exist")]
764 InvalidInstanceEventWindowNotFound(String),
765
766 #[error("The instance event '{0}' does not exist")]
767 InvalidInstanceEventNotFound(String),
768
769 #[error("The host reservation '{0}' does not exist")]
770 InvalidHostReservationNotFound(String),
771
772 #[error("The scheduled instance '{0}' does not exist")]
773 InvalidScheduledInstanceNotFound(String),
774
775 #[error("The reserved instances listing '{0}' does not exist")]
776 InvalidReservedInstancesListingNotFound(String),
777
778 #[error("The reserved instances exchange '{0}' does not exist")]
779 InvalidReservedInstancesExchangeNotFound(String),
780
781 #[error("The reserved instances modification '{0}' does not exist")]
782 InvalidReservedInstancesModificationNotFound(String),
783
784 #[error("The queued reserved instances purchase '{0}' does not exist")]
785 InvalidQueuedReservedInstancesNotFound(String),
786
787 #[error("The launch template version '{0}' does not exist")]
788 InvalidLaunchTemplateVersionNotFound(i64),
789
790 #[error("The image '{0}' is not in the recycle bin")]
791 ImageNotInRecycleBin(String),
792
793 #[error("The network insights access scope '{0}' does not exist")]
794 InvalidNetworkInsightsAccessScopeNotFound(String),
795
796 #[error("The network insights access scope analysis '{0}' does not exist")]
797 InvalidNetworkInsightsAccessScopeAnalysisNotFound(String),
798
799 #[error("The network insights path '{0}' does not exist")]
800 InvalidNetworkInsightsPathNotFound(String),
801
802 #[error("The network insights analysis '{0}' does not exist")]
803 InvalidNetworkInsightsAnalysisNotFound(String),
804
805 #[error("The network insights path '{0}' has active analyses and cannot be deleted")]
806 NetworkInsightsPathHasAnalyses(String),
807
808 #[error("The traffic mirror filter '{0}' does not exist")]
809 InvalidTrafficMirrorFilterNotFound(String),
810
811 #[error("The traffic mirror filter '{0}' is in use by one or more sessions")]
812 TrafficMirrorFilterInUse(String),
813
814 #[error("The traffic mirror filter rule '{0}' does not exist")]
815 InvalidTrafficMirrorFilterRuleNotFound(String),
816
817 #[error("The traffic mirror session '{0}' does not exist")]
818 InvalidTrafficMirrorSessionNotFound(String),
819
820 #[error("The traffic mirror target '{0}' does not exist")]
821 InvalidTrafficMirrorTargetNotFound(String),
822
823 #[error("The traffic mirror target '{0}' is in use by one or more sessions")]
824 TrafficMirrorTargetInUse(String),
825
826 #[error("The Client VPN endpoint '{0}' does not exist")]
828 InvalidClientVpnEndpointNotFound(String),
829
830 #[error("The Client VPN endpoint '{0}' is in use and cannot be deleted")]
831 ClientVpnEndpointInUse(String),
832
833 #[error("The Client VPN target-network association '{0}' does not exist")]
834 InvalidClientVpnTargetNetworkAssociationNotFound(String),
835
836 #[error("The Client VPN authorization rule for endpoint '{0}' / cidr '{1}' does not exist")]
837 InvalidClientVpnAuthorizationRuleNotFound(String, String),
838
839 #[error("The Client VPN route for endpoint '{0}' / cidr '{1}' / subnet '{2}' does not exist")]
840 InvalidClientVpnRouteNotFound(String, String, String),
841
842 #[error("The local gateway '{0}' does not exist")]
843 InvalidLocalGatewayNotFound(String),
844
845 #[error("The local gateway route table '{0}' does not exist")]
846 InvalidLocalGatewayRouteTableNotFound(String),
847
848 #[error("The local gateway route table '{0}' is in use and has dependent routes")]
849 LocalGatewayRouteTableInUse(String),
850
851 #[error("The local gateway route ({0}, {1}) does not exist")]
852 InvalidLocalGatewayRouteNotFound(String, String),
853
854 #[error("The local gateway virtual interface '{0}' does not exist")]
855 InvalidLocalGatewayVirtualInterfaceNotFound(String),
856
857 #[error("The local gateway virtual interface '{0}' is in use by a virtual interface group")]
858 LocalGatewayVirtualInterfaceInUse(String),
859
860 #[error("The local gateway virtual interface group '{0}' does not exist")]
861 InvalidLocalGatewayVirtualInterfaceGroupNotFound(String),
862
863 #[error(
864 "The local gateway virtual interface group '{0}' is in use and has dependent interfaces or associations"
865 )]
866 LocalGatewayVirtualInterfaceGroupInUse(String),
867
868 #[error(
869 "The local gateway route table virtual interface group association '{0}' does not exist"
870 )]
871 InvalidLocalGatewayRouteTableVirtualInterfaceGroupAssociationNotFound(String),
872
873 #[error("The local gateway route table VPC association '{0}' does not exist")]
874 InvalidLocalGatewayRouteTableVpcAssociationNotFound(String),
875
876 #[error("The route server '{0}' does not exist")]
878 InvalidRouteServerNotFound(String),
879
880 #[error("The route server '{0}' is in use and cannot be deleted")]
881 RouteServerInUse(String),
882
883 #[error("The route server endpoint '{0}' does not exist")]
884 InvalidRouteServerEndpointNotFound(String),
885
886 #[error("The route server endpoint '{0}' is in use and cannot be deleted")]
887 RouteServerEndpointInUse(String),
888
889 #[error("The route server peer '{0}' does not exist")]
890 InvalidRouteServerPeerNotFound(String),
891
892 #[error("The route server association for route server '{0}' and VPC '{1}' does not exist")]
893 InvalidRouteServerAssociationNotFound(String, String),
894
895 #[error("The Verified Access instance '{0}' does not exist")]
897 InvalidVerifiedAccessInstanceNotFound(String),
898
899 #[error("The Verified Access instance '{0}' is in use and cannot be deleted")]
900 VerifiedAccessInstanceInUse(String),
901
902 #[error("The Verified Access trust provider '{0}' does not exist")]
903 InvalidVerifiedAccessTrustProviderNotFound(String),
904
905 #[error("The Verified Access trust provider '{0}' is in use and cannot be deleted")]
906 VerifiedAccessTrustProviderInUse(String),
907
908 #[error("The Verified Access group '{0}' does not exist")]
909 InvalidVerifiedAccessGroupNotFound(String),
910
911 #[error("The Verified Access group '{0}' is in use and cannot be deleted")]
912 VerifiedAccessGroupInUse(String),
913
914 #[error("The Verified Access endpoint '{0}' does not exist")]
915 InvalidVerifiedAccessEndpointNotFound(String),
916
917 #[error(
918 "The Verified Access trust provider attachment (instance '{0}', trust provider '{1}') does not exist"
919 )]
920 InvalidVerifiedAccessTrustProviderAttachmentNotFound(String, String),
921
922 #[error(
924 "The billing ownership offer for capacity reservation '{0}' and account '{1}' does not exist"
925 )]
926 InvalidBillingOwnershipOfferNotFound(String, String),
927
928 #[error(
929 "A billing ownership offer for capacity reservation '{0}' and account '{1}' already exists"
930 )]
931 BillingOwnershipOfferAlreadyExists(String, String),
932
933 #[error("The capacity manager data export '{0}' does not exist")]
934 InvalidCapacityManagerDataExportNotFound(String),
935
936 #[error("The interruptible capacity reservation allocation '{0}' does not exist")]
937 InvalidInterruptibleCapacityReservationAllocationNotFound(String),
938
939 #[error("The capacity block '{0}' does not exist")]
940 InvalidCapacityBlockNotFound(String),
941
942 #[error("The capacity block extension '{0}' does not exist")]
943 InvalidCapacityBlockExtensionNotFound(String),
944
945 #[error("Insufficient capacity in capacity reservation '{0}': have {1}, requested {2}")]
946 InsufficientCapacityReservationCapacity(String, i32, i32),
947
948 #[error("The TGW multicast domain '{0}' does not exist")]
950 InvalidTgwMulticastDomainNotFound(String),
951
952 #[error("The TGW multicast domain '{0}' is in use and cannot be deleted")]
953 TgwMulticastDomainInUse(String),
954
955 #[error("The TGW multicast domain association (domain '{0}', attachment '{1}') does not exist")]
956 InvalidTgwMulticastDomainAssociationNotFound(String, String),
957
958 #[error("The TGW multicast group member '{0}' does not exist")]
959 InvalidTgwMulticastGroupMemberNotFound(String),
960
961 #[error("The TGW multicast group source '{0}' does not exist")]
962 InvalidTgwMulticastGroupSourceNotFound(String),
963
964 #[error("The TGW connect '{0}' does not exist")]
965 InvalidTgwConnectNotFound(String),
966
967 #[error("The TGW connect '{0}' has dependent connect peers")]
968 TgwConnectInUse(String),
969
970 #[error("The TGW connect peer '{0}' does not exist")]
971 InvalidTgwConnectPeerNotFound(String),
972
973 #[error("The TGW metering policy '{0}' does not exist")]
974 InvalidTgwMeteringPolicyNotFound(String),
975
976 #[error("The TGW metering policy entry '{0}' does not exist")]
977 InvalidTgwMeteringPolicyEntryNotFound(String),
978
979 #[error("The TGW policy table '{0}' does not exist")]
980 InvalidTgwPolicyTableNotFound(String),
981
982 #[error(
983 "The TGW policy table association (policy table '{0}', attachment '{1}') does not exist"
984 )]
985 InvalidTgwPolicyTableAssociationNotFound(String, String),
986
987 #[error("The TGW prefix list reference (route table '{0}', prefix list '{1}') does not exist")]
988 InvalidTgwPrefixListReferenceNotFound(String, String),
989
990 #[error("The TGW route table announcement '{0}' does not exist")]
991 InvalidTgwRouteTableAnnouncementNotFound(String),
992
993 #[error("The TGW attachment '{0}' is not in the pendingAcceptance state")]
994 TgwAttachmentNotPendingAcceptance(String),
995
996 #[error("No TGW route in route table '{0}' for destination CIDR '{1}'")]
997 TgwRouteNotFound(String, String),
998
999 #[error("The IPAM '{0}' does not exist")]
1001 InvalidIpamNotFound(String),
1002
1003 #[error("The IPAM '{0}' has dependent scopes or pools and cannot be deleted")]
1004 IpamInUse(String),
1005
1006 #[error("The IPAM scope '{0}' does not exist")]
1007 InvalidIpamScopeNotFound(String),
1008
1009 #[error("The IPAM scope '{0}' has dependent pools and cannot be deleted")]
1010 IpamScopeInUse(String),
1011
1012 #[error("The IPAM scope '{0}' is the default scope and cannot be deleted")]
1013 IpamScopeIsDefault(String),
1014
1015 #[error("The IPAM pool '{0}' does not exist")]
1016 InvalidIpamPoolNotFound(String),
1017
1018 #[error("The IPAM pool '{0}' has dependent allocations and cannot be deleted")]
1019 IpamPoolInUse(String),
1020
1021 #[error("The IPAM pool CIDR ({0}, {1}) does not exist")]
1022 InvalidIpamPoolCidrNotFound(String, String),
1023
1024 #[error("The IPAM pool allocation ({0}, {1}) does not exist")]
1025 InvalidIpamPoolAllocationNotFound(String, String),
1026
1027 #[error("The IPAM resource discovery '{0}' does not exist")]
1028 InvalidIpamResourceDiscoveryNotFound(String),
1029
1030 #[error("The IPAM resource discovery '{0}' is in use and cannot be deleted")]
1031 IpamResourceDiscoveryInUse(String),
1032
1033 #[error("The IPAM resource discovery association '{0}' does not exist")]
1034 InvalidIpamResourceDiscoveryAssociationNotFound(String),
1035
1036 #[error("The IPAM BYO-ASN ({0}, {1}) does not exist")]
1037 InvalidIpamByoasnNotFound(String, String),
1038
1039 #[error("The IPAM BYO-ASN '{0}' is already associated with a CIDR")]
1040 IpamByoasnAlreadyAssociated(String),
1041
1042 #[error("The IPAM external resource verification token '{0}' does not exist")]
1043 InvalidIpamExternalResourceVerificationTokenNotFound(String),
1044
1045 #[error("The IPAM policy '{0}' does not exist")]
1046 InvalidIpamPolicyNotFound(String),
1047
1048 #[error("The IPAM prefix list resolver '{0}' does not exist")]
1049 InvalidIpamPrefixListResolverNotFound(String),
1050
1051 #[error("The IPAM prefix list resolver '{0}' has dependent targets and cannot be deleted")]
1052 IpamPrefixListResolverInUse(String),
1053
1054 #[error("The IPAM prefix list resolver target ({0}, {1}) does not exist")]
1055 InvalidIpamPrefixListResolverTargetNotFound(String, String),
1056
1057 #[error("The volume '{0}' has no modifications")]
1059 InvalidVolumeNotFound(String),
1060
1061 #[error("The bundle task '{0}' does not exist")]
1062 InvalidBundleTaskNotFound(String),
1063
1064 #[error("The import volume task '{0}' does not exist")]
1065 InvalidImportVolumeTaskNotFound(String),
1066
1067 #[error("The export image task '{0}' does not exist")]
1068 InvalidExportImageTaskNotFound(String),
1069
1070 #[error("The outpost LAG '{0}' does not exist")]
1071 InvalidOutpostLagNotFound(String),
1072}
1073
1074impl Ec2State {
1075 fn next_vpc_id(&mut self) -> String {
1076 self.counters.vpc += 1;
1077 format!("vpc-{:08x}", self.counters.vpc)
1078 }
1079
1080 fn next_subnet_id(&mut self) -> String {
1081 self.counters.subnet += 1;
1082 format!("subnet-{:08x}", self.counters.subnet)
1083 }
1084
1085 fn next_igw_id(&mut self) -> String {
1086 self.counters.igw += 1;
1087 format!("igw-{:08x}", self.counters.igw)
1088 }
1089
1090 fn next_sg_id(&mut self) -> String {
1091 self.counters.sg += 1;
1092 format!("sg-{:08x}", self.counters.sg)
1093 }
1094
1095 pub fn next_sgr_id(&mut self) -> String {
1096 self.counters.sgr += 1;
1097 format!("sgr-{:08x}", self.counters.sgr)
1098 }
1099
1100 fn next_rtb_id(&mut self) -> String {
1101 self.counters.rtb += 1;
1102 format!("rtb-{:08x}", self.counters.rtb)
1103 }
1104
1105 fn next_rtbassoc_id(&mut self) -> String {
1106 self.counters.rtbassoc += 1;
1107 format!("rtbassoc-{:08x}", self.counters.rtbassoc)
1108 }
1109
1110 fn next_keypair_id(&mut self) -> String {
1111 self.counters.keypair += 1;
1112 format!("key-{:08x}", self.counters.keypair)
1113 }
1114
1115 fn next_nacl_id(&mut self) -> String {
1116 self.counters.nacl += 1;
1117 format!("acl-{:08x}", self.counters.nacl)
1118 }
1119
1120 fn next_nacl_assoc_id(&mut self) -> String {
1121 self.counters.nacl_assoc += 1;
1122 format!("aclassoc-{:08x}", self.counters.nacl_assoc)
1123 }
1124
1125 fn next_eip_id(&mut self) -> String {
1126 self.counters.eip += 1;
1127 format!("eipalloc-{:08x}", self.counters.eip)
1128 }
1129
1130 fn next_nat_id(&mut self) -> String {
1131 self.counters.nat += 1;
1132 format!("nat-{:08x}", self.counters.nat)
1133 }
1134
1135 fn next_dopt_id(&mut self) -> String {
1136 self.counters.dopt += 1;
1137 format!("dopt-{:08x}", self.counters.dopt)
1138 }
1139
1140 fn next_tgw_id(&mut self) -> String {
1141 self.counters.tgw += 1;
1142 format!("tgw-{:08x}", self.counters.tgw)
1143 }
1144
1145 fn next_tgw_attach_id(&mut self) -> String {
1146 self.counters.tgw_attach += 1;
1147 format!("tgw-attach-{:08x}", self.counters.tgw_attach)
1148 }
1149
1150 fn next_tgw_rtb_id(&mut self) -> String {
1151 self.counters.tgw_rtb += 1;
1152 format!("tgw-rtb-{:08x}", self.counters.tgw_rtb)
1153 }
1154
1155 pub fn next_tgw_multicast_domain_id(&mut self) -> String {
1156 self.counters.tgw_multicast_domain += 1;
1157 format!(
1158 "tgw-mcast-domain-{:08x}",
1159 self.counters.tgw_multicast_domain
1160 )
1161 }
1162
1163 pub fn next_tgw_connect_id(&mut self) -> String {
1164 self.counters.tgw_connect = self.counters.tgw_connect.wrapping_add(1);
1167 self.next_tgw_attach_id()
1168 }
1169
1170 pub fn next_tgw_connect_peer_id(&mut self) -> String {
1171 self.counters.tgw_connect_peer += 1;
1172 format!("tgw-connect-peer-{:08x}", self.counters.tgw_connect_peer)
1173 }
1174
1175 pub fn next_tgw_metering_policy_id(&mut self) -> String {
1176 self.counters.tgw_metering_policy += 1;
1177 format!("tgw-mp-{:08x}", self.counters.tgw_metering_policy)
1178 }
1179
1180 pub fn next_tgw_metering_policy_entry_id(&mut self) -> String {
1181 self.counters.tgw_metering_policy_entry += 1;
1182 format!(
1183 "tgw-mp-entry-{:08x}",
1184 self.counters.tgw_metering_policy_entry
1185 )
1186 }
1187
1188 pub fn next_tgw_policy_table_id(&mut self) -> String {
1189 self.counters.tgw_policy_table += 1;
1190 format!("tgw-rtb-policy-{:08x}", self.counters.tgw_policy_table)
1191 }
1192
1193 pub fn next_tgw_route_table_announcement_id(&mut self) -> String {
1194 self.counters.tgw_route_table_announcement += 1;
1195 format!(
1196 "tgw-rtb-ann-{:08x}",
1197 self.counters.tgw_route_table_announcement
1198 )
1199 }
1200
1201 fn next_instance_id(&mut self) -> String {
1202 self.counters.instance += 1;
1203 format!("i-{:08x}", self.counters.instance)
1204 }
1205
1206 fn next_vol_id(&mut self) -> String {
1207 self.counters.vol += 1;
1208 format!("vol-{:08x}", self.counters.vol)
1209 }
1210
1211 fn next_snapshot_id(&mut self) -> String {
1212 self.counters.snapshot += 1;
1213 format!("snap-{:08x}", self.counters.snapshot)
1214 }
1215
1216 fn next_ami_id(&mut self) -> String {
1217 self.counters.ami += 1;
1218 format!("ami-{:08x}", self.counters.ami)
1219 }
1220
1221 fn next_lt_id(&mut self) -> String {
1222 self.counters.lt += 1;
1223 format!("lt-{:08x}", self.counters.lt)
1224 }
1225
1226 fn next_spot_id(&mut self) -> String {
1227 self.counters.spot += 1;
1228 format!("sir-{:08x}", self.counters.spot)
1229 }
1230
1231 fn next_iam_assoc_id(&mut self) -> String {
1232 self.counters.iam_assoc += 1;
1233 format!("iip-assoc-{:08x}", self.counters.iam_assoc)
1234 }
1235
1236 fn next_host_id(&mut self) -> String {
1237 self.counters.host += 1;
1238 format!("h-{:08x}", self.counters.host)
1239 }
1240
1241 fn next_fleet_id(&mut self) -> String {
1242 self.counters.fleet += 1;
1243 format!("fleet-{:08x}", self.counters.fleet)
1244 }
1245
1246 fn next_vpce_svc_id(&mut self) -> String {
1247 self.counters.vpce_svc += 1;
1248 format!("vpce-svc-{:08x}", self.counters.vpce_svc)
1249 }
1250
1251 fn next_spot_fleet_id(&mut self) -> String {
1252 self.counters.spot_fleet += 1;
1253 format!("sfr-{:08x}", self.counters.spot_fleet)
1254 }
1255
1256 fn next_subnet_cidr_res_id(&mut self) -> String {
1257 self.counters.subnet_cidr_res += 1;
1258 format!("scr-{:08x}", self.counters.subnet_cidr_res)
1259 }
1260
1261 fn next_subnet_ipv6_assoc_id(&mut self) -> String {
1262 self.counters.subnet_ipv6_assoc += 1;
1263 format!("subnet-cidr-assoc-{:08x}", self.counters.subnet_ipv6_assoc)
1264 }
1265
1266 pub fn create_network_acl(
1269 &mut self,
1270 vpc_id: &str,
1271 tags: Tags,
1272 ) -> Result<&NetworkAcl, Ec2Error> {
1273 if !self.vpcs.contains_key(vpc_id) {
1274 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
1275 }
1276 let nacl_id = self.next_nacl_id();
1277 let nacl = NetworkAcl {
1278 network_acl_id: nacl_id.clone(),
1279 vpc_id: vpc_id.to_string(),
1280 is_default: false,
1281 entries: vec![
1282 NetworkAclEntry {
1284 rule_number: 32767,
1285 protocol: "-1".to_string(),
1286 rule_action: "deny".to_string(),
1287 egress: false,
1288 cidr_block: Some("0.0.0.0/0".to_string()),
1289 ipv6_cidr_block: None,
1290 port_range: None,
1291 icmp_type_code: None,
1292 },
1293 NetworkAclEntry {
1295 rule_number: 32767,
1296 protocol: "-1".to_string(),
1297 rule_action: "deny".to_string(),
1298 egress: true,
1299 cidr_block: Some("0.0.0.0/0".to_string()),
1300 ipv6_cidr_block: None,
1301 port_range: None,
1302 icmp_type_code: None,
1303 },
1304 ],
1305 associations: Vec::new(),
1306 tags,
1307 };
1308 self.network_acls.insert(nacl_id.clone(), nacl);
1309 Ok(self.network_acls.get(&nacl_id).unwrap())
1310 }
1311
1312 pub fn delete_network_acl(&mut self, nacl_id: &str) -> Result<(), Ec2Error> {
1313 let nacl = self
1314 .network_acls
1315 .get(nacl_id)
1316 .ok_or_else(|| Ec2Error::NetworkAclNotFound(nacl_id.to_string()))?;
1317 if nacl.is_default {
1318 return Err(Ec2Error::CannotDeleteDefaultNetworkAcl);
1319 }
1320 self.network_acls.remove(nacl_id);
1321 Ok(())
1322 }
1323
1324 pub fn create_network_acl_entry(
1325 &mut self,
1326 nacl_id: &str,
1327 entry: NetworkAclEntry,
1328 ) -> Result<(), Ec2Error> {
1329 let nacl = self
1330 .network_acls
1331 .get_mut(nacl_id)
1332 .ok_or_else(|| Ec2Error::NetworkAclNotFound(nacl_id.to_string()))?;
1333 nacl.entries.push(entry);
1334 Ok(())
1335 }
1336
1337 pub fn delete_network_acl_entry(
1338 &mut self,
1339 nacl_id: &str,
1340 rule_number: i32,
1341 egress: bool,
1342 ) -> Result<(), Ec2Error> {
1343 let nacl = self
1344 .network_acls
1345 .get_mut(nacl_id)
1346 .ok_or_else(|| Ec2Error::NetworkAclNotFound(nacl_id.to_string()))?;
1347 nacl.entries
1348 .retain(|e| !(e.rule_number == rule_number && e.egress == egress));
1349 Ok(())
1350 }
1351
1352 pub fn replace_network_acl_entry(
1353 &mut self,
1354 nacl_id: &str,
1355 entry: NetworkAclEntry,
1356 ) -> Result<(), Ec2Error> {
1357 let nacl = self
1358 .network_acls
1359 .get_mut(nacl_id)
1360 .ok_or_else(|| Ec2Error::NetworkAclNotFound(nacl_id.to_string()))?;
1361 nacl.entries
1362 .retain(|e| !(e.rule_number == entry.rule_number && e.egress == entry.egress));
1363 nacl.entries.push(entry);
1364 Ok(())
1365 }
1366
1367 pub fn replace_network_acl_association(
1368 &mut self,
1369 assoc_id: &str,
1370 new_nacl_id: &str,
1371 ) -> Result<String, Ec2Error> {
1372 if !self.network_acls.contains_key(new_nacl_id) {
1373 return Err(Ec2Error::NetworkAclNotFound(new_nacl_id.to_string()));
1374 }
1375 let mut found_subnet_id: Option<String> = None;
1377 for nacl in self.network_acls.values_mut() {
1378 if let Some(pos) = nacl
1379 .associations
1380 .iter()
1381 .position(|a| a.network_acl_association_id == assoc_id)
1382 {
1383 found_subnet_id = Some(nacl.associations[pos].subnet_id.clone());
1384 nacl.associations.remove(pos);
1385 break;
1386 }
1387 }
1388 let subnet_id =
1389 found_subnet_id.ok_or_else(|| Ec2Error::AssociationNotFound(assoc_id.to_string()))?;
1390 let new_assoc_id = self.next_nacl_assoc_id();
1391 let new_nacl = self.network_acls.get_mut(new_nacl_id).unwrap();
1392 new_nacl.associations.push(NetworkAclAssociation {
1393 network_acl_association_id: new_assoc_id.clone(),
1394 network_acl_id: new_nacl_id.to_string(),
1395 subnet_id,
1396 });
1397 Ok(new_assoc_id)
1398 }
1399
1400 pub fn allocate_address(&mut self, tags: Tags) -> &ElasticIp {
1403 let alloc_id = self.next_eip_id();
1404 let count = self.counters.eip;
1405 let eip = ElasticIp {
1406 allocation_id: alloc_id.clone(),
1407 public_ip: format!(
1408 "54.{}.{}.{}",
1409 (count >> 16) & 0xff,
1410 (count >> 8) & 0xff,
1411 count & 0xff
1412 ),
1413 association_id: None,
1414 instance_id: None,
1415 network_interface_id: None,
1416 private_ip_address: None,
1417 address_attribute_ptr_record: None,
1418 domain: "vpc".to_string(),
1419 pending_transfer: None,
1420 tags,
1421 };
1422 self.elastic_ips.insert(alloc_id.clone(), eip);
1423 self.elastic_ips.get(&alloc_id).unwrap()
1424 }
1425
1426 pub fn release_address(&mut self, allocation_id: &str) -> Result<(), Ec2Error> {
1427 if self.elastic_ips.remove(allocation_id).is_none() {
1428 return Err(Ec2Error::AllocationNotFound(allocation_id.to_string()));
1429 }
1430 Ok(())
1431 }
1432
1433 pub fn associate_address(
1434 &mut self,
1435 allocation_id: &str,
1436 instance_id: Option<String>,
1437 network_interface_id: Option<String>,
1438 private_ip: Option<String>,
1439 ) -> Result<String, Ec2Error> {
1440 if !self.elastic_ips.contains_key(allocation_id) {
1441 return Err(Ec2Error::AllocationNotFound(allocation_id.to_string()));
1442 }
1443 self.counters.eip_assoc += 1;
1444 let assoc_id = format!("eipassoc-{:08x}", self.counters.eip_assoc);
1445 let eip = self.elastic_ips.get_mut(allocation_id).unwrap();
1446 eip.association_id = Some(assoc_id.clone());
1447 eip.instance_id = instance_id;
1448 eip.network_interface_id = network_interface_id;
1449 eip.private_ip_address = private_ip;
1450 Ok(assoc_id)
1451 }
1452
1453 pub fn disassociate_address(&mut self, association_id: &str) -> Result<(), Ec2Error> {
1454 for eip in self.elastic_ips.values_mut() {
1455 if eip.association_id.as_deref() == Some(association_id) {
1456 eip.association_id = None;
1457 eip.instance_id = None;
1458 eip.network_interface_id = None;
1459 eip.private_ip_address = None;
1460 return Ok(());
1461 }
1462 }
1463 Err(Ec2Error::AssociationIdNotFound(association_id.to_string()))
1464 }
1465
1466 pub fn create_nat_gateway(
1469 &mut self,
1470 subnet_id: &str,
1471 connectivity_type: &str,
1472 allocation_id: Option<String>,
1473 tags: Tags,
1474 ) -> Result<&NatGateway, Ec2Error> {
1475 let subnet = self
1476 .subnets
1477 .get(subnet_id)
1478 .ok_or_else(|| Ec2Error::SubnetNotFound(subnet_id.to_string()))?;
1479 let vpc_id = subnet.vpc_id.clone();
1480 let nat_id = self.next_nat_id();
1481 let public_ip = if connectivity_type == "public" {
1482 allocation_id
1483 .as_ref()
1484 .and_then(|id| self.elastic_ips.get(id).map(|e| e.public_ip.clone()))
1485 } else {
1486 None
1487 };
1488 let nat = NatGateway {
1489 nat_gateway_id: nat_id.clone(),
1490 vpc_id,
1491 subnet_id: subnet_id.to_string(),
1492 state: "available".to_string(),
1493 connectivity_type: connectivity_type.to_string(),
1494 allocation_id,
1495 public_ip,
1496 secondary_addresses: Vec::new(),
1497 tags,
1498 };
1499 self.nat_gateways.insert(nat_id.clone(), nat);
1500 Ok(self.nat_gateways.get(&nat_id).unwrap())
1501 }
1502
1503 pub fn delete_nat_gateway(&mut self, nat_id: &str) -> Result<(), Ec2Error> {
1504 let nat = self
1505 .nat_gateways
1506 .get_mut(nat_id)
1507 .ok_or_else(|| Ec2Error::NatGatewayNotFound(nat_id.to_string()))?;
1508 nat.state = "deleted".to_string();
1509 Ok(())
1510 }
1511
1512 pub fn create_dhcp_options(
1515 &mut self,
1516 configurations: Vec<DhcpConfiguration>,
1517 tags: Tags,
1518 ) -> &DhcpOptions {
1519 let dopt_id = self.next_dopt_id();
1520 let dopt = DhcpOptions {
1521 dhcp_options_id: dopt_id.clone(),
1522 configurations,
1523 tags,
1524 };
1525 self.dhcp_options.insert(dopt_id.clone(), dopt);
1526 self.dhcp_options.get(&dopt_id).unwrap()
1527 }
1528
1529 pub fn delete_dhcp_options(&mut self, dopt_id: &str) -> Result<(), Ec2Error> {
1530 for vpc in self.vpcs.values() {
1532 if vpc.dhcp_options_id == dopt_id {
1533 return Err(Ec2Error::DhcpOptionsAssociatedWithVpc);
1534 }
1535 }
1536 if self.dhcp_options.remove(dopt_id).is_none() {
1537 return Err(Ec2Error::DhcpOptionsNotFound(dopt_id.to_string()));
1538 }
1539 Ok(())
1540 }
1541
1542 pub fn associate_dhcp_options(&mut self, vpc_id: &str, dopt_id: &str) -> Result<(), Ec2Error> {
1543 if dopt_id != "default" && !self.dhcp_options.contains_key(dopt_id) {
1544 return Err(Ec2Error::DhcpOptionsNotFound(dopt_id.to_string()));
1545 }
1546 let vpc = self
1547 .vpcs
1548 .get_mut(vpc_id)
1549 .ok_or_else(|| Ec2Error::VpcNotFound(vpc_id.to_string()))?;
1550 vpc.dhcp_options_id = dopt_id.to_string();
1551 Ok(())
1552 }
1553
1554 pub fn create_vpc(
1555 &mut self,
1556 cidr_block: &str,
1557 instance_tenancy: &str,
1558 tags: Tags,
1559 ) -> Result<&Vpc, Ec2Error> {
1560 let vpc_id = self.next_vpc_id();
1561 let dhcp_options_id = format!("dopt-{:08x}", self.counters.vpc);
1562 let vpc = Vpc {
1563 vpc_id: vpc_id.clone(),
1564 cidr_block: cidr_block.to_string(),
1565 state: "available".to_string(),
1566 dhcp_options_id,
1567 instance_tenancy: instance_tenancy.to_string(),
1568 is_default: false,
1569 enable_dns_hostnames: false,
1570 enable_dns_support: true,
1571 secondary_cidr_blocks: Vec::new(),
1572 tags,
1573 classic_link_enabled: false,
1574 };
1575 self.vpcs.insert(vpc_id.clone(), vpc);
1576 let nacl_id = self.next_nacl_id();
1578 let default_nacl = NetworkAcl {
1579 network_acl_id: nacl_id.clone(),
1580 vpc_id: vpc_id.clone(),
1581 is_default: true,
1582 entries: vec![
1583 NetworkAclEntry {
1585 rule_number: 100,
1586 protocol: "-1".to_string(),
1587 rule_action: "allow".to_string(),
1588 egress: false,
1589 cidr_block: Some("0.0.0.0/0".to_string()),
1590 ipv6_cidr_block: None,
1591 port_range: None,
1592 icmp_type_code: None,
1593 },
1594 NetworkAclEntry {
1596 rule_number: 32767,
1597 protocol: "-1".to_string(),
1598 rule_action: "deny".to_string(),
1599 egress: false,
1600 cidr_block: Some("0.0.0.0/0".to_string()),
1601 ipv6_cidr_block: None,
1602 port_range: None,
1603 icmp_type_code: None,
1604 },
1605 NetworkAclEntry {
1607 rule_number: 100,
1608 protocol: "-1".to_string(),
1609 rule_action: "allow".to_string(),
1610 egress: true,
1611 cidr_block: Some("0.0.0.0/0".to_string()),
1612 ipv6_cidr_block: None,
1613 port_range: None,
1614 icmp_type_code: None,
1615 },
1616 NetworkAclEntry {
1618 rule_number: 32767,
1619 protocol: "-1".to_string(),
1620 rule_action: "deny".to_string(),
1621 egress: true,
1622 cidr_block: Some("0.0.0.0/0".to_string()),
1623 ipv6_cidr_block: None,
1624 port_range: None,
1625 icmp_type_code: None,
1626 },
1627 ],
1628 associations: Vec::new(),
1629 tags: Tags::new(),
1630 };
1631 self.network_acls.insert(nacl_id, default_nacl);
1632 Ok(self.vpcs.get(&vpc_id).unwrap())
1633 }
1634
1635 pub fn delete_vpc(&mut self, vpc_id: &str) -> Result<(), Ec2Error> {
1636 if self.vpcs.remove(vpc_id).is_none() {
1637 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
1638 }
1639 Ok(())
1640 }
1641
1642 pub fn modify_vpc_attribute(
1643 &mut self,
1644 vpc_id: &str,
1645 attribute: &str,
1646 value: bool,
1647 ) -> Result<(), Ec2Error> {
1648 let vpc = self
1649 .vpcs
1650 .get_mut(vpc_id)
1651 .ok_or_else(|| Ec2Error::VpcNotFound(vpc_id.to_string()))?;
1652 match attribute {
1653 "enableDnsHostnames" => vpc.enable_dns_hostnames = value,
1654 "enableDnsSupport" => vpc.enable_dns_support = value,
1655 _ => {}
1656 }
1657 Ok(())
1658 }
1659
1660 pub fn create_subnet(
1661 &mut self,
1662 vpc_id: &str,
1663 cidr_block: &str,
1664 availability_zone: &str,
1665 tags: Tags,
1666 ) -> Result<&Subnet, Ec2Error> {
1667 if !self.vpcs.contains_key(vpc_id) {
1668 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
1669 }
1670 let subnet_id = self.next_subnet_id();
1671 let subnet = Subnet {
1672 subnet_id: subnet_id.clone(),
1673 vpc_id: vpc_id.to_string(),
1674 cidr_block: cidr_block.to_string(),
1675 availability_zone: availability_zone.to_string(),
1676 state: "available".to_string(),
1677 available_ip_address_count: 251,
1678 map_public_ip_on_launch: false,
1679 ipv6_cidr_blocks: Vec::new(),
1680 tags,
1681 };
1682 self.subnets.insert(subnet_id.clone(), subnet);
1683 let nacl_id_opt = self
1685 .network_acls
1686 .values()
1687 .find(|nacl| nacl.vpc_id == vpc_id && nacl.is_default)
1688 .map(|nacl| nacl.network_acl_id.clone());
1689 if let Some(nacl_id) = nacl_id_opt {
1690 let assoc_id = self.next_nacl_assoc_id();
1691 if let Some(nacl) = self.network_acls.get_mut(&nacl_id) {
1692 nacl.associations.push(NetworkAclAssociation {
1693 network_acl_association_id: assoc_id,
1694 network_acl_id: nacl_id.clone(),
1695 subnet_id: subnet_id.clone(),
1696 });
1697 }
1698 }
1699 Ok(self.subnets.get(&subnet_id).unwrap())
1700 }
1701
1702 pub fn modify_subnet_attribute(
1703 &mut self,
1704 subnet_id: &str,
1705 map_public_ip_on_launch: bool,
1706 ) -> Result<(), Ec2Error> {
1707 let subnet = self
1708 .subnets
1709 .get_mut(subnet_id)
1710 .ok_or_else(|| Ec2Error::SubnetNotFound(subnet_id.to_string()))?;
1711 subnet.map_public_ip_on_launch = map_public_ip_on_launch;
1712 Ok(())
1713 }
1714
1715 pub fn delete_subnet(&mut self, subnet_id: &str) -> Result<(), Ec2Error> {
1716 if self.subnets.remove(subnet_id).is_none() {
1717 return Err(Ec2Error::SubnetNotFound(subnet_id.to_string()));
1718 }
1719 Ok(())
1720 }
1721
1722 pub fn create_internet_gateway(&mut self, tags: Tags) -> &InternetGateway {
1723 let igw_id = self.next_igw_id();
1724 let igw = InternetGateway {
1725 igw_id: igw_id.clone(),
1726 attachments: Vec::new(),
1727 tags,
1728 };
1729 self.igws.insert(igw_id.clone(), igw);
1730 self.igws.get(&igw_id).unwrap()
1731 }
1732
1733 pub fn attach_internet_gateway(&mut self, igw_id: &str, vpc_id: &str) -> Result<(), Ec2Error> {
1734 if !self.vpcs.contains_key(vpc_id) {
1735 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
1736 }
1737 let igw = self
1738 .igws
1739 .get_mut(igw_id)
1740 .ok_or_else(|| Ec2Error::InternetGatewayNotFound(igw_id.to_string()))?;
1741 igw.attachments.push(IgwAttachment {
1742 vpc_id: vpc_id.to_string(),
1743 state: "available".to_string(),
1744 });
1745 Ok(())
1746 }
1747
1748 pub fn detach_internet_gateway(&mut self, igw_id: &str, vpc_id: &str) -> Result<(), Ec2Error> {
1749 let igw = self
1750 .igws
1751 .get_mut(igw_id)
1752 .ok_or_else(|| Ec2Error::InternetGatewayNotFound(igw_id.to_string()))?;
1753 igw.attachments.retain(|a| a.vpc_id != vpc_id);
1754 Ok(())
1755 }
1756
1757 pub fn delete_internet_gateway(&mut self, igw_id: &str) -> Result<(), Ec2Error> {
1758 if self.igws.remove(igw_id).is_none() {
1759 return Err(Ec2Error::InternetGatewayNotFound(igw_id.to_string()));
1760 }
1761 Ok(())
1762 }
1763
1764 pub fn create_security_group(
1765 &mut self,
1766 group_name: &str,
1767 description: &str,
1768 vpc_id: &str,
1769 owner_id: &str,
1770 tags: Tags,
1771 ) -> Result<&SecurityGroup, Ec2Error> {
1772 if !self.vpcs.contains_key(vpc_id) {
1773 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
1774 }
1775 let group_id = self.next_sg_id();
1776 let sg = SecurityGroup {
1777 group_id: group_id.clone(),
1778 group_name: group_name.to_string(),
1779 description: description.to_string(),
1780 vpc_id: vpc_id.to_string(),
1781 owner_id: owner_id.to_string(),
1782 ingress_rules: Vec::new(),
1783 egress_rules: vec![IpPermission {
1784 rule_id: self.next_sgr_id(),
1785 from_port: None,
1786 to_port: None,
1787 ip_protocol: "-1".to_string(),
1788 ip_ranges: vec![IpRange {
1789 cidr_ip: "0.0.0.0/0".to_string(),
1790 description: None,
1791 }],
1792 ipv6_ranges: Vec::new(),
1793 user_id_group_pairs: Vec::new(),
1794 }],
1795 tags,
1796 };
1797 self.security_groups.insert(group_id.clone(), sg);
1798 Ok(self.security_groups.get(&group_id).unwrap())
1799 }
1800
1801 pub fn authorize_security_group_ingress(
1802 &mut self,
1803 group_id: &str,
1804 rules: Vec<IpPermission>,
1805 ) -> Result<(), Ec2Error> {
1806 let rules_with_ids: Vec<IpPermission> = rules
1807 .into_iter()
1808 .map(|mut r| {
1809 if r.rule_id.is_empty() {
1810 r.rule_id = self.next_sgr_id();
1811 }
1812 r
1813 })
1814 .collect();
1815 let sg = self
1816 .security_groups
1817 .get_mut(group_id)
1818 .ok_or_else(|| Ec2Error::SecurityGroupNotFound(group_id.to_string()))?;
1819 sg.ingress_rules.extend(rules_with_ids);
1820 Ok(())
1821 }
1822
1823 pub fn authorize_security_group_egress(
1824 &mut self,
1825 group_id: &str,
1826 rules: Vec<IpPermission>,
1827 ) -> Result<(), Ec2Error> {
1828 let rules_with_ids: Vec<IpPermission> = rules
1829 .into_iter()
1830 .map(|mut r| {
1831 if r.rule_id.is_empty() {
1832 r.rule_id = self.next_sgr_id();
1833 }
1834 r
1835 })
1836 .collect();
1837 let sg = self
1838 .security_groups
1839 .get_mut(group_id)
1840 .ok_or_else(|| Ec2Error::SecurityGroupNotFound(group_id.to_string()))?;
1841 sg.egress_rules.extend(rules_with_ids);
1842 Ok(())
1843 }
1844
1845 pub fn revoke_security_group_ingress(
1846 &mut self,
1847 group_id: &str,
1848 rules: &[IpPermission],
1849 ) -> Result<(), Ec2Error> {
1850 let sg = self
1851 .security_groups
1852 .get_mut(group_id)
1853 .ok_or_else(|| Ec2Error::SecurityGroupNotFound(group_id.to_string()))?;
1854 for rule in rules {
1855 sg.ingress_rules.retain(|r| {
1856 r.ip_protocol != rule.ip_protocol
1857 || r.from_port != rule.from_port
1858 || r.to_port != rule.to_port
1859 });
1860 }
1861 Ok(())
1862 }
1863
1864 pub fn revoke_security_group_egress(
1865 &mut self,
1866 group_id: &str,
1867 rules: &[IpPermission],
1868 ) -> Result<(), Ec2Error> {
1869 let sg = self
1870 .security_groups
1871 .get_mut(group_id)
1872 .ok_or_else(|| Ec2Error::SecurityGroupNotFound(group_id.to_string()))?;
1873 for rule in rules {
1874 sg.egress_rules.retain(|r| {
1875 r.ip_protocol != rule.ip_protocol
1876 || r.from_port != rule.from_port
1877 || r.to_port != rule.to_port
1878 });
1879 }
1880 Ok(())
1881 }
1882
1883 pub fn delete_security_group(&mut self, group_id: &str) -> Result<(), Ec2Error> {
1884 if self.security_groups.remove(group_id).is_none() {
1885 return Err(Ec2Error::SecurityGroupNotFound(group_id.to_string()));
1886 }
1887 Ok(())
1888 }
1889
1890 pub fn create_route_table(
1891 &mut self,
1892 vpc_id: &str,
1893 tags: Tags,
1894 ) -> Result<&RouteTable, Ec2Error> {
1895 let vpc = self
1896 .vpcs
1897 .get(vpc_id)
1898 .ok_or_else(|| Ec2Error::VpcNotFound(vpc_id.to_string()))?;
1899 let local_cidr = vpc.cidr_block.clone();
1900 let rtb_id = self.next_rtb_id();
1901 let rtb = RouteTable {
1902 route_table_id: rtb_id.clone(),
1903 vpc_id: vpc_id.to_string(),
1904 routes: vec![Route {
1905 destination_cidr_block: Some(local_cidr),
1906 destination_ipv6_cidr_block: None,
1907 gateway_id: Some("local".to_string()),
1908 state: "active".to_string(),
1909 origin: "CreateRouteTable".to_string(),
1910 }],
1911 associations: Vec::new(),
1912 propagating_vgws: Vec::new(),
1913 tags,
1914 };
1915 self.route_tables.insert(rtb_id.clone(), rtb);
1916 Ok(self.route_tables.get(&rtb_id).unwrap())
1917 }
1918
1919 pub fn associate_route_table(
1920 &mut self,
1921 rtb_id: &str,
1922 subnet_id: &str,
1923 ) -> Result<String, Ec2Error> {
1924 if !self.subnets.contains_key(subnet_id) {
1925 return Err(Ec2Error::SubnetNotFound(subnet_id.to_string()));
1926 }
1927 let assoc_id = self.next_rtbassoc_id();
1928 let rtb = self
1929 .route_tables
1930 .get_mut(rtb_id)
1931 .ok_or_else(|| Ec2Error::RouteTableNotFound(rtb_id.to_string()))?;
1932 rtb.associations.push(RouteTableAssociation {
1933 association_id: assoc_id.clone(),
1934 subnet_id: Some(subnet_id.to_string()),
1935 gateway_id: None,
1936 main: false,
1937 state: "associated".to_string(),
1938 });
1939 Ok(assoc_id)
1940 }
1941
1942 pub fn disassociate_route_table(&mut self, assoc_id: &str) -> Result<(), Ec2Error> {
1943 for rtb in self.route_tables.values_mut() {
1944 let before = rtb.associations.len();
1945 rtb.associations.retain(|a| a.association_id != assoc_id);
1946 if rtb.associations.len() < before {
1947 return Ok(());
1948 }
1949 }
1950 Err(Ec2Error::AssociationIdNotFound(assoc_id.to_string()))
1951 }
1952
1953 pub fn create_route(
1954 &mut self,
1955 rtb_id: &str,
1956 destination_cidr: Option<String>,
1957 destination_ipv6_cidr: Option<String>,
1958 gateway_id: Option<String>,
1959 ) -> Result<(), Ec2Error> {
1960 let rtb = self
1961 .route_tables
1962 .get_mut(rtb_id)
1963 .ok_or_else(|| Ec2Error::RouteTableNotFound(rtb_id.to_string()))?;
1964 rtb.routes.push(Route {
1965 destination_cidr_block: destination_cidr,
1966 destination_ipv6_cidr_block: destination_ipv6_cidr,
1967 gateway_id,
1968 state: "active".to_string(),
1969 origin: "CreateRoute".to_string(),
1970 });
1971 Ok(())
1972 }
1973
1974 pub fn delete_route(&mut self, rtb_id: &str, destination_cidr: &str) -> Result<(), Ec2Error> {
1975 let rtb = self
1976 .route_tables
1977 .get_mut(rtb_id)
1978 .ok_or_else(|| Ec2Error::RouteTableNotFound(rtb_id.to_string()))?;
1979 rtb.routes.retain(|r| {
1980 r.destination_cidr_block.as_deref() != Some(destination_cidr)
1981 && r.destination_ipv6_cidr_block.as_deref() != Some(destination_cidr)
1982 });
1983 Ok(())
1984 }
1985
1986 pub fn delete_route_table(&mut self, rtb_id: &str) -> Result<(), Ec2Error> {
1987 if self.route_tables.remove(rtb_id).is_none() {
1988 return Err(Ec2Error::RouteTableNotFound(rtb_id.to_string()));
1989 }
1990 Ok(())
1991 }
1992
1993 pub fn import_key_pair(
1994 &mut self,
1995 key_name: &str,
1996 _public_key_material: &str,
1997 tags: Tags,
1998 ) -> &KeyPair {
1999 let key_pair_id = self.next_keypair_id();
2000 let kp = KeyPair {
2001 key_pair_id: key_pair_id.clone(),
2002 key_name: key_name.to_string(),
2003 fingerprint: "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99".to_string(),
2004 tags,
2005 };
2006 self.key_pairs.insert(key_name.to_string(), kp);
2007 self.key_pairs.get(key_name).unwrap()
2008 }
2009
2010 pub fn delete_key_pair(
2011 &mut self,
2012 key_name: Option<&str>,
2013 key_pair_id: Option<&str>,
2014 ) -> Result<(), Ec2Error> {
2015 if let Some(name) = key_name {
2016 self.key_pairs.remove(name);
2017 return Ok(());
2018 }
2019 if let Some(id) = key_pair_id {
2020 let name = self
2021 .key_pairs
2022 .iter()
2023 .find(|(_, kp)| kp.key_pair_id == id)
2024 .map(|(k, _)| k.clone());
2025 if let Some(n) = name {
2026 self.key_pairs.remove(&n);
2027 return Ok(());
2028 }
2029 }
2030 Ok(())
2031 }
2032
2033 pub fn create_tags(&mut self, resource_ids: &[String], tags: &Tags) {
2034 for id in resource_ids {
2035 if let Some(r) = self.vpcs.get_mut(id.as_str()) {
2036 r.tags.extend(tags.clone());
2037 } else if let Some(r) = self.subnets.get_mut(id.as_str()) {
2038 r.tags.extend(tags.clone());
2039 } else if let Some(r) = self.igws.get_mut(id.as_str()) {
2040 r.tags.extend(tags.clone());
2041 } else if let Some(r) = self.security_groups.get_mut(id.as_str()) {
2042 r.tags.extend(tags.clone());
2043 } else if let Some(r) = self.route_tables.get_mut(id.as_str()) {
2044 r.tags.extend(tags.clone());
2045 } else {
2046 let name = self
2048 .key_pairs
2049 .iter()
2050 .find(|(_, kp)| kp.key_pair_id == *id)
2051 .map(|(k, _)| k.clone());
2052 if let Some(n) = name {
2053 if let Some(kp) = self.key_pairs.get_mut(&n) {
2054 kp.tags.extend(tags.clone());
2055 }
2056 }
2057 }
2058 }
2059 }
2060
2061 pub fn delete_tags(&mut self, resource_ids: &[String], tag_keys: &[String]) {
2062 for id in resource_ids {
2063 let remove = |tags: &mut Tags| {
2064 for k in tag_keys {
2065 tags.remove(k);
2066 }
2067 };
2068 if let Some(r) = self.vpcs.get_mut(id.as_str()) {
2069 remove(&mut r.tags);
2070 } else if let Some(r) = self.subnets.get_mut(id.as_str()) {
2071 remove(&mut r.tags);
2072 } else if let Some(r) = self.igws.get_mut(id.as_str()) {
2073 remove(&mut r.tags);
2074 } else if let Some(r) = self.security_groups.get_mut(id.as_str()) {
2075 remove(&mut r.tags);
2076 } else if let Some(r) = self.route_tables.get_mut(id.as_str()) {
2077 remove(&mut r.tags);
2078 }
2079 }
2080 }
2081
2082 pub fn replace_route(
2085 &mut self,
2086 rtb_id: &str,
2087 destination_cidr: &str,
2088 gateway_id: Option<String>,
2089 ) -> Result<(), Ec2Error> {
2090 let rtb = self
2091 .route_tables
2092 .get_mut(rtb_id)
2093 .ok_or_else(|| Ec2Error::RouteTableNotFound(rtb_id.to_string()))?;
2094 for route in rtb.routes.iter_mut() {
2095 if route.destination_cidr_block.as_deref() == Some(destination_cidr)
2096 || route.destination_ipv6_cidr_block.as_deref() == Some(destination_cidr)
2097 {
2098 route.gateway_id = gateway_id;
2099 return Ok(());
2100 }
2101 }
2102 Err(Ec2Error::RouteNotFound(destination_cidr.to_string()))
2103 }
2104
2105 pub fn replace_route_table_association(
2106 &mut self,
2107 assoc_id: &str,
2108 new_rtb_id: &str,
2109 ) -> Result<String, Ec2Error> {
2110 if !self.route_tables.contains_key(new_rtb_id) {
2111 return Err(Ec2Error::RouteTableNotFound(new_rtb_id.to_string()));
2112 }
2113 let mut found_assoc: Option<RouteTableAssociation> = None;
2114 for rtb in self.route_tables.values_mut() {
2115 if let Some(pos) = rtb
2116 .associations
2117 .iter()
2118 .position(|a| a.association_id == assoc_id)
2119 {
2120 found_assoc = Some(rtb.associations.remove(pos));
2121 break;
2122 }
2123 }
2124 let old_assoc =
2125 found_assoc.ok_or_else(|| Ec2Error::AssociationIdNotFound(assoc_id.to_string()))?;
2126 let new_assoc_id = self.next_rtbassoc_id();
2127 let rtb = self.route_tables.get_mut(new_rtb_id).unwrap();
2128 rtb.associations.push(RouteTableAssociation {
2129 association_id: new_assoc_id.clone(),
2130 subnet_id: old_assoc.subnet_id,
2131 gateway_id: old_assoc.gateway_id,
2132 main: old_assoc.main,
2133 state: "associated".to_string(),
2134 });
2135 Ok(new_assoc_id)
2136 }
2137
2138 pub fn create_key_pair(&mut self, key_name: &str, tags: Tags) -> &KeyPair {
2141 let key_pair_id = self.next_keypair_id();
2142 let kp = KeyPair {
2143 key_pair_id: key_pair_id.clone(),
2144 key_name: key_name.to_string(),
2145 fingerprint: "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99".to_string(),
2146 tags,
2147 };
2148 self.key_pairs.insert(key_name.to_string(), kp);
2149 self.key_pairs.get(key_name).unwrap()
2150 }
2151
2152 fn next_eigw_id(&mut self) -> String {
2155 self.counters.eigw += 1;
2156 format!("eigw-{:08x}", self.counters.eigw)
2157 }
2158
2159 pub fn create_egress_only_igw(
2160 &mut self,
2161 vpc_id: &str,
2162 tags: Tags,
2163 ) -> Result<&EgressOnlyInternetGateway, Ec2Error> {
2164 if !self.vpcs.contains_key(vpc_id) {
2165 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
2166 }
2167 let eigw_id = self.next_eigw_id();
2168 let eigw = EgressOnlyInternetGateway {
2169 eigw_id: eigw_id.clone(),
2170 state: "available".to_string(),
2171 attachments: vec![EoigwAttachment {
2172 vpc_id: vpc_id.to_string(),
2173 state: "attached".to_string(),
2174 }],
2175 tags,
2176 };
2177 self.egress_only_igws.insert(eigw_id.clone(), eigw);
2178 Ok(self.egress_only_igws.get(&eigw_id).unwrap())
2179 }
2180
2181 pub fn delete_egress_only_igw(&mut self, eigw_id: &str) -> Result<(), Ec2Error> {
2182 if self.egress_only_igws.remove(eigw_id).is_none() {
2183 return Err(Ec2Error::EgressOnlyIgwNotFound(eigw_id.to_string()));
2184 }
2185 Ok(())
2186 }
2187
2188 fn next_flow_log_id(&mut self) -> String {
2191 self.counters.flow_log += 1;
2192 format!("fl-{:08x}", self.counters.flow_log)
2193 }
2194
2195 pub fn create_flow_log(
2196 &mut self,
2197 resource_id: &str,
2198 traffic_type: &str,
2199 log_destination_type: &str,
2200 log_destination: Option<String>,
2201 log_group_name: Option<String>,
2202 tags: Tags,
2203 ) -> String {
2204 let flow_log_id = self.next_flow_log_id();
2205 let fl = FlowLog {
2206 flow_log_id: flow_log_id.clone(),
2207 resource_id: resource_id.to_string(),
2208 traffic_type: traffic_type.to_string(),
2209 log_destination_type: log_destination_type.to_string(),
2210 log_destination,
2211 log_group_name,
2212 deliver_logs_status: "SUCCESS".to_string(),
2213 flow_log_status: "ACTIVE".to_string(),
2214 tags,
2215 };
2216 self.flow_logs.insert(flow_log_id.clone(), fl);
2217 flow_log_id
2218 }
2219
2220 pub fn delete_flow_logs(&mut self, flow_log_ids: &[String]) {
2221 for id in flow_log_ids {
2222 self.flow_logs.remove(id);
2223 }
2224 }
2225
2226 fn next_peering_id(&mut self) -> String {
2229 self.counters.vpc_peering += 1;
2230 format!("pcx-{:08x}", self.counters.vpc_peering)
2231 }
2232
2233 pub fn create_vpc_peering_connection(
2234 &mut self,
2235 requester_vpc_id: &str,
2236 accepter_vpc_id: &str,
2237 tags: Tags,
2238 ) -> &VpcPeeringConnection {
2239 let peering_id = self.next_peering_id();
2240 let conn = VpcPeeringConnection {
2241 peering_id: peering_id.clone(),
2242 requester_vpc_id: requester_vpc_id.to_string(),
2243 accepter_vpc_id: Some(accepter_vpc_id.to_string()),
2244 status: "pending-acceptance".to_string(),
2245 tags,
2246 requester_peering_options: None,
2247 accepter_peering_options: None,
2248 };
2249 self.vpc_peering_connections
2250 .insert(peering_id.clone(), conn);
2251 self.vpc_peering_connections.get(&peering_id).unwrap()
2252 }
2253
2254 pub fn accept_vpc_peering_connection(&mut self, peering_id: &str) -> Result<(), Ec2Error> {
2255 let conn = self
2256 .vpc_peering_connections
2257 .get_mut(peering_id)
2258 .ok_or_else(|| Ec2Error::VpcPeeringConnectionNotFound(peering_id.to_string()))?;
2259 conn.status = "active".to_string();
2260 Ok(())
2261 }
2262
2263 pub fn reject_vpc_peering_connection(&mut self, peering_id: &str) -> Result<(), Ec2Error> {
2264 let conn = self
2265 .vpc_peering_connections
2266 .get_mut(peering_id)
2267 .ok_or_else(|| Ec2Error::VpcPeeringConnectionNotFound(peering_id.to_string()))?;
2268 conn.status = "rejected".to_string();
2269 Ok(())
2270 }
2271
2272 pub fn delete_vpc_peering_connection(&mut self, peering_id: &str) -> Result<(), Ec2Error> {
2273 let conn = self
2274 .vpc_peering_connections
2275 .get_mut(peering_id)
2276 .ok_or_else(|| Ec2Error::VpcPeeringConnectionNotFound(peering_id.to_string()))?;
2277 conn.status = "deleted".to_string();
2278 Ok(())
2279 }
2280
2281 fn next_endpoint_id(&mut self) -> String {
2284 self.counters.vpc_endpoint += 1;
2285 format!("vpce-{:08x}", self.counters.vpc_endpoint)
2286 }
2287
2288 pub fn create_vpc_endpoint(
2289 &mut self,
2290 vpc_id: &str,
2291 service_name: &str,
2292 endpoint_type: &str,
2293 policy_document: Option<String>,
2294 route_table_ids: Vec<String>,
2295 subnet_ids: Vec<String>,
2296 security_group_ids: Vec<String>,
2297 tags: Tags,
2298 ) -> Result<&VpcEndpoint, Ec2Error> {
2299 if !self.vpcs.contains_key(vpc_id) {
2300 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
2301 }
2302 let endpoint_id = self.next_endpoint_id();
2303 let matched_service: Option<(String, bool)> = self
2307 .vpc_endpoint_service_configs
2308 .values()
2309 .find(|s| s.service_name == service_name)
2310 .map(|s| (s.service_id.clone(), s.acceptance_required));
2311 let ep = VpcEndpoint {
2312 endpoint_id: endpoint_id.clone(),
2313 vpc_id: vpc_id.to_string(),
2314 service_name: service_name.to_string(),
2315 endpoint_type: endpoint_type.to_string(),
2316 state: "available".to_string(),
2317 policy_document,
2318 route_table_ids,
2319 subnet_ids,
2320 security_group_ids,
2321 private_dns_enabled: None,
2322 tags,
2323 };
2324 self.vpc_endpoints.insert(endpoint_id.clone(), ep);
2325 if let Some((svc_id, acceptance_required)) = matched_service {
2326 let conn_state = if acceptance_required {
2327 "pendingAcceptance"
2328 } else {
2329 "available"
2330 };
2331 self.upsert_vpc_endpoint_connection(&svc_id, &endpoint_id, "000000000000", conn_state);
2332 }
2333 Ok(self.vpc_endpoints.get(&endpoint_id).unwrap())
2334 }
2335
2336 pub fn delete_vpc_endpoints(&mut self, endpoint_ids: &[String]) {
2337 for id in endpoint_ids {
2338 self.vpc_endpoints.remove(id);
2339 }
2340 }
2341
2342 fn next_prefix_list_id(&mut self) -> String {
2345 self.counters.prefix_list += 1;
2346 format!("pl-{:08x}", self.counters.prefix_list)
2347 }
2348
2349 pub fn create_managed_prefix_list(
2350 &mut self,
2351 name: &str,
2352 max_entries: i32,
2353 address_family: &str,
2354 entries: Vec<TypesPrefixListEntry>,
2355 tags: Tags,
2356 ) -> &ManagedPrefixList {
2357 let id = self.next_prefix_list_id();
2358 let pl = ManagedPrefixList {
2359 prefix_list_id: id.clone(),
2360 prefix_list_name: name.to_string(),
2361 state: "create-complete".to_string(),
2362 address_family: address_family.to_string(),
2363 max_entries,
2364 entries: entries.clone(),
2365 tags,
2366 version: 1,
2367 version_history: vec![ManagedPrefixListVersion {
2368 version: 1,
2369 entries,
2370 }],
2371 };
2372 self.managed_prefix_lists.insert(id.clone(), pl);
2373 self.managed_prefix_lists.get(&id).unwrap()
2374 }
2375
2376 pub fn delete_managed_prefix_list(&mut self, id: &str) -> Result<&ManagedPrefixList, Ec2Error> {
2377 let pl = self
2378 .managed_prefix_lists
2379 .get_mut(id)
2380 .ok_or_else(|| Ec2Error::PrefixListNotFound(id.to_string()))?;
2381 pl.state = "delete-complete".to_string();
2382 Ok(self.managed_prefix_lists.get(id).unwrap())
2383 }
2384
2385 pub fn modify_managed_prefix_list(
2386 &mut self,
2387 id: &str,
2388 add_entries: Vec<TypesPrefixListEntry>,
2389 remove_cidrs: Vec<String>,
2390 ) -> Result<(), Ec2Error> {
2391 let pl = self
2392 .managed_prefix_lists
2393 .get_mut(id)
2394 .ok_or_else(|| Ec2Error::PrefixListNotFound(id.to_string()))?;
2395 pl.entries.retain(|e| !remove_cidrs.contains(&e.cidr));
2396 pl.entries.extend(add_entries);
2397 pl.version += 1;
2398 pl.version_history.push(ManagedPrefixListVersion {
2399 version: pl.version,
2400 entries: pl.entries.clone(),
2401 });
2402 Ok(())
2403 }
2404
2405 fn next_cgw_id(&mut self) -> String {
2408 self.counters.cgw += 1;
2409 format!("cgw-{:08x}", self.counters.cgw)
2410 }
2411
2412 pub fn create_customer_gateway(
2413 &mut self,
2414 bgp_asn: &str,
2415 ip_address: &str,
2416 gateway_type: &str,
2417 tags: Tags,
2418 ) -> &CustomerGateway {
2419 let id = self.next_cgw_id();
2420 let cgw = CustomerGateway {
2421 customer_gateway_id: id.clone(),
2422 bgp_asn: bgp_asn.to_string(),
2423 ip_address: ip_address.to_string(),
2424 gateway_type: gateway_type.to_string(),
2425 state: "available".to_string(),
2426 tags,
2427 };
2428 self.customer_gateways.insert(id.clone(), cgw);
2429 self.customer_gateways.get(&id).unwrap()
2430 }
2431
2432 pub fn delete_customer_gateway(&mut self, id: &str) -> Result<(), Ec2Error> {
2433 if self.customer_gateways.remove(id).is_none() {
2434 return Err(Ec2Error::CustomerGatewayNotFound(id.to_string()));
2435 }
2436 Ok(())
2437 }
2438
2439 fn next_vgw_id(&mut self) -> String {
2442 self.counters.vgw += 1;
2443 format!("vgw-{:08x}", self.counters.vgw)
2444 }
2445
2446 pub fn create_vpn_gateway(
2447 &mut self,
2448 gateway_type: &str,
2449 amazon_side_asn: Option<i64>,
2450 tags: Tags,
2451 ) -> &VpnGateway {
2452 let id = self.next_vgw_id();
2453 let vgw = VpnGateway {
2454 vpn_gateway_id: id.clone(),
2455 gateway_type: gateway_type.to_string(),
2456 state: "available".to_string(),
2457 amazon_side_asn,
2458 vpc_attachments: Vec::new(),
2459 tags,
2460 };
2461 self.vpn_gateways.insert(id.clone(), vgw);
2462 self.vpn_gateways.get(&id).unwrap()
2463 }
2464
2465 pub fn delete_vpn_gateway(&mut self, id: &str) -> Result<(), Ec2Error> {
2466 let vgw = self
2467 .vpn_gateways
2468 .get_mut(id)
2469 .ok_or_else(|| Ec2Error::VpnGatewayNotFound(id.to_string()))?;
2470 vgw.state = "deleted".to_string();
2471 Ok(())
2472 }
2473
2474 pub fn attach_vpn_gateway(&mut self, vgw_id: &str, vpc_id: &str) -> Result<(), Ec2Error> {
2475 if !self.vpcs.contains_key(vpc_id) {
2476 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
2477 }
2478 let vgw = self
2479 .vpn_gateways
2480 .get_mut(vgw_id)
2481 .ok_or_else(|| Ec2Error::VpnGatewayNotFound(vgw_id.to_string()))?;
2482 vgw.vpc_attachments.push(VgwVpcAttachment {
2483 vpc_id: vpc_id.to_string(),
2484 state: "attached".to_string(),
2485 });
2486 Ok(())
2487 }
2488
2489 pub fn detach_vpn_gateway(&mut self, vgw_id: &str, vpc_id: &str) -> Result<(), Ec2Error> {
2490 let vgw = self
2491 .vpn_gateways
2492 .get_mut(vgw_id)
2493 .ok_or_else(|| Ec2Error::VpnGatewayNotFound(vgw_id.to_string()))?;
2494 vgw.vpc_attachments.retain(|a| a.vpc_id != vpc_id);
2495 Ok(())
2496 }
2497
2498 fn next_vpn_id(&mut self) -> String {
2501 self.counters.vpn += 1;
2502 format!("vpn-{:08x}", self.counters.vpn)
2503 }
2504
2505 pub fn create_vpn_connection(
2506 &mut self,
2507 vgw_id: &str,
2508 cgw_id: &str,
2509 connection_type: &str,
2510 tags: Tags,
2511 ) -> &VpnConnection {
2512 let id = self.next_vpn_id();
2513 let vpn = VpnConnection {
2514 vpn_connection_id: id.clone(),
2515 vpn_gateway_id: vgw_id.to_string(),
2516 customer_gateway_id: cgw_id.to_string(),
2517 transit_gateway_id: None,
2518 connection_type: connection_type.to_string(),
2519 state: "available".to_string(),
2520 tags,
2521 routes: Vec::new(),
2522 options: Some(VpnConnectionOptions {
2526 tunnel_options: vec![
2527 VpnTunnelOptions {
2528 outside_ip_address: Some("203.0.113.1".to_string()),
2529 ..Default::default()
2530 },
2531 VpnTunnelOptions {
2532 outside_ip_address: Some("203.0.113.2".to_string()),
2533 ..Default::default()
2534 },
2535 ],
2536 ..Default::default()
2537 }),
2538 tunnel_replacement_status: None,
2539 };
2540 self.vpn_connections.insert(id.clone(), vpn);
2541 self.vpn_connections.get(&id).unwrap()
2542 }
2543
2544 pub fn delete_vpn_connection(&mut self, id: &str) -> Result<(), Ec2Error> {
2545 let vpn = self
2546 .vpn_connections
2547 .get_mut(id)
2548 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(id.to_string()))?;
2549 vpn.state = "deleted".to_string();
2550 Ok(())
2551 }
2552
2553 pub fn create_vpn_connection_route(
2554 &mut self,
2555 vpn_id: &str,
2556 destination_cidr: &str,
2557 ) -> Result<(), Ec2Error> {
2558 let vpn = self
2559 .vpn_connections
2560 .get_mut(vpn_id)
2561 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(vpn_id.to_string()))?;
2562 if !vpn
2564 .routes
2565 .iter()
2566 .any(|r| r.destination_cidr_block == destination_cidr)
2567 {
2568 vpn.routes.push(VpnStaticRoute {
2569 destination_cidr_block: destination_cidr.to_string(),
2570 source: "Static".to_string(),
2571 state: "available".to_string(),
2572 });
2573 }
2574 Ok(())
2575 }
2576
2577 pub fn delete_vpn_connection_route(
2578 &mut self,
2579 vpn_id: &str,
2580 destination_cidr: &str,
2581 ) -> Result<(), Ec2Error> {
2582 let vpn = self
2583 .vpn_connections
2584 .get_mut(vpn_id)
2585 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(vpn_id.to_string()))?;
2586 let before = vpn.routes.len();
2587 vpn.routes
2588 .retain(|r| r.destination_cidr_block != destination_cidr);
2589 if vpn.routes.len() == before {
2590 return Err(Ec2Error::InvalidVpnConnectionRouteNotFound {
2591 vpn_connection_id: vpn_id.to_string(),
2592 destination_cidr: destination_cidr.to_string(),
2593 });
2594 }
2595 Ok(())
2596 }
2597
2598 pub fn modify_vpn_connection(
2599 &mut self,
2600 vpn_id: &str,
2601 new_cgw: Option<String>,
2602 new_vgw: Option<String>,
2603 new_tgw: Option<String>,
2604 ) -> Result<&VpnConnection, Ec2Error> {
2605 let vpn = self
2606 .vpn_connections
2607 .get_mut(vpn_id)
2608 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(vpn_id.to_string()))?;
2609 if let Some(c) = new_cgw {
2610 vpn.customer_gateway_id = c;
2611 }
2612 if let Some(v) = new_vgw {
2613 vpn.vpn_gateway_id = v;
2614 }
2615 if let Some(t) = new_tgw {
2616 vpn.transit_gateway_id = Some(t);
2617 }
2618 Ok(self.vpn_connections.get(vpn_id).unwrap())
2619 }
2620
2621 #[allow(clippy::too_many_arguments)]
2622 pub fn modify_vpn_connection_options(
2623 &mut self,
2624 vpn_id: &str,
2625 local_ipv4: Option<String>,
2626 local_ipv6: Option<String>,
2627 remote_ipv4: Option<String>,
2628 remote_ipv6: Option<String>,
2629 tunnel_inside_ip_version: Option<String>,
2630 ) -> Result<&VpnConnection, Ec2Error> {
2631 let vpn = self
2632 .vpn_connections
2633 .get_mut(vpn_id)
2634 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(vpn_id.to_string()))?;
2635 let opts = vpn
2636 .options
2637 .get_or_insert_with(VpnConnectionOptions::default);
2638 if let Some(v) = local_ipv4 {
2639 opts.local_ipv4_network_cidr = Some(v);
2640 }
2641 if let Some(v) = local_ipv6 {
2642 opts.local_ipv6_network_cidr = Some(v);
2643 }
2644 if let Some(v) = remote_ipv4 {
2645 opts.remote_ipv4_network_cidr = Some(v);
2646 }
2647 if let Some(v) = remote_ipv6 {
2648 opts.remote_ipv6_network_cidr = Some(v);
2649 }
2650 if let Some(v) = tunnel_inside_ip_version {
2651 opts.tunnel_inside_ip_version = Some(v);
2652 }
2653 Ok(self.vpn_connections.get(vpn_id).unwrap())
2654 }
2655
2656 pub fn modify_vpn_tunnel_certificate(
2657 &mut self,
2658 vpn_id: &str,
2659 outside_ip: &str,
2660 ) -> Result<&VpnConnection, Ec2Error> {
2661 let vpn = self
2662 .vpn_connections
2663 .get_mut(vpn_id)
2664 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(vpn_id.to_string()))?;
2665 let opts = vpn
2666 .options
2667 .get_or_insert_with(VpnConnectionOptions::default);
2668 let tunnel = opts
2669 .tunnel_options
2670 .iter_mut()
2671 .find(|t| t.outside_ip_address.as_deref() == Some(outside_ip))
2672 .ok_or_else(|| Ec2Error::InvalidVpnTunnelNotFound {
2673 vpn_connection_id: vpn_id.to_string(),
2674 outside_ip_address: outside_ip.to_string(),
2675 })?;
2676 tunnel.certificate_arn = Some(format!(
2677 "arn:aws:acm:us-east-1:000000000000:certificate/{}-{}",
2678 vpn_id, outside_ip
2679 ));
2680 Ok(self.vpn_connections.get(vpn_id).unwrap())
2681 }
2682
2683 pub fn modify_vpn_tunnel_options(
2684 &mut self,
2685 vpn_id: &str,
2686 outside_ip: &str,
2687 tunnel_inside_cidr: Option<String>,
2688 tunnel_inside_ipv6_cidr: Option<String>,
2689 pre_shared_key: Option<String>,
2690 ) -> Result<&VpnConnection, Ec2Error> {
2691 let vpn = self
2692 .vpn_connections
2693 .get_mut(vpn_id)
2694 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(vpn_id.to_string()))?;
2695 let opts = vpn
2696 .options
2697 .get_or_insert_with(VpnConnectionOptions::default);
2698 let tunnel = opts
2699 .tunnel_options
2700 .iter_mut()
2701 .find(|t| t.outside_ip_address.as_deref() == Some(outside_ip))
2702 .ok_or_else(|| Ec2Error::InvalidVpnTunnelNotFound {
2703 vpn_connection_id: vpn_id.to_string(),
2704 outside_ip_address: outside_ip.to_string(),
2705 })?;
2706 if let Some(v) = tunnel_inside_cidr {
2707 tunnel.tunnel_inside_cidr = Some(v);
2708 }
2709 if let Some(v) = tunnel_inside_ipv6_cidr {
2710 tunnel.tunnel_inside_ipv6_cidr = Some(v);
2711 }
2712 if let Some(v) = pre_shared_key {
2713 tunnel.pre_shared_key = Some(v);
2714 }
2715 Ok(self.vpn_connections.get(vpn_id).unwrap())
2716 }
2717
2718 pub fn replace_vpn_tunnel(&mut self, vpn_id: &str, outside_ip: &str) -> Result<(), Ec2Error> {
2719 let vpn = self
2720 .vpn_connections
2721 .get_mut(vpn_id)
2722 .ok_or_else(|| Ec2Error::VpnConnectionNotFound(vpn_id.to_string()))?;
2723 let exists = vpn
2725 .options
2726 .as_ref()
2727 .map(|o| {
2728 o.tunnel_options
2729 .iter()
2730 .any(|t| t.outside_ip_address.as_deref() == Some(outside_ip))
2731 })
2732 .unwrap_or(false);
2733 if !exists {
2734 return Err(Ec2Error::InvalidVpnTunnelNotFound {
2735 vpn_connection_id: vpn_id.to_string(),
2736 outside_ip_address: outside_ip.to_string(),
2737 });
2738 }
2739 vpn.tunnel_replacement_status = Some("pending".to_string());
2740 Ok(())
2741 }
2742
2743 fn next_vpn_concentrator_id(&mut self) -> String {
2746 self.counters.vpn_concentrator += 1;
2747 format!("vpn-concentrator-{:08x}", self.counters.vpn_concentrator)
2748 }
2749
2750 pub fn create_vpn_concentrator(
2751 &mut self,
2752 concentrator_type: &str,
2753 transit_gateway_id: Option<String>,
2754 tags: Tags,
2755 ) -> &VpnConcentrator {
2756 let id = self.next_vpn_concentrator_id();
2757 let attach_id = transit_gateway_id
2761 .as_ref()
2762 .map(|tgw| format!("tgw-attach-vpnc-{}-{tgw}", &id[16..]));
2763 let vc = VpnConcentrator {
2764 vpn_concentrator_id: id.clone(),
2765 concentrator_type: concentrator_type.to_string(),
2766 state: "available".to_string(),
2767 transit_gateway_id,
2768 transit_gateway_attachment_id: attach_id,
2769 tags,
2770 };
2771 self.vpn_concentrators.insert(id.clone(), vc);
2772 self.vpn_concentrators.get(&id).unwrap()
2773 }
2774
2775 pub fn delete_vpn_concentrator(&mut self, id: &str) -> Result<(), Ec2Error> {
2776 if self.vpn_concentrators.remove(id).is_none() {
2777 return Err(Ec2Error::InvalidVpnConcentratorNotFound(id.to_string()));
2778 }
2779 Ok(())
2780 }
2781
2782 fn next_carrier_gw_id(&mut self) -> String {
2785 self.counters.cgw_carrier += 1;
2786 format!("cagw-{:08x}", self.counters.cgw_carrier)
2787 }
2788
2789 pub fn create_carrier_gateway(
2790 &mut self,
2791 vpc_id: &str,
2792 tags: Tags,
2793 ) -> Result<&CarrierGateway, Ec2Error> {
2794 if !self.vpcs.contains_key(vpc_id) {
2795 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
2796 }
2797 let id = self.next_carrier_gw_id();
2798 let cgw = CarrierGateway {
2799 carrier_gateway_id: id.clone(),
2800 vpc_id: vpc_id.to_string(),
2801 state: "available".to_string(),
2802 tags,
2803 };
2804 self.carrier_gateways.insert(id.clone(), cgw);
2805 Ok(self.carrier_gateways.get(&id).unwrap())
2806 }
2807
2808 pub fn delete_carrier_gateway(&mut self, id: &str) -> Result<(), Ec2Error> {
2809 let cgw = self
2810 .carrier_gateways
2811 .get_mut(id)
2812 .ok_or_else(|| Ec2Error::CarrierGatewayNotFound(id.to_string()))?;
2813 cgw.state = "deleted".to_string();
2814 Ok(())
2815 }
2816
2817 fn next_eni_id(&mut self) -> String {
2820 self.counters.eni += 1;
2821 format!("eni-{:08x}", self.counters.eni)
2822 }
2823
2824 fn next_eni_attach_id(&mut self) -> String {
2825 self.counters.eni_attach += 1;
2826 format!("eni-attach-{:08x}", self.counters.eni_attach)
2827 }
2828
2829 pub fn create_network_interface(
2830 &mut self,
2831 subnet_id: &str,
2832 description: &str,
2833 private_ip_address: &str,
2834 security_groups: Vec<String>,
2835 tags: Tags,
2836 ) -> Result<&NetworkInterface, Ec2Error> {
2837 let vpc_id = {
2838 let subnet = self
2839 .subnets
2840 .get(subnet_id)
2841 .ok_or_else(|| Ec2Error::SubnetNotFound(subnet_id.to_string()))?;
2842 subnet.vpc_id.clone()
2843 };
2844 let eni_id = self.next_eni_id();
2845 let eni = NetworkInterface {
2846 network_interface_id: eni_id.clone(),
2847 subnet_id: subnet_id.to_string(),
2848 vpc_id,
2849 description: description.to_string(),
2850 private_ip_address: if private_ip_address.is_empty() {
2851 "10.0.0.100".to_string()
2852 } else {
2853 private_ip_address.to_string()
2854 },
2855 status: "available".to_string(),
2856 attachment_id: None,
2857 instance_id: None,
2858 device_index: None,
2859 security_groups,
2860 source_dest_check: true,
2861 tags,
2862 public_ip_dns_hostname_type: None,
2863 };
2864 self.network_interfaces.insert(eni_id.clone(), eni);
2865 Ok(self.network_interfaces.get(&eni_id).unwrap())
2866 }
2867
2868 pub fn delete_network_interface(&mut self, eni_id: &str) -> Result<(), Ec2Error> {
2869 if self.network_interfaces.remove(eni_id).is_none() {
2870 return Err(Ec2Error::NetworkInterfaceNotFound(eni_id.to_string()));
2871 }
2872 Ok(())
2873 }
2874
2875 pub fn attach_network_interface(
2876 &mut self,
2877 eni_id: &str,
2878 instance_id: &str,
2879 device_index: i32,
2880 ) -> Result<String, Ec2Error> {
2881 let attach_id = self.next_eni_attach_id();
2882 let eni = self
2883 .network_interfaces
2884 .get_mut(eni_id)
2885 .ok_or_else(|| Ec2Error::NetworkInterfaceNotFound(eni_id.to_string()))?;
2886 eni.status = "in-use".to_string();
2887 eni.attachment_id = Some(attach_id.clone());
2888 eni.instance_id = Some(instance_id.to_string());
2889 eni.device_index = Some(device_index);
2890 Ok(attach_id)
2891 }
2892
2893 pub fn detach_network_interface(&mut self, attachment_id: &str) -> Result<(), Ec2Error> {
2894 for eni in self.network_interfaces.values_mut() {
2895 if eni.attachment_id.as_deref() == Some(attachment_id) {
2896 eni.status = "available".to_string();
2897 eni.attachment_id = None;
2898 eni.instance_id = None;
2899 eni.device_index = None;
2900 return Ok(());
2901 }
2902 }
2903 Err(Ec2Error::AttachmentNotFound(attachment_id.to_string()))
2904 }
2905
2906 fn next_cidr_assoc_id(&mut self) -> String {
2909 self.counters.vpc_cidr_assoc += 1;
2910 format!("vpc-cidr-assoc-{:08x}", self.counters.vpc_cidr_assoc)
2911 }
2912
2913 pub fn associate_vpc_cidr_block(
2914 &mut self,
2915 vpc_id: &str,
2916 cidr_block: &str,
2917 ) -> Result<String, Ec2Error> {
2918 if !self.vpcs.contains_key(vpc_id) {
2919 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
2920 }
2921 let assoc_id = self.next_cidr_assoc_id();
2922 let vpc = self.vpcs.get_mut(vpc_id).unwrap();
2923 vpc.secondary_cidr_blocks
2924 .push((assoc_id.clone(), cidr_block.to_string()));
2925 self.vpc_cidr_associations
2926 .insert(assoc_id.clone(), vpc_id.to_string());
2927 Ok(assoc_id)
2928 }
2929
2930 pub fn disassociate_vpc_cidr_block(&mut self, assoc_id: &str) -> Result<String, Ec2Error> {
2931 let vpc_id = self
2932 .vpc_cidr_associations
2933 .remove(assoc_id)
2934 .ok_or_else(|| Ec2Error::VpcCidrBlockAssociationNotFound(assoc_id.to_string()))?;
2935 if let Some(vpc) = self.vpcs.get_mut(&vpc_id) {
2936 vpc.secondary_cidr_blocks.retain(|(id, _)| id != assoc_id);
2937 }
2938 Ok(vpc_id)
2939 }
2940
2941 pub fn describe_security_group_rules(
2944 &self,
2945 group_id: Option<&str>,
2946 rule_ids: &[String],
2947 ) -> Vec<(&str, bool, &IpPermission)> {
2948 let mut results = Vec::new();
2950 for sg in self.security_groups.values() {
2951 let matches_group = group_id.is_none_or(|gid| sg.group_id == gid);
2952 if !matches_group {
2953 continue;
2954 }
2955 for rule in &sg.ingress_rules {
2956 if rule_ids.is_empty() || rule_ids.contains(&rule.rule_id) {
2957 results.push((sg.group_id.as_str(), false, rule));
2958 }
2959 }
2960 for rule in &sg.egress_rules {
2961 if rule_ids.is_empty() || rule_ids.contains(&rule.rule_id) {
2962 results.push((sg.group_id.as_str(), true, rule));
2963 }
2964 }
2965 }
2966 results
2967 }
2968
2969 pub fn update_sgr_description_ingress(
2970 &mut self,
2971 group_id: &str,
2972 rule_id: &str,
2973 description: Option<String>,
2974 ) -> Result<(), Ec2Error> {
2975 let sg = self
2976 .security_groups
2977 .get_mut(group_id)
2978 .ok_or_else(|| Ec2Error::SecurityGroupNotFound(group_id.to_string()))?;
2979 for rule in sg.ingress_rules.iter_mut() {
2980 if rule.rule_id == rule_id {
2981 for range in rule.ip_ranges.iter_mut() {
2982 range.description = description.clone();
2983 }
2984 return Ok(());
2985 }
2986 }
2987 Ok(())
2988 }
2989
2990 pub fn update_sgr_description_egress(
2991 &mut self,
2992 group_id: &str,
2993 rule_id: &str,
2994 description: Option<String>,
2995 ) -> Result<(), Ec2Error> {
2996 let sg = self
2997 .security_groups
2998 .get_mut(group_id)
2999 .ok_or_else(|| Ec2Error::SecurityGroupNotFound(group_id.to_string()))?;
3000 for rule in sg.egress_rules.iter_mut() {
3001 if rule.rule_id == rule_id {
3002 for range in rule.ip_ranges.iter_mut() {
3003 range.description = description.clone();
3004 }
3005 return Ok(());
3006 }
3007 }
3008 Ok(())
3009 }
3010
3011 pub fn run_instances(
3014 &mut self,
3015 image_id: &str,
3016 instance_type: &str,
3017 min_count: i32,
3018 max_count: i32,
3019 key_name: Option<String>,
3020 subnet_id: Option<String>,
3021 security_group_ids: Vec<String>,
3022 tags: Tags,
3023 iam_instance_profile_arn: Option<String>,
3024 placement_az: &str,
3025 owner_id: &str,
3026 ) -> Vec<Instance> {
3027 let count = max_count.max(min_count).max(1) as usize;
3028 let now = chrono::Utc::now()
3029 .format("%Y-%m-%dT%H:%M:%S.000Z")
3030 .to_string();
3031 let vpc_id = subnet_id
3032 .as_deref()
3033 .and_then(|sid| self.subnets.get(sid).map(|s| s.vpc_id.clone()));
3034 let mut instances = Vec::new();
3035 for _ in 0..count {
3036 let instance_id = self.next_instance_id();
3037 let private_ip = Some(format!(
3038 "10.0.{}.{}",
3039 (self.counters.instance >> 8) & 0xff,
3040 self.counters.instance & 0xff
3041 ));
3042 let instance = Instance {
3043 instance_id: instance_id.clone(),
3044 image_id: image_id.to_string(),
3045 instance_type: instance_type.to_string(),
3046 state: InstanceState {
3047 code: 16,
3048 name: "running".to_string(),
3049 },
3050 private_ip_address: private_ip,
3051 public_ip_address: None,
3052 subnet_id: subnet_id.clone(),
3053 vpc_id: vpc_id.clone(),
3054 key_name: key_name.clone(),
3055 security_groups: security_group_ids.clone(),
3056 launch_time: now.clone(),
3057 tags: tags.clone(),
3058 iam_instance_profile_arn: iam_instance_profile_arn.clone(),
3059 monitoring_state: "disabled".to_string(),
3060 placement_az: placement_az.to_string(),
3061 placement_group_name: None,
3062 placement_tenancy: None,
3063 placement_host_id: None,
3064 placement_affinity: None,
3065 placement_partition_number: None,
3066 owner_id: owner_id.to_string(),
3067 classic_link_vpc: None,
3068 private_dns_hostname_type: None,
3069 enable_resource_name_dns_a_record: None,
3070 enable_resource_name_dns_aaaa_record: None,
3071 credit_specification: None,
3072 cpu_options: None,
3073 maintenance_options: None,
3074 network_bandwidth_weighting: None,
3075 lifecycle: None,
3076 product_codes: Vec::new(),
3077 capacity_reservation_specification: None,
3078 };
3079 self.instances.insert(instance_id, instance.clone());
3080 instances.push(instance);
3081 }
3082 instances
3083 }
3084
3085 pub fn start_instances(
3086 &mut self,
3087 instance_ids: &[String],
3088 ) -> Vec<(String, i32, String, i32, String)> {
3089 let mut changes = Vec::new();
3091 for id in instance_ids {
3092 if let Some(inst) = self.instances.get_mut(id) {
3093 let prev_code = inst.state.code;
3094 let prev_name = inst.state.name.clone();
3095 inst.state = InstanceState {
3096 code: 16,
3097 name: "running".to_string(),
3098 };
3099 changes.push((id.clone(), prev_code, prev_name, 16, "running".to_string()));
3100 }
3101 }
3102 changes
3103 }
3104
3105 pub fn stop_instances(
3106 &mut self,
3107 instance_ids: &[String],
3108 ) -> Vec<(String, i32, String, i32, String)> {
3109 let mut changes = Vec::new();
3110 for id in instance_ids {
3111 if let Some(inst) = self.instances.get_mut(id) {
3112 let prev_code = inst.state.code;
3113 let prev_name = inst.state.name.clone();
3114 inst.state = InstanceState {
3115 code: 80,
3116 name: "stopped".to_string(),
3117 };
3118 changes.push((id.clone(), prev_code, prev_name, 80, "stopped".to_string()));
3119 }
3120 }
3121 changes
3122 }
3123
3124 pub fn terminate_instances(
3125 &mut self,
3126 instance_ids: &[String],
3127 ) -> Vec<(String, i32, String, i32, String)> {
3128 let mut changes = Vec::new();
3129 for id in instance_ids {
3130 if let Some(inst) = self.instances.get_mut(id) {
3131 let prev_code = inst.state.code;
3132 let prev_name = inst.state.name.clone();
3133 inst.state = InstanceState {
3134 code: 48,
3135 name: "terminated".to_string(),
3136 };
3137 changes.push((
3138 id.clone(),
3139 prev_code,
3140 prev_name,
3141 48,
3142 "terminated".to_string(),
3143 ));
3144 }
3145 }
3146 changes
3147 }
3148
3149 pub fn create_volume(
3152 &mut self,
3153 size: i32,
3154 availability_zone: &str,
3155 snapshot_id: Option<String>,
3156 volume_type: &str,
3157 iops: Option<i32>,
3158 throughput: Option<i32>,
3159 encrypted: bool,
3160 tags: Tags,
3161 ) -> &Volume {
3162 let vol_id = self.next_vol_id();
3163 let now = chrono::Utc::now()
3164 .format("%Y-%m-%dT%H:%M:%S.000Z")
3165 .to_string();
3166 let vol = Volume {
3167 volume_id: vol_id.clone(),
3168 size,
3169 snapshot_id,
3170 availability_zone: availability_zone.to_string(),
3171 state: "available".to_string(),
3172 volume_type: volume_type.to_string(),
3173 iops,
3174 throughput,
3175 encrypted,
3176 create_time: now,
3177 attachments: Vec::new(),
3178 tags,
3179 recycle_bin_state: None,
3180 source_volume_id: None,
3181 };
3182 self.volumes.insert(vol_id.clone(), vol);
3183 self.volumes.get(&vol_id).unwrap()
3184 }
3185
3186 pub fn delete_volume(&mut self, vol_id: &str) -> Result<(), Ec2Error> {
3187 let mut vol = self
3192 .volumes
3193 .remove(vol_id)
3194 .ok_or_else(|| Ec2Error::VolumeNotFound(vol_id.to_string()))?;
3195 vol.state = "deleted-pending-recycle".to_string();
3196 vol.recycle_bin_state = Some("in-recycle-bin".to_string());
3197 self.deleted_volumes_recycle_bin
3198 .insert(vol_id.to_string(), vol);
3199 Ok(())
3200 }
3201
3202 pub fn restore_volume_from_recycle_bin(&mut self, vol_id: &str) -> Result<(), Ec2Error> {
3203 let mut vol = self
3204 .deleted_volumes_recycle_bin
3205 .remove(vol_id)
3206 .ok_or_else(|| Ec2Error::VolumeNotInRecycleBin(vol_id.to_string()))?;
3207 vol.state = "available".to_string();
3208 vol.recycle_bin_state = None;
3209 self.volumes.insert(vol_id.to_string(), vol);
3210 Ok(())
3211 }
3212
3213 pub fn copy_volumes(
3214 &mut self,
3215 source_volume_ids: &[String],
3216 tags: Tags,
3217 ) -> Result<Vec<String>, Ec2Error> {
3218 for src in source_volume_ids {
3220 if !self.volumes.contains_key(src) {
3221 return Err(Ec2Error::VolumeNotFound(src.clone()));
3222 }
3223 }
3224 let mut new_ids = Vec::new();
3225 for src in source_volume_ids {
3226 let (size, az, vol_type, iops, throughput, encrypted, snapshot_id) = {
3229 let v = self.volumes.get(src).unwrap();
3230 (
3231 v.size,
3232 v.availability_zone.clone(),
3233 v.volume_type.clone(),
3234 v.iops,
3235 v.throughput,
3236 v.encrypted,
3237 v.snapshot_id.clone(),
3238 )
3239 };
3240 let new_id = self.next_vol_id();
3241 let now = chrono::Utc::now()
3242 .format("%Y-%m-%dT%H:%M:%S.000Z")
3243 .to_string();
3244 let new_vol = Volume {
3245 volume_id: new_id.clone(),
3246 size,
3247 snapshot_id,
3248 availability_zone: az,
3249 state: "available".to_string(),
3250 volume_type: vol_type,
3251 iops,
3252 throughput,
3253 encrypted,
3254 create_time: now,
3255 attachments: Vec::new(),
3256 tags: tags.clone(),
3257 recycle_bin_state: None,
3258 source_volume_id: Some(src.clone()),
3259 };
3260 self.volumes.insert(new_id.clone(), new_vol);
3261 new_ids.push(new_id);
3262 }
3263 Ok(new_ids)
3264 }
3265
3266 pub fn attach_volume(
3267 &mut self,
3268 vol_id: &str,
3269 instance_id: &str,
3270 device: &str,
3271 ) -> Result<&VolumeAttachment, Ec2Error> {
3272 if !self.instances.contains_key(instance_id) {
3273 return Err(Ec2Error::InstanceNotFound(instance_id.to_string()));
3274 }
3275 let now = chrono::Utc::now()
3276 .format("%Y-%m-%dT%H:%M:%S.000Z")
3277 .to_string();
3278 let vol = self
3279 .volumes
3280 .get_mut(vol_id)
3281 .ok_or_else(|| Ec2Error::VolumeNotFound(vol_id.to_string()))?;
3282 vol.state = "in-use".to_string();
3283 vol.attachments.push(VolumeAttachment {
3284 volume_id: vol_id.to_string(),
3285 instance_id: instance_id.to_string(),
3286 device: device.to_string(),
3287 state: "attached".to_string(),
3288 attach_time: now,
3289 delete_on_termination: false,
3290 });
3291 Ok(vol.attachments.last().unwrap())
3292 }
3293
3294 pub fn detach_volume(
3295 &mut self,
3296 vol_id: &str,
3297 instance_id: Option<&str>,
3298 device: Option<&str>,
3299 ) -> Result<VolumeAttachment, Ec2Error> {
3300 let vol = self
3301 .volumes
3302 .get_mut(vol_id)
3303 .ok_or_else(|| Ec2Error::VolumeNotFound(vol_id.to_string()))?;
3304 let pos = vol
3305 .attachments
3306 .iter()
3307 .position(|a| {
3308 instance_id.is_none_or(|id| a.instance_id == id)
3309 && device.is_none_or(|d| a.device == d)
3310 })
3311 .ok_or(Ec2Error::VolumeNotAttached)?;
3312 let att = vol.attachments.remove(pos);
3313 if vol.attachments.is_empty() {
3314 vol.state = "available".to_string();
3315 }
3316 Ok(att)
3317 }
3318
3319 pub fn create_snapshot(
3322 &mut self,
3323 vol_id: &str,
3324 description: &str,
3325 tags: Tags,
3326 owner_id: &str,
3327 ) -> Result<&Snapshot, Ec2Error> {
3328 let vol = self
3329 .volumes
3330 .get(vol_id)
3331 .ok_or_else(|| Ec2Error::VolumeNotFound(vol_id.to_string()))?;
3332 let vol_size = vol.size;
3333 let encrypted = vol.encrypted;
3334 let snap_id = self.next_snapshot_id();
3335 let now = chrono::Utc::now()
3336 .format("%Y-%m-%dT%H:%M:%S.000Z")
3337 .to_string();
3338 let snap = Snapshot {
3339 snapshot_id: snap_id.clone(),
3340 volume_id: vol_id.to_string(),
3341 volume_size: vol_size,
3342 state: "completed".to_string(),
3343 description: description.to_string(),
3344 start_time: now,
3345 progress: "100%".to_string(),
3346 owner_id: owner_id.to_string(),
3347 encrypted,
3348 tags,
3349 lock_state: "none".to_string(),
3350 lock_duration: None,
3351 lock_created_on: None,
3352 lock_expires_on: None,
3353 lock_duration_start_time: None,
3354 cool_off_period: None,
3355 cool_off_period_expires_on: None,
3356 storage_tier: "standard".to_string(),
3357 last_tiering_operation_status: None,
3358 fast_snapshot_restore_states: Vec::new(),
3359 };
3360 self.snapshots.insert(snap_id.clone(), snap);
3361 Ok(self.snapshots.get(&snap_id).unwrap())
3362 }
3363
3364 pub fn delete_snapshot(&mut self, snap_id: &str) -> Result<(), Ec2Error> {
3365 let snap = self
3368 .snapshots
3369 .get(snap_id)
3370 .ok_or_else(|| Ec2Error::SnapshotNotFound(snap_id.to_string()))?;
3371 if snap.lock_state == "compliance" || snap.lock_state == "governance" {
3372 return Err(Ec2Error::SnapshotIsLocked(snap_id.to_string()));
3373 }
3374 let mut snap = self.snapshots.remove(snap_id).unwrap();
3375 snap.state = "recycled".to_string();
3376 self.deleted_snapshots_recycle_bin
3377 .insert(snap_id.to_string(), snap);
3378 Ok(())
3379 }
3380
3381 pub fn restore_snapshot_from_recycle_bin(
3382 &mut self,
3383 snap_id: &str,
3384 ) -> Result<&Snapshot, Ec2Error> {
3385 let mut snap = self
3386 .deleted_snapshots_recycle_bin
3387 .remove(snap_id)
3388 .ok_or_else(|| Ec2Error::SnapshotNotInRecycleBin(snap_id.to_string()))?;
3389 snap.state = "completed".to_string();
3390 self.snapshots.insert(snap_id.to_string(), snap);
3391 Ok(self.snapshots.get(snap_id).unwrap())
3392 }
3393
3394 pub fn lock_snapshot(
3395 &mut self,
3396 snap_id: &str,
3397 lock_mode: &str,
3398 lock_duration: Option<i32>,
3399 cool_off_period: Option<i32>,
3400 ) -> Result<&Snapshot, Ec2Error> {
3401 let snap = self
3402 .snapshots
3403 .get_mut(snap_id)
3404 .ok_or_else(|| Ec2Error::SnapshotNotFound(snap_id.to_string()))?;
3405 if snap.lock_state == "compliance" {
3407 return Err(Ec2Error::SnapshotIsLocked(snap_id.to_string()));
3408 }
3409 let now = chrono::Utc::now()
3410 .format("%Y-%m-%dT%H:%M:%S.000Z")
3411 .to_string();
3412 snap.lock_state = lock_mode.to_string();
3413 snap.lock_created_on = Some(now.clone());
3414 snap.lock_duration_start_time = Some(now.clone());
3415 snap.lock_duration = lock_duration;
3416 snap.lock_expires_on = lock_duration.map(|d| {
3417 let expires = chrono::Utc::now() + chrono::Duration::days(d as i64);
3418 expires.format("%Y-%m-%dT%H:%M:%S.000Z").to_string()
3419 });
3420 snap.cool_off_period = cool_off_period;
3421 snap.cool_off_period_expires_on = cool_off_period.map(|cp| {
3422 let expires = chrono::Utc::now() + chrono::Duration::hours(cp as i64);
3423 expires.format("%Y-%m-%dT%H:%M:%S.000Z").to_string()
3424 });
3425 Ok(self.snapshots.get(snap_id).unwrap())
3426 }
3427
3428 pub fn unlock_snapshot(&mut self, snap_id: &str) -> Result<(), Ec2Error> {
3429 let snap = self
3430 .snapshots
3431 .get_mut(snap_id)
3432 .ok_or_else(|| Ec2Error::SnapshotNotFound(snap_id.to_string()))?;
3433 if snap.lock_state == "compliance" {
3434 return Err(Ec2Error::SnapshotIsLocked(snap_id.to_string()));
3435 }
3436 snap.lock_state = "none".to_string();
3437 snap.lock_duration = None;
3438 snap.lock_created_on = None;
3439 snap.lock_expires_on = None;
3440 snap.lock_duration_start_time = None;
3441 snap.cool_off_period = None;
3442 snap.cool_off_period_expires_on = None;
3443 Ok(())
3444 }
3445
3446 pub fn modify_snapshot_tier(
3447 &mut self,
3448 snap_id: &str,
3449 storage_tier: &str,
3450 ) -> Result<String, Ec2Error> {
3451 let snap = self
3452 .snapshots
3453 .get_mut(snap_id)
3454 .ok_or_else(|| Ec2Error::SnapshotNotFound(snap_id.to_string()))?;
3455 snap.storage_tier = storage_tier.to_string();
3456 snap.last_tiering_operation_status = Some(format!("tiering-in-progress-to-{storage_tier}"));
3457 let now = chrono::Utc::now()
3458 .format("%Y-%m-%dT%H:%M:%S.000Z")
3459 .to_string();
3460 Ok(now)
3461 }
3462
3463 pub fn restore_snapshot_tier(&mut self, snap_id: &str) -> Result<String, Ec2Error> {
3464 let snap = self
3465 .snapshots
3466 .get_mut(snap_id)
3467 .ok_or_else(|| Ec2Error::SnapshotNotFound(snap_id.to_string()))?;
3468 snap.storage_tier = "standard".to_string();
3469 snap.last_tiering_operation_status = Some("tiering-finished".to_string());
3470 let now = chrono::Utc::now()
3471 .format("%Y-%m-%dT%H:%M:%S.000Z")
3472 .to_string();
3473 Ok(now)
3474 }
3475
3476 pub fn create_image(
3479 &mut self,
3480 instance_id: &str,
3481 name: &str,
3482 description: &str,
3483 tags: Tags,
3484 owner_id: &str,
3485 ) -> Result<String, Ec2Error> {
3486 if !self.instances.contains_key(instance_id) {
3487 return Err(Ec2Error::InstanceNotFound(instance_id.to_string()));
3488 }
3489 let source_instance_type = self
3490 .instances
3491 .get(instance_id)
3492 .unwrap()
3493 .instance_type
3494 .clone();
3495 let image_id = self.next_ami_id();
3496 let img = Image {
3497 image_id: image_id.clone(),
3498 name: name.to_string(),
3499 description: description.to_string(),
3500 state: "available".to_string(),
3501 owner_id: owner_id.to_string(),
3502 architecture: "x86_64".to_string(),
3503 image_type: "machine".to_string(),
3504 platform: None,
3505 virtualization_type: "hvm".to_string(),
3506 root_device_type: "ebs".to_string(),
3507 root_device_name: "/dev/xvda".to_string(),
3508 public: false,
3509 tags,
3510 source_instance_id: Some(instance_id.to_string()),
3511 source_instance_type,
3512 launch_permissions: Vec::new(),
3513 recycle_bin_state: None,
3514 deprecation_time: None,
3515 recycle_bin_enter_time: None,
3516 product_codes: Vec::new(),
3517 fast_launch_state: None,
3518 deregistration_protection: None,
3519 kernel_id: None,
3520 ramdisk_id: None,
3521 ena_support: None,
3522 sriov_net_support: None,
3523 tpm_support: None,
3524 boot_mode: None,
3525 imds_support: None,
3526 image_location: None,
3527 source_image_id: None,
3528 source_region: None,
3529 };
3530 self.images.insert(image_id.clone(), img);
3531 Ok(image_id)
3532 }
3533
3534 pub fn modify_image_launch_permissions(
3535 &mut self,
3536 image_id: &str,
3537 add: &[crate::types::LaunchPermission],
3538 remove: &[crate::types::LaunchPermission],
3539 ) -> Result<(), Ec2Error> {
3540 let img = self
3541 .images
3542 .get_mut(image_id)
3543 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
3544 for perm in remove {
3545 img.launch_permissions.retain(|p| p != perm);
3546 }
3547 for perm in add {
3548 if !img.launch_permissions.contains(perm) {
3549 img.launch_permissions.push(perm.clone());
3550 }
3551 }
3552 Ok(())
3553 }
3554
3555 pub fn modify_image_description(
3556 &mut self,
3557 image_id: &str,
3558 description: &str,
3559 ) -> Result<(), Ec2Error> {
3560 let img = self
3561 .images
3562 .get_mut(image_id)
3563 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
3564 img.description = description.to_string();
3565 Ok(())
3566 }
3567
3568 pub fn get_image(&self, image_id: &str) -> Result<&crate::types::Image, Ec2Error> {
3569 self.images
3570 .get(image_id)
3571 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))
3572 }
3573
3574 pub fn deregister_image(&mut self, image_id: &str) -> Result<(), Ec2Error> {
3575 let img = self
3578 .images
3579 .get_mut(image_id)
3580 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
3581 img.recycle_bin_state = Some("recycled".to_string());
3582 img.state = "deregistered".to_string();
3583 self.deregistered_images.insert(image_id.to_string());
3584 Ok(())
3585 }
3586
3587 pub fn create_launch_template(
3590 &mut self,
3591 name: &str,
3592 version_description: &str,
3593 data: std::collections::HashMap<String, String>,
3594 tags: Tags,
3595 ) -> Result<&LaunchTemplate, Ec2Error> {
3596 if self
3598 .launch_templates
3599 .values()
3600 .any(|lt| lt.launch_template_name == name)
3601 {
3602 return Err(Ec2Error::LaunchTemplateAlreadyExists(name.to_string()));
3603 }
3604 let lt_id = self.next_lt_id();
3605 let lt = LaunchTemplate {
3606 launch_template_id: lt_id.clone(),
3607 launch_template_name: name.to_string(),
3608 default_version_number: 1,
3609 latest_version_number: 1,
3610 tags: tags.clone(),
3611 };
3612 self.launch_templates.insert(lt_id.clone(), lt);
3613 let version = LaunchTemplateVersion {
3614 version_number: 1,
3615 launch_template_id: lt_id.clone(),
3616 launch_template_name: name.to_string(),
3617 version_description: version_description.to_string(),
3618 data,
3619 default_version: true,
3620 };
3621 self.launch_template_versions
3622 .entry(lt_id.clone())
3623 .or_default()
3624 .push(version);
3625 Ok(self.launch_templates.get(<_id).unwrap())
3626 }
3627
3628 pub fn delete_launch_template(
3629 &mut self,
3630 lt_id_or_name: &str,
3631 ) -> Result<LaunchTemplate, Ec2Error> {
3632 let lt_id = if self.launch_templates.contains_key(lt_id_or_name) {
3634 lt_id_or_name.to_string()
3635 } else {
3636 self.launch_templates
3637 .values()
3638 .find(|lt| lt.launch_template_name == lt_id_or_name)
3639 .map(|lt| lt.launch_template_id.clone())
3640 .ok_or_else(|| Ec2Error::LaunchTemplateNotFound(lt_id_or_name.to_string()))?
3641 };
3642 self.launch_template_versions.remove(<_id);
3643 Ok(self.launch_templates.remove(<_id).unwrap())
3644 }
3645
3646 pub fn create_launch_template_version(
3649 &mut self,
3650 lt_id: &str,
3651 description: &str,
3652 data: HashMap<String, String>,
3653 ) -> Result<i64, Ec2Error> {
3654 let lt_id_resolved = if self.launch_templates.contains_key(lt_id) {
3656 lt_id.to_string()
3657 } else {
3658 self.launch_templates
3659 .values()
3660 .find(|lt| lt.launch_template_name == lt_id)
3661 .map(|lt| lt.launch_template_id.clone())
3662 .ok_or_else(|| Ec2Error::LaunchTemplateNotFound(lt_id.to_string()))?
3663 };
3664 let lt = self.launch_templates.get_mut(<_id_resolved).unwrap();
3665 lt.latest_version_number += 1;
3666 let version_number = lt.latest_version_number;
3667 let lt_name = lt.launch_template_name.clone();
3668 let version = LaunchTemplateVersion {
3669 version_number,
3670 launch_template_id: lt_id_resolved.clone(),
3671 launch_template_name: lt_name,
3672 version_description: description.to_string(),
3673 data,
3674 default_version: false,
3675 };
3676 self.launch_template_versions
3677 .entry(lt_id_resolved)
3678 .or_default()
3679 .push(version);
3680 Ok(version_number)
3681 }
3682
3683 pub fn modify_launch_template(
3684 &mut self,
3685 lt_id: &str,
3686 default_version: Option<i64>,
3687 ) -> Result<(), Ec2Error> {
3688 let lt = self
3689 .launch_templates
3690 .get_mut(lt_id)
3691 .ok_or_else(|| Ec2Error::LaunchTemplateNotFound(lt_id.to_string()))?;
3692 if let Some(v) = default_version {
3693 lt.default_version_number = v;
3694 if let Some(versions) = self.launch_template_versions.get_mut(lt_id) {
3695 for ver in versions.iter_mut() {
3696 ver.default_version = ver.version_number == v;
3697 }
3698 }
3699 }
3700 Ok(())
3701 }
3702
3703 pub fn get_launch_template_data_from_instance(
3704 &self,
3705 instance_id: &str,
3706 ) -> HashMap<String, String> {
3707 let mut data = HashMap::new();
3708 if let Some(inst) = self.instances.get(instance_id) {
3709 data.insert(
3710 "LaunchTemplateData.ImageId".to_string(),
3711 inst.image_id.clone(),
3712 );
3713 data.insert(
3714 "LaunchTemplateData.InstanceType".to_string(),
3715 inst.instance_type.clone(),
3716 );
3717 if let Some(key) = &inst.key_name {
3718 data.insert("LaunchTemplateData.KeyName".to_string(), key.clone());
3719 }
3720 }
3721 data
3722 }
3723
3724 pub fn create_transit_gateway(
3727 &mut self,
3728 description: &str,
3729 amazon_side_asn: i64,
3730 dns_support: &str,
3731 vpn_ecmp_support: &str,
3732 tags: Tags,
3733 ) -> &TransitGateway {
3734 let tgw_id = self.next_tgw_id();
3735 let tgw = TransitGateway {
3736 transit_gateway_id: tgw_id.clone(),
3737 state: "available".to_string(),
3738 amazon_side_asn,
3739 description: description.to_string(),
3740 dns_support: dns_support.to_string(),
3741 vpn_ecmp_support: vpn_ecmp_support.to_string(),
3742 multicast_support: "disable".to_string(),
3743 tags,
3744 };
3745 self.transit_gateways.insert(tgw_id.clone(), tgw);
3746 self.transit_gateways.get(&tgw_id).unwrap()
3747 }
3748
3749 pub fn delete_transit_gateway(&mut self, tgw_id: &str) -> Result<(), Ec2Error> {
3750 let tgw = self
3751 .transit_gateways
3752 .get_mut(tgw_id)
3753 .ok_or_else(|| Ec2Error::TransitGatewayNotFound(tgw_id.to_string()))?;
3754 tgw.state = "deleted".to_string();
3755 Ok(())
3756 }
3757
3758 pub fn create_transit_gateway_vpc_attachment(
3759 &mut self,
3760 tgw_id: &str,
3761 vpc_id: &str,
3762 subnet_ids: Vec<String>,
3763 tags: Tags,
3764 ) -> Result<&TransitGatewayVpcAttachment, Ec2Error> {
3765 if !self.transit_gateways.contains_key(tgw_id) {
3766 return Err(Ec2Error::TransitGatewayNotFound(tgw_id.to_string()));
3767 }
3768 let attach_id = self.next_tgw_attach_id();
3769 let att = TransitGatewayVpcAttachment {
3770 attachment_id: attach_id.clone(),
3771 transit_gateway_id: tgw_id.to_string(),
3772 vpc_id: vpc_id.to_string(),
3773 subnet_ids,
3774 state: "available".to_string(),
3775 tags,
3776 };
3777 self.tgw_vpc_attachments.insert(attach_id.clone(), att);
3778 Ok(self.tgw_vpc_attachments.get(&attach_id).unwrap())
3779 }
3780
3781 pub fn delete_transit_gateway_vpc_attachment(
3782 &mut self,
3783 attach_id: &str,
3784 ) -> Result<(), Ec2Error> {
3785 let att = self
3786 .tgw_vpc_attachments
3787 .get_mut(attach_id)
3788 .ok_or_else(|| Ec2Error::TgwVpcAttachmentNotFound(attach_id.to_string()))?;
3789 att.state = "deleted".to_string();
3790 Ok(())
3791 }
3792
3793 pub fn create_transit_gateway_route_table(
3794 &mut self,
3795 tgw_id: &str,
3796 tags: Tags,
3797 ) -> Result<&TransitGatewayRouteTable, Ec2Error> {
3798 if !self.transit_gateways.contains_key(tgw_id) {
3799 return Err(Ec2Error::TransitGatewayNotFound(tgw_id.to_string()));
3800 }
3801 let rtb_id = self.next_tgw_rtb_id();
3802 let rtb = TransitGatewayRouteTable {
3803 route_table_id: rtb_id.clone(),
3804 transit_gateway_id: tgw_id.to_string(),
3805 state: "available".to_string(),
3806 default_association_route_table: false,
3807 default_propagation_route_table: false,
3808 tags,
3809 };
3810 self.tgw_route_tables.insert(rtb_id.clone(), rtb);
3811 Ok(self.tgw_route_tables.get(&rtb_id).unwrap())
3812 }
3813
3814 pub fn delete_transit_gateway_route_table(&mut self, rtb_id: &str) -> Result<(), Ec2Error> {
3815 let rtb = self
3816 .tgw_route_tables
3817 .get_mut(rtb_id)
3818 .ok_or_else(|| Ec2Error::TgwRouteTableNotFound(rtb_id.to_string()))?;
3819 rtb.state = "deleted".to_string();
3820 Ok(())
3821 }
3822
3823 pub fn create_transit_gateway_route(
3824 &mut self,
3825 rtb_id: &str,
3826 cidr: &str,
3827 attachment_id: Option<String>,
3828 ) -> Result<(), Ec2Error> {
3829 if !self.tgw_route_tables.contains_key(rtb_id) {
3830 return Err(Ec2Error::TgwRouteTableNotFound(rtb_id.to_string()));
3831 }
3832 let route = TransitGatewayRoute {
3833 destination_cidr_block: cidr.to_string(),
3834 route_type: "static".to_string(),
3835 state: "active".to_string(),
3836 attachment_id,
3837 };
3838 self.tgw_routes
3839 .entry(rtb_id.to_string())
3840 .or_default()
3841 .push(route);
3842 Ok(())
3843 }
3844
3845 pub fn delete_transit_gateway_route(
3846 &mut self,
3847 rtb_id: &str,
3848 cidr: &str,
3849 ) -> Result<(), Ec2Error> {
3850 let routes = self
3851 .tgw_routes
3852 .get_mut(rtb_id)
3853 .ok_or_else(|| Ec2Error::TgwRouteTableNotFound(rtb_id.to_string()))?;
3854 routes.retain(|r| r.destination_cidr_block != cidr);
3855 Ok(())
3856 }
3857
3858 pub fn create_transit_gateway_peering_attachment(
3859 &mut self,
3860 tgw_id: &str,
3861 peer_tgw_id: &str,
3862 ) -> &TransitGatewayPeeringAttachment {
3863 let attach_id = self.next_tgw_attach_id();
3864 let att = TransitGatewayPeeringAttachment {
3865 attachment_id: attach_id.clone(),
3866 transit_gateway_id: tgw_id.to_string(),
3867 peer_transit_gateway_id: peer_tgw_id.to_string(),
3868 peer_account_id: "123456789012".to_string(),
3869 peer_region: "us-east-1".to_string(),
3870 state: "pendingAcceptance".to_string(),
3871 tags: HashMap::new(),
3872 };
3873 self.tgw_peering_attachments.insert(attach_id.clone(), att);
3874 self.tgw_peering_attachments.get(&attach_id).unwrap()
3875 }
3876
3877 pub fn accept_transit_gateway_peering_attachment(
3878 &mut self,
3879 attach_id: &str,
3880 ) -> Result<(), Ec2Error> {
3881 let att = self
3882 .tgw_peering_attachments
3883 .get_mut(attach_id)
3884 .ok_or_else(|| Ec2Error::TgwPeeringAttachmentNotFound(attach_id.to_string()))?;
3885 att.state = "available".to_string();
3886 Ok(())
3887 }
3888
3889 pub fn reject_transit_gateway_peering_attachment(
3890 &mut self,
3891 attach_id: &str,
3892 ) -> Result<(), Ec2Error> {
3893 let att = self
3894 .tgw_peering_attachments
3895 .get_mut(attach_id)
3896 .ok_or_else(|| Ec2Error::TgwPeeringAttachmentNotFound(attach_id.to_string()))?;
3897 att.state = "rejected".to_string();
3898 Ok(())
3899 }
3900
3901 pub fn delete_transit_gateway_peering_attachment(
3902 &mut self,
3903 attach_id: &str,
3904 ) -> Result<(), Ec2Error> {
3905 let att = self
3906 .tgw_peering_attachments
3907 .get_mut(attach_id)
3908 .ok_or_else(|| Ec2Error::TgwPeeringAttachmentNotFound(attach_id.to_string()))?;
3909 att.state = "deleted".to_string();
3910 Ok(())
3911 }
3912
3913 pub fn request_spot_instances(
3916 &mut self,
3917 spot_price: &str,
3918 count: i32,
3919 image_id: &str,
3920 instance_type: &str,
3921 owner_id: &str,
3922 ) -> Vec<String> {
3923 let count = count.max(1) as usize;
3924 let now = chrono::Utc::now()
3925 .format("%Y-%m-%dT%H:%M:%S.000Z")
3926 .to_string();
3927 let mut request_ids = Vec::new();
3928 for _ in 0..count {
3929 let spot_id = self.next_spot_id();
3930 let instance_id = self.next_instance_id();
3931 let instance = Instance {
3932 instance_id: instance_id.clone(),
3933 image_id: image_id.to_string(),
3934 instance_type: instance_type.to_string(),
3935 state: InstanceState {
3936 code: 16,
3937 name: "running".to_string(),
3938 },
3939 private_ip_address: Some(format!(
3940 "10.0.{}.{}",
3941 (self.counters.instance >> 8) & 0xff,
3942 self.counters.instance & 0xff
3943 )),
3944 public_ip_address: None,
3945 subnet_id: None,
3946 vpc_id: None,
3947 key_name: None,
3948 security_groups: Vec::new(),
3949 launch_time: now.clone(),
3950 tags: HashMap::new(),
3951 iam_instance_profile_arn: None,
3952 monitoring_state: "disabled".to_string(),
3953 placement_az: "us-east-1a".to_string(),
3954 placement_group_name: None,
3955 placement_tenancy: None,
3956 placement_host_id: None,
3957 placement_affinity: None,
3958 placement_partition_number: None,
3959 owner_id: owner_id.to_string(),
3960 classic_link_vpc: None,
3961 private_dns_hostname_type: None,
3962 enable_resource_name_dns_a_record: None,
3963 enable_resource_name_dns_aaaa_record: None,
3964 credit_specification: None,
3965 cpu_options: None,
3966 maintenance_options: None,
3967 network_bandwidth_weighting: None,
3968 lifecycle: Some("spot".to_string()),
3969 product_codes: Vec::new(),
3970 capacity_reservation_specification: None,
3971 };
3972 self.instances.insert(instance_id.clone(), instance);
3973 let req = SpotInstanceRequest {
3974 spot_instance_request_id: spot_id.clone(),
3975 spot_price: spot_price.to_string(),
3976 instance_type: instance_type.to_string(),
3977 image_id: image_id.to_string(),
3978 state: "active".to_string(),
3979 status_code: "fulfilled".to_string(),
3980 instance_id: Some(instance_id),
3981 tags: HashMap::new(),
3982 };
3983 self.spot_requests.insert(spot_id.clone(), req);
3984 request_ids.push(spot_id);
3985 }
3986 request_ids
3987 }
3988
3989 pub fn cancel_spot_instance_requests(&mut self, ids: &[String]) {
3990 for id in ids {
3991 if let Some(req) = self.spot_requests.get_mut(id) {
3992 req.state = "cancelled".to_string();
3993 }
3994 }
3995 }
3996
3997 pub fn create_spot_datafeed_subscription(
4000 &mut self,
4001 bucket: String,
4002 prefix: Option<String>,
4003 owner_id: String,
4004 ) -> Result<&SpotDatafeedSubscription, Ec2Error> {
4005 if self.spot_datafeed_subscription.is_some() {
4006 return Err(Ec2Error::SpotDatafeedAlreadyExists);
4007 }
4008 self.spot_datafeed_subscription = Some(SpotDatafeedSubscription {
4009 bucket,
4010 prefix,
4011 owner_id,
4012 state: "Active".to_string(),
4013 });
4014 Ok(self.spot_datafeed_subscription.as_ref().unwrap())
4015 }
4016
4017 pub fn delete_spot_datafeed_subscription(&mut self) {
4020 self.spot_datafeed_subscription = None;
4021 }
4022
4023 pub fn describe_spot_datafeed_subscription(
4026 &self,
4027 ) -> Result<&SpotDatafeedSubscription, Ec2Error> {
4028 self.spot_datafeed_subscription
4029 .as_ref()
4030 .ok_or(Ec2Error::SpotDatafeedNotFound)
4031 }
4032
4033 pub fn associate_iam_instance_profile(
4036 &mut self,
4037 instance_id: &str,
4038 arn: &str,
4039 name: &str,
4040 ) -> &IamInstanceProfileAssociation {
4041 let assoc_id = self.next_iam_assoc_id();
4042 let assoc = IamInstanceProfileAssociation {
4043 association_id: assoc_id.clone(),
4044 instance_id: instance_id.to_string(),
4045 iam_instance_profile_arn: arn.to_string(),
4046 iam_instance_profile_name: name.to_string(),
4047 state: "associated".to_string(),
4048 };
4049 if let Some(inst) = self.instances.get_mut(instance_id) {
4051 inst.iam_instance_profile_arn = Some(arn.to_string());
4052 }
4053 self.iam_instance_profile_associations
4054 .insert(assoc_id.clone(), assoc);
4055 self.iam_instance_profile_associations
4056 .get(&assoc_id)
4057 .unwrap()
4058 }
4059
4060 pub fn disassociate_iam_instance_profile(&mut self, assoc_id: &str) -> Result<(), Ec2Error> {
4061 let assoc = self
4062 .iam_instance_profile_associations
4063 .remove(assoc_id)
4064 .ok_or_else(|| Ec2Error::AssociationNotFound(assoc_id.to_string()))?;
4065 if let Some(inst) = self.instances.get_mut(&assoc.instance_id) {
4066 inst.iam_instance_profile_arn = None;
4067 }
4068 Ok(())
4069 }
4070
4071 pub fn replace_iam_instance_profile_association(
4072 &mut self,
4073 assoc_id: &str,
4074 arn: &str,
4075 name: &str,
4076 ) -> Result<(), Ec2Error> {
4077 let assoc = self
4078 .iam_instance_profile_associations
4079 .get_mut(assoc_id)
4080 .ok_or_else(|| Ec2Error::AssociationNotFound(assoc_id.to_string()))?;
4081 assoc.iam_instance_profile_arn = arn.to_string();
4082 assoc.iam_instance_profile_name = name.to_string();
4083 let instance_id = assoc.instance_id.clone();
4084 if let Some(inst) = self.instances.get_mut(&instance_id) {
4085 inst.iam_instance_profile_arn = Some(arn.to_string());
4086 }
4087 Ok(())
4088 }
4089
4090 pub fn register_image(
4093 &mut self,
4094 name: &str,
4095 description: &str,
4096 architecture: &str,
4097 virtualization_type: &str,
4098 root_device_name: &str,
4099 tags: Tags,
4100 owner_id: &str,
4101 ) -> String {
4102 let image_id = self.next_ami_id();
4103 let img = Image {
4104 image_id: image_id.clone(),
4105 name: name.to_string(),
4106 description: description.to_string(),
4107 state: "available".to_string(),
4108 owner_id: owner_id.to_string(),
4109 architecture: architecture.to_string(),
4110 image_type: "machine".to_string(),
4111 platform: None,
4112 virtualization_type: virtualization_type.to_string(),
4113 root_device_type: "ebs".to_string(),
4114 root_device_name: root_device_name.to_string(),
4115 public: false,
4116 tags,
4117 source_instance_id: None,
4118 source_instance_type: String::new(),
4119 launch_permissions: Vec::new(),
4120 recycle_bin_state: None,
4121 deprecation_time: None,
4122 recycle_bin_enter_time: None,
4123 product_codes: Vec::new(),
4124 fast_launch_state: None,
4125 deregistration_protection: None,
4126 kernel_id: None,
4127 ramdisk_id: None,
4128 ena_support: None,
4129 sriov_net_support: None,
4130 tpm_support: None,
4131 boot_mode: None,
4132 imds_support: None,
4133 image_location: None,
4134 source_image_id: None,
4135 source_region: None,
4136 };
4137 self.images.insert(image_id.clone(), img);
4138 image_id
4139 }
4140
4141 pub fn copy_image(
4142 &mut self,
4143 source_image_id: &str,
4144 name: &str,
4145 owner_id: &str,
4146 ) -> Result<String, Ec2Error> {
4147 let mut new_img = self
4148 .images
4149 .get(source_image_id)
4150 .ok_or_else(|| Ec2Error::AmiNotFound(source_image_id.to_string()))?
4151 .clone();
4152 let new_id = self.next_ami_id();
4153 new_img.image_id = new_id.clone();
4154 new_img.name = name.to_string();
4155 new_img.owner_id = owner_id.to_string();
4156 new_img.source_image_id = Some(source_image_id.to_string());
4157 self.images.insert(new_id.clone(), new_img);
4158 Ok(new_id)
4159 }
4160
4161 pub fn copy_snapshot(
4164 &mut self,
4165 source_snapshot_id: &str,
4166 owner_id: &str,
4167 ) -> Result<String, Ec2Error> {
4168 let mut new_snap = self
4169 .snapshots
4170 .get(source_snapshot_id)
4171 .ok_or_else(|| Ec2Error::SnapshotNotFound(source_snapshot_id.to_string()))?
4172 .clone();
4173 let new_id = self.next_snapshot_id();
4174 let now = chrono::Utc::now()
4175 .format("%Y-%m-%dT%H:%M:%S.000Z")
4176 .to_string();
4177 new_snap.snapshot_id = new_id.clone();
4178 new_snap.owner_id = owner_id.to_string();
4179 new_snap.start_time = now;
4180 self.snapshots.insert(new_id.clone(), new_snap);
4181 Ok(new_id)
4182 }
4183
4184 pub fn create_snapshots_from_instance(
4185 &mut self,
4186 instance_id: &str,
4187 owner_id: &str,
4188 ) -> Result<Vec<String>, Ec2Error> {
4189 if !self.instances.contains_key(instance_id) {
4190 return Err(Ec2Error::InstanceNotFound(instance_id.to_string()));
4191 }
4192 let vol_ids: Vec<String> = self
4193 .volumes
4194 .values()
4195 .filter(|v| v.attachments.iter().any(|a| a.instance_id == instance_id))
4196 .map(|v| v.volume_id.clone())
4197 .collect();
4198 let now = chrono::Utc::now()
4199 .format("%Y-%m-%dT%H:%M:%S.000Z")
4200 .to_string();
4201 let mut snap_ids = Vec::new();
4202 for vol_id in vol_ids {
4203 let (vol_size, encrypted) = {
4204 let v = self.volumes.get(&vol_id).unwrap();
4205 (v.size, v.encrypted)
4206 };
4207 let snap_id = self.next_snapshot_id();
4208 let snap = Snapshot {
4209 snapshot_id: snap_id.clone(),
4210 volume_id: vol_id,
4211 volume_size: vol_size,
4212 state: "completed".to_string(),
4213 description: format!("Created by CreateSnapshots for instance {instance_id}"),
4214 start_time: now.clone(),
4215 progress: "100%".to_string(),
4216 owner_id: owner_id.to_string(),
4217 encrypted,
4218 tags: HashMap::new(),
4219 lock_state: "none".to_string(),
4220 lock_duration: None,
4221 lock_created_on: None,
4222 lock_expires_on: None,
4223 lock_duration_start_time: None,
4224 cool_off_period: None,
4225 cool_off_period_expires_on: None,
4226 storage_tier: "standard".to_string(),
4227 last_tiering_operation_status: None,
4228 fast_snapshot_restore_states: Vec::new(),
4229 };
4230 self.snapshots.insert(snap_id.clone(), snap);
4231 snap_ids.push(snap_id);
4232 }
4233 Ok(snap_ids)
4234 }
4235
4236 pub fn modify_vpc_endpoint(&mut self, endpoint_id: &str) -> Result<(), Ec2Error> {
4239 if !self.vpc_endpoints.contains_key(endpoint_id) {
4240 return Err(Ec2Error::VpcEndpointNotFound(endpoint_id.to_string()));
4241 }
4242 Ok(())
4243 }
4244
4245 pub fn allocate_hosts(
4248 &mut self,
4249 availability_zone: &str,
4250 instance_type: Option<String>,
4251 quantity: usize,
4252 auto_placement: &str,
4253 host_recovery: &str,
4254 tags: Tags,
4255 ) -> Vec<String> {
4256 let now = chrono::Utc::now()
4257 .format("%Y-%m-%dT%H:%M:%S.000Z")
4258 .to_string();
4259 let _ = now;
4260 let mut ids = Vec::new();
4261 for _ in 0..quantity {
4262 let host_id = self.next_host_id();
4263 let host = DedicatedHost {
4264 host_id: host_id.clone(),
4265 availability_zone: availability_zone.to_string(),
4266 instance_type: instance_type.clone(),
4267 auto_placement: auto_placement.to_string(),
4268 host_recovery: host_recovery.to_string(),
4269 state: "available".to_string(),
4270 tags: tags.clone(),
4271 };
4272 self.dedicated_hosts.insert(host_id.clone(), host);
4273 ids.push(host_id);
4274 }
4275 ids
4276 }
4277
4278 pub fn release_hosts(&mut self, host_ids: &[String]) -> Vec<String> {
4279 let mut successful = Vec::new();
4280 for id in host_ids {
4281 if let Some(h) = self.dedicated_hosts.get_mut(id) {
4282 h.state = "released".to_string();
4283 successful.push(id.clone());
4284 }
4285 }
4286 successful
4287 }
4288
4289 pub fn modify_hosts(&mut self, host_ids: &[String]) -> Vec<String> {
4290 let mut successful = Vec::new();
4291 for id in host_ids {
4292 if self.dedicated_hosts.contains_key(id) {
4293 successful.push(id.clone());
4294 }
4295 }
4296 successful
4297 }
4298
4299 pub fn create_fleet(&mut self, fleet_type: &str, tags: Tags) -> String {
4302 let fleet_id = self.next_fleet_id();
4303 let now = chrono::Utc::now()
4304 .format("%Y-%m-%dT%H:%M:%S.000Z")
4305 .to_string();
4306 let fleet = Ec2Fleet {
4307 fleet_id: fleet_id.clone(),
4308 state: "active".to_string(),
4309 fleet_type: fleet_type.to_string(),
4310 create_time: now,
4311 tags,
4312 total_target_capacity: None,
4313 on_demand_target_capacity: None,
4314 spot_target_capacity: None,
4315 context: None,
4316 };
4317 self.ec2_fleets.insert(fleet_id.clone(), fleet);
4318 fleet_id
4319 }
4320
4321 pub fn delete_fleets(&mut self, fleet_ids: &[String]) -> Vec<String> {
4322 let mut successful = Vec::new();
4323 for id in fleet_ids {
4324 if let Some(f) = self.ec2_fleets.get_mut(id) {
4325 f.state = "deleted".to_string();
4326 successful.push(id.clone());
4327 }
4328 }
4329 successful
4330 }
4331
4332 pub fn create_vpc_endpoint_service_configuration(
4335 &mut self,
4336 acceptance_required: bool,
4337 network_load_balancer_arns: Vec<String>,
4338 gateway_load_balancer_arns: Vec<String>,
4339 tags: Tags,
4340 ) -> &VpcEndpointServiceConfiguration {
4341 let svc_id = self.next_vpce_svc_id();
4342 let svc_name = format!("com.amazonaws.vpce.us-east-1.{svc_id}");
4343 let svc_type = if !gateway_load_balancer_arns.is_empty() {
4344 "GatewayLoadBalancer"
4345 } else {
4346 "Interface"
4347 };
4348 let config = VpcEndpointServiceConfiguration {
4349 service_id: svc_id.clone(),
4350 service_name: svc_name,
4351 service_type: svc_type.to_string(),
4352 acceptance_required,
4353 state: "available".to_string(),
4354 network_load_balancer_arns,
4355 gateway_load_balancer_arns,
4356 allowed_principals: Vec::new(),
4357 tags,
4358 payer_responsibility: Some("ServiceOwner".to_string()),
4359 private_dns_state: None,
4360 };
4361 self.vpc_endpoint_service_configs
4362 .insert(svc_id.clone(), config);
4363 self.vpc_endpoint_service_configs.get(&svc_id).unwrap()
4364 }
4365
4366 pub fn upsert_vpc_endpoint_connection(
4370 &mut self,
4371 service_id: &str,
4372 endpoint_id: &str,
4373 owner: &str,
4374 state: &str,
4375 ) {
4376 self.vpc_endpoint_connections.insert(
4377 (service_id.to_string(), endpoint_id.to_string()),
4378 VpcEndpointConnection {
4379 service_id: service_id.to_string(),
4380 vpc_endpoint_id: endpoint_id.to_string(),
4381 vpc_endpoint_owner: owner.to_string(),
4382 vpc_endpoint_state: state.to_string(),
4383 creation_timestamp: chrono::Utc::now()
4384 .format("%Y-%m-%dT%H:%M:%S.000Z")
4385 .to_string(),
4386 },
4387 );
4388 }
4389
4390 pub fn accept_vpc_endpoint_connections(
4391 &mut self,
4392 service_id: &str,
4393 endpoint_ids: &[String],
4394 ) -> Result<Vec<String>, Ec2Error> {
4395 if !self.vpc_endpoint_service_configs.contains_key(service_id) {
4396 return Err(Ec2Error::VpcEndpointServiceNotFound(service_id.to_string()));
4397 }
4398 let mut accepted = Vec::new();
4399 for ep in endpoint_ids {
4400 let key = (service_id.to_string(), ep.clone());
4401 if let Some(conn) = self.vpc_endpoint_connections.get_mut(&key) {
4402 if conn.vpc_endpoint_state == "pendingAcceptance" {
4403 conn.vpc_endpoint_state = "available".to_string();
4404 accepted.push(ep.clone());
4405 }
4406 }
4407 }
4408 Ok(accepted)
4409 }
4410
4411 pub fn reject_vpc_endpoint_connections(
4412 &mut self,
4413 service_id: &str,
4414 endpoint_ids: &[String],
4415 ) -> Result<Vec<String>, Ec2Error> {
4416 if !self.vpc_endpoint_service_configs.contains_key(service_id) {
4417 return Err(Ec2Error::VpcEndpointServiceNotFound(service_id.to_string()));
4418 }
4419 let mut rejected = Vec::new();
4420 for ep in endpoint_ids {
4421 let key = (service_id.to_string(), ep.clone());
4422 if let Some(conn) = self.vpc_endpoint_connections.get_mut(&key) {
4423 if conn.vpc_endpoint_state == "pendingAcceptance" {
4424 conn.vpc_endpoint_state = "rejected".to_string();
4425 rejected.push(ep.clone());
4426 }
4427 }
4428 }
4429 Ok(rejected)
4430 }
4431
4432 pub fn modify_vpc_endpoint_service_payer_responsibility(
4433 &mut self,
4434 service_id: &str,
4435 payer_responsibility: &str,
4436 ) -> Result<(), Ec2Error> {
4437 let cfg = self
4438 .vpc_endpoint_service_configs
4439 .get_mut(service_id)
4440 .ok_or_else(|| Ec2Error::VpcEndpointServiceNotFound(service_id.to_string()))?;
4441 cfg.payer_responsibility = Some(payer_responsibility.to_string());
4442 Ok(())
4443 }
4444
4445 pub fn start_vpc_endpoint_service_private_dns_verification(
4446 &mut self,
4447 service_id: &str,
4448 ) -> Result<(), Ec2Error> {
4449 let cfg = self
4450 .vpc_endpoint_service_configs
4451 .get_mut(service_id)
4452 .ok_or_else(|| Ec2Error::VpcEndpointServiceNotFound(service_id.to_string()))?;
4453 cfg.private_dns_state = Some("verified".to_string());
4457 Ok(())
4458 }
4459
4460 fn next_vpc_endpoint_connection_notification_id(&mut self) -> String {
4463 self.counters.vpc_endpoint_connection_notification += 1;
4464 format!(
4465 "vpce-notif-{:08x}",
4466 self.counters.vpc_endpoint_connection_notification
4467 )
4468 }
4469
4470 pub fn create_vpc_endpoint_connection_notification(
4471 &mut self,
4472 sns_topic_arn: &str,
4473 connection_events: Vec<String>,
4474 service_id: Option<String>,
4475 vpc_endpoint_id: Option<String>,
4476 ) -> &VpcEndpointConnectionNotification {
4477 let id = self.next_vpc_endpoint_connection_notification_id();
4478 let notification_type = if vpc_endpoint_id.is_some() {
4479 "Endpoint".to_string()
4480 } else {
4481 "Topic".to_string()
4482 };
4483 let notification = VpcEndpointConnectionNotification {
4484 connection_notification_id: id.clone(),
4485 connection_notification_arn: sns_topic_arn.to_string(),
4486 connection_events,
4487 connection_notification_state: "Enabled".to_string(),
4488 connection_notification_type: notification_type,
4489 service_id,
4490 vpc_endpoint_id,
4491 };
4492 self.vpc_endpoint_connection_notifications
4493 .insert(id.clone(), notification);
4494 self.vpc_endpoint_connection_notifications.get(&id).unwrap()
4495 }
4496
4497 pub fn delete_vpc_endpoint_connection_notifications(&mut self, ids: &[String]) -> Vec<String> {
4498 let mut not_found = Vec::new();
4499 for id in ids {
4500 if self
4501 .vpc_endpoint_connection_notifications
4502 .remove(id)
4503 .is_none()
4504 {
4505 not_found.push(id.clone());
4506 }
4507 }
4508 not_found
4509 }
4510
4511 pub fn modify_vpc_endpoint_connection_notification(
4512 &mut self,
4513 id: &str,
4514 connection_notification_arn: Option<String>,
4515 connection_events: Option<Vec<String>>,
4516 ) -> Result<(), Ec2Error> {
4517 let n = self
4518 .vpc_endpoint_connection_notifications
4519 .get_mut(id)
4520 .ok_or_else(|| {
4521 Ec2Error::InvalidVpcEndpointConnectionNotificationNotFound(id.to_string())
4522 })?;
4523 if let Some(arn) = connection_notification_arn {
4524 n.connection_notification_arn = arn;
4525 }
4526 if let Some(evs) = connection_events {
4527 n.connection_events = evs;
4528 }
4529 Ok(())
4530 }
4531
4532 fn next_vpc_block_public_access_exclusion_id(&mut self) -> String {
4535 self.counters.vpc_block_public_access_exclusion += 1;
4536 format!(
4537 "vpcbpa-exclusion-{:08x}",
4538 self.counters.vpc_block_public_access_exclusion
4539 )
4540 }
4541
4542 pub fn create_vpc_block_public_access_exclusion(
4543 &mut self,
4544 resource_arn: &str,
4545 internet_gateway_exclusion_mode: &str,
4546 tags: Tags,
4547 ) -> &VpcBlockPublicAccessExclusion {
4548 let id = self.next_vpc_block_public_access_exclusion_id();
4549 let now = chrono::Utc::now()
4550 .format("%Y-%m-%dT%H:%M:%S.000Z")
4551 .to_string();
4552 let ex = VpcBlockPublicAccessExclusion {
4553 exclusion_id: id.clone(),
4554 internet_gateway_exclusion_mode: internet_gateway_exclusion_mode.to_string(),
4555 resource_arn: resource_arn.to_string(),
4556 state: "create-complete".to_string(),
4557 creation_timestamp: now.clone(),
4558 last_update_timestamp: now,
4559 tags,
4560 };
4561 self.vpc_block_public_access_exclusions
4562 .insert(id.clone(), ex);
4563 self.vpc_block_public_access_exclusions.get(&id).unwrap()
4564 }
4565
4566 pub fn delete_vpc_block_public_access_exclusion(
4567 &mut self,
4568 id: &str,
4569 ) -> Result<VpcBlockPublicAccessExclusion, Ec2Error> {
4570 let mut ex = self
4571 .vpc_block_public_access_exclusions
4572 .remove(id)
4573 .ok_or_else(|| {
4574 Ec2Error::InvalidVpcBlockPublicAccessExclusionNotFound(id.to_string())
4575 })?;
4576 ex.state = "delete-complete".to_string();
4577 ex.last_update_timestamp = chrono::Utc::now()
4578 .format("%Y-%m-%dT%H:%M:%S.000Z")
4579 .to_string();
4580 Ok(ex)
4581 }
4582
4583 pub fn modify_vpc_block_public_access_exclusion(
4584 &mut self,
4585 id: &str,
4586 new_mode: &str,
4587 ) -> Result<&VpcBlockPublicAccessExclusion, Ec2Error> {
4588 let ex = self
4589 .vpc_block_public_access_exclusions
4590 .get_mut(id)
4591 .ok_or_else(|| {
4592 Ec2Error::InvalidVpcBlockPublicAccessExclusionNotFound(id.to_string())
4593 })?;
4594 ex.internet_gateway_exclusion_mode = new_mode.to_string();
4595 ex.state = "update-complete".to_string();
4596 ex.last_update_timestamp = chrono::Utc::now()
4597 .format("%Y-%m-%dT%H:%M:%S.000Z")
4598 .to_string();
4599 Ok(self.vpc_block_public_access_exclusions.get(id).unwrap())
4600 }
4601
4602 pub fn modify_vpc_block_public_access_options(
4603 &mut self,
4604 account_id: &str,
4605 region: &str,
4606 block_mode: &str,
4607 ) -> &VpcBlockPublicAccessOptions {
4608 let now = chrono::Utc::now()
4609 .format("%Y-%m-%dT%H:%M:%S.000Z")
4610 .to_string();
4611 self.vpc_block_public_access_options = Some(VpcBlockPublicAccessOptions {
4612 aws_account_id: account_id.to_string(),
4613 aws_region: region.to_string(),
4614 internet_gateway_block_mode: block_mode.to_string(),
4615 state: "update-complete".to_string(),
4616 last_update_timestamp: now,
4617 });
4618 self.vpc_block_public_access_options.as_ref().unwrap()
4619 }
4620
4621 fn next_vpc_encryption_control_id(&mut self) -> String {
4624 self.counters.vpc_encryption_control += 1;
4625 format!(
4626 "vpc-encryption-control-{:08x}",
4627 self.counters.vpc_encryption_control
4628 )
4629 }
4630
4631 pub fn create_vpc_encryption_control(
4632 &mut self,
4633 vpc_id: &str,
4634 tags: Tags,
4635 ) -> Result<&VpcEncryptionControl, Ec2Error> {
4636 if !self.vpcs.contains_key(vpc_id) {
4637 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
4638 }
4639 let id = self.next_vpc_encryption_control_id();
4640 let now = chrono::Utc::now()
4641 .format("%Y-%m-%dT%H:%M:%S.000Z")
4642 .to_string();
4643 let mode = "monitor".to_string();
4644 let vec = VpcEncryptionControl {
4645 vpc_encryption_control_id: id.clone(),
4646 vpc_id: vpc_id.to_string(),
4647 mode: mode.clone(),
4648 state: "monitor-complete".to_string(),
4649 mode_history: vec![(mode, now)],
4650 tags,
4651 };
4652 self.vpc_encryption_controls.insert(id.clone(), vec);
4653 Ok(self.vpc_encryption_controls.get(&id).unwrap())
4654 }
4655
4656 pub fn delete_vpc_encryption_control(
4657 &mut self,
4658 id: &str,
4659 ) -> Result<VpcEncryptionControl, Ec2Error> {
4660 let mut vec = self
4661 .vpc_encryption_controls
4662 .remove(id)
4663 .ok_or_else(|| Ec2Error::InvalidVpcEncryptionControlNotFound(id.to_string()))?;
4664 vec.state = "delete-complete".to_string();
4665 Ok(vec)
4666 }
4667
4668 pub fn modify_vpc_encryption_control(
4669 &mut self,
4670 id: &str,
4671 new_mode: Option<&str>,
4672 ) -> Result<&VpcEncryptionControl, Ec2Error> {
4673 let vec = self
4674 .vpc_encryption_controls
4675 .get_mut(id)
4676 .ok_or_else(|| Ec2Error::InvalidVpcEncryptionControlNotFound(id.to_string()))?;
4677 if let Some(m) = new_mode {
4678 let now = chrono::Utc::now()
4679 .format("%Y-%m-%dT%H:%M:%S.000Z")
4680 .to_string();
4681 vec.mode = m.to_string();
4682 vec.state = match m {
4683 "enforce" => "enforce-complete".to_string(),
4684 _ => "monitor-complete".to_string(),
4685 };
4686 vec.mode_history.push((m.to_string(), now));
4687 }
4688 Ok(self.vpc_encryption_controls.get(id).unwrap())
4689 }
4690
4691 pub fn delete_vpc_endpoint_service_configurations(
4692 &mut self,
4693 svc_ids: &[String],
4694 ) -> Vec<String> {
4695 let mut removed = Vec::new();
4696 for id in svc_ids {
4697 if self.vpc_endpoint_service_configs.remove(id).is_some() {
4698 removed.push(id.clone());
4699 }
4700 }
4701 removed
4702 }
4703
4704 pub fn modify_vpc_endpoint_service_permissions(
4705 &mut self,
4706 svc_id: &str,
4707 add_arns: Vec<String>,
4708 remove_arns: Vec<String>,
4709 ) -> Result<(), Ec2Error> {
4710 let config = self
4711 .vpc_endpoint_service_configs
4712 .get_mut(svc_id)
4713 .ok_or_else(|| Ec2Error::VpcEndpointServiceNotFound(svc_id.to_string()))?;
4714 for arn in add_arns {
4715 if !config.allowed_principals.contains(&arn) {
4716 config.allowed_principals.push(arn);
4717 }
4718 }
4719 config
4720 .allowed_principals
4721 .retain(|p| !remove_arns.contains(p));
4722 Ok(())
4723 }
4724
4725 pub fn request_spot_fleet(
4728 &mut self,
4729 target_capacity: i32,
4730 iam_fleet_role: &str,
4731 tags: Tags,
4732 ) -> String {
4733 let id = self.next_spot_fleet_id();
4734 let now = chrono::Utc::now()
4735 .format("%Y-%m-%dT%H:%M:%S.000Z")
4736 .to_string();
4737 let req = SpotFleetRequest {
4738 spot_fleet_request_id: id.clone(),
4739 spot_fleet_request_state: "active".to_string(),
4740 target_capacity,
4741 iam_fleet_role: iam_fleet_role.to_string(),
4742 create_time: now,
4743 tags,
4744 };
4745 self.spot_fleet_requests.insert(id.clone(), req);
4746 id
4747 }
4748
4749 pub fn cancel_spot_fleet_requests(&mut self, ids: &[String]) -> Vec<String> {
4750 let mut successful = Vec::new();
4751 for id in ids {
4752 if let Some(r) = self.spot_fleet_requests.get_mut(id) {
4753 r.spot_fleet_request_state = "cancelled".to_string();
4754 successful.push(id.clone());
4755 }
4756 }
4757 successful
4758 }
4759
4760 pub fn create_subnet_cidr_reservation(
4763 &mut self,
4764 subnet_id: &str,
4765 cidr: &str,
4766 reservation_type: &str,
4767 description: &str,
4768 owner_id: &str,
4769 ) -> Result<SubnetCidrReservationEntry, Ec2Error> {
4770 if !self.subnets.contains_key(subnet_id) {
4771 return Err(Ec2Error::SubnetNotFound(subnet_id.to_string()));
4772 }
4773 let id = self.next_subnet_cidr_res_id();
4774 let entry = SubnetCidrReservationEntry {
4775 reservation_id: id.clone(),
4776 subnet_id: subnet_id.to_string(),
4777 cidr: cidr.to_string(),
4778 reservation_type: reservation_type.to_string(),
4779 description: description.to_string(),
4780 owner_id: owner_id.to_string(),
4781 };
4782 self.subnet_cidr_reservations.insert(id, entry.clone());
4783 Ok(entry)
4784 }
4785
4786 pub fn delete_subnet_cidr_reservation(
4787 &mut self,
4788 reservation_id: &str,
4789 ) -> Result<SubnetCidrReservationEntry, Ec2Error> {
4790 self.subnet_cidr_reservations
4791 .remove(reservation_id)
4792 .ok_or_else(|| Ec2Error::SubnetCidrReservationNotFound(reservation_id.to_string()))
4793 }
4794
4795 pub fn associate_subnet_cidr_block(
4798 &mut self,
4799 subnet_id: &str,
4800 ipv6_cidr_block: &str,
4801 ) -> Result<(String, String), Ec2Error> {
4802 let subnet = self
4803 .subnets
4804 .get_mut(subnet_id)
4805 .ok_or_else(|| Ec2Error::SubnetNotFound(subnet_id.to_string()))?;
4806 let assoc_id = {
4807 self.counters.subnet_ipv6_assoc += 1;
4808 format!("subnet-cidr-assoc-{:08x}", self.counters.subnet_ipv6_assoc)
4809 };
4810 subnet.ipv6_cidr_blocks.push(SubnetIpv6CidrAssoc {
4811 association_id: assoc_id.clone(),
4812 ipv6_cidr_block: ipv6_cidr_block.to_string(),
4813 state: "associated".to_string(),
4814 });
4815 Ok((assoc_id, ipv6_cidr_block.to_string()))
4816 }
4817
4818 pub fn disassociate_subnet_cidr_block(
4819 &mut self,
4820 association_id: &str,
4821 ) -> Result<(String, String), Ec2Error> {
4822 for subnet in self.subnets.values_mut() {
4823 if let Some(pos) = subnet
4824 .ipv6_cidr_blocks
4825 .iter()
4826 .position(|a| a.association_id == association_id)
4827 {
4828 let cidr = subnet.ipv6_cidr_blocks[pos].ipv6_cidr_block.clone();
4829 let subnet_id = subnet.subnet_id.clone();
4830 subnet.ipv6_cidr_blocks.remove(pos);
4831 return Ok((subnet_id, cidr));
4832 }
4833 }
4834 Err(Ec2Error::AssociationNotFound(association_id.to_string()))
4835 }
4836
4837 pub fn create_default_vpc(&mut self) -> Result<&Vpc, Ec2Error> {
4840 if self.vpcs.values().any(|v| v.is_default) {
4842 return Err(Ec2Error::DefaultVpcAlreadyExists);
4843 }
4844 let vpc_id = self.next_vpc_id();
4845 let dhcp_options_id = format!("dopt-{:08x}", self.counters.vpc);
4846 let vpc = Vpc {
4847 vpc_id: vpc_id.clone(),
4848 cidr_block: "172.31.0.0/16".to_string(),
4849 state: "available".to_string(),
4850 dhcp_options_id,
4851 instance_tenancy: "default".to_string(),
4852 is_default: true,
4853 enable_dns_hostnames: true,
4854 enable_dns_support: true,
4855 secondary_cidr_blocks: Vec::new(),
4856 tags: HashMap::new(),
4857 classic_link_enabled: false,
4858 };
4859 self.vpcs.insert(vpc_id.clone(), vpc);
4860 Ok(self.vpcs.get(&vpc_id).unwrap())
4861 }
4862
4863 pub fn create_default_subnet(&mut self, availability_zone: &str) -> Result<&Subnet, Ec2Error> {
4864 let default_vpc_id = self
4865 .vpcs
4866 .values()
4867 .find(|v| v.is_default)
4868 .map(|v| v.vpc_id.clone())
4869 .ok_or(Ec2Error::DefaultVpcNotFound)?;
4870 let subnet_id = self.next_subnet_id();
4871 let subnet = Subnet {
4872 subnet_id: subnet_id.clone(),
4873 vpc_id: default_vpc_id,
4874 cidr_block: "172.31.0.0/20".to_string(),
4875 availability_zone: availability_zone.to_string(),
4876 state: "available".to_string(),
4877 available_ip_address_count: 4091,
4878 map_public_ip_on_launch: true,
4879 ipv6_cidr_blocks: Vec::new(),
4880 tags: HashMap::new(),
4881 };
4882 self.subnets.insert(subnet_id.clone(), subnet);
4883 Ok(self.subnets.get(&subnet_id).unwrap())
4884 }
4885
4886 fn next_pg_id(&mut self) -> String {
4887 self.counters.placement_group += 1;
4888 format!("pg-{:08x}", self.counters.placement_group)
4889 }
4890
4891 pub fn create_placement_group(
4892 &mut self,
4893 group_name: &str,
4894 strategy: &str,
4895 partition_count: Option<i32>,
4896 spread_level: Option<String>,
4897 account_id: &str,
4898 region: &str,
4899 tags: Tags,
4900 ) -> Result<&PlacementGroup, Ec2Error> {
4901 if self
4902 .placement_groups
4903 .values()
4904 .any(|g| g.group_name == group_name)
4905 {
4906 return Err(Ec2Error::PlacementGroupAlreadyExists(
4907 group_name.to_string(),
4908 ));
4909 }
4910 match strategy {
4911 "cluster" | "spread" | "partition" => {}
4912 other => {
4913 return Err(Ec2Error::InvalidParameterValue(format!(
4914 "Invalid placement strategy: {other}"
4915 )));
4916 }
4917 }
4918 if strategy == "partition" {
4919 let pc = partition_count.unwrap_or(2);
4920 if !(2..=7).contains(&pc) {
4921 return Err(Ec2Error::InvalidParameterValue(
4922 "Partition count must be between 2 and 7".to_string(),
4923 ));
4924 }
4925 }
4926 let resolved_partition_count = if strategy == "partition" {
4927 Some(partition_count.unwrap_or(2))
4928 } else {
4929 None
4930 };
4931 let group_id = self.next_pg_id();
4932 let group_arn = format!("arn:aws:ec2:{region}:{account_id}:placement-group/{group_name}");
4933 let pg = PlacementGroup {
4934 group_id: group_id.clone(),
4935 group_name: group_name.to_string(),
4936 group_arn,
4937 strategy: strategy.to_string(),
4938 state: "available".to_string(),
4939 partition_count: resolved_partition_count,
4940 spread_level,
4941 tags,
4942 };
4943 self.placement_groups.insert(group_id.clone(), pg);
4944 Ok(self.placement_groups.get(&group_id).unwrap())
4945 }
4946
4947 pub fn delete_placement_group(&mut self, group_name: &str) -> Result<(), Ec2Error> {
4948 let id = self
4949 .placement_groups
4950 .values()
4951 .find(|g| g.group_name == group_name)
4952 .map(|g| g.group_id.clone())
4953 .ok_or_else(|| Ec2Error::PlacementGroupNotFound(group_name.to_string()))?;
4954 self.placement_groups.remove(&id);
4955 Ok(())
4956 }
4957
4958 fn next_eni_permission_id(&mut self) -> String {
4959 self.counters.eni_permission += 1;
4960 format!("eni-perm-{:08x}", self.counters.eni_permission)
4961 }
4962
4963 pub fn create_network_interface_permission(
4964 &mut self,
4965 network_interface_id: &str,
4966 aws_account_id: Option<String>,
4967 aws_service: Option<String>,
4968 permission: &str,
4969 ) -> Result<&NetworkInterfacePermission, Ec2Error> {
4970 if !self.network_interfaces.contains_key(network_interface_id) {
4971 return Err(Ec2Error::NetworkInterfaceNotFound(
4972 network_interface_id.to_string(),
4973 ));
4974 }
4975 match permission {
4976 "INSTANCE-ATTACH" | "EIP-ASSOCIATE" => {}
4977 other => {
4978 return Err(Ec2Error::InvalidParameterValue(format!(
4979 "Invalid permission: {other}"
4980 )));
4981 }
4982 }
4983 let id = self.next_eni_permission_id();
4984 let perm = NetworkInterfacePermission {
4985 network_interface_permission_id: id.clone(),
4986 network_interface_id: network_interface_id.to_string(),
4987 aws_account_id,
4988 aws_service,
4989 permission: permission.to_string(),
4990 permission_state: "GRANTED".to_string(),
4991 };
4992 self.network_interface_permissions.insert(id.clone(), perm);
4993 Ok(self.network_interface_permissions.get(&id).unwrap())
4994 }
4995
4996 pub fn delete_network_interface_permission(
4997 &mut self,
4998 network_interface_permission_id: &str,
4999 ) -> Result<(), Ec2Error> {
5000 if self
5001 .network_interface_permissions
5002 .remove(network_interface_permission_id)
5003 .is_none()
5004 {
5005 return Err(Ec2Error::NetworkInterfacePermissionNotFound(
5006 network_interface_permission_id.to_string(),
5007 ));
5008 }
5009 Ok(())
5010 }
5011
5012 fn next_instance_connect_endpoint_id(&mut self) -> String {
5013 self.counters.instance_connect_endpoint += 1;
5014 format!("eice-{:08x}", self.counters.instance_connect_endpoint)
5015 }
5016
5017 #[allow(clippy::too_many_arguments)]
5018 pub fn create_instance_connect_endpoint(
5019 &mut self,
5020 subnet_id: &str,
5021 security_group_ids: Vec<String>,
5022 preserve_client_ip: bool,
5023 ip_address_type: Option<String>,
5024 account_id: &str,
5025 region: &str,
5026 tags: Tags,
5027 ) -> Result<&InstanceConnectEndpoint, Ec2Error> {
5028 let subnet = self
5029 .subnets
5030 .get(subnet_id)
5031 .ok_or_else(|| Ec2Error::SubnetNotFound(subnet_id.to_string()))?;
5032 let vpc_id = subnet.vpc_id.clone();
5033 let availability_zone = subnet.availability_zone.clone();
5034 let id = self.next_instance_connect_endpoint_id();
5035 let arn = format!("arn:aws:ec2:{region}:{account_id}:instance-connect-endpoint/{id}");
5036 let dns_name = format!("{id}.{region}.ec2-instance-connect-endpoint.amazonaws.com");
5037 let fips_dns_name =
5038 format!("{id}.{region}.fips.ec2-instance-connect-endpoint.amazonaws.com");
5039 let ice = InstanceConnectEndpoint {
5040 instance_connect_endpoint_id: id.clone(),
5041 instance_connect_endpoint_arn: arn,
5042 subnet_id: subnet_id.to_string(),
5043 vpc_id,
5044 availability_zone,
5045 state: "create-complete".to_string(),
5046 created_at: "1970-01-01T00:00:00Z".to_string(),
5047 preserve_client_ip,
5048 security_group_ids,
5049 network_interface_ids: vec![],
5050 dns_name,
5051 fips_dns_name,
5052 ip_address_type: ip_address_type.unwrap_or_else(|| "ipv4".to_string()),
5053 owner_id: account_id.to_string(),
5054 tags,
5055 };
5056 self.instance_connect_endpoints.insert(id.clone(), ice);
5057 Ok(self.instance_connect_endpoints.get(&id).unwrap())
5058 }
5059
5060 pub fn delete_instance_connect_endpoint(&mut self, endpoint_id: &str) -> Result<(), Ec2Error> {
5061 let ice = self
5062 .instance_connect_endpoints
5063 .get_mut(endpoint_id)
5064 .ok_or_else(|| Ec2Error::InstanceConnectEndpointNotFound(endpoint_id.to_string()))?;
5065 ice.state = "delete-complete".to_string();
5066 self.instance_connect_endpoints.remove(endpoint_id);
5069 Ok(())
5070 }
5071
5072 fn next_capacity_reservation_id(&mut self) -> String {
5073 self.counters.capacity_reservation += 1;
5074 format!("cr-{:08x}", self.counters.capacity_reservation)
5075 }
5076
5077 #[allow(clippy::too_many_arguments)]
5078 pub fn create_capacity_reservation(
5079 &mut self,
5080 instance_type: &str,
5081 instance_platform: &str,
5082 availability_zone: String,
5083 instance_count: i32,
5084 ebs_optimized: bool,
5085 ephemeral_storage: bool,
5086 tenancy: String,
5087 instance_match_criteria: String,
5088 end_date: Option<String>,
5089 end_date_type: String,
5090 outpost_arn: Option<String>,
5091 placement_group_arn: Option<String>,
5092 account_id: &str,
5093 region: &str,
5094 tags: Tags,
5095 ) -> Result<&CapacityReservation, Ec2Error> {
5096 if instance_count <= 0 {
5097 return Err(Ec2Error::InvalidParameterValue(
5098 "InstanceCount must be > 0".to_string(),
5099 ));
5100 }
5101 match end_date_type.as_str() {
5102 "unlimited" | "limited" => {}
5103 _ => {
5104 return Err(Ec2Error::InvalidParameterValue(format!(
5105 "Invalid EndDateType: {end_date_type}"
5106 )));
5107 }
5108 }
5109 let id = self.next_capacity_reservation_id();
5110 let arn = format!("arn:aws:ec2:{region}:{account_id}:capacity-reservation/{id}");
5111 let cr = CapacityReservation {
5112 capacity_reservation_id: id.clone(),
5113 capacity_reservation_arn: arn,
5114 owner_id: account_id.to_string(),
5115 instance_type: instance_type.to_string(),
5116 instance_platform: instance_platform.to_string(),
5117 availability_zone,
5118 tenancy,
5119 total_instance_count: instance_count,
5120 available_instance_count: instance_count,
5121 ebs_optimized,
5122 ephemeral_storage,
5123 state: "active".to_string(),
5124 start_date: "1970-01-01T00:00:00Z".to_string(),
5125 end_date,
5126 end_date_type,
5127 instance_match_criteria,
5128 create_date: "1970-01-01T00:00:00Z".to_string(),
5129 outpost_arn,
5130 placement_group_arn,
5131 tags,
5132 pending_billing_owner_account_id: None,
5133 billing_owner_account_id: Some(account_id.to_string()),
5134 target_capacity_reservation_id: None,
5135 reservation_type: Some("default".to_string()),
5136 commitment_info: None,
5137 };
5138 self.capacity_reservations.insert(id.clone(), cr);
5139 Ok(self.capacity_reservations.get(&id).unwrap())
5140 }
5141
5142 pub fn cancel_capacity_reservation(&mut self, id: &str) -> Result<(), Ec2Error> {
5143 let cr = self
5144 .capacity_reservations
5145 .get_mut(id)
5146 .ok_or_else(|| Ec2Error::CapacityReservationNotFound(id.to_string()))?;
5147 cr.state = "cancelled".to_string();
5148 Ok(())
5149 }
5150
5151 pub fn modify_capacity_reservation(
5152 &mut self,
5153 id: &str,
5154 instance_count: Option<i32>,
5155 end_date: Option<String>,
5156 end_date_type: Option<String>,
5157 instance_match_criteria: Option<String>,
5158 ) -> Result<&CapacityReservation, Ec2Error> {
5159 let cr = self
5160 .capacity_reservations
5161 .get_mut(id)
5162 .ok_or_else(|| Ec2Error::CapacityReservationNotFound(id.to_string()))?;
5163 if let Some(c) = instance_count {
5164 if c <= 0 {
5165 return Err(Ec2Error::InvalidParameterValue(
5166 "InstanceCount must be > 0".to_string(),
5167 ));
5168 }
5169 cr.total_instance_count = c;
5170 cr.available_instance_count = c;
5171 }
5172 if let Some(d) = end_date {
5173 cr.end_date = Some(d);
5174 }
5175 if let Some(t) = end_date_type {
5176 cr.end_date_type = t;
5177 }
5178 if let Some(m) = instance_match_criteria {
5179 cr.instance_match_criteria = m;
5180 }
5181 Ok(self.capacity_reservations.get(id).unwrap())
5182 }
5183
5184 #[allow(clippy::too_many_arguments)]
5187 pub fn modify_instance_placement(
5188 &mut self,
5189 instance_id: &str,
5190 group_name: Option<String>,
5191 partition_number: Option<i32>,
5192 host_id: Option<String>,
5193 affinity: Option<String>,
5194 tenancy: Option<String>,
5195 ) -> Result<(), Ec2Error> {
5196 let inst = self
5197 .instances
5198 .get_mut(instance_id)
5199 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
5200 if let Some(g) = group_name {
5201 if g.is_empty() {
5205 inst.placement_group_name = None;
5206 } else {
5207 inst.placement_group_name = Some(g);
5208 }
5209 }
5210 if let Some(p) = partition_number {
5211 inst.placement_partition_number = Some(p);
5212 }
5213 if let Some(h) = host_id {
5214 if h.is_empty() {
5215 inst.placement_host_id = None;
5216 } else {
5217 inst.placement_host_id = Some(h);
5218 }
5219 }
5220 if let Some(a) = affinity {
5221 if a.is_empty() {
5222 inst.placement_affinity = None;
5223 } else {
5224 inst.placement_affinity = Some(a);
5225 }
5226 }
5227 if let Some(t) = tenancy {
5228 if t.is_empty() {
5229 inst.placement_tenancy = None;
5230 } else {
5231 inst.placement_tenancy = Some(t);
5232 }
5233 }
5234 Ok(())
5235 }
5236
5237 fn next_capacity_reservation_fleet_id(&mut self) -> String {
5240 self.counters.capacity_reservation_fleet += 1;
5241 format!("crf-{:08x}", self.counters.capacity_reservation_fleet)
5242 }
5243
5244 #[allow(clippy::too_many_arguments)]
5245 pub fn create_capacity_reservation_fleet(
5246 &mut self,
5247 allocation_strategy: Option<String>,
5248 tenancy: Option<String>,
5249 instance_match_criteria: Option<String>,
5250 total_target_capacity: i32,
5251 end_date: Option<String>,
5252 instance_specs: Vec<CapacityReservationFleetInstanceSpec>,
5253 account_id: &str,
5254 region: &str,
5255 tags: Tags,
5256 ) -> Result<&CapacityReservationFleet, Ec2Error> {
5257 if total_target_capacity <= 0 {
5258 return Err(Ec2Error::InvalidParameterValue(
5259 "TotalTargetCapacity must be > 0".to_string(),
5260 ));
5261 }
5262 if instance_specs.is_empty() {
5263 return Err(Ec2Error::InvalidParameterValue(
5264 "At least one InstanceTypeSpecification is required".to_string(),
5265 ));
5266 }
5267 let id = self.next_capacity_reservation_fleet_id();
5268 let arn = format!("arn:aws:ec2:{region}:{account_id}:capacity-reservation-fleet/{id}");
5269 let fulfilled: f64 = instance_specs.iter().map(|s| s.weight.unwrap_or(1.0)).sum();
5271 let fleet = CapacityReservationFleet {
5272 capacity_reservation_fleet_id: id.clone(),
5273 capacity_reservation_fleet_arn: arn,
5274 state: "active".to_string(),
5275 tenancy: tenancy.unwrap_or_else(|| "default".to_string()),
5276 allocation_strategy: allocation_strategy.unwrap_or_else(|| "prioritized".to_string()),
5277 instance_match_criteria: instance_match_criteria.unwrap_or_else(|| "open".to_string()),
5278 total_target_capacity,
5279 total_fulfilled_capacity: fulfilled,
5280 create_time: "1970-01-01T00:00:00Z".to_string(),
5281 end_date,
5282 instance_type_specifications: instance_specs,
5283 tags,
5284 };
5285 self.capacity_reservation_fleets.insert(id.clone(), fleet);
5286 Ok(self.capacity_reservation_fleets.get(&id).unwrap())
5287 }
5288
5289 pub fn cancel_capacity_reservation_fleet(&mut self, id: &str) -> Result<(), Ec2Error> {
5290 let fleet = self
5291 .capacity_reservation_fleets
5292 .get_mut(id)
5293 .ok_or_else(|| Ec2Error::CapacityReservationFleetNotFound(id.to_string()))?;
5294 fleet.state = "cancelled".to_string();
5295 Ok(())
5296 }
5297
5298 pub fn modify_capacity_reservation_fleet(
5299 &mut self,
5300 id: &str,
5301 total_target_capacity: Option<i32>,
5302 end_date: Option<String>,
5303 remove_end_date: Option<bool>,
5304 ) -> Result<&CapacityReservationFleet, Ec2Error> {
5305 let fleet = self
5306 .capacity_reservation_fleets
5307 .get_mut(id)
5308 .ok_or_else(|| Ec2Error::CapacityReservationFleetNotFound(id.to_string()))?;
5309 if let Some(c) = total_target_capacity {
5310 if c <= 0 {
5311 return Err(Ec2Error::InvalidParameterValue(
5312 "TotalTargetCapacity must be > 0".to_string(),
5313 ));
5314 }
5315 fleet.total_target_capacity = c;
5316 }
5317 if remove_end_date == Some(true) {
5318 fleet.end_date = None;
5319 } else if let Some(d) = end_date {
5320 fleet.end_date = Some(d);
5321 }
5322 Ok(self.capacity_reservation_fleets.get(id).unwrap())
5323 }
5324
5325 fn next_coip_pool_id(&mut self) -> String {
5328 self.counters.coip_pool += 1;
5329 format!("ipv4pool-coip-{:08x}", self.counters.coip_pool)
5330 }
5331
5332 pub fn create_coip_pool(
5333 &mut self,
5334 local_gateway_route_table_id: &str,
5335 pool_cidrs: Vec<String>,
5336 account_id: &str,
5337 region: &str,
5338 tags: Tags,
5339 ) -> &CoipPool {
5340 let pool_id = self.next_coip_pool_id();
5341 let arn = format!("arn:aws:ec2:{region}:{account_id}:coip-pool/{pool_id}");
5342 let pool = CoipPool {
5343 pool_id: pool_id.clone(),
5344 pool_arn: arn,
5345 local_gateway_route_table_id: local_gateway_route_table_id.to_string(),
5346 pool_cidrs,
5347 tags,
5348 };
5349 self.coip_pools.insert(pool_id.clone(), pool);
5350 self.coip_pools.get(&pool_id).unwrap()
5351 }
5352
5353 pub fn delete_coip_pool(&mut self, pool_id: &str) -> Result<CoipPool, Ec2Error> {
5354 self.coip_pools
5355 .remove(pool_id)
5356 .ok_or_else(|| Ec2Error::CoipPoolNotFound(pool_id.to_string()))
5357 }
5358
5359 pub fn attach_classic_link_vpc(
5362 &mut self,
5363 instance_id: &str,
5364 vpc_id: &str,
5365 groups: Vec<String>,
5366 ) -> Result<(), Ec2Error> {
5367 if !self.vpcs.contains_key(vpc_id) {
5368 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
5369 }
5370 let inst = self
5371 .instances
5372 .get_mut(instance_id)
5373 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
5374 inst.classic_link_vpc = Some((vpc_id.to_string(), groups));
5375 Ok(())
5376 }
5377
5378 pub fn detach_classic_link_vpc(
5379 &mut self,
5380 instance_id: &str,
5381 vpc_id: &str,
5382 ) -> Result<(), Ec2Error> {
5383 let inst = self
5384 .instances
5385 .get_mut(instance_id)
5386 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
5387 match &inst.classic_link_vpc {
5388 Some((linked_vpc, _)) if linked_vpc == vpc_id => {
5389 inst.classic_link_vpc = None;
5390 Ok(())
5391 }
5392 _ => Err(Ec2Error::InvalidParameterValue(format!(
5393 "Instance '{instance_id}' is not linked to VPC '{vpc_id}'"
5394 ))),
5395 }
5396 }
5397
5398 pub fn associate_security_group_vpc(
5401 &mut self,
5402 group_id: &str,
5403 vpc_id: &str,
5404 vpc_owner_id: &str,
5405 ) -> Result<&SecurityGroupVpcAssociation, Ec2Error> {
5406 if !self.security_groups.contains_key(group_id) {
5407 return Err(Ec2Error::SecurityGroupNotFound(group_id.to_string()));
5408 }
5409 let key = (group_id.to_string(), vpc_id.to_string());
5410 let assoc = SecurityGroupVpcAssociation {
5411 group_id: group_id.to_string(),
5412 vpc_id: vpc_id.to_string(),
5413 vpc_owner_id: vpc_owner_id.to_string(),
5414 state: "associated".to_string(),
5415 };
5416 self.security_group_vpc_associations
5417 .insert(key.clone(), assoc);
5418 Ok(self.security_group_vpc_associations.get(&key).unwrap())
5419 }
5420
5421 pub fn disassociate_security_group_vpc(
5422 &mut self,
5423 group_id: &str,
5424 vpc_id: &str,
5425 ) -> Result<SecurityGroupVpcAssociation, Ec2Error> {
5426 let key = (group_id.to_string(), vpc_id.to_string());
5427 self.security_group_vpc_associations
5428 .remove(&key)
5429 .ok_or_else(|| {
5430 Ec2Error::SecurityGroupVpcAssociationNotFound(
5431 group_id.to_string(),
5432 vpc_id.to_string(),
5433 )
5434 })
5435 }
5436
5437 pub fn associate_enclave_certificate_iam_role(
5440 &mut self,
5441 certificate_arn: &str,
5442 role_arn: &str,
5443 ) -> &EnclaveCertificateIamRoleAssociation {
5444 let key = (certificate_arn.to_string(), role_arn.to_string());
5445 let bucket = format!(
5447 "aws-ec2-enclave-certificate-{:016x}",
5448 self.enclave_certificate_iam_role_associations.len() as u64 + 1
5449 );
5450 let object_key = format!("{certificate_arn}/{role_arn}.pem");
5451 let kms_key_id = format!(
5452 "arn:aws:kms:us-east-1:000000000000:key/{:08x}-mock-mock-mock-{:012x}",
5453 self.enclave_certificate_iam_role_associations.len() as u64 + 1,
5454 self.enclave_certificate_iam_role_associations.len() as u64 + 1
5455 );
5456 let assoc = EnclaveCertificateIamRoleAssociation {
5457 certificate_arn: certificate_arn.to_string(),
5458 role_arn: role_arn.to_string(),
5459 certificate_s3_bucket_name: bucket,
5460 certificate_s3_object_key: object_key,
5461 encryption_kms_key_id: kms_key_id,
5462 };
5463 self.enclave_certificate_iam_role_associations
5464 .insert(key.clone(), assoc);
5465 self.enclave_certificate_iam_role_associations
5466 .get(&key)
5467 .unwrap()
5468 }
5469
5470 pub fn disassociate_enclave_certificate_iam_role(
5471 &mut self,
5472 certificate_arn: &str,
5473 role_arn: &str,
5474 ) -> Result<(), Ec2Error> {
5475 let key = (certificate_arn.to_string(), role_arn.to_string());
5476 if self
5477 .enclave_certificate_iam_role_associations
5478 .remove(&key)
5479 .is_none()
5480 {
5481 return Err(Ec2Error::EnclaveCertificateIamRoleAssociationNotFound(
5482 certificate_arn.to_string(),
5483 role_arn.to_string(),
5484 ));
5485 }
5486 Ok(())
5487 }
5488
5489 pub fn modify_private_dns_name_options(
5492 &mut self,
5493 instance_id: &str,
5494 private_dns_hostname_type: Option<String>,
5495 enable_resource_name_dns_a_record: Option<bool>,
5496 enable_resource_name_dns_aaaa_record: Option<bool>,
5497 ) -> Result<(), Ec2Error> {
5498 let inst = self
5499 .instances
5500 .get_mut(instance_id)
5501 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
5502 if let Some(t) = private_dns_hostname_type {
5503 inst.private_dns_hostname_type = Some(t);
5504 }
5505 if let Some(v) = enable_resource_name_dns_a_record {
5506 inst.enable_resource_name_dns_a_record = Some(v);
5507 }
5508 if let Some(v) = enable_resource_name_dns_aaaa_record {
5509 inst.enable_resource_name_dns_aaaa_record = Some(v);
5510 }
5511 Ok(())
5512 }
5513
5514 pub fn modify_public_ip_dns_name_options(
5515 &mut self,
5516 network_interface_id: &str,
5517 hostname_type: &str,
5518 ) -> Result<(), Ec2Error> {
5519 let eni = self
5520 .network_interfaces
5521 .get_mut(network_interface_id)
5522 .ok_or_else(|| Ec2Error::NetworkInterfaceNotFound(network_interface_id.to_string()))?;
5523 eni.public_ip_dns_hostname_type = Some(hostname_type.to_string());
5524 Ok(())
5525 }
5526
5527 fn next_mac_sip_task_id(&mut self) -> String {
5530 self.counters.mac_sip_task += 1;
5531 format!("macmodification-{:08x}", self.counters.mac_sip_task)
5532 }
5533
5534 #[allow(clippy::too_many_arguments)]
5535 pub fn create_mac_sip_modification_task(
5536 &mut self,
5537 instance_id: &str,
5538 apple_internal: Option<String>,
5539 base_system: Option<String>,
5540 debugging_restrictions: Option<String>,
5541 dtrace_restrictions: Option<String>,
5542 filesystem_protections: Option<String>,
5543 kext_signing: Option<String>,
5544 nvram_protections: Option<String>,
5545 tags: Tags,
5546 ) -> Result<&MacSipModificationTask, Ec2Error> {
5547 if !self.instances.contains_key(instance_id) {
5548 return Err(Ec2Error::InstanceNotFound(instance_id.to_string()));
5549 }
5550 let task_id = self.next_mac_sip_task_id();
5551 let now = chrono::Utc::now()
5552 .format("%Y-%m-%dT%H:%M:%S.000Z")
5553 .to_string();
5554 let task = MacSipModificationTask {
5555 task_id: task_id.clone(),
5556 instance_id: instance_id.to_string(),
5557 task_type: "sip-modification".to_string(),
5558 task_state: "in-progress".to_string(),
5559 start_time: now,
5560 apple_internal,
5561 base_system,
5562 debugging_restrictions,
5563 dtrace_restrictions,
5564 filesystem_protections,
5565 kext_signing,
5566 nvram_protections,
5567 status: Some("pending".to_string()),
5568 tags,
5569 };
5570 self.mac_sip_modification_tasks
5571 .insert(task_id.clone(), task);
5572 Ok(self.mac_sip_modification_tasks.get(&task_id).unwrap())
5573 }
5574
5575 fn next_declarative_policies_report_id(&mut self) -> String {
5578 self.counters.declarative_policies_report += 1;
5579 format!("report-{:016x}", self.counters.declarative_policies_report)
5580 }
5581
5582 pub fn start_declarative_policies_report(
5583 &mut self,
5584 target_id: &str,
5585 s3_bucket: &str,
5586 s3_prefix: Option<String>,
5587 tags: Tags,
5588 ) -> &DeclarativePoliciesReport {
5589 let report_id = self.next_declarative_policies_report_id();
5590 let now = chrono::Utc::now()
5591 .format("%Y-%m-%dT%H:%M:%S.000Z")
5592 .to_string();
5593 let report = DeclarativePoliciesReport {
5594 report_id: report_id.clone(),
5595 s3_bucket: s3_bucket.to_string(),
5596 s3_prefix,
5597 target_id: target_id.to_string(),
5598 status: "running".to_string(),
5599 start_time: now,
5600 end_time: None,
5601 tags,
5602 };
5603 self.declarative_policies_reports
5604 .insert(report_id.clone(), report);
5605 self.declarative_policies_reports.get(&report_id).unwrap()
5606 }
5607
5608 pub fn cancel_declarative_policies_report(&mut self, report_id: &str) -> Result<(), Ec2Error> {
5609 let report = self
5610 .declarative_policies_reports
5611 .get_mut(report_id)
5612 .ok_or_else(|| Ec2Error::DeclarativePoliciesReportNotFound(report_id.to_string()))?;
5613 if report.status != "running" {
5614 return Err(Ec2Error::DeclarativePoliciesReportNotCancellable(
5615 report_id.to_string(),
5616 ));
5617 }
5618 let now = chrono::Utc::now()
5619 .format("%Y-%m-%dT%H:%M:%S.000Z")
5620 .to_string();
5621 report.status = "cancelled".to_string();
5622 report.end_time = Some(now);
5623 Ok(())
5624 }
5625
5626 pub fn provision_byoip_cidr(
5629 &mut self,
5630 cidr: &str,
5631 description: Option<String>,
5632 ) -> Result<&ByoipCidr, Ec2Error> {
5633 if self.byoip_cidrs.contains_key(cidr) {
5634 return Err(Ec2Error::ByoipCidrAlreadyExists(cidr.to_string()));
5635 }
5636 let entry = ByoipCidr {
5637 cidr: cidr.to_string(),
5638 description,
5639 state: "provisioned".to_string(),
5640 asn_association: None,
5641 ipam_pool_id: None,
5642 };
5643 self.byoip_cidrs.insert(cidr.to_string(), entry);
5644 Ok(self.byoip_cidrs.get(cidr).unwrap())
5645 }
5646
5647 pub fn advertise_byoip_cidr(&mut self, cidr: &str) -> Result<&ByoipCidr, Ec2Error> {
5648 let entry = self
5649 .byoip_cidrs
5650 .get_mut(cidr)
5651 .ok_or_else(|| Ec2Error::InvalidByoipCidrNotFound(cidr.to_string()))?;
5652 if entry.state != "provisioned" && entry.state != "deprovisioned" {
5653 return Err(Ec2Error::ByoipCidrInvalidState(cidr.to_string()));
5654 }
5655 entry.state = "advertised".to_string();
5656 Ok(entry)
5657 }
5658
5659 pub fn withdraw_byoip_cidr(&mut self, cidr: &str) -> Result<&ByoipCidr, Ec2Error> {
5660 let entry = self
5661 .byoip_cidrs
5662 .get_mut(cidr)
5663 .ok_or_else(|| Ec2Error::InvalidByoipCidrNotFound(cidr.to_string()))?;
5664 if entry.state != "advertised" {
5665 return Err(Ec2Error::ByoipCidrInvalidState(cidr.to_string()));
5666 }
5667 entry.state = "provisioned".to_string();
5668 Ok(entry)
5669 }
5670
5671 pub fn deprovision_byoip_cidr(&mut self, cidr: &str) -> Result<&ByoipCidr, Ec2Error> {
5672 let entry = self
5673 .byoip_cidrs
5674 .get_mut(cidr)
5675 .ok_or_else(|| Ec2Error::InvalidByoipCidrNotFound(cidr.to_string()))?;
5676 if entry.state == "advertised" {
5677 return Err(Ec2Error::ByoipCidrInvalidState(cidr.to_string()));
5678 }
5679 entry.state = "deprovisioned".to_string();
5680 Ok(entry)
5681 }
5682
5683 fn next_public_ipv4_pool_id(&mut self) -> String {
5686 self.counters.public_ipv4_pool += 1;
5687 format!("ipv4pool-ec2-{:08x}", self.counters.public_ipv4_pool)
5688 }
5689
5690 pub fn create_public_ipv4_pool(
5691 &mut self,
5692 description: Option<String>,
5693 network_border_group: Option<String>,
5694 tags: Tags,
5695 ) -> &PublicIpv4Pool {
5696 let pool_id = self.next_public_ipv4_pool_id();
5697 let pool = PublicIpv4Pool {
5698 pool_id: pool_id.clone(),
5699 description,
5700 network_border_group,
5701 total_address_count: 0,
5702 total_available_address_count: 0,
5703 pool_address_ranges: Vec::new(),
5704 tags,
5705 };
5706 self.public_ipv4_pools.insert(pool_id.clone(), pool);
5707 self.public_ipv4_pools.get(&pool_id).unwrap()
5708 }
5709
5710 pub fn delete_public_ipv4_pool(&mut self, pool_id: &str) -> Result<(), Ec2Error> {
5711 let pool = self
5712 .public_ipv4_pools
5713 .get(pool_id)
5714 .ok_or_else(|| Ec2Error::InvalidPublicIpv4PoolNotFound(pool_id.to_string()))?;
5715 if !pool.pool_address_ranges.is_empty() {
5716 return Err(Ec2Error::PublicIpv4PoolNotEmpty(pool_id.to_string()));
5717 }
5718 self.public_ipv4_pools.remove(pool_id);
5719 Ok(())
5720 }
5721
5722 pub fn provision_public_ipv4_pool_cidr(
5723 &mut self,
5724 pool_id: &str,
5725 cidr: &str,
5726 netmask_length: i32,
5727 ) -> Result<(String, PublicIpv4PoolRange), Ec2Error> {
5728 let pool = self
5729 .public_ipv4_pools
5730 .get_mut(pool_id)
5731 .ok_or_else(|| Ec2Error::InvalidPublicIpv4PoolNotFound(pool_id.to_string()))?;
5732 let count = if (1..=32).contains(&netmask_length) {
5737 1i32 << ((32 - netmask_length).max(0) as u32)
5738 } else {
5739 1
5740 };
5741 let first = cidr.split('/').next().unwrap_or("0.0.0.0").to_string();
5742 let last_octet = count.saturating_sub(1).min(255) as u8;
5744 let mut octets: Vec<u8> = first
5745 .split('.')
5746 .filter_map(|p| p.parse().ok())
5747 .collect::<Vec<u8>>();
5748 if octets.len() == 4 {
5749 octets[3] = octets[3].saturating_add(last_octet);
5750 }
5751 let last = if octets.len() == 4 {
5752 format!("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3])
5753 } else {
5754 first.clone()
5755 };
5756 let range = PublicIpv4PoolRange {
5757 first_address: first.clone(),
5758 last_address: last,
5759 address_count: count,
5760 available_address_count: count,
5761 };
5762 pool.pool_address_ranges.push(range.clone());
5763 pool.total_address_count += count;
5764 pool.total_available_address_count += count;
5765 Ok((first, range))
5766 }
5767
5768 pub fn deprovision_public_ipv4_pool_cidr(
5769 &mut self,
5770 pool_id: &str,
5771 cidr: &str,
5772 ) -> Result<Vec<String>, Ec2Error> {
5773 let pool = self
5774 .public_ipv4_pools
5775 .get_mut(pool_id)
5776 .ok_or_else(|| Ec2Error::InvalidPublicIpv4PoolNotFound(pool_id.to_string()))?;
5777 let first_addr = cidr.split('/').next().unwrap_or("").to_string();
5778 let pos = pool
5779 .pool_address_ranges
5780 .iter()
5781 .position(|r| r.first_address == first_addr)
5782 .ok_or_else(|| {
5783 Ec2Error::PublicIpv4PoolCidrNotFound(cidr.to_string(), pool_id.to_string())
5784 })?;
5785 let range = pool.pool_address_ranges.remove(pos);
5786 pool.total_address_count -= range.address_count;
5787 pool.total_available_address_count -= range.available_address_count;
5788 let mut deprovisioned = Vec::new();
5789 for i in 0..range.address_count {
5790 let octets: Vec<u8> = range
5791 .first_address
5792 .split('.')
5793 .filter_map(|p| p.parse().ok())
5794 .collect();
5795 if octets.len() == 4 {
5796 let new_last = octets[3].saturating_add(i as u8);
5797 deprovisioned.push(format!(
5798 "{}.{}.{}.{}",
5799 octets[0], octets[1], octets[2], new_last
5800 ));
5801 }
5802 }
5803 Ok(deprovisioned)
5804 }
5805
5806 pub fn create_coip_cidr(
5809 &mut self,
5810 cidr: &str,
5811 coip_pool_id: &str,
5812 ) -> Result<&CoipCidr, Ec2Error> {
5813 if !self.coip_pools.contains_key(coip_pool_id) {
5814 return Err(Ec2Error::CoipPoolNotFound(coip_pool_id.to_string()));
5815 }
5816 let key = (cidr.to_string(), coip_pool_id.to_string());
5817 if self.coip_cidrs.contains_key(&key) {
5818 return Err(Ec2Error::CoipCidrAlreadyExists(
5819 cidr.to_string(),
5820 coip_pool_id.to_string(),
5821 ));
5822 }
5823 if let Some(pool) = self.coip_pools.get_mut(coip_pool_id)
5826 && !pool.pool_cidrs.iter().any(|c| c == cidr)
5827 {
5828 pool.pool_cidrs.push(cidr.to_string());
5829 }
5830 let cidr_entry = CoipCidr {
5831 cidr: cidr.to_string(),
5832 coip_pool_id: coip_pool_id.to_string(),
5833 };
5834 self.coip_cidrs.insert(key.clone(), cidr_entry);
5835 Ok(self.coip_cidrs.get(&key).unwrap())
5836 }
5837
5838 pub fn delete_coip_cidr(
5839 &mut self,
5840 cidr: &str,
5841 coip_pool_id: &str,
5842 ) -> Result<CoipCidr, Ec2Error> {
5843 let key = (cidr.to_string(), coip_pool_id.to_string());
5844 let entry = self.coip_cidrs.remove(&key).ok_or_else(|| {
5845 Ec2Error::CoipCidrNotFound(cidr.to_string(), coip_pool_id.to_string())
5846 })?;
5847 if let Some(pool) = self.coip_pools.get_mut(coip_pool_id) {
5848 pool.pool_cidrs.retain(|c| c != cidr);
5849 }
5850 Ok(entry)
5851 }
5852
5853 pub fn accept_address_transfer(
5856 &mut self,
5857 allocation_id: &str,
5858 ) -> Result<&AddressTransfer, Ec2Error> {
5859 let entry = self
5860 .address_transfers
5861 .get_mut(allocation_id)
5862 .ok_or_else(|| Ec2Error::InvalidAddressTransferNotFound(allocation_id.to_string()))?;
5863 let now = chrono::Utc::now()
5864 .format("%Y-%m-%dT%H:%M:%S.000Z")
5865 .to_string();
5866 entry.address_transfer_status = "accepted".to_string();
5867 entry.transfer_offer_accepted_timestamp = Some(now);
5868 Ok(entry)
5869 }
5870
5871 pub fn modify_address_attribute(
5874 &mut self,
5875 allocation_id: &str,
5876 domain_name: Option<String>,
5877 ) -> Result<&ElasticIp, Ec2Error> {
5878 let eip = self
5879 .elastic_ips
5880 .get_mut(allocation_id)
5881 .ok_or_else(|| Ec2Error::AllocationNotFound(allocation_id.to_string()))?;
5882 eip.address_attribute_ptr_record = domain_name;
5883 Ok(eip)
5884 }
5885
5886 pub fn reset_address_attribute(&mut self, allocation_id: &str) -> Result<&ElasticIp, Ec2Error> {
5887 let eip = self
5888 .elastic_ips
5889 .get_mut(allocation_id)
5890 .ok_or_else(|| Ec2Error::AllocationNotFound(allocation_id.to_string()))?;
5891 eip.address_attribute_ptr_record = None;
5892 Ok(eip)
5893 }
5894
5895 pub fn move_address_to_vpc(&mut self, public_ip: &str) -> Result<&ElasticIp, Ec2Error> {
5896 let eip = self
5897 .elastic_ips
5898 .values_mut()
5899 .find(|e| e.public_ip == public_ip)
5900 .ok_or_else(|| Ec2Error::AllocationNotFound(public_ip.to_string()))?;
5901 eip.domain = "vpc".to_string();
5902 Ok(eip)
5903 }
5904
5905 pub fn restore_address_to_classic(&mut self, public_ip: &str) -> Result<&ElasticIp, Ec2Error> {
5906 let eip = self
5907 .elastic_ips
5908 .values_mut()
5909 .find(|e| e.public_ip == public_ip)
5910 .ok_or_else(|| Ec2Error::AllocationNotFound(public_ip.to_string()))?;
5911 eip.domain = "standard".to_string();
5912 Ok(eip)
5913 }
5914
5915 fn next_nat_gw_assoc_id(&mut self) -> String {
5918 self.counters.nat_gateway_address_assoc += 1;
5919 format!(
5920 "eipassoc-natgw-{:08x}",
5921 self.counters.nat_gateway_address_assoc
5922 )
5923 }
5924
5925 pub fn assign_private_nat_gateway_address(
5926 &mut self,
5927 nat_gateway_id: &str,
5928 private_ips: Vec<String>,
5929 ) -> Result<&NatGateway, Ec2Error> {
5930 let nat = self
5934 .nat_gateways
5935 .get_mut(nat_gateway_id)
5936 .ok_or_else(|| Ec2Error::NatGatewayNotFound(nat_gateway_id.to_string()))?;
5937 let mut next_octet = nat.secondary_addresses.len() as u8 + 10;
5938 let resolved: Vec<String> = if private_ips.is_empty() {
5939 let ip = format!("10.0.0.{next_octet}");
5941 next_octet = next_octet.saturating_add(1);
5942 vec![ip]
5943 } else {
5944 private_ips
5945 };
5946 let _ = next_octet; for ip in resolved {
5948 nat.secondary_addresses.push(NatGatewayAddressAssociation {
5949 allocation_id: None,
5950 association_id: None,
5951 network_interface_id: Some(format!("eni-{nat_gateway_id}")),
5952 private_ip: Some(ip),
5953 public_ip: None,
5954 status: "succeeded".to_string(),
5955 is_primary: false,
5956 });
5957 }
5958 Ok(self.nat_gateways.get(nat_gateway_id).unwrap())
5959 }
5960
5961 pub fn unassign_private_nat_gateway_address(
5962 &mut self,
5963 nat_gateway_id: &str,
5964 private_ips: Vec<String>,
5965 ) -> Result<&NatGateway, Ec2Error> {
5966 let nat = self
5967 .nat_gateways
5968 .get_mut(nat_gateway_id)
5969 .ok_or_else(|| Ec2Error::NatGatewayNotFound(nat_gateway_id.to_string()))?;
5970 let before = nat.secondary_addresses.len();
5972 nat.secondary_addresses.retain(|a| {
5973 !private_ips
5974 .iter()
5975 .any(|ip| a.private_ip.as_deref() == Some(ip))
5976 });
5977 if nat.secondary_addresses.len() == before && !private_ips.is_empty() {
5978 return Err(Ec2Error::InvalidNatGatewaySecondaryAddressNotFound(
5979 String::new(),
5980 private_ips.first().cloned().unwrap_or_default(),
5981 ));
5982 }
5983 Ok(self.nat_gateways.get(nat_gateway_id).unwrap())
5984 }
5985
5986 pub fn associate_nat_gateway_address(
5987 &mut self,
5988 nat_gateway_id: &str,
5989 allocation_ids: Vec<String>,
5990 private_ips: Vec<String>,
5991 ) -> Result<&NatGateway, Ec2Error> {
5992 let mut resolved: Vec<(String, Option<String>, Option<String>, String)> = Vec::new();
5995 for (idx, alloc) in allocation_ids.iter().enumerate() {
5996 let public_ip = self
5997 .elastic_ips
5998 .get(alloc)
5999 .map(|e| e.public_ip.clone())
6000 .ok_or_else(|| Ec2Error::AllocationNotFound(alloc.clone()))?;
6001 let private_ip = private_ips.get(idx).cloned();
6002 let assoc_id = self.next_nat_gw_assoc_id();
6003 resolved.push((alloc.clone(), Some(public_ip), private_ip, assoc_id));
6004 }
6005 let nat = self
6006 .nat_gateways
6007 .get_mut(nat_gateway_id)
6008 .ok_or_else(|| Ec2Error::NatGatewayNotFound(nat_gateway_id.to_string()))?;
6009 for (alloc, public_ip, private_ip, assoc_id) in resolved {
6010 nat.secondary_addresses.push(NatGatewayAddressAssociation {
6011 allocation_id: Some(alloc),
6012 association_id: Some(assoc_id),
6013 network_interface_id: Some(format!("eni-{nat_gateway_id}")),
6014 private_ip,
6015 public_ip,
6016 status: "succeeded".to_string(),
6017 is_primary: false,
6018 });
6019 }
6020 Ok(self.nat_gateways.get(nat_gateway_id).unwrap())
6021 }
6022
6023 pub fn disassociate_nat_gateway_address(
6024 &mut self,
6025 nat_gateway_id: &str,
6026 association_ids: Vec<String>,
6027 ) -> Result<&NatGateway, Ec2Error> {
6028 let nat = self
6029 .nat_gateways
6030 .get_mut(nat_gateway_id)
6031 .ok_or_else(|| Ec2Error::NatGatewayNotFound(nat_gateway_id.to_string()))?;
6032 let before = nat.secondary_addresses.len();
6033 nat.secondary_addresses.retain(|a| {
6034 !association_ids
6035 .iter()
6036 .any(|id| a.association_id.as_deref() == Some(id))
6037 });
6038 if nat.secondary_addresses.len() == before && !association_ids.is_empty() {
6039 return Err(Ec2Error::InvalidNatGatewaySecondaryAddressNotFound(
6040 association_ids.first().cloned().unwrap_or_default(),
6041 String::new(),
6042 ));
6043 }
6044 Ok(self.nat_gateways.get(nat_gateway_id).unwrap())
6045 }
6046
6047 fn next_mac_volume_ownership_task_id(&mut self) -> String {
6050 self.counters.mac_volume_ownership_task += 1;
6051 format!(
6052 "macmodification-{:08x}",
6053 self.counters.mac_volume_ownership_task
6054 )
6055 }
6056
6057 fn next_replace_root_volume_task_id(&mut self) -> String {
6058 self.counters.replace_root_volume_task += 1;
6059 format!("replacevol-{:08x}", self.counters.replace_root_volume_task)
6060 }
6061
6062 fn next_snapshot_import_task_id(&mut self) -> String {
6063 self.counters.snapshot_import_task += 1;
6064 format!("import-snap-{:08x}", self.counters.snapshot_import_task)
6065 }
6066
6067 fn next_conversion_task_id(&mut self) -> String {
6068 self.counters.conversion_task += 1;
6069 format!("import-i-{:08x}", self.counters.conversion_task)
6070 }
6071
6072 fn next_export_task_id(&mut self) -> String {
6073 self.counters.export_task += 1;
6074 format!("export-i-{:08x}", self.counters.export_task)
6075 }
6076
6077 fn next_import_task_id(&mut self) -> String {
6078 self.counters.import_task += 1;
6079 format!("import-ami-{:08x}", self.counters.import_task)
6080 }
6081
6082 fn next_trunk_assoc_id(&mut self) -> String {
6083 self.counters.trunk_interface_assoc += 1;
6084 format!("trunk-assoc-{:08x}", self.counters.trunk_interface_assoc)
6085 }
6086
6087 fn next_secondary_network_id(&mut self) -> String {
6088 self.counters.secondary_network += 1;
6089 format!("snet-{:08x}", self.counters.secondary_network)
6090 }
6091
6092 fn next_secondary_subnet_id(&mut self) -> String {
6093 self.counters.secondary_subnet += 1;
6094 format!("ssubnet-{:08x}", self.counters.secondary_subnet)
6095 }
6096
6097 pub fn create_delegate_mac_volume_ownership_task(
6100 &mut self,
6101 instance_id: &str,
6102 source_account: &str,
6103 target_account: &str,
6104 ) -> &MacVolumeOwnershipTask {
6105 let task_id = self.next_mac_volume_ownership_task_id();
6106 let now = chrono::Utc::now()
6107 .format("%Y-%m-%dT%H:%M:%S.000Z")
6108 .to_string();
6109 let task = MacVolumeOwnershipTask {
6110 task_id: task_id.clone(),
6111 mac_volume_ownership_task_state: "pending".to_string(),
6112 volume_id: instance_id.to_string(),
6113 source_volume_owner_account_id: source_account.to_string(),
6114 target_volume_owner_account_id: target_account.to_string(),
6115 creation_time: now,
6116 completion_time: None,
6117 };
6118 self.mac_volume_ownership_tasks
6119 .insert(task_id.clone(), task);
6120 self.mac_volume_ownership_tasks.get(&task_id).unwrap()
6121 }
6122
6123 pub fn create_replace_root_volume_task(
6124 &mut self,
6125 instance_id: &str,
6126 image_id: Option<String>,
6127 snapshot_id: Option<String>,
6128 delete_replaced_root_volume: bool,
6129 tags: Tags,
6130 ) -> Result<&ReplaceRootVolumeTask, Ec2Error> {
6131 if !self.instances.contains_key(instance_id) {
6132 return Err(Ec2Error::InstanceNotFound(instance_id.to_string()));
6133 }
6134 let task_id = self.next_replace_root_volume_task_id();
6135 let now = chrono::Utc::now()
6136 .format("%Y-%m-%dT%H:%M:%S.000Z")
6137 .to_string();
6138 let task = ReplaceRootVolumeTask {
6139 task_id: task_id.clone(),
6140 instance_id: instance_id.to_string(),
6141 task_state: "pending".to_string(),
6142 image_id,
6143 snapshot_id,
6144 delete_replaced_root_volume,
6145 start_time: now,
6146 complete_time: None,
6147 tags,
6148 };
6149 self.replace_root_volume_tasks.insert(task_id.clone(), task);
6150 Ok(self.replace_root_volume_tasks.get(&task_id).unwrap())
6151 }
6152
6153 #[allow(clippy::too_many_arguments)]
6156 pub fn import_snapshot(
6157 &mut self,
6158 description: Option<String>,
6159 format: Option<String>,
6160 url: Option<String>,
6161 s3_bucket: Option<String>,
6162 s3_key: Option<String>,
6163 encrypted: bool,
6164 kms_key_id: Option<String>,
6165 owner_id: &str,
6166 tags: Tags,
6167 ) -> &SnapshotImportTask {
6168 let task_id = self.next_snapshot_import_task_id();
6169 let snap_id = self.next_snapshot_id();
6172 let now = chrono::Utc::now()
6173 .format("%Y-%m-%dT%H:%M:%S.000Z")
6174 .to_string();
6175 let snap = Snapshot {
6176 snapshot_id: snap_id.clone(),
6177 volume_id: String::new(),
6178 volume_size: 8,
6179 state: "completed".to_string(),
6180 description: description.clone().unwrap_or_default(),
6181 start_time: now.clone(),
6182 progress: "100%".to_string(),
6183 owner_id: owner_id.to_string(),
6184 encrypted,
6185 tags: tags.clone(),
6186 lock_state: "none".to_string(),
6187 lock_duration: None,
6188 lock_created_on: None,
6189 lock_expires_on: None,
6190 lock_duration_start_time: None,
6191 cool_off_period: None,
6192 cool_off_period_expires_on: None,
6193 storage_tier: "standard".to_string(),
6194 last_tiering_operation_status: None,
6195 fast_snapshot_restore_states: Vec::new(),
6196 };
6197 self.snapshots.insert(snap_id.clone(), snap);
6198 let task = SnapshotImportTask {
6199 import_task_id: task_id.clone(),
6200 status: "completed".to_string(),
6201 description,
6202 disk_image_size: Some(8.0 * 1024.0 * 1024.0 * 1024.0),
6203 format,
6204 url,
6205 user_bucket_s3_bucket: s3_bucket,
6206 user_bucket_s3_key: s3_key,
6207 owner_id: owner_id.to_string(),
6208 encrypted,
6209 kms_key_id,
6210 snapshot_id: Some(snap_id),
6211 tags,
6212 };
6213 self.snapshot_import_tasks.insert(task_id.clone(), task);
6214 self.import_tasks
6217 .insert(task_id.clone(), ("completed".to_string(), None));
6218 self.snapshot_import_tasks.get(&task_id).unwrap()
6219 }
6220
6221 pub fn cancel_import_task(&mut self, task_id: &str) -> Result<(String, String), Ec2Error> {
6222 let entry = self
6223 .import_tasks
6224 .get_mut(task_id)
6225 .ok_or_else(|| Ec2Error::InvalidImportTaskNotFound(task_id.to_string()))?;
6226 let previous = entry.0.clone();
6227 entry.0 = "cancelled".to_string();
6228 entry.1 = Some(previous.clone());
6229 if let Some(t) = self.snapshot_import_tasks.get_mut(task_id) {
6232 t.status = "cancelled".to_string();
6233 }
6234 Ok((previous, "cancelled".to_string()))
6235 }
6236
6237 pub fn import_instance(
6240 &mut self,
6241 description: Option<String>,
6242 platform: &str,
6243 tags: Tags,
6244 ) -> &ConversionTask {
6245 let task_id = self.next_conversion_task_id();
6246 let now = chrono::Utc::now();
6247 let expires = (now + chrono::Duration::days(7))
6248 .format("%Y-%m-%dT%H:%M:%S.000Z")
6249 .to_string();
6250 let task = ConversionTask {
6251 conversion_task_id: task_id.clone(),
6252 expiration_time: expires,
6253 description,
6254 instance_id: None,
6255 platform: platform.to_string(),
6256 volumes: Vec::new(),
6257 state: "active".to_string(),
6258 status_message: None,
6259 tags,
6260 };
6261 self.conversion_tasks.insert(task_id.clone(), task);
6262 self.conversion_tasks.get(&task_id).unwrap()
6263 }
6264
6265 pub fn cancel_conversion_task(&mut self, task_id: &str) -> Result<(), Ec2Error> {
6266 if let Some(task) = self.conversion_tasks.get_mut(task_id) {
6269 task.state = "cancelled".to_string();
6270 return Ok(());
6271 }
6272 if let Some(task) = self.import_volume_tasks.get_mut(task_id) {
6273 task.status = "cancelled".to_string();
6274 return Ok(());
6275 }
6276 Err(Ec2Error::InvalidConversionTaskNotFound(task_id.to_string()))
6277 }
6278
6279 #[allow(clippy::too_many_arguments)]
6280 pub fn create_instance_export_task(
6281 &mut self,
6282 description: String,
6283 instance_id: String,
6284 target_environment: String,
6285 disk_image_format: String,
6286 container_format: Option<String>,
6287 s3_bucket: String,
6288 s3_prefix: Option<String>,
6289 tags: Tags,
6290 ) -> &ExportTask {
6291 let task_id = self.next_export_task_id();
6292 let s3_key = format!(
6293 "{}{}/{}.{}",
6294 s3_prefix.clone().unwrap_or_default(),
6295 instance_id,
6296 task_id,
6297 match disk_image_format.as_str() {
6298 "vmdk" => "vmdk",
6299 "raw" => "raw",
6300 "vhd" => "vhd",
6301 _ => "vmdk",
6302 }
6303 );
6304 let task = ExportTask {
6305 export_task_id: task_id.clone(),
6306 description,
6307 instance_id,
6308 target_environment,
6309 disk_image_format,
6310 container_format,
6311 s3_bucket,
6312 s3_prefix,
6313 s3_key,
6314 status: "active".to_string(),
6315 status_message: None,
6316 tags,
6317 };
6318 self.export_tasks.insert(task_id.clone(), task);
6319 self.export_tasks.get(&task_id).unwrap()
6320 }
6321
6322 pub fn cancel_export_task(&mut self, task_id: &str) -> Result<(), Ec2Error> {
6323 let task = self
6324 .export_tasks
6325 .get_mut(task_id)
6326 .ok_or_else(|| Ec2Error::InvalidExportTaskNotFound(task_id.to_string()))?;
6327 task.status = "cancelled".to_string();
6328 Ok(())
6329 }
6330
6331 #[allow(clippy::too_many_arguments)]
6334 pub fn associate_trunk_interface(
6335 &mut self,
6336 branch_interface_id: String,
6337 trunk_interface_id: String,
6338 interface_protocol: String,
6339 vlan_id: Option<i32>,
6340 gre_key: Option<i32>,
6341 tags: Tags,
6342 ) -> &TrunkInterfaceAssociation {
6343 let assoc_id = self.next_trunk_assoc_id();
6344 let assoc = TrunkInterfaceAssociation {
6345 association_id: assoc_id.clone(),
6346 branch_interface_id,
6347 trunk_interface_id,
6348 interface_protocol,
6349 vlan_id,
6350 gre_key,
6351 tags,
6352 };
6353 self.trunk_interface_associations
6354 .insert(assoc_id.clone(), assoc);
6355 self.trunk_interface_associations.get(&assoc_id).unwrap()
6356 }
6357
6358 pub fn disassociate_trunk_interface(&mut self, assoc_id: &str) -> Result<(), Ec2Error> {
6359 if self.trunk_interface_associations.remove(assoc_id).is_none() {
6360 return Err(Ec2Error::InvalidTrunkInterfaceAssociationNotFound(
6361 assoc_id.to_string(),
6362 ));
6363 }
6364 Ok(())
6365 }
6366
6367 pub fn create_secondary_network(
6370 &mut self,
6371 vpc_id: String,
6372 primary_cidr_block: String,
6373 network_border_group: Option<String>,
6374 tags: Tags,
6375 ) -> &SecondaryNetwork {
6376 let id = self.next_secondary_network_id();
6377 let network = SecondaryNetwork {
6378 network_id: id.clone(),
6379 vpc_id,
6380 primary_cidr_block,
6381 secondary_cidr_blocks: Vec::new(),
6382 state: "available".to_string(),
6383 network_border_group,
6384 tags,
6385 };
6386 self.secondary_networks.insert(id.clone(), network);
6387 self.secondary_networks.get(&id).unwrap()
6388 }
6389
6390 pub fn delete_secondary_network(&mut self, id: &str) -> Result<SecondaryNetwork, Ec2Error> {
6391 if !self.secondary_networks.contains_key(id) {
6392 return Err(Ec2Error::InvalidSecondaryNetworkNotFound(id.to_string()));
6393 }
6394 let has_subnets = self
6395 .secondary_subnets
6396 .values()
6397 .any(|s| s.secondary_network_id == id);
6398 if has_subnets {
6399 return Err(Ec2Error::SecondaryNetworkHasSubnets(id.to_string()));
6400 }
6401 let mut removed = self.secondary_networks.remove(id).unwrap();
6402 removed.state = "deleted".to_string();
6403 Ok(removed)
6404 }
6405
6406 pub fn create_secondary_subnet(
6407 &mut self,
6408 secondary_network_id: String,
6409 cidr_block: String,
6410 availability_zone: String,
6411 tags: Tags,
6412 ) -> Result<&SecondarySubnet, Ec2Error> {
6413 let net = self
6414 .secondary_networks
6415 .get(&secondary_network_id)
6416 .ok_or_else(|| {
6417 Ec2Error::InvalidSecondaryNetworkNotFound(secondary_network_id.clone())
6418 })?;
6419 let vpc_id = net.vpc_id.clone();
6420 let id = self.next_secondary_subnet_id();
6421 let subnet = SecondarySubnet {
6422 subnet_id: id.clone(),
6423 vpc_id,
6424 secondary_network_id,
6425 cidr_block,
6426 availability_zone,
6427 state: "available".to_string(),
6428 tags,
6429 };
6430 self.secondary_subnets.insert(id.clone(), subnet);
6431 Ok(self.secondary_subnets.get(&id).unwrap())
6432 }
6433
6434 pub fn delete_secondary_subnet(&mut self, id: &str) -> Result<SecondarySubnet, Ec2Error> {
6435 let mut removed = self
6436 .secondary_subnets
6437 .remove(id)
6438 .ok_or_else(|| Ec2Error::InvalidSecondarySubnetNotFound(id.to_string()))?;
6439 removed.state = "deleted".to_string();
6440 Ok(removed)
6441 }
6442
6443 fn now_iso(&self) -> String {
6446 chrono::Utc::now()
6447 .format("%Y-%m-%dT%H:%M:%S.000Z")
6448 .to_string()
6449 }
6450
6451 fn next_reserved_instances_exchange_id(&mut self) -> String {
6452 self.counters.reserved_instances_exchange += 1;
6453 format!("riex-{:08x}", self.counters.reserved_instances_exchange)
6454 }
6455
6456 fn next_reserved_instances_listing_id(&mut self) -> String {
6457 self.counters.reserved_instances_listing += 1;
6458 format!("ril-{:08x}", self.counters.reserved_instances_listing)
6459 }
6460
6461 fn next_reserved_instances_purchase_id(&mut self) -> String {
6462 self.counters.reserved_instances_purchase += 1;
6463 format!("rip-{:08x}", self.counters.reserved_instances_purchase)
6464 }
6465
6466 fn next_reserved_instances_id(&mut self) -> String {
6467 self.counters.reserved_instances += 1;
6468 format!("ri-{:08x}", self.counters.reserved_instances)
6469 }
6470
6471 fn next_reserved_instances_modification_id(&mut self) -> String {
6472 self.counters.reserved_instances_modification += 1;
6473 format!("rim-{:08x}", self.counters.reserved_instances_modification)
6474 }
6475
6476 fn next_fpga_image_id(&mut self) -> String {
6477 self.counters.fpga_image += 1;
6478 format!("afi-{:08x}", self.counters.fpga_image)
6479 }
6480
6481 fn next_image_usage_report_id(&mut self) -> String {
6482 self.counters.image_usage_report += 1;
6483 format!("iur-{:08x}", self.counters.image_usage_report)
6484 }
6485
6486 fn next_import_image_task_id(&mut self) -> String {
6487 self.counters.import_image_task += 1;
6488 format!("import-ami-{:08x}", self.counters.import_image_task)
6489 }
6490
6491 fn next_instance_event_window_id(&mut self) -> String {
6492 self.counters.instance_event_window += 1;
6493 format!("iew-{:08x}", self.counters.instance_event_window)
6494 }
6495
6496 fn next_instance_event_id(&mut self) -> String {
6497 self.counters.instance_event += 1;
6498 format!("event-{:08x}", self.counters.instance_event)
6499 }
6500
6501 fn next_host_reservation_id(&mut self) -> String {
6502 self.counters.host_reservation += 1;
6503 format!("hr-{:08x}", self.counters.host_reservation)
6504 }
6505
6506 fn next_scheduled_instance_id(&mut self) -> String {
6507 self.counters.scheduled_instance += 1;
6508 format!("sci-{:08x}", self.counters.scheduled_instance)
6509 }
6510
6511 pub fn next_instance_id_pub(&mut self) -> String {
6512 self.counters.instance += 1;
6513 format!("i-{:08x}", self.counters.instance)
6514 }
6515
6516 pub fn accept_reserved_instances_exchange_quote(
6519 &mut self,
6520 target_reserved_instances_ids: Vec<String>,
6521 source_reserved_instances_ids: Vec<String>,
6522 ) -> String {
6523 let id = self.next_reserved_instances_exchange_id();
6524 let now = self.now_iso();
6525 self.reserved_instances_exchanges.insert(
6526 id.clone(),
6527 ReservedInstancesExchange {
6528 exchange_id: id.clone(),
6529 target_reserved_instances_ids,
6530 source_reserved_instances_ids,
6531 status: "completed".to_string(),
6532 status_message: None,
6533 time: now,
6534 },
6535 );
6536 id
6537 }
6538
6539 pub fn cancel_reserved_instances_listing(
6540 &mut self,
6541 listing_id: &str,
6542 ) -> Result<&ReservedInstancesListing, Ec2Error> {
6543 let listing = self
6544 .reserved_instances_listings
6545 .get_mut(listing_id)
6546 .ok_or_else(|| {
6547 Ec2Error::InvalidReservedInstancesListingNotFound(listing_id.to_string())
6548 })?;
6549 listing.status = "cancelled".to_string();
6550 listing.update_date = chrono::Utc::now()
6551 .format("%Y-%m-%dT%H:%M:%S.000Z")
6552 .to_string();
6553 Ok(self.reserved_instances_listings.get(listing_id).unwrap())
6554 }
6555
6556 pub fn create_reserved_instances_listing(
6557 &mut self,
6558 reserved_instances_id: &str,
6559 instance_count: i32,
6560 price_schedules: Vec<PriceSchedule>,
6561 client_token: Option<String>,
6562 tags: Tags,
6563 ) -> &ReservedInstancesListing {
6564 let id = self.next_reserved_instances_listing_id();
6565 let now = self.now_iso();
6566 let listing = ReservedInstancesListing {
6567 listing_id: id.clone(),
6568 reserved_instances_id: reserved_instances_id.to_string(),
6569 instance_count,
6570 price_schedules,
6571 status: "active".to_string(),
6572 status_message: None,
6573 create_date: now.clone(),
6574 update_date: now,
6575 client_token,
6576 tags,
6577 };
6578 self.reserved_instances_listings.insert(id.clone(), listing);
6579 self.reserved_instances_listings.get(&id).unwrap()
6580 }
6581
6582 pub fn delete_queued_reserved_instances(
6583 &mut self,
6584 ids: &[String],
6585 ) -> (Vec<String>, Vec<String>) {
6586 let mut succ = Vec::new();
6587 let mut fail = Vec::new();
6588 for id in ids {
6589 if self
6590 .queued_reserved_instances_purchases
6591 .remove(id)
6592 .is_some()
6593 {
6594 succ.push(id.clone());
6595 } else {
6596 fail.push(id.clone());
6597 }
6598 }
6599 (succ, fail)
6600 }
6601
6602 pub fn modify_fleet(
6603 &mut self,
6604 fleet_id: &str,
6605 total_target_capacity: Option<i32>,
6606 on_demand_target_capacity: Option<i32>,
6607 spot_target_capacity: Option<i32>,
6608 context: Option<String>,
6609 ) -> Result<(), Ec2Error> {
6610 let fleet = self.ec2_fleets.get_mut(fleet_id).ok_or_else(|| {
6611 Ec2Error::InvalidParameterValue(format!("InvalidFleetId: {fleet_id}"))
6612 })?;
6613 if let Some(v) = total_target_capacity {
6614 fleet.total_target_capacity = Some(v);
6615 }
6616 if let Some(v) = on_demand_target_capacity {
6617 fleet.on_demand_target_capacity = Some(v);
6618 }
6619 if let Some(v) = spot_target_capacity {
6620 fleet.spot_target_capacity = Some(v);
6621 }
6622 if let Some(v) = context {
6623 fleet.context = Some(v);
6624 }
6625 Ok(())
6626 }
6627
6628 pub fn modify_reserved_instances(
6629 &mut self,
6630 reserved_instances_ids: Vec<String>,
6631 target_configurations: Vec<ReservedInstancesConfiguration>,
6632 client_token: Option<String>,
6633 ) -> String {
6634 let id = self.next_reserved_instances_modification_id();
6635 let now = self.now_iso();
6636 let modification = ReservedInstancesModification {
6637 modification_id: id.clone(),
6638 reserved_instances_ids,
6639 target_configurations,
6640 status: "fulfilled".to_string(),
6641 status_message: None,
6642 create_date: now.clone(),
6643 update_date: now.clone(),
6644 effective_date: now,
6645 client_token,
6646 };
6647 self.reserved_instances_modifications
6648 .insert(id.clone(), modification);
6649 id
6650 }
6651
6652 pub fn purchase_reserved_instances_offering(
6653 &mut self,
6654 offering_id: &str,
6655 instance_count: i32,
6656 limit_price: Option<String>,
6657 tags: Tags,
6658 ) -> String {
6659 let now = self.now_iso();
6660 let ri_id = self.next_reserved_instances_id();
6661 let purchase_id = self.next_reserved_instances_purchase_id();
6662 let ri = ReservedInstances {
6663 reserved_instances_id: ri_id.clone(),
6664 instance_type: "t3.micro".to_string(),
6665 instance_count,
6666 product_description: "Linux/UNIX".to_string(),
6667 scope: "Region".to_string(),
6668 currency_code: "USD".to_string(),
6669 duration: 31536000,
6670 fixed_price: 0.0,
6671 usage_price: 0.0,
6672 offering_class: "standard".to_string(),
6673 offering_type: "No Upfront".to_string(),
6674 instance_tenancy: "default".to_string(),
6675 start: now.clone(),
6676 end: now.clone(),
6677 state: "active".to_string(),
6678 tags: tags.clone(),
6679 };
6680 self.reserved_instances.insert(ri_id.clone(), ri);
6681 let purchase = ReservedInstancesPurchase {
6682 purchase_id: purchase_id.clone(),
6683 reserved_instances_offering_id: offering_id.to_string(),
6684 instance_count,
6685 limit_price,
6686 purchase_time: now,
6687 tags,
6688 queued: false,
6689 reserved_instances_id: Some(ri_id.clone()),
6690 };
6691 self.reserved_instances_purchases
6692 .insert(purchase_id, purchase);
6693 ri_id
6694 }
6695
6696 pub fn cancel_image_launch_permission(&mut self, image_id: &str) -> Result<(), Ec2Error> {
6699 let img = self
6700 .images
6701 .get_mut(image_id)
6702 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
6703 img.launch_permissions.clear();
6704 Ok(())
6705 }
6706
6707 pub fn create_fpga_image(
6708 &mut self,
6709 name: Option<String>,
6710 description: Option<String>,
6711 owner_id: &str,
6712 tags: Tags,
6713 ) -> &FpgaImage {
6714 let id = self.next_fpga_image_id();
6715 let global_id = format!("agfi-{:08x}", self.counters.fpga_image);
6716 let now = self.now_iso();
6717 let img = FpgaImage {
6718 fpga_image_id: id.clone(),
6719 fpga_image_global_id: global_id,
6720 name: name.unwrap_or_else(|| format!("fpga-{}", self.counters.fpga_image)),
6721 description,
6722 shell_version: Some("0x04261818".to_string()),
6723 pci_id_vendor: None,
6724 pci_id_device: None,
6725 state: "available".to_string(),
6726 create_time: now.clone(),
6727 update_time: now,
6728 owner_id: owner_id.to_string(),
6729 owner_alias: None,
6730 product_codes: Vec::new(),
6731 tags,
6732 public: false,
6733 data_retention_support: false,
6734 instance_types: Vec::new(),
6735 load_permissions: Vec::new(),
6736 };
6737 self.fpga_images.insert(id.clone(), img);
6738 self.fpga_images.get(&id).unwrap()
6739 }
6740
6741 pub fn copy_fpga_image(
6742 &mut self,
6743 source_fpga_image_id: &str,
6744 name: Option<String>,
6745 description: Option<String>,
6746 owner_id: &str,
6747 ) -> Result<String, Ec2Error> {
6748 let src = self
6749 .fpga_images
6750 .get(source_fpga_image_id)
6751 .ok_or_else(|| Ec2Error::InvalidFpgaImageNotFound(source_fpga_image_id.to_string()))?
6752 .clone();
6753 let id = self.next_fpga_image_id();
6754 let global_id = format!("agfi-{:08x}", self.counters.fpga_image);
6755 let now = self.now_iso();
6756 let img = FpgaImage {
6757 fpga_image_id: id.clone(),
6758 fpga_image_global_id: global_id,
6759 name: name.unwrap_or(src.name),
6760 description: description.or(src.description),
6761 shell_version: src.shell_version,
6762 pci_id_vendor: src.pci_id_vendor,
6763 pci_id_device: src.pci_id_device,
6764 state: "available".to_string(),
6765 create_time: now.clone(),
6766 update_time: now,
6767 owner_id: owner_id.to_string(),
6768 owner_alias: None,
6769 product_codes: Vec::new(),
6770 tags: Tags::new(),
6771 public: false,
6772 data_retention_support: src.data_retention_support,
6773 instance_types: src.instance_types,
6774 load_permissions: Vec::new(),
6775 };
6776 self.fpga_images.insert(id.clone(), img);
6777 Ok(id)
6778 }
6779
6780 pub fn delete_fpga_image(&mut self, id: &str) -> Result<(), Ec2Error> {
6781 self.fpga_images
6782 .remove(id)
6783 .ok_or_else(|| Ec2Error::InvalidFpgaImageNotFound(id.to_string()))?;
6784 Ok(())
6785 }
6786
6787 pub fn modify_fpga_image_attribute(
6788 &mut self,
6789 id: &str,
6790 name: Option<String>,
6791 description: Option<String>,
6792 add_load: Vec<(String, String)>,
6793 remove_load: Vec<(String, String)>,
6794 ) -> Result<&FpgaImage, Ec2Error> {
6795 let img = self
6796 .fpga_images
6797 .get_mut(id)
6798 .ok_or_else(|| Ec2Error::InvalidFpgaImageNotFound(id.to_string()))?;
6799 if let Some(n) = name {
6800 img.name = n;
6801 }
6802 if let Some(d) = description {
6803 img.description = Some(d);
6804 }
6805 for r in &remove_load {
6806 img.load_permissions.retain(|p| p != r);
6807 }
6808 for a in add_load {
6809 if !img.load_permissions.contains(&a) {
6810 img.load_permissions.push(a);
6811 }
6812 }
6813 Ok(self.fpga_images.get(id).unwrap())
6814 }
6815
6816 pub fn reset_fpga_image_attribute(&mut self, id: &str, attr: &str) -> Result<(), Ec2Error> {
6817 let img = self
6818 .fpga_images
6819 .get_mut(id)
6820 .ok_or_else(|| Ec2Error::InvalidFpgaImageNotFound(id.to_string()))?;
6821 match attr {
6822 "loadPermission" => img.load_permissions.clear(),
6823 "description" => img.description = None,
6824 _ => {}
6825 }
6826 Ok(())
6827 }
6828
6829 pub fn create_image_usage_report(
6830 &mut self,
6831 image_id: &str,
6832 account_filters: Vec<String>,
6833 resource_types: Vec<String>,
6834 tags: Tags,
6835 ) -> Result<String, Ec2Error> {
6836 if !self.images.contains_key(image_id) {
6837 return Err(Ec2Error::AmiNotFound(image_id.to_string()));
6838 }
6839 let id = self.next_image_usage_report_id();
6840 let now = self.now_iso();
6841 self.image_usage_reports.insert(
6842 id.clone(),
6843 ImageUsageReport {
6844 report_id: id.clone(),
6845 image_id: image_id.to_string(),
6846 account_filters,
6847 resource_types,
6848 status: "completed".to_string(),
6849 creation_time: now.clone(),
6850 completion_time: Some(now),
6851 tags,
6852 },
6853 );
6854 Ok(id)
6855 }
6856
6857 pub fn delete_image_usage_report(&mut self, id: &str) -> Result<(), Ec2Error> {
6858 self.image_usage_reports
6859 .remove(id)
6860 .ok_or_else(|| Ec2Error::InvalidImageUsageReportNotFound(id.to_string()))?;
6861 Ok(())
6862 }
6863
6864 pub fn create_restore_image_task(
6865 &mut self,
6866 bucket: &str,
6867 object_key: &str,
6868 name: Option<String>,
6869 owner_id: &str,
6870 ) -> String {
6871 let image_id = self.next_ami_id();
6872 let now = self.now_iso();
6873 let img_name = name.clone().unwrap_or_else(|| image_id.clone());
6874 self.images.insert(
6876 image_id.clone(),
6877 Image {
6878 image_id: image_id.clone(),
6879 name: img_name.clone(),
6880 description: format!("Restored from s3://{bucket}/{object_key}"),
6881 state: "available".to_string(),
6882 owner_id: owner_id.to_string(),
6883 architecture: "x86_64".to_string(),
6884 image_type: "machine".to_string(),
6885 platform: None,
6886 virtualization_type: "hvm".to_string(),
6887 root_device_type: "ebs".to_string(),
6888 root_device_name: "/dev/xvda".to_string(),
6889 public: false,
6890 tags: Tags::new(),
6891 source_instance_id: None,
6892 source_instance_type: String::new(),
6893 launch_permissions: Vec::new(),
6894 recycle_bin_state: None,
6895 deprecation_time: None,
6896 recycle_bin_enter_time: None,
6897 product_codes: Vec::new(),
6898 fast_launch_state: None,
6899 deregistration_protection: None,
6900 kernel_id: None,
6901 ramdisk_id: None,
6902 ena_support: None,
6903 sriov_net_support: None,
6904 tpm_support: None,
6905 boot_mode: None,
6906 imds_support: None,
6907 image_location: Some(format!("s3://{bucket}/{object_key}")),
6908 source_image_id: None,
6909 source_region: None,
6910 },
6911 );
6912 self.restore_image_tasks.insert(
6913 image_id.clone(),
6914 RestoreImageTask {
6915 image_id: image_id.clone(),
6916 name: img_name,
6917 s3_object_url: format!("s3://{bucket}/{object_key}"),
6918 status: "completed".to_string(),
6919 status_message: None,
6920 creation_time: now,
6921 },
6922 );
6923 image_id
6924 }
6925
6926 pub fn create_store_image_task(
6927 &mut self,
6928 ami_id: &str,
6929 bucket: &str,
6930 ) -> Result<String, Ec2Error> {
6931 if !self.images.contains_key(ami_id) {
6932 return Err(Ec2Error::AmiNotFound(ami_id.to_string()));
6933 }
6934 let object_key = format!("{ami_id}.bin");
6935 let now = self.now_iso();
6936 self.store_image_tasks.insert(
6937 ami_id.to_string(),
6938 StoreImageTask {
6939 image_id: ami_id.to_string(),
6940 ami_id: ami_id.to_string(),
6941 bucket: bucket.to_string(),
6942 s3_object_key: object_key.clone(),
6943 store_task_state: "completed".to_string(),
6944 store_task_failure_reason: None,
6945 progress_percentage: 100,
6946 task_start_time: now,
6947 },
6948 );
6949 Ok(object_key)
6950 }
6951
6952 pub fn import_image(
6953 &mut self,
6954 description: Option<String>,
6955 license_type: Option<String>,
6956 platform: Option<String>,
6957 architecture: Option<String>,
6958 encrypted: bool,
6959 tags: Tags,
6960 ) -> &ImportImageTask {
6961 let id = self.next_import_image_task_id();
6962 let task = ImportImageTask {
6963 import_task_id: id.clone(),
6964 architecture,
6965 description,
6966 encrypted,
6967 hypervisor: Some("xen".to_string()),
6968 image_id: None,
6969 license_type,
6970 platform,
6971 progress: Some("0".to_string()),
6972 snapshot_details: Vec::new(),
6973 status: "active".to_string(),
6974 status_message: Some("pending".to_string()),
6975 tags,
6976 usage_operation: None,
6977 boot_mode: Some("uefi".to_string()),
6978 };
6979 self.import_image_tasks.insert(id.clone(), task);
6980 self.import_image_tasks.get(&id).unwrap()
6981 }
6982
6983 pub fn restore_image_from_recycle_bin(&mut self, image_id: &str) -> Result<(), Ec2Error> {
6984 let img = self
6985 .images
6986 .get_mut(image_id)
6987 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
6988 if img.recycle_bin_state.as_deref() != Some("recycled") {
6989 return Err(Ec2Error::ImageNotInRecycleBin(image_id.to_string()));
6990 }
6991 img.recycle_bin_state = None;
6992 img.state = "available".to_string();
6993 self.deregistered_images.remove(image_id);
6994 Ok(())
6995 }
6996
6997 pub fn replace_allowed_image_criteria(&mut self, criteria: Vec<AllowedImageCriterion>) {
6998 self.allowed_image_criteria = criteria;
6999 }
7000
7001 pub fn modify_default_credit_specification(
7004 &mut self,
7005 instance_family: &str,
7006 cpu_credits: &str,
7007 ) -> InstanceFamilyCreditPair {
7008 self.default_credit_specifications
7009 .insert(instance_family.to_string(), cpu_credits.to_string());
7010 InstanceFamilyCreditPair {
7011 instance_family: instance_family.to_string(),
7012 cpu_credits: cpu_credits.to_string(),
7013 }
7014 }
7015
7016 pub fn modify_instance_connect_endpoint(
7017 &mut self,
7018 endpoint_id: &str,
7019 preserve_client_ip: Option<bool>,
7020 security_group_ids: Option<Vec<String>>,
7021 ) -> Result<(), Ec2Error> {
7022 let ep = self
7023 .instance_connect_endpoints
7024 .get_mut(endpoint_id)
7025 .ok_or_else(|| Ec2Error::InstanceConnectEndpointNotFound(endpoint_id.to_string()))?;
7026 if let Some(v) = preserve_client_ip {
7027 ep.preserve_client_ip = v;
7028 }
7029 if let Some(v) = security_group_ids {
7030 ep.security_group_ids = v;
7031 }
7032 Ok(())
7033 }
7034
7035 pub fn modify_instance_cpu_options(
7036 &mut self,
7037 instance_id: &str,
7038 core_count: Option<i32>,
7039 threads_per_core: Option<i32>,
7040 ) -> Result<&Instance, Ec2Error> {
7041 let inst = self
7042 .instances
7043 .get_mut(instance_id)
7044 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
7045 let mut opts = inst.cpu_options.clone().unwrap_or_default();
7046 if let Some(v) = core_count {
7047 opts.core_count = Some(v);
7048 }
7049 if let Some(v) = threads_per_core {
7050 opts.threads_per_core = Some(v);
7051 }
7052 inst.cpu_options = Some(opts);
7053 Ok(self.instances.get(instance_id).unwrap())
7054 }
7055
7056 pub fn modify_instance_credit_specification(
7057 &mut self,
7058 specs: &[(String, String)],
7059 ) -> (Vec<String>, Vec<String>) {
7060 let mut succ = Vec::new();
7061 let mut fail = Vec::new();
7062 for (instance_id, cpu_credits) in specs {
7063 if let Some(inst) = self.instances.get_mut(instance_id) {
7064 inst.credit_specification = Some(cpu_credits.clone());
7065 succ.push(instance_id.clone());
7066 } else {
7067 fail.push(instance_id.clone());
7068 }
7069 }
7070 (succ, fail)
7071 }
7072
7073 pub fn modify_instance_maintenance_options(
7074 &mut self,
7075 instance_id: &str,
7076 auto_recovery: Option<String>,
7077 reboot_migration: Option<String>,
7078 ) -> Result<&Instance, Ec2Error> {
7079 let inst = self
7080 .instances
7081 .get_mut(instance_id)
7082 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
7083 let mut opts = inst.maintenance_options.clone().unwrap_or_default();
7084 if let Some(v) = auto_recovery {
7085 opts.auto_recovery = Some(v);
7086 }
7087 if let Some(v) = reboot_migration {
7088 opts.reboot_migration = Some(v);
7089 }
7090 inst.maintenance_options = Some(opts);
7091 Ok(self.instances.get(instance_id).unwrap())
7092 }
7093
7094 pub fn modify_instance_metadata_defaults(
7095 &mut self,
7096 http_tokens: Option<String>,
7097 http_put_response_hop_limit: Option<i32>,
7098 http_endpoint: Option<String>,
7099 instance_metadata_tags: Option<String>,
7100 ) {
7101 let mut defaults = self.instance_metadata_defaults.clone().unwrap_or_default();
7102 if let Some(v) = http_tokens {
7103 defaults.http_tokens = Some(v);
7104 }
7105 if let Some(v) = http_put_response_hop_limit {
7106 defaults.http_put_response_hop_limit = Some(v);
7107 }
7108 if let Some(v) = http_endpoint {
7109 defaults.http_endpoint = Some(v);
7110 }
7111 if let Some(v) = instance_metadata_tags {
7112 defaults.instance_metadata_tags = Some(v);
7113 }
7114 self.instance_metadata_defaults = Some(defaults);
7115 }
7116
7117 pub fn modify_instance_network_performance_options(
7118 &mut self,
7119 instance_id: &str,
7120 bandwidth_weighting: &str,
7121 ) -> Result<(), Ec2Error> {
7122 let inst = self
7123 .instances
7124 .get_mut(instance_id)
7125 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
7126 inst.network_bandwidth_weighting = Some(bandwidth_weighting.to_string());
7127 Ok(())
7128 }
7129
7130 pub fn reset_instance_attribute(&mut self, instance_id: &str) -> Result<(), Ec2Error> {
7131 if !self.instances.contains_key(instance_id) {
7132 return Err(Ec2Error::InstanceNotFound(instance_id.to_string()));
7133 }
7134 Ok(())
7135 }
7136
7137 pub fn create_instance_event_window(
7140 &mut self,
7141 name: Option<String>,
7142 time_ranges: Vec<InstanceEventWindowTimeRange>,
7143 cron_expression: Option<String>,
7144 tags: Tags,
7145 ) -> &InstanceEventWindow {
7146 let id = self.next_instance_event_window_id();
7147 let win = InstanceEventWindow {
7148 instance_event_window_id: id.clone(),
7149 name: name
7150 .unwrap_or_else(|| format!("event-window-{}", self.counters.instance_event_window)),
7151 time_ranges,
7152 cron_expression,
7153 association_target: None,
7154 state: "active".to_string(),
7155 tags,
7156 };
7157 self.instance_event_windows.insert(id.clone(), win);
7158 self.instance_event_windows.get(&id).unwrap()
7159 }
7160
7161 pub fn modify_instance_event_window(
7162 &mut self,
7163 id: &str,
7164 name: Option<String>,
7165 time_ranges: Option<Vec<InstanceEventWindowTimeRange>>,
7166 cron_expression: Option<String>,
7167 ) -> Result<&InstanceEventWindow, Ec2Error> {
7168 let win = self
7169 .instance_event_windows
7170 .get_mut(id)
7171 .ok_or_else(|| Ec2Error::InvalidInstanceEventWindowNotFound(id.to_string()))?;
7172 if let Some(n) = name {
7173 win.name = n;
7174 }
7175 if let Some(t) = time_ranges {
7176 win.time_ranges = t;
7177 }
7178 if let Some(c) = cron_expression {
7179 win.cron_expression = Some(c);
7180 }
7181 Ok(self.instance_event_windows.get(id).unwrap())
7182 }
7183
7184 pub fn delete_instance_event_window(&mut self, id: &str) -> Result<(), Ec2Error> {
7185 self.instance_event_windows
7186 .remove(id)
7187 .ok_or_else(|| Ec2Error::InvalidInstanceEventWindowNotFound(id.to_string()))?;
7188 Ok(())
7189 }
7190
7191 pub fn associate_instance_event_window(
7192 &mut self,
7193 id: &str,
7194 instance_ids: Vec<String>,
7195 dedicated_host_ids: Vec<String>,
7196 tags: Vec<(String, String)>,
7197 ) -> Result<&InstanceEventWindow, Ec2Error> {
7198 let win = self
7199 .instance_event_windows
7200 .get_mut(id)
7201 .ok_or_else(|| Ec2Error::InvalidInstanceEventWindowNotFound(id.to_string()))?;
7202 let mut assoc = win.association_target.clone().unwrap_or_default();
7203 assoc.instance_ids.extend(instance_ids);
7204 assoc.dedicated_host_ids.extend(dedicated_host_ids);
7205 assoc.tags.extend(tags);
7206 win.association_target = Some(assoc);
7207 Ok(self.instance_event_windows.get(id).unwrap())
7208 }
7209
7210 pub fn disassociate_instance_event_window(
7211 &mut self,
7212 id: &str,
7213 instance_ids: Vec<String>,
7214 dedicated_host_ids: Vec<String>,
7215 ) -> Result<&InstanceEventWindow, Ec2Error> {
7216 let win = self
7217 .instance_event_windows
7218 .get_mut(id)
7219 .ok_or_else(|| Ec2Error::InvalidInstanceEventWindowNotFound(id.to_string()))?;
7220 if let Some(assoc) = win.association_target.as_mut() {
7221 assoc.instance_ids.retain(|i| !instance_ids.contains(i));
7222 assoc
7223 .dedicated_host_ids
7224 .retain(|h| !dedicated_host_ids.contains(h));
7225 }
7226 Ok(self.instance_event_windows.get(id).unwrap())
7227 }
7228
7229 pub fn modify_instance_event_start_time(
7230 &mut self,
7231 instance_id: &str,
7232 instance_event_id: &str,
7233 not_before: &str,
7234 ) -> Result<&InstanceEvent, Ec2Error> {
7235 if !self.instance_events.contains_key(instance_event_id) {
7238 if !self.instances.contains_key(instance_id) {
7239 return Err(Ec2Error::InstanceNotFound(instance_id.to_string()));
7240 }
7241 let event = InstanceEvent {
7242 event_id: instance_event_id.to_string(),
7243 instance_id: instance_id.to_string(),
7244 code: "instance-retirement".to_string(),
7245 description: "Scheduled instance retirement".to_string(),
7246 not_before: not_before.to_string(),
7247 not_after: not_before.to_string(),
7248 not_before_deadline: None,
7249 };
7250 self.instance_events
7251 .insert(instance_event_id.to_string(), event);
7252 }
7253 let event = self.instance_events.get_mut(instance_event_id).unwrap();
7254 event.not_before = not_before.to_string();
7255 Ok(self.instance_events.get(instance_event_id).unwrap())
7256 }
7257
7258 pub fn register_instance_event_notification_attributes(
7259 &mut self,
7260 include_all_tags_of_instance: Option<bool>,
7261 instance_tag_keys: Vec<String>,
7262 ) -> &InstanceTagNotificationAttributes {
7263 let mut attrs = self
7264 .instance_event_notification_attributes
7265 .clone()
7266 .unwrap_or_default();
7267 if let Some(v) = include_all_tags_of_instance {
7268 attrs.include_all_tags_of_instance = v;
7269 }
7270 for k in instance_tag_keys {
7271 if !attrs.instance_tag_keys.contains(&k) {
7272 attrs.instance_tag_keys.push(k);
7273 }
7274 }
7275 self.instance_event_notification_attributes = Some(attrs);
7276 self.instance_event_notification_attributes
7277 .as_ref()
7278 .unwrap()
7279 }
7280
7281 pub fn deregister_instance_event_notification_attributes(
7282 &mut self,
7283 include_all_tags_of_instance: Option<bool>,
7284 instance_tag_keys: &[String],
7285 ) -> InstanceTagNotificationAttributes {
7286 let mut attrs = self
7287 .instance_event_notification_attributes
7288 .clone()
7289 .unwrap_or_default();
7290 if include_all_tags_of_instance == Some(true) {
7291 attrs.include_all_tags_of_instance = false;
7292 }
7293 attrs
7294 .instance_tag_keys
7295 .retain(|k| !instance_tag_keys.contains(k));
7296 self.instance_event_notification_attributes = Some(attrs.clone());
7297 attrs
7298 }
7299
7300 pub fn confirm_product_instance(
7303 &self,
7304 instance_id: &str,
7305 product_code: &str,
7306 ) -> Result<(String, bool), Ec2Error> {
7307 let inst = self
7308 .instances
7309 .get(instance_id)
7310 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
7311 let matched = inst.product_codes.iter().any(|(c, _)| c == product_code);
7312 Ok((
7313 inst.owner_id.clone(),
7314 matched || inst.product_codes.is_empty(),
7315 ))
7316 }
7317
7318 pub fn delete_launch_template_versions(
7319 &mut self,
7320 lt_id: &str,
7321 versions: &[i64],
7322 ) -> Result<(Vec<i64>, Vec<i64>), Ec2Error> {
7323 let lt_id_resolved = if self.launch_templates.contains_key(lt_id) {
7324 lt_id.to_string()
7325 } else {
7326 self.launch_templates
7327 .values()
7328 .find(|lt| lt.launch_template_name == lt_id)
7329 .map(|lt| lt.launch_template_id.clone())
7330 .ok_or_else(|| Ec2Error::LaunchTemplateNotFound(lt_id.to_string()))?
7331 };
7332 let entries = self
7333 .launch_template_versions
7334 .get_mut(<_id_resolved)
7335 .ok_or_else(|| Ec2Error::LaunchTemplateNotFound(lt_id.to_string()))?;
7336 let mut succ = Vec::new();
7337 let mut fail = Vec::new();
7338 for v in versions {
7339 let before = entries.len();
7340 entries.retain(|e| e.version_number != *v);
7341 if entries.len() < before {
7342 succ.push(*v);
7343 } else {
7344 fail.push(*v);
7345 }
7346 }
7347 Ok((succ, fail))
7348 }
7349
7350 pub fn modify_availability_zone_group(&mut self, group_name: &str, opt_in_status: &str) {
7351 self.az_group_opt_in
7352 .insert(group_name.to_string(), opt_in_status.to_string());
7353 }
7354
7355 pub fn purchase_host_reservation(
7356 &mut self,
7357 host_id_set: Vec<String>,
7358 offering_id: &str,
7359 currency_code: Option<String>,
7360 instance_family: &str,
7361 tags: Tags,
7362 ) -> &HostReservation {
7363 let id = self.next_host_reservation_id();
7364 let now = self.now_iso();
7365 let res = HostReservation {
7366 host_reservation_id: id.clone(),
7367 host_id_set,
7368 currency_code: currency_code.unwrap_or_else(|| "USD".to_string()),
7369 duration: 31536000,
7370 end: None,
7371 hourly_price: "0.0".to_string(),
7372 instance_family: instance_family.to_string(),
7373 offering_id: offering_id.to_string(),
7374 payment_option: "NoUpfront".to_string(),
7375 start: now,
7376 state: "active".to_string(),
7377 upfront_price: "0.0".to_string(),
7378 tags,
7379 };
7380 self.host_reservations.insert(id.clone(), res);
7381 self.host_reservations.get(&id).unwrap()
7382 }
7383
7384 pub fn purchase_scheduled_instances(
7385 &mut self,
7386 requests: Vec<ScheduledInstancePurchaseRequest>,
7387 ) -> Vec<ScheduledInstance> {
7388 let mut out = Vec::new();
7389 for req in requests {
7390 let id = self.next_scheduled_instance_id();
7391 let now = self.now_iso();
7392 let si = ScheduledInstance {
7393 scheduled_instance_id: id.clone(),
7394 instance_type: req.instance_type,
7395 platform: req.platform.unwrap_or_else(|| "Linux/UNIX".to_string()),
7396 network_platform: req
7397 .network_platform
7398 .unwrap_or_else(|| "EC2-VPC".to_string()),
7399 availability_zone: req.availability_zone,
7400 instance_count: req.instance_count,
7401 hourly_price: req.hourly_price.unwrap_or_else(|| "0.0".to_string()),
7402 total_scheduled_instance_hours: req.total_scheduled_instance_hours,
7403 term_start_date: now.clone(),
7404 term_end_date: now.clone(),
7405 recurrence: req.recurrence,
7406 slot_duration_in_hours: req.slot_duration_in_hours,
7407 previous_slot_end_time: None,
7408 next_slot_start_time: Some(now.clone()),
7409 create_date: now,
7410 };
7411 self.scheduled_instances.insert(id, si.clone());
7412 out.push(si);
7413 }
7414 out
7415 }
7416
7417 pub fn report_instance_status(&mut self, report: InstanceStatusReport) {
7418 self.instance_status_reports.push(report);
7419 }
7420
7421 pub fn restore_managed_prefix_list_version(
7422 &mut self,
7423 prefix_list_id: &str,
7424 previous_version: i64,
7425 ) -> Result<&ManagedPrefixList, Ec2Error> {
7426 let pl = self
7427 .managed_prefix_lists
7428 .get_mut(prefix_list_id)
7429 .ok_or_else(|| Ec2Error::PrefixListNotFound(prefix_list_id.to_string()))?;
7430 let snap = pl
7431 .version_history
7432 .iter()
7433 .find(|v| v.version == previous_version)
7434 .cloned()
7435 .ok_or_else(|| {
7436 Ec2Error::InvalidParameterValue(format!(
7437 "PreviousVersion {previous_version} does not exist"
7438 ))
7439 })?;
7440 pl.entries = snap.entries;
7441 pl.version += 1;
7442 pl.version_history.push(ManagedPrefixListVersion {
7443 version: pl.version,
7444 entries: pl.entries.clone(),
7445 });
7446 Ok(self.managed_prefix_lists.get(prefix_list_id).unwrap())
7447 }
7448
7449 pub fn run_scheduled_instances(
7450 &mut self,
7451 scheduled_instance_id: &str,
7452 instance_count: i32,
7453 owner_id: &str,
7454 ) -> Result<Vec<String>, Ec2Error> {
7455 if !self.scheduled_instances.contains_key(scheduled_instance_id) {
7456 return Err(Ec2Error::InvalidScheduledInstanceNotFound(
7457 scheduled_instance_id.to_string(),
7458 ));
7459 }
7460 let now = self.now_iso();
7461 let mut ids = Vec::new();
7462 for _ in 0..instance_count.max(1) {
7463 let instance_id = self.next_instance_id_pub();
7464 let inst = Instance {
7465 instance_id: instance_id.clone(),
7466 image_id: "ami-scheduled".to_string(),
7467 instance_type: "t3.micro".to_string(),
7468 state: InstanceState {
7469 code: 16,
7470 name: "running".to_string(),
7471 },
7472 private_ip_address: None,
7473 public_ip_address: None,
7474 subnet_id: None,
7475 vpc_id: None,
7476 key_name: None,
7477 security_groups: Vec::new(),
7478 launch_time: now.clone(),
7479 tags: Tags::new(),
7480 iam_instance_profile_arn: None,
7481 monitoring_state: "disabled".to_string(),
7482 placement_az: "us-east-1a".to_string(),
7483 placement_group_name: None,
7484 placement_tenancy: None,
7485 placement_host_id: None,
7486 placement_affinity: None,
7487 placement_partition_number: None,
7488 owner_id: owner_id.to_string(),
7489 classic_link_vpc: None,
7490 private_dns_hostname_type: None,
7491 enable_resource_name_dns_a_record: None,
7492 enable_resource_name_dns_aaaa_record: None,
7493 credit_specification: None,
7494 cpu_options: None,
7495 maintenance_options: None,
7496 network_bandwidth_weighting: None,
7497 lifecycle: Some("scheduled".to_string()),
7498 product_codes: Vec::new(),
7499 capacity_reservation_specification: None,
7500 };
7501 self.instances.insert(instance_id.clone(), inst);
7502 ids.push(instance_id);
7503 }
7504 Ok(ids)
7505 }
7506
7507 pub fn deregister_image_to_recycle_bin(&mut self, image_id: &str) -> Result<(), Ec2Error> {
7508 let img = self
7509 .images
7510 .get_mut(image_id)
7511 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
7512 img.recycle_bin_state = Some("recycled".to_string());
7513 img.state = "deregistered".to_string();
7514 self.deregistered_images.insert(image_id.to_string());
7515 Ok(())
7516 }
7517
7518 fn next_network_insights_access_scope_id(&mut self) -> String {
7521 self.counters.network_insights_access_scope += 1;
7522 format!("nis-{:08x}", self.counters.network_insights_access_scope)
7523 }
7524
7525 fn next_network_insights_access_scope_analysis_id(&mut self) -> String {
7526 self.counters.network_insights_access_scope_analysis += 1;
7527 format!(
7528 "nisa-{:08x}",
7529 self.counters.network_insights_access_scope_analysis
7530 )
7531 }
7532
7533 fn next_network_insights_path_id(&mut self) -> String {
7534 self.counters.network_insights_path += 1;
7535 format!("nip-{:08x}", self.counters.network_insights_path)
7536 }
7537
7538 fn next_network_insights_analysis_id(&mut self) -> String {
7539 self.counters.network_insights_analysis += 1;
7540 format!("nia-{:08x}", self.counters.network_insights_analysis)
7541 }
7542
7543 fn next_traffic_mirror_filter_id(&mut self) -> String {
7544 self.counters.traffic_mirror_filter += 1;
7545 format!("tmf-{:08x}", self.counters.traffic_mirror_filter)
7546 }
7547
7548 fn next_traffic_mirror_filter_rule_id(&mut self) -> String {
7549 self.counters.traffic_mirror_filter_rule += 1;
7550 format!("tmfr-{:08x}", self.counters.traffic_mirror_filter_rule)
7551 }
7552
7553 fn next_traffic_mirror_session_id(&mut self) -> String {
7554 self.counters.traffic_mirror_session += 1;
7555 format!("tms-{:08x}", self.counters.traffic_mirror_session)
7556 }
7557
7558 fn next_traffic_mirror_target_id(&mut self) -> String {
7559 self.counters.traffic_mirror_target += 1;
7560 format!("tmt-{:08x}", self.counters.traffic_mirror_target)
7561 }
7562
7563 pub fn create_network_insights_access_scope(
7566 &mut self,
7567 match_paths: Vec<AccessScopePathSpec>,
7568 exclude_paths: Vec<AccessScopePathSpec>,
7569 tags: Tags,
7570 ) -> &NetworkInsightsAccessScope {
7571 let id = self.next_network_insights_access_scope_id();
7572 let now = self.now_iso();
7573 let arn = format!("arn:aws:ec2:us-east-1:123456789012:network-insights-access-scope/{id}");
7574 let scope = NetworkInsightsAccessScope {
7575 network_insights_access_scope_id: id.clone(),
7576 network_insights_access_scope_arn: arn,
7577 created_date: now.clone(),
7578 updated_date: now,
7579 tags,
7580 match_paths,
7581 exclude_paths,
7582 };
7583 self.network_insights_access_scopes
7584 .insert(id.clone(), scope);
7585 self.network_insights_access_scopes.get(&id).unwrap()
7586 }
7587
7588 pub fn delete_network_insights_access_scope(&mut self, scope_id: &str) -> Result<(), Ec2Error> {
7589 if self
7590 .network_insights_access_scopes
7591 .remove(scope_id)
7592 .is_none()
7593 {
7594 return Err(Ec2Error::InvalidNetworkInsightsAccessScopeNotFound(
7595 scope_id.to_string(),
7596 ));
7597 }
7598 Ok(())
7599 }
7600
7601 pub fn delete_network_insights_access_scope_analysis(
7602 &mut self,
7603 analysis_id: &str,
7604 ) -> Result<(), Ec2Error> {
7605 if self
7606 .network_insights_access_scope_analyses
7607 .remove(analysis_id)
7608 .is_none()
7609 {
7610 return Err(Ec2Error::InvalidNetworkInsightsAccessScopeAnalysisNotFound(
7611 analysis_id.to_string(),
7612 ));
7613 }
7614 Ok(())
7615 }
7616
7617 pub fn start_network_insights_access_scope_analysis(
7618 &mut self,
7619 scope_id: &str,
7620 tags: Tags,
7621 ) -> Result<&NetworkInsightsAccessScopeAnalysis, Ec2Error> {
7622 if !self.network_insights_access_scopes.contains_key(scope_id) {
7623 return Err(Ec2Error::InvalidNetworkInsightsAccessScopeNotFound(
7624 scope_id.to_string(),
7625 ));
7626 }
7627 let id = self.next_network_insights_access_scope_analysis_id();
7628 let now = self.now_iso();
7629 let arn = format!(
7630 "arn:aws:ec2:us-east-1:123456789012:network-insights-access-scope-analysis/{id}"
7631 );
7632 let analysis = NetworkInsightsAccessScopeAnalysis {
7633 network_insights_access_scope_analysis_id: id.clone(),
7634 network_insights_access_scope_analysis_arn: arn,
7635 network_insights_access_scope_id: scope_id.to_string(),
7636 status: "succeeded".to_string(),
7637 status_message: None,
7638 warning_message: None,
7639 start_date: now.clone(),
7640 end_date: Some(now),
7641 findings_found: "false".to_string(),
7642 analyzed_eni_count: 0,
7643 tags,
7644 };
7645 self.network_insights_access_scope_analyses
7646 .insert(id.clone(), analysis);
7647 Ok(self
7648 .network_insights_access_scope_analyses
7649 .get(&id)
7650 .unwrap())
7651 }
7652
7653 #[allow(clippy::too_many_arguments)]
7656 pub fn create_network_insights_path(
7657 &mut self,
7658 source: Option<String>,
7659 destination: Option<String>,
7660 source_arn: Option<String>,
7661 destination_arn: Option<String>,
7662 source_ip: Option<String>,
7663 destination_ip: Option<String>,
7664 protocol: String,
7665 destination_port: Option<i32>,
7666 filter_at_source: NetworkInsightsPathFilter,
7667 filter_at_destination: NetworkInsightsPathFilter,
7668 tags: Tags,
7669 ) -> &NetworkInsightsPath {
7670 let id = self.next_network_insights_path_id();
7671 let now = self.now_iso();
7672 let arn = format!("arn:aws:ec2:us-east-1:123456789012:network-insights-path/{id}");
7673 let path = NetworkInsightsPath {
7674 network_insights_path_id: id.clone(),
7675 network_insights_path_arn: arn,
7676 created_date: now,
7677 source,
7678 destination,
7679 source_arn,
7680 destination_arn,
7681 source_ip,
7682 destination_ip,
7683 protocol,
7684 destination_port,
7685 tags,
7686 filter_at_source,
7687 filter_at_destination,
7688 };
7689 self.network_insights_paths.insert(id.clone(), path);
7690 self.network_insights_paths.get(&id).unwrap()
7691 }
7692
7693 pub fn delete_network_insights_path(&mut self, path_id: &str) -> Result<(), Ec2Error> {
7694 if !self.network_insights_paths.contains_key(path_id) {
7695 return Err(Ec2Error::InvalidNetworkInsightsPathNotFound(
7696 path_id.to_string(),
7697 ));
7698 }
7699 let has_analyses = self
7700 .network_insights_analyses
7701 .values()
7702 .any(|a| a.network_insights_path_id == path_id);
7703 if has_analyses {
7704 return Err(Ec2Error::NetworkInsightsPathHasAnalyses(
7705 path_id.to_string(),
7706 ));
7707 }
7708 self.network_insights_paths.remove(path_id);
7709 Ok(())
7710 }
7711
7712 pub fn delete_network_insights_analysis(&mut self, analysis_id: &str) -> Result<(), Ec2Error> {
7713 if self.network_insights_analyses.remove(analysis_id).is_none() {
7714 return Err(Ec2Error::InvalidNetworkInsightsAnalysisNotFound(
7715 analysis_id.to_string(),
7716 ));
7717 }
7718 Ok(())
7719 }
7720
7721 pub fn start_network_insights_analysis(
7722 &mut self,
7723 path_id: &str,
7724 additional_accounts: Vec<String>,
7725 filter_in_arns: Vec<String>,
7726 tags: Tags,
7727 ) -> Result<&NetworkInsightsAnalysis, Ec2Error> {
7728 if !self.network_insights_paths.contains_key(path_id) {
7729 return Err(Ec2Error::InvalidNetworkInsightsPathNotFound(
7730 path_id.to_string(),
7731 ));
7732 }
7733 let id = self.next_network_insights_analysis_id();
7734 let now = self.now_iso();
7735 let arn = format!("arn:aws:ec2:us-east-1:123456789012:network-insights-analysis/{id}");
7736 let analysis = NetworkInsightsAnalysis {
7737 network_insights_analysis_id: id.clone(),
7738 network_insights_analysis_arn: arn,
7739 network_insights_path_id: path_id.to_string(),
7740 additional_accounts,
7741 filter_in_arns,
7742 start_date: now.clone(),
7743 end_date: Some(now),
7744 status: "succeeded".to_string(),
7745 status_message: None,
7746 warning_message: None,
7747 network_path_found: true,
7748 tags,
7749 };
7750 self.network_insights_analyses.insert(id.clone(), analysis);
7751 Ok(self.network_insights_analyses.get(&id).unwrap())
7752 }
7753
7754 pub fn create_traffic_mirror_filter(
7757 &mut self,
7758 description: Option<String>,
7759 tags: Tags,
7760 ) -> &TrafficMirrorFilter {
7761 let id = self.next_traffic_mirror_filter_id();
7762 let filter = TrafficMirrorFilter {
7763 traffic_mirror_filter_id: id.clone(),
7764 description,
7765 ingress_filter_rules: Vec::new(),
7766 egress_filter_rules: Vec::new(),
7767 network_services: Vec::new(),
7768 tags,
7769 };
7770 self.traffic_mirror_filters.insert(id.clone(), filter);
7771 self.traffic_mirror_filters.get(&id).unwrap()
7772 }
7773
7774 pub fn delete_traffic_mirror_filter(&mut self, filter_id: &str) -> Result<(), Ec2Error> {
7775 if !self.traffic_mirror_filters.contains_key(filter_id) {
7776 return Err(Ec2Error::InvalidTrafficMirrorFilterNotFound(
7777 filter_id.to_string(),
7778 ));
7779 }
7780 if self
7781 .traffic_mirror_sessions
7782 .values()
7783 .any(|s| s.traffic_mirror_filter_id == filter_id)
7784 {
7785 return Err(Ec2Error::TrafficMirrorFilterInUse(filter_id.to_string()));
7786 }
7787 self.traffic_mirror_filters.remove(filter_id);
7788 Ok(())
7789 }
7790
7791 #[allow(clippy::too_many_arguments)]
7792 pub fn create_traffic_mirror_filter_rule(
7793 &mut self,
7794 filter_id: &str,
7795 traffic_direction: String,
7796 rule_number: i32,
7797 rule_action: String,
7798 protocol: Option<i32>,
7799 destination_port_range: Option<TrafficMirrorPortRange>,
7800 source_port_range: Option<TrafficMirrorPortRange>,
7801 destination_cidr_block: String,
7802 source_cidr_block: String,
7803 description: Option<String>,
7804 tags: Tags,
7805 ) -> Result<TrafficMirrorFilterRule, Ec2Error> {
7806 if !self.traffic_mirror_filters.contains_key(filter_id) {
7807 return Err(Ec2Error::InvalidTrafficMirrorFilterNotFound(
7808 filter_id.to_string(),
7809 ));
7810 }
7811 let rule_id = self.next_traffic_mirror_filter_rule_id();
7812 let rule = TrafficMirrorFilterRule {
7813 traffic_mirror_filter_rule_id: rule_id,
7814 traffic_mirror_filter_id: filter_id.to_string(),
7815 traffic_direction: traffic_direction.clone(),
7816 rule_number,
7817 rule_action,
7818 protocol,
7819 destination_port_range,
7820 source_port_range,
7821 destination_cidr_block,
7822 source_cidr_block,
7823 description,
7824 tags,
7825 };
7826 let filter = self.traffic_mirror_filters.get_mut(filter_id).unwrap();
7827 if traffic_direction == "egress" {
7828 filter.egress_filter_rules.push(rule.clone());
7829 } else {
7830 filter.ingress_filter_rules.push(rule.clone());
7831 }
7832 Ok(rule)
7833 }
7834
7835 pub fn delete_traffic_mirror_filter_rule(&mut self, rule_id: &str) -> Result<(), Ec2Error> {
7836 for filter in self.traffic_mirror_filters.values_mut() {
7837 let before_in = filter.ingress_filter_rules.len();
7838 filter
7839 .ingress_filter_rules
7840 .retain(|r| r.traffic_mirror_filter_rule_id != rule_id);
7841 let before_out = filter.egress_filter_rules.len();
7842 filter
7843 .egress_filter_rules
7844 .retain(|r| r.traffic_mirror_filter_rule_id != rule_id);
7845 if filter.ingress_filter_rules.len() != before_in
7846 || filter.egress_filter_rules.len() != before_out
7847 {
7848 return Ok(());
7849 }
7850 }
7851 Err(Ec2Error::InvalidTrafficMirrorFilterRuleNotFound(
7852 rule_id.to_string(),
7853 ))
7854 }
7855
7856 #[allow(clippy::too_many_arguments)]
7857 pub fn modify_traffic_mirror_filter_rule(
7858 &mut self,
7859 rule_id: &str,
7860 rule_number: Option<i32>,
7861 rule_action: Option<String>,
7862 protocol: Option<i32>,
7863 destination_port_range: Option<TrafficMirrorPortRange>,
7864 source_port_range: Option<TrafficMirrorPortRange>,
7865 destination_cidr_block: Option<String>,
7866 source_cidr_block: Option<String>,
7867 description: Option<String>,
7868 traffic_direction: Option<String>,
7869 remove_fields: &[String],
7870 ) -> Result<TrafficMirrorFilterRule, Ec2Error> {
7871 for filter in self.traffic_mirror_filters.values_mut() {
7872 for rule in filter
7873 .ingress_filter_rules
7874 .iter_mut()
7875 .chain(filter.egress_filter_rules.iter_mut())
7876 {
7877 if rule.traffic_mirror_filter_rule_id == rule_id {
7878 if let Some(n) = rule_number {
7879 rule.rule_number = n;
7880 }
7881 if let Some(a) = rule_action {
7882 rule.rule_action = a;
7883 }
7884 if protocol.is_some() {
7885 rule.protocol = protocol;
7886 }
7887 if destination_port_range.is_some() {
7888 rule.destination_port_range = destination_port_range;
7889 }
7890 if source_port_range.is_some() {
7891 rule.source_port_range = source_port_range;
7892 }
7893 if let Some(c) = destination_cidr_block {
7894 rule.destination_cidr_block = c;
7895 }
7896 if let Some(c) = source_cidr_block {
7897 rule.source_cidr_block = c;
7898 }
7899 if description.is_some() {
7900 rule.description = description;
7901 }
7902 if let Some(td) = traffic_direction {
7903 rule.traffic_direction = td;
7904 }
7905 for f in remove_fields {
7906 match f.as_str() {
7907 "protocol" => rule.protocol = None,
7908 "description" => rule.description = None,
7909 "destination-port-range" => rule.destination_port_range = None,
7910 "source-port-range" => rule.source_port_range = None,
7911 _ => {}
7912 }
7913 }
7914 return Ok(rule.clone());
7915 }
7916 }
7917 }
7918 Err(Ec2Error::InvalidTrafficMirrorFilterRuleNotFound(
7919 rule_id.to_string(),
7920 ))
7921 }
7922
7923 pub fn modify_traffic_mirror_filter_network_services(
7924 &mut self,
7925 filter_id: &str,
7926 add: &[String],
7927 remove: &[String],
7928 ) -> Result<TrafficMirrorFilter, Ec2Error> {
7929 let filter = self
7930 .traffic_mirror_filters
7931 .get_mut(filter_id)
7932 .ok_or_else(|| Ec2Error::InvalidTrafficMirrorFilterNotFound(filter_id.to_string()))?;
7933 for s in add {
7934 if !filter.network_services.contains(s) {
7935 filter.network_services.push(s.clone());
7936 }
7937 }
7938 filter.network_services.retain(|s| !remove.contains(s));
7939 Ok(filter.clone())
7940 }
7941
7942 #[allow(clippy::too_many_arguments)]
7945 pub fn create_traffic_mirror_session(
7946 &mut self,
7947 traffic_mirror_target_id: String,
7948 traffic_mirror_filter_id: String,
7949 network_interface_id: String,
7950 owner_id: String,
7951 packet_length: Option<i32>,
7952 session_number: i32,
7953 virtual_network_id: Option<i32>,
7954 description: Option<String>,
7955 tags: Tags,
7956 ) -> Result<&TrafficMirrorSession, Ec2Error> {
7957 if !self
7958 .traffic_mirror_filters
7959 .contains_key(&traffic_mirror_filter_id)
7960 {
7961 return Err(Ec2Error::InvalidTrafficMirrorFilterNotFound(
7962 traffic_mirror_filter_id,
7963 ));
7964 }
7965 if !self
7966 .traffic_mirror_targets
7967 .contains_key(&traffic_mirror_target_id)
7968 {
7969 return Err(Ec2Error::InvalidTrafficMirrorTargetNotFound(
7970 traffic_mirror_target_id,
7971 ));
7972 }
7973 let id = self.next_traffic_mirror_session_id();
7974 let session = TrafficMirrorSession {
7975 traffic_mirror_session_id: id.clone(),
7976 traffic_mirror_target_id,
7977 traffic_mirror_filter_id,
7978 network_interface_id,
7979 owner_id,
7980 packet_length,
7981 session_number,
7982 virtual_network_id,
7983 description,
7984 tags,
7985 };
7986 self.traffic_mirror_sessions.insert(id.clone(), session);
7987 Ok(self.traffic_mirror_sessions.get(&id).unwrap())
7988 }
7989
7990 pub fn delete_traffic_mirror_session(&mut self, session_id: &str) -> Result<(), Ec2Error> {
7991 if self.traffic_mirror_sessions.remove(session_id).is_none() {
7992 return Err(Ec2Error::InvalidTrafficMirrorSessionNotFound(
7993 session_id.to_string(),
7994 ));
7995 }
7996 Ok(())
7997 }
7998
7999 #[allow(clippy::too_many_arguments)]
8000 pub fn modify_traffic_mirror_session(
8001 &mut self,
8002 session_id: &str,
8003 traffic_mirror_target_id: Option<String>,
8004 traffic_mirror_filter_id: Option<String>,
8005 packet_length: Option<i32>,
8006 session_number: Option<i32>,
8007 virtual_network_id: Option<i32>,
8008 description: Option<String>,
8009 remove_fields: &[String],
8010 ) -> Result<TrafficMirrorSession, Ec2Error> {
8011 let session = self
8012 .traffic_mirror_sessions
8013 .get_mut(session_id)
8014 .ok_or_else(|| Ec2Error::InvalidTrafficMirrorSessionNotFound(session_id.to_string()))?;
8015 if let Some(t) = traffic_mirror_target_id {
8016 session.traffic_mirror_target_id = t;
8017 }
8018 if let Some(f) = traffic_mirror_filter_id {
8019 session.traffic_mirror_filter_id = f;
8020 }
8021 if packet_length.is_some() {
8022 session.packet_length = packet_length;
8023 }
8024 if let Some(n) = session_number {
8025 session.session_number = n;
8026 }
8027 if virtual_network_id.is_some() {
8028 session.virtual_network_id = virtual_network_id;
8029 }
8030 if description.is_some() {
8031 session.description = description;
8032 }
8033 for f in remove_fields {
8034 match f.as_str() {
8035 "packet-length" => session.packet_length = None,
8036 "description" => session.description = None,
8037 "virtual-network-id" => session.virtual_network_id = None,
8038 _ => {}
8039 }
8040 }
8041 Ok(session.clone())
8042 }
8043
8044 #[allow(clippy::too_many_arguments)]
8047 pub fn create_traffic_mirror_target(
8048 &mut self,
8049 network_interface_id: Option<String>,
8050 network_load_balancer_arn: Option<String>,
8051 gateway_load_balancer_endpoint_id: Option<String>,
8052 description: Option<String>,
8053 owner_id: String,
8054 tags: Tags,
8055 ) -> &TrafficMirrorTarget {
8056 let id = self.next_traffic_mirror_target_id();
8057 let r#type = if network_load_balancer_arn.is_some() {
8058 "network-load-balancer".to_string()
8059 } else if gateway_load_balancer_endpoint_id.is_some() {
8060 "gateway-load-balancer-endpoint".to_string()
8061 } else {
8062 "network-interface".to_string()
8063 };
8064 let target = TrafficMirrorTarget {
8065 traffic_mirror_target_id: id.clone(),
8066 network_interface_id,
8067 network_load_balancer_arn,
8068 gateway_load_balancer_endpoint_id,
8069 r#type,
8070 description,
8071 owner_id,
8072 tags,
8073 };
8074 self.traffic_mirror_targets.insert(id.clone(), target);
8075 self.traffic_mirror_targets.get(&id).unwrap()
8076 }
8077
8078 pub fn delete_traffic_mirror_target(&mut self, target_id: &str) -> Result<(), Ec2Error> {
8079 if !self.traffic_mirror_targets.contains_key(target_id) {
8080 return Err(Ec2Error::InvalidTrafficMirrorTargetNotFound(
8081 target_id.to_string(),
8082 ));
8083 }
8084 if self
8085 .traffic_mirror_sessions
8086 .values()
8087 .any(|s| s.traffic_mirror_target_id == target_id)
8088 {
8089 return Err(Ec2Error::TrafficMirrorTargetInUse(target_id.to_string()));
8090 }
8091 self.traffic_mirror_targets.remove(target_id);
8092 Ok(())
8093 }
8094
8095 fn next_client_vpn_endpoint_id(&mut self) -> String {
8098 self.counters.client_vpn_endpoint += 1;
8099 format!("cvpn-endpoint-{:08x}", self.counters.client_vpn_endpoint)
8100 }
8101
8102 fn next_client_vpn_target_network_association_id(&mut self) -> String {
8103 self.counters.client_vpn_target_network_association += 1;
8104 format!(
8105 "cvpn-assoc-{:08x}",
8106 self.counters.client_vpn_target_network_association
8107 )
8108 }
8109
8110 fn next_client_vpn_connection_id(&mut self) -> String {
8111 self.counters.client_vpn_connection += 1;
8112 format!(
8113 "cvpn-connection-{:08x}",
8114 self.counters.client_vpn_connection
8115 )
8116 }
8117
8118 #[allow(clippy::too_many_arguments)]
8119 pub fn create_client_vpn_endpoint(
8120 &mut self,
8121 description: Option<String>,
8122 client_cidr_block: String,
8123 server_certificate_arn: String,
8124 dns_servers: Vec<String>,
8125 transport_protocol: String,
8126 vpn_port: i32,
8127 split_tunnel: bool,
8128 authentication_options: Vec<String>,
8129 connection_log_options_enabled: bool,
8130 connection_log_options_cloudwatch_log_group: Option<String>,
8131 connection_log_options_cloudwatch_log_stream: Option<String>,
8132 security_group_ids: Vec<String>,
8133 vpc_id: Option<String>,
8134 self_service_portal: String,
8135 session_timeout_hours: i32,
8136 client_login_banner_enabled: bool,
8137 client_login_banner_text: Option<String>,
8138 disconnect_on_session_timeout: bool,
8139 client_route_enforcement_enforced: bool,
8140 tags: Tags,
8141 ) -> &ClientVpnEndpoint {
8142 let id = self.next_client_vpn_endpoint_id();
8143 let dns_name = format!("{id}.cvpn-endpoint.amazonaws.com");
8144 let endpoint = ClientVpnEndpoint {
8145 client_vpn_endpoint_id: id.clone(),
8146 description,
8147 status: ClientVpnEndpointStatus {
8148 code: "pending-associate".to_string(),
8149 message: None,
8150 },
8151 creation_time: self.now_iso(),
8152 deletion_time: None,
8153 dns_name,
8154 client_cidr_block,
8155 dns_servers,
8156 split_tunnel,
8157 vpn_protocol: "openvpn".to_string(),
8158 transport_protocol,
8159 vpn_port,
8160 server_certificate_arn,
8161 authentication_options,
8162 connection_log_options_enabled,
8163 connection_log_options_cloudwatch_log_group,
8164 connection_log_options_cloudwatch_log_stream,
8165 tags,
8166 security_group_ids,
8167 vpc_id,
8168 self_service_portal_url: None,
8169 self_service_portal,
8170 session_timeout_hours,
8171 client_login_banner_enabled,
8172 client_login_banner_text,
8173 disconnect_on_session_timeout,
8174 client_route_enforcement_enforced,
8175 client_certificate_revocation_list: None,
8176 };
8177 self.client_vpn_endpoints.insert(id.clone(), endpoint);
8178 self.client_vpn_endpoints.get(&id).unwrap()
8179 }
8180
8181 pub fn delete_client_vpn_endpoint(
8182 &mut self,
8183 endpoint_id: &str,
8184 ) -> Result<ClientVpnEndpointStatus, Ec2Error> {
8185 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8186 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8187 endpoint_id.to_string(),
8188 ));
8189 }
8190 let has_assoc = self
8191 .client_vpn_target_network_associations
8192 .values()
8193 .any(|a| a.client_vpn_endpoint_id == endpoint_id);
8194 let has_routes = self
8195 .client_vpn_routes
8196 .keys()
8197 .any(|(eid, _, _)| eid == endpoint_id);
8198 if has_assoc || has_routes {
8199 return Err(Ec2Error::ClientVpnEndpointInUse(endpoint_id.to_string()));
8200 }
8201 self.client_vpn_endpoints.remove(endpoint_id);
8202 Ok(ClientVpnEndpointStatus {
8203 code: "deleting".to_string(),
8204 message: None,
8205 })
8206 }
8207
8208 pub fn associate_client_vpn_target_network(
8209 &mut self,
8210 endpoint_id: &str,
8211 subnet_id: &str,
8212 ) -> Result<&ClientVpnTargetNetworkAssociation, Ec2Error> {
8213 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8214 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8215 endpoint_id.to_string(),
8216 ));
8217 }
8218 let vpc_id = self
8219 .subnets
8220 .get(subnet_id)
8221 .map(|s| s.vpc_id.clone())
8222 .unwrap_or_default();
8223 let assoc_id = self.next_client_vpn_target_network_association_id();
8224 let assoc = ClientVpnTargetNetworkAssociation {
8225 association_id: assoc_id.clone(),
8226 vpc_id: vpc_id.clone(),
8227 target_network_id: subnet_id.to_string(),
8228 client_vpn_endpoint_id: endpoint_id.to_string(),
8229 security_groups: Vec::new(),
8230 status: ClientVpnAssociationStatus {
8231 code: "associating".to_string(),
8232 message: None,
8233 },
8234 };
8235 self.client_vpn_target_network_associations
8236 .insert(assoc_id.clone(), assoc);
8237 if let Some(ep) = self.client_vpn_endpoints.get_mut(endpoint_id)
8239 && ep.vpc_id.is_none()
8240 && !vpc_id.is_empty()
8241 {
8242 ep.vpc_id = Some(vpc_id);
8243 }
8244 Ok(self
8245 .client_vpn_target_network_associations
8246 .get(&assoc_id)
8247 .unwrap())
8248 }
8249
8250 pub fn disassociate_client_vpn_target_network(
8251 &mut self,
8252 endpoint_id: &str,
8253 association_id: &str,
8254 ) -> Result<ClientVpnAssociationStatus, Ec2Error> {
8255 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8256 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8257 endpoint_id.to_string(),
8258 ));
8259 }
8260 let assoc = self
8261 .client_vpn_target_network_associations
8262 .remove(association_id)
8263 .ok_or_else(|| {
8264 Ec2Error::InvalidClientVpnTargetNetworkAssociationNotFound(
8265 association_id.to_string(),
8266 )
8267 })?;
8268 if assoc.client_vpn_endpoint_id != endpoint_id {
8269 self.client_vpn_target_network_associations
8271 .insert(association_id.to_string(), assoc);
8272 return Err(Ec2Error::InvalidClientVpnTargetNetworkAssociationNotFound(
8273 association_id.to_string(),
8274 ));
8275 }
8276 Ok(ClientVpnAssociationStatus {
8277 code: "disassociating".to_string(),
8278 message: None,
8279 })
8280 }
8281
8282 pub fn apply_security_groups_to_client_vpn_target_network(
8283 &mut self,
8284 endpoint_id: &str,
8285 vpc_id: &str,
8286 security_group_ids: Vec<String>,
8287 ) -> Result<Vec<String>, Ec2Error> {
8288 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8289 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8290 endpoint_id.to_string(),
8291 ));
8292 }
8293 let mut applied = false;
8294 for assoc in self.client_vpn_target_network_associations.values_mut() {
8295 if assoc.client_vpn_endpoint_id == endpoint_id && assoc.vpc_id == vpc_id {
8296 assoc.security_groups = security_group_ids.clone();
8297 applied = true;
8298 }
8299 }
8300 if let Some(ep) = self.client_vpn_endpoints.get_mut(endpoint_id) {
8302 ep.security_group_ids = security_group_ids.clone();
8303 }
8304 if !applied {
8305 }
8307 Ok(security_group_ids)
8308 }
8309
8310 #[allow(clippy::too_many_arguments)]
8311 pub fn authorize_client_vpn_ingress(
8312 &mut self,
8313 endpoint_id: &str,
8314 destination_cidr: String,
8315 access_all: bool,
8316 group_id: Option<String>,
8317 description: Option<String>,
8318 ) -> Result<ClientVpnAuthorizationRuleStatus, Ec2Error> {
8319 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8320 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8321 endpoint_id.to_string(),
8322 ));
8323 }
8324 let key = (
8325 endpoint_id.to_string(),
8326 destination_cidr.clone(),
8327 group_id.clone().unwrap_or_default(),
8328 );
8329 let status = ClientVpnAuthorizationRuleStatus {
8330 code: "active".to_string(),
8331 message: None,
8332 };
8333 let rule = ClientVpnAuthorizationRule {
8334 client_vpn_endpoint_id: endpoint_id.to_string(),
8335 group_id,
8336 access_all,
8337 destination_cidr,
8338 description,
8339 status: status.clone(),
8340 };
8341 self.client_vpn_authorization_rules.insert(key, rule);
8342 Ok(status)
8343 }
8344
8345 pub fn revoke_client_vpn_ingress(
8346 &mut self,
8347 endpoint_id: &str,
8348 destination_cidr: &str,
8349 group_id: Option<String>,
8350 ) -> Result<ClientVpnAuthorizationRuleStatus, Ec2Error> {
8351 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8352 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8353 endpoint_id.to_string(),
8354 ));
8355 }
8356 let key = (
8357 endpoint_id.to_string(),
8358 destination_cidr.to_string(),
8359 group_id.unwrap_or_default(),
8360 );
8361 if self.client_vpn_authorization_rules.remove(&key).is_some() {
8362 Ok(ClientVpnAuthorizationRuleStatus {
8363 code: "revoking".to_string(),
8364 message: None,
8365 })
8366 } else {
8367 Err(Ec2Error::InvalidClientVpnAuthorizationRuleNotFound(
8368 endpoint_id.to_string(),
8369 destination_cidr.to_string(),
8370 ))
8371 }
8372 }
8373
8374 pub fn create_client_vpn_route(
8375 &mut self,
8376 endpoint_id: &str,
8377 destination_cidr: String,
8378 target_subnet: String,
8379 description: Option<String>,
8380 ) -> Result<ClientVpnRouteStatus, Ec2Error> {
8381 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8382 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8383 endpoint_id.to_string(),
8384 ));
8385 }
8386 let key = (
8387 endpoint_id.to_string(),
8388 destination_cidr.clone(),
8389 target_subnet.clone(),
8390 );
8391 let status = ClientVpnRouteStatus {
8392 code: "creating".to_string(),
8393 message: None,
8394 };
8395 let route = ClientVpnRoute {
8396 client_vpn_endpoint_id: endpoint_id.to_string(),
8397 destination_cidr,
8398 target_subnet,
8399 r#type: "Nat".to_string(),
8400 origin: "add-route".to_string(),
8401 status: status.clone(),
8402 description,
8403 };
8404 self.client_vpn_routes.insert(key, route);
8405 Ok(status)
8406 }
8407
8408 pub fn delete_client_vpn_route(
8409 &mut self,
8410 endpoint_id: &str,
8411 destination_cidr: &str,
8412 target_subnet: &str,
8413 ) -> Result<ClientVpnRouteStatus, Ec2Error> {
8414 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8415 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8416 endpoint_id.to_string(),
8417 ));
8418 }
8419 let key = (
8420 endpoint_id.to_string(),
8421 destination_cidr.to_string(),
8422 target_subnet.to_string(),
8423 );
8424 if self.client_vpn_routes.remove(&key).is_some() {
8425 Ok(ClientVpnRouteStatus {
8426 code: "deleting".to_string(),
8427 message: None,
8428 })
8429 } else {
8430 Err(Ec2Error::InvalidClientVpnRouteNotFound(
8431 endpoint_id.to_string(),
8432 destination_cidr.to_string(),
8433 target_subnet.to_string(),
8434 ))
8435 }
8436 }
8437
8438 pub fn import_client_vpn_revocation_list(
8439 &mut self,
8440 endpoint_id: &str,
8441 crl: String,
8442 ) -> Result<(), Ec2Error> {
8443 let ep = self
8444 .client_vpn_endpoints
8445 .get_mut(endpoint_id)
8446 .ok_or_else(|| Ec2Error::InvalidClientVpnEndpointNotFound(endpoint_id.to_string()))?;
8447 ep.client_certificate_revocation_list = Some(crl);
8448 Ok(())
8449 }
8450
8451 #[allow(clippy::too_many_arguments)]
8452 pub fn modify_client_vpn_endpoint(
8453 &mut self,
8454 endpoint_id: &str,
8455 server_certificate_arn: Option<String>,
8456 connection_log_options_enabled: Option<bool>,
8457 connection_log_options_cloudwatch_log_group: Option<String>,
8458 connection_log_options_cloudwatch_log_stream: Option<String>,
8459 dns_servers_custom: Option<Vec<String>>,
8460 dns_servers_enabled: Option<bool>,
8461 vpn_port: Option<i32>,
8462 description: Option<String>,
8463 split_tunnel: Option<bool>,
8464 security_group_ids: Option<Vec<String>>,
8465 vpc_id: Option<String>,
8466 self_service_portal: Option<String>,
8467 session_timeout_hours: Option<i32>,
8468 client_login_banner_enabled: Option<bool>,
8469 client_login_banner_text: Option<String>,
8470 disconnect_on_session_timeout: Option<bool>,
8471 client_route_enforcement_enforced: Option<bool>,
8472 ) -> Result<(), Ec2Error> {
8473 let ep = self
8474 .client_vpn_endpoints
8475 .get_mut(endpoint_id)
8476 .ok_or_else(|| Ec2Error::InvalidClientVpnEndpointNotFound(endpoint_id.to_string()))?;
8477 if let Some(s) = server_certificate_arn {
8478 ep.server_certificate_arn = s;
8479 }
8480 if let Some(b) = connection_log_options_enabled {
8481 ep.connection_log_options_enabled = b;
8482 }
8483 if connection_log_options_cloudwatch_log_group.is_some() {
8484 ep.connection_log_options_cloudwatch_log_group =
8485 connection_log_options_cloudwatch_log_group;
8486 }
8487 if connection_log_options_cloudwatch_log_stream.is_some() {
8488 ep.connection_log_options_cloudwatch_log_stream =
8489 connection_log_options_cloudwatch_log_stream;
8490 }
8491 if let Some(enabled) = dns_servers_enabled {
8492 if !enabled {
8493 ep.dns_servers.clear();
8494 } else if let Some(custom) = dns_servers_custom {
8495 ep.dns_servers = custom;
8496 }
8497 } else if let Some(custom) = dns_servers_custom {
8498 ep.dns_servers = custom;
8499 }
8500 if let Some(p) = vpn_port {
8501 ep.vpn_port = p;
8502 }
8503 if description.is_some() {
8504 ep.description = description;
8505 }
8506 if let Some(b) = split_tunnel {
8507 ep.split_tunnel = b;
8508 }
8509 if let Some(s) = security_group_ids {
8510 ep.security_group_ids = s;
8511 }
8512 if vpc_id.is_some() {
8513 ep.vpc_id = vpc_id;
8514 }
8515 if let Some(s) = self_service_portal {
8516 ep.self_service_portal = s;
8517 }
8518 if let Some(h) = session_timeout_hours {
8519 ep.session_timeout_hours = h;
8520 }
8521 if let Some(b) = client_login_banner_enabled {
8522 ep.client_login_banner_enabled = b;
8523 }
8524 if client_login_banner_text.is_some() {
8525 ep.client_login_banner_text = client_login_banner_text;
8526 }
8527 if let Some(b) = disconnect_on_session_timeout {
8528 ep.disconnect_on_session_timeout = b;
8529 }
8530 if let Some(b) = client_route_enforcement_enforced {
8531 ep.client_route_enforcement_enforced = b;
8532 }
8533 Ok(())
8534 }
8535
8536 #[allow(dead_code)]
8538 pub fn seed_client_vpn_connection(
8539 &mut self,
8540 endpoint_id: &str,
8541 username: Option<String>,
8542 ) -> String {
8543 let id = self.next_client_vpn_connection_id();
8544 let now = self.now_iso();
8545 let conn = ClientVpnConnection {
8546 connection_id: id.clone(),
8547 client_vpn_endpoint_id: endpoint_id.to_string(),
8548 username,
8549 status: ClientVpnConnectionStatus {
8550 code: "active".to_string(),
8551 message: None,
8552 },
8553 posture_compliance_statuses: Vec::new(),
8554 common_name: None,
8555 connection_established_time: now.clone(),
8556 connection_end_time: None,
8557 ingress_bytes: "0".to_string(),
8558 egress_bytes: "0".to_string(),
8559 ingress_packets: "0".to_string(),
8560 egress_packets: "0".to_string(),
8561 client_ip: None,
8562 client_port: None,
8563 timestamp: now,
8564 };
8565 self.client_vpn_connections.insert(id.clone(), conn);
8566 id
8567 }
8568
8569 pub fn terminate_client_vpn_connections(
8572 &mut self,
8573 endpoint_id: &str,
8574 connection_ids: Vec<String>,
8575 username: Option<String>,
8576 ) -> Result<Vec<(String, ClientVpnConnectionStatus, ClientVpnConnectionStatus)>, Ec2Error> {
8577 if !self.client_vpn_endpoints.contains_key(endpoint_id) {
8578 return Err(Ec2Error::InvalidClientVpnEndpointNotFound(
8579 endpoint_id.to_string(),
8580 ));
8581 }
8582 let mut out = Vec::new();
8583 let to_terminate: Vec<String> = if !connection_ids.is_empty() {
8584 connection_ids
8585 } else if let Some(u) = username.as_ref() {
8586 self.client_vpn_connections
8587 .values()
8588 .filter(|c| {
8589 c.client_vpn_endpoint_id == endpoint_id
8590 && c.username.as_deref() == Some(u.as_str())
8591 })
8592 .map(|c| c.connection_id.clone())
8593 .collect()
8594 } else {
8595 Vec::new()
8596 };
8597 for id in to_terminate {
8598 if let Some(conn) = self.client_vpn_connections.get_mut(&id) {
8599 let prev = conn.status.clone();
8600 conn.status = ClientVpnConnectionStatus {
8601 code: "terminating".to_string(),
8602 message: None,
8603 };
8604 out.push((id, conn.status.clone(), prev));
8605 }
8606 }
8607 Ok(out)
8608 }
8609
8610 fn next_local_gateway_id(&mut self) -> String {
8613 self.counters.local_gateway += 1;
8614 format!("lgw-{:08x}", self.counters.local_gateway)
8615 }
8616
8617 fn next_local_gateway_route_table_id(&mut self) -> String {
8618 self.counters.local_gateway_route_table += 1;
8619 format!("lgw-rtb-{:08x}", self.counters.local_gateway_route_table)
8620 }
8621
8622 fn next_local_gateway_route_table_virtual_interface_group_association_id(&mut self) -> String {
8623 self.counters
8624 .local_gateway_route_table_virtual_interface_group_association += 1;
8625 format!(
8626 "lgw-vif-grp-assoc-{:08x}",
8627 self.counters
8628 .local_gateway_route_table_virtual_interface_group_association
8629 )
8630 }
8631
8632 fn next_local_gateway_route_table_vpc_association_id(&mut self) -> String {
8633 self.counters.local_gateway_route_table_vpc_association += 1;
8634 format!(
8635 "lgw-vpc-assoc-{:08x}",
8636 self.counters.local_gateway_route_table_vpc_association
8637 )
8638 }
8639
8640 fn next_local_gateway_virtual_interface_id(&mut self) -> String {
8641 self.counters.local_gateway_virtual_interface += 1;
8642 format!(
8643 "lgw-vif-{:08x}",
8644 self.counters.local_gateway_virtual_interface
8645 )
8646 }
8647
8648 fn next_local_gateway_virtual_interface_group_id(&mut self) -> String {
8649 self.counters.local_gateway_virtual_interface_group += 1;
8650 format!(
8651 "lgw-vif-grp-{:08x}",
8652 self.counters.local_gateway_virtual_interface_group
8653 )
8654 }
8655
8656 pub fn seed_local_gateway(&mut self, owner_id: &str) -> String {
8659 let id = self.next_local_gateway_id();
8660 let lgw = LocalGateway {
8661 local_gateway_id: id.clone(),
8662 outpost_arn: format!(
8663 "arn:aws:outposts:us-east-1:{owner_id}:outpost/op-{:08x}",
8664 self.counters.local_gateway
8665 ),
8666 owner_id: owner_id.to_string(),
8667 state: "available".to_string(),
8668 tags: Tags::new(),
8669 };
8670 self.local_gateways.insert(id.clone(), lgw);
8671 id
8672 }
8673
8674 pub fn seed_local_gateway_route_table_with_id(&mut self, table_id: &str, owner_id: &str) {
8679 if self.local_gateway_route_tables.contains_key(table_id) {
8680 return;
8681 }
8682 let lgw_id = table_id
8683 .strip_prefix("lgw-rtb-")
8684 .map(|suffix| format!("lgw-{suffix}"))
8685 .unwrap_or_else(|| format!("lgw-injected-{table_id}"));
8686 if !self.local_gateways.contains_key(&lgw_id) {
8687 self.local_gateways.insert(
8688 lgw_id.clone(),
8689 LocalGateway {
8690 local_gateway_id: lgw_id.clone(),
8691 outpost_arn: format!(
8692 "arn:aws:outposts:us-east-1:{owner_id}:outpost/op-injected"
8693 ),
8694 owner_id: owner_id.to_string(),
8695 state: "available".to_string(),
8696 tags: Tags::new(),
8697 },
8698 );
8699 }
8700 let arn = format!("arn:aws:ec2:us-east-1:{owner_id}:local-gateway-route-table/{table_id}");
8701 self.local_gateway_route_tables.insert(
8702 table_id.to_string(),
8703 LocalGatewayRouteTable {
8704 local_gateway_route_table_id: table_id.to_string(),
8705 local_gateway_route_table_arn: arn,
8706 local_gateway_id: lgw_id,
8707 owner_id: owner_id.to_string(),
8708 state: "available".to_string(),
8709 mode: "direct-vpc-routing".to_string(),
8710 tags: Tags::new(),
8711 state_reason_code: None,
8712 state_reason_message: None,
8713 },
8714 );
8715 }
8716
8717 pub fn create_local_gateway_route_table(
8718 &mut self,
8719 local_gateway_id: &str,
8720 mode: Option<String>,
8721 owner_id: &str,
8722 tags: Tags,
8723 ) -> Result<&LocalGatewayRouteTable, Ec2Error> {
8724 if !self.local_gateways.contains_key(local_gateway_id) {
8725 return Err(Ec2Error::InvalidLocalGatewayNotFound(
8726 local_gateway_id.to_string(),
8727 ));
8728 }
8729 let id = self.next_local_gateway_route_table_id();
8730 let arn = format!("arn:aws:ec2:us-east-1:{owner_id}:local-gateway-route-table/{id}");
8731 let table = LocalGatewayRouteTable {
8732 local_gateway_route_table_id: id.clone(),
8733 local_gateway_route_table_arn: arn,
8734 local_gateway_id: local_gateway_id.to_string(),
8735 owner_id: owner_id.to_string(),
8736 state: "available".to_string(),
8737 mode: mode.unwrap_or_else(|| "direct-vpc-routing".to_string()),
8738 tags,
8739 state_reason_code: None,
8740 state_reason_message: None,
8741 };
8742 self.local_gateway_route_tables.insert(id.clone(), table);
8743 Ok(self.local_gateway_route_tables.get(&id).unwrap())
8744 }
8745
8746 pub fn delete_local_gateway_route_table(
8747 &mut self,
8748 table_id: &str,
8749 ) -> Result<LocalGatewayRouteTable, Ec2Error> {
8750 if !self.local_gateway_route_tables.contains_key(table_id) {
8751 return Err(Ec2Error::InvalidLocalGatewayRouteTableNotFound(
8752 table_id.to_string(),
8753 ));
8754 }
8755 let has_routes = self
8756 .local_gateway_routes
8757 .keys()
8758 .any(|(rid, _)| rid == table_id);
8759 if has_routes {
8760 return Err(Ec2Error::LocalGatewayRouteTableInUse(table_id.to_string()));
8761 }
8762 let mut t = self.local_gateway_route_tables.remove(table_id).unwrap();
8763 t.state = "deleting".to_string();
8764 Ok(t)
8765 }
8766
8767 #[allow(clippy::too_many_arguments)]
8768 pub fn create_local_gateway_route(
8769 &mut self,
8770 local_gateway_route_table_id: &str,
8771 destination_cidr_block: String,
8772 owner_id: &str,
8773 local_gateway_virtual_interface_group_id: Option<String>,
8774 network_interface_id: Option<String>,
8775 subnet_id: Option<String>,
8776 destination_prefix_list_id: Option<String>,
8777 coip_pool_id: Option<String>,
8778 ) -> Result<&LocalGatewayRoute, Ec2Error> {
8779 let table_arn = match self
8780 .local_gateway_route_tables
8781 .get(local_gateway_route_table_id)
8782 {
8783 Some(t) => Some(t.local_gateway_route_table_arn.clone()),
8784 None => {
8785 return Err(Ec2Error::InvalidLocalGatewayRouteTableNotFound(
8786 local_gateway_route_table_id.to_string(),
8787 ));
8788 }
8789 };
8790 let r#type = "static".to_string();
8791 let route = LocalGatewayRoute {
8792 destination_cidr_block: destination_cidr_block.clone(),
8793 local_gateway_route_table_id: local_gateway_route_table_id.to_string(),
8794 r#type,
8795 state: "active".to_string(),
8796 local_gateway_route_table_arn: table_arn,
8797 owner_id: owner_id.to_string(),
8798 subnet_id,
8799 network_interface_id,
8800 destination_prefix_list_id,
8801 coip_pool_id,
8802 local_gateway_virtual_interface_group_id,
8803 };
8804 let key = (
8805 local_gateway_route_table_id.to_string(),
8806 destination_cidr_block,
8807 );
8808 self.local_gateway_routes.insert(key.clone(), route);
8809 Ok(self.local_gateway_routes.get(&key).unwrap())
8810 }
8811
8812 pub fn delete_local_gateway_route(
8813 &mut self,
8814 table_id: &str,
8815 destination_cidr_block: &str,
8816 ) -> Result<LocalGatewayRoute, Ec2Error> {
8817 let key = (table_id.to_string(), destination_cidr_block.to_string());
8818 let mut r = self.local_gateway_routes.remove(&key).ok_or_else(|| {
8819 Ec2Error::InvalidLocalGatewayRouteNotFound(
8820 table_id.to_string(),
8821 destination_cidr_block.to_string(),
8822 )
8823 })?;
8824 r.state = "deleted".to_string();
8825 Ok(r)
8826 }
8827
8828 pub fn modify_local_gateway_route(
8829 &mut self,
8830 table_id: &str,
8831 destination_cidr_block: &str,
8832 new_subnet_id: Option<String>,
8833 new_network_interface_id: Option<String>,
8834 ) -> Result<&LocalGatewayRoute, Ec2Error> {
8835 let key = (table_id.to_string(), destination_cidr_block.to_string());
8836 let route = self.local_gateway_routes.get_mut(&key).ok_or_else(|| {
8837 Ec2Error::InvalidLocalGatewayRouteNotFound(
8838 table_id.to_string(),
8839 destination_cidr_block.to_string(),
8840 )
8841 })?;
8842 if new_subnet_id.is_some() {
8843 route.subnet_id = new_subnet_id;
8844 }
8845 if new_network_interface_id.is_some() {
8846 route.network_interface_id = new_network_interface_id;
8847 }
8848 Ok(self.local_gateway_routes.get(&key).unwrap())
8849 }
8850
8851 pub fn create_local_gateway_virtual_interface(
8852 &mut self,
8853 local_gateway_virtual_interface_group_id: &str,
8854 vlan: i32,
8855 local_address: String,
8856 peer_address: String,
8857 peer_bgp_asn: Option<i32>,
8858 peer_bgp_asn_extended: Option<i64>,
8859 owner_id: &str,
8860 tags: Tags,
8861 ) -> Result<&LocalGatewayVirtualInterface, Ec2Error> {
8862 let group = self
8864 .local_gateway_virtual_interface_groups
8865 .get(local_gateway_virtual_interface_group_id)
8866 .ok_or_else(|| {
8867 Ec2Error::InvalidLocalGatewayVirtualInterfaceGroupNotFound(
8868 local_gateway_virtual_interface_group_id.to_string(),
8869 )
8870 })?;
8871 let local_gateway_id = group.local_gateway_id.clone();
8872 let local_bgp_asn = group.local_bgp_asn;
8873 let id = self.next_local_gateway_virtual_interface_id();
8874 let arn = format!("arn:aws:ec2:us-east-1:{owner_id}:local-gateway-virtual-interface/{id}");
8875 let vif = LocalGatewayVirtualInterface {
8876 local_gateway_virtual_interface_id: id.clone(),
8877 local_gateway_id,
8878 vlan,
8879 local_address,
8880 peer_address,
8881 local_bgp_asn,
8882 peer_bgp_asn: peer_bgp_asn.unwrap_or(0),
8883 owner_id: owner_id.to_string(),
8884 tags,
8885 configuration_state: "available".to_string(),
8886 peer_bgp_asn_extended,
8887 local_gateway_virtual_interface_arn: Some(arn),
8888 };
8889 self.local_gateway_virtual_interfaces
8890 .insert(id.clone(), vif);
8891 let group = self
8893 .local_gateway_virtual_interface_groups
8894 .get_mut(local_gateway_virtual_interface_group_id)
8895 .unwrap();
8896 group.local_gateway_virtual_interface_ids.push(id.clone());
8897 Ok(self.local_gateway_virtual_interfaces.get(&id).unwrap())
8898 }
8899
8900 pub fn delete_local_gateway_virtual_interface(
8901 &mut self,
8902 vif_id: &str,
8903 ) -> Result<LocalGatewayVirtualInterface, Ec2Error> {
8904 if !self.local_gateway_virtual_interfaces.contains_key(vif_id) {
8905 return Err(Ec2Error::InvalidLocalGatewayVirtualInterfaceNotFound(
8906 vif_id.to_string(),
8907 ));
8908 }
8909 if self
8911 .local_gateway_virtual_interface_groups
8912 .values()
8913 .any(|g| {
8914 g.local_gateway_virtual_interface_ids
8915 .iter()
8916 .any(|i| i == vif_id)
8917 })
8918 {
8919 return Err(Ec2Error::LocalGatewayVirtualInterfaceInUse(
8920 vif_id.to_string(),
8921 ));
8922 }
8923 let v = self
8924 .local_gateway_virtual_interfaces
8925 .remove(vif_id)
8926 .unwrap();
8927 Ok(v)
8928 }
8929
8930 pub fn create_local_gateway_virtual_interface_group(
8931 &mut self,
8932 local_gateway_id: &str,
8933 local_bgp_asn: i32,
8934 local_bgp_asn_extended: Option<i64>,
8935 owner_id: &str,
8936 tags: Tags,
8937 ) -> Result<&LocalGatewayVirtualInterfaceGroup, Ec2Error> {
8938 if !self.local_gateways.contains_key(local_gateway_id) {
8939 return Err(Ec2Error::InvalidLocalGatewayNotFound(
8940 local_gateway_id.to_string(),
8941 ));
8942 }
8943 let id = self.next_local_gateway_virtual_interface_group_id();
8944 let arn =
8945 format!("arn:aws:ec2:us-east-1:{owner_id}:local-gateway-virtual-interface-group/{id}");
8946 let group = LocalGatewayVirtualInterfaceGroup {
8947 local_gateway_virtual_interface_group_id: id.clone(),
8948 local_gateway_virtual_interface_ids: Vec::new(),
8949 local_gateway_id: local_gateway_id.to_string(),
8950 owner_id: owner_id.to_string(),
8951 tags,
8952 configuration_state: Some("available".to_string()),
8953 local_bgp_asn,
8954 local_bgp_asn_extended,
8955 local_gateway_virtual_interface_group_arn: Some(arn),
8956 };
8957 self.local_gateway_virtual_interface_groups
8958 .insert(id.clone(), group);
8959 Ok(self
8960 .local_gateway_virtual_interface_groups
8961 .get(&id)
8962 .unwrap())
8963 }
8964
8965 pub fn delete_local_gateway_virtual_interface_group(
8966 &mut self,
8967 group_id: &str,
8968 ) -> Result<LocalGatewayVirtualInterfaceGroup, Ec2Error> {
8969 let g = self
8970 .local_gateway_virtual_interface_groups
8971 .get(group_id)
8972 .ok_or_else(|| {
8973 Ec2Error::InvalidLocalGatewayVirtualInterfaceGroupNotFound(group_id.to_string())
8974 })?;
8975 if !g.local_gateway_virtual_interface_ids.is_empty() {
8976 return Err(Ec2Error::LocalGatewayVirtualInterfaceGroupInUse(
8977 group_id.to_string(),
8978 ));
8979 }
8980 if self
8982 .local_gateway_route_table_virtual_interface_group_associations
8983 .values()
8984 .any(|a| a.local_gateway_virtual_interface_group_id == group_id)
8985 {
8986 return Err(Ec2Error::LocalGatewayVirtualInterfaceGroupInUse(
8987 group_id.to_string(),
8988 ));
8989 }
8990 let g = self
8991 .local_gateway_virtual_interface_groups
8992 .remove(group_id)
8993 .unwrap();
8994 Ok(g)
8995 }
8996
8997 pub fn create_local_gateway_route_table_virtual_interface_group_association(
8998 &mut self,
8999 local_gateway_route_table_id: &str,
9000 local_gateway_virtual_interface_group_id: &str,
9001 owner_id: &str,
9002 tags: Tags,
9003 ) -> Result<&LocalGatewayRouteTableVirtualInterfaceGroupAssociation, Ec2Error> {
9004 let table = self
9005 .local_gateway_route_tables
9006 .get(local_gateway_route_table_id)
9007 .ok_or_else(|| {
9008 Ec2Error::InvalidLocalGatewayRouteTableNotFound(
9009 local_gateway_route_table_id.to_string(),
9010 )
9011 })?;
9012 let local_gateway_id = table.local_gateway_id.clone();
9013 let local_gateway_route_table_arn = table.local_gateway_route_table_arn.clone();
9014 if !self
9015 .local_gateway_virtual_interface_groups
9016 .contains_key(local_gateway_virtual_interface_group_id)
9017 {
9018 return Err(Ec2Error::InvalidLocalGatewayVirtualInterfaceGroupNotFound(
9019 local_gateway_virtual_interface_group_id.to_string(),
9020 ));
9021 }
9022 let id = self.next_local_gateway_route_table_virtual_interface_group_association_id();
9023 let assoc = LocalGatewayRouteTableVirtualInterfaceGroupAssociation {
9024 local_gateway_route_table_virtual_interface_group_association_id: id.clone(),
9025 local_gateway_virtual_interface_group_id: local_gateway_virtual_interface_group_id
9026 .to_string(),
9027 local_gateway_route_table_id: local_gateway_route_table_id.to_string(),
9028 local_gateway_route_table_arn,
9029 local_gateway_id,
9030 owner_id: owner_id.to_string(),
9031 state: "associated".to_string(),
9032 tags,
9033 };
9034 self.local_gateway_route_table_virtual_interface_group_associations
9035 .insert(id.clone(), assoc);
9036 Ok(self
9037 .local_gateway_route_table_virtual_interface_group_associations
9038 .get(&id)
9039 .unwrap())
9040 }
9041
9042 pub fn delete_local_gateway_route_table_virtual_interface_group_association(
9043 &mut self,
9044 association_id: &str,
9045 ) -> Result<LocalGatewayRouteTableVirtualInterfaceGroupAssociation, Ec2Error> {
9046 let mut a = self
9047 .local_gateway_route_table_virtual_interface_group_associations
9048 .remove(association_id)
9049 .ok_or_else(|| {
9050 Ec2Error::InvalidLocalGatewayRouteTableVirtualInterfaceGroupAssociationNotFound(
9051 association_id.to_string(),
9052 )
9053 })?;
9054 a.state = "disassociated".to_string();
9055 Ok(a)
9056 }
9057
9058 pub fn create_local_gateway_route_table_vpc_association(
9059 &mut self,
9060 local_gateway_route_table_id: &str,
9061 vpc_id: &str,
9062 owner_id: &str,
9063 tags: Tags,
9064 ) -> Result<&LocalGatewayRouteTableVpcAssociation, Ec2Error> {
9065 let table = self
9066 .local_gateway_route_tables
9067 .get(local_gateway_route_table_id)
9068 .ok_or_else(|| {
9069 Ec2Error::InvalidLocalGatewayRouteTableNotFound(
9070 local_gateway_route_table_id.to_string(),
9071 )
9072 })?;
9073 let local_gateway_id = table.local_gateway_id.clone();
9074 let local_gateway_route_table_arn = table.local_gateway_route_table_arn.clone();
9075 let id = self.next_local_gateway_route_table_vpc_association_id();
9076 let assoc = LocalGatewayRouteTableVpcAssociation {
9077 local_gateway_route_table_vpc_association_id: id.clone(),
9078 local_gateway_route_table_id: local_gateway_route_table_id.to_string(),
9079 local_gateway_route_table_arn,
9080 local_gateway_id,
9081 vpc_id: vpc_id.to_string(),
9082 owner_id: owner_id.to_string(),
9083 state: "associated".to_string(),
9084 tags,
9085 };
9086 self.local_gateway_route_table_vpc_associations
9087 .insert(id.clone(), assoc);
9088 Ok(self
9089 .local_gateway_route_table_vpc_associations
9090 .get(&id)
9091 .unwrap())
9092 }
9093
9094 pub fn delete_local_gateway_route_table_vpc_association(
9095 &mut self,
9096 association_id: &str,
9097 ) -> Result<LocalGatewayRouteTableVpcAssociation, Ec2Error> {
9098 let mut a = self
9099 .local_gateway_route_table_vpc_associations
9100 .remove(association_id)
9101 .ok_or_else(|| {
9102 Ec2Error::InvalidLocalGatewayRouteTableVpcAssociationNotFound(
9103 association_id.to_string(),
9104 )
9105 })?;
9106 a.state = "disassociated".to_string();
9107 Ok(a)
9108 }
9109
9110 fn next_route_server_id(&mut self) -> String {
9113 self.counters.route_server += 1;
9114 format!("rs-{:08x}", self.counters.route_server)
9115 }
9116
9117 fn next_route_server_endpoint_id(&mut self) -> String {
9118 self.counters.route_server_endpoint += 1;
9119 format!("rse-{:08x}", self.counters.route_server_endpoint)
9120 }
9121
9122 fn next_route_server_peer_id(&mut self) -> String {
9123 self.counters.route_server_peer += 1;
9124 format!("rsp-{:08x}", self.counters.route_server_peer)
9125 }
9126
9127 #[allow(clippy::too_many_arguments)]
9128 pub fn create_route_server(
9129 &mut self,
9130 amazon_side_asn: i64,
9131 persist_routes: Option<String>,
9132 persist_routes_duration: Option<i64>,
9133 sns_notifications_enabled: Option<bool>,
9134 owner_id: &str,
9135 tags: Tags,
9136 ) -> &RouteServer {
9137 let id = self.next_route_server_id();
9138 let arn = format!("arn:aws:ec2:us-east-1:{owner_id}:route-server/{id}");
9139 let rs = RouteServer {
9140 route_server_id: id.clone(),
9141 route_server_arn: arn,
9142 amazon_side_asn,
9143 state: "available".to_string(),
9144 persist_routes: action_to_persist_routes_state(persist_routes.as_deref()),
9147 persist_routes_duration,
9148 sns_notifications_enabled: sns_notifications_enabled.unwrap_or(false),
9149 sns_topic_arn: None,
9150 tags,
9151 };
9152 self.route_servers.insert(id.clone(), rs);
9153 self.route_servers.get(&id).unwrap()
9154 }
9155
9156 pub fn delete_route_server(&mut self, route_server_id: &str) -> Result<RouteServer, Ec2Error> {
9157 if !self.route_servers.contains_key(route_server_id) {
9158 return Err(Ec2Error::InvalidRouteServerNotFound(
9159 route_server_id.to_string(),
9160 ));
9161 }
9162 let has_endpoints = self
9163 .route_server_endpoints
9164 .values()
9165 .any(|e| e.route_server_id == route_server_id);
9166 let has_associations = self
9167 .route_server_associations
9168 .keys()
9169 .any(|(rsid, _)| rsid == route_server_id);
9170 if has_endpoints || has_associations {
9171 return Err(Ec2Error::RouteServerInUse(route_server_id.to_string()));
9172 }
9173 let mut rs = self.route_servers.remove(route_server_id).unwrap();
9174 rs.state = "deleting".to_string();
9175 Ok(rs)
9176 }
9177
9178 pub fn modify_route_server(
9179 &mut self,
9180 route_server_id: &str,
9181 persist_routes: Option<String>,
9182 persist_routes_duration: Option<i64>,
9183 sns_notifications_enabled: Option<bool>,
9184 ) -> Result<&RouteServer, Ec2Error> {
9185 let rs = self
9186 .route_servers
9187 .get_mut(route_server_id)
9188 .ok_or_else(|| Ec2Error::InvalidRouteServerNotFound(route_server_id.to_string()))?;
9189 if let Some(p) = persist_routes {
9190 rs.persist_routes = action_to_persist_routes_state(Some(p.as_str()));
9191 }
9192 if let Some(d) = persist_routes_duration {
9193 rs.persist_routes_duration = Some(d);
9194 }
9195 if let Some(e) = sns_notifications_enabled {
9196 rs.sns_notifications_enabled = e;
9197 }
9198 Ok(self.route_servers.get(route_server_id).unwrap())
9199 }
9200
9201 pub fn associate_route_server(
9202 &mut self,
9203 route_server_id: &str,
9204 vpc_id: &str,
9205 ) -> Result<&RouteServerAssociation, Ec2Error> {
9206 if !self.route_servers.contains_key(route_server_id) {
9207 return Err(Ec2Error::InvalidRouteServerNotFound(
9208 route_server_id.to_string(),
9209 ));
9210 }
9211 if !self.vpcs.contains_key(vpc_id) {
9212 return Err(Ec2Error::VpcNotFound(vpc_id.to_string()));
9213 }
9214 let key = (route_server_id.to_string(), vpc_id.to_string());
9215 let assoc = RouteServerAssociation {
9216 route_server_id: route_server_id.to_string(),
9217 vpc_id: vpc_id.to_string(),
9218 state: "associated".to_string(),
9219 propagations: Vec::new(),
9220 };
9221 self.route_server_associations.insert(key.clone(), assoc);
9222 Ok(self.route_server_associations.get(&key).unwrap())
9223 }
9224
9225 pub fn disassociate_route_server(
9226 &mut self,
9227 route_server_id: &str,
9228 vpc_id: &str,
9229 ) -> Result<RouteServerAssociation, Ec2Error> {
9230 if !self.route_servers.contains_key(route_server_id) {
9231 return Err(Ec2Error::InvalidRouteServerNotFound(
9232 route_server_id.to_string(),
9233 ));
9234 }
9235 let key = (route_server_id.to_string(), vpc_id.to_string());
9236 let mut a = self.route_server_associations.remove(&key).ok_or_else(|| {
9237 Ec2Error::InvalidRouteServerAssociationNotFound(
9238 route_server_id.to_string(),
9239 vpc_id.to_string(),
9240 )
9241 })?;
9242 a.state = "disassociated".to_string();
9243 Ok(a)
9244 }
9245
9246 pub fn create_route_server_endpoint(
9247 &mut self,
9248 route_server_id: &str,
9249 subnet_id: &str,
9250 tags: Tags,
9251 ) -> Result<&RouteServerEndpoint, Ec2Error> {
9252 if !self.route_servers.contains_key(route_server_id) {
9253 return Err(Ec2Error::InvalidRouteServerNotFound(
9254 route_server_id.to_string(),
9255 ));
9256 }
9257 let vpc_id = self
9258 .subnets
9259 .get(subnet_id)
9260 .map(|s| s.vpc_id.clone())
9261 .unwrap_or_default();
9262 let id = self.next_route_server_endpoint_id();
9263 let eni_id = format!("eni-rse{:08x}", self.counters.route_server_endpoint);
9267 let endpoint = RouteServerEndpoint {
9268 route_server_endpoint_id: id.clone(),
9269 route_server_id: route_server_id.to_string(),
9270 vpc_id,
9271 subnet_id: subnet_id.to_string(),
9272 eni_id,
9273 eni_address: None,
9274 state: "available".to_string(),
9275 failure_reason: None,
9276 tags,
9277 };
9278 self.route_server_endpoints.insert(id.clone(), endpoint);
9279 Ok(self.route_server_endpoints.get(&id).unwrap())
9280 }
9281
9282 pub fn delete_route_server_endpoint(
9283 &mut self,
9284 endpoint_id: &str,
9285 ) -> Result<RouteServerEndpoint, Ec2Error> {
9286 if !self.route_server_endpoints.contains_key(endpoint_id) {
9287 return Err(Ec2Error::InvalidRouteServerEndpointNotFound(
9288 endpoint_id.to_string(),
9289 ));
9290 }
9291 let has_peers = self
9292 .route_server_peers
9293 .values()
9294 .any(|p| p.route_server_endpoint_id == endpoint_id);
9295 if has_peers {
9296 return Err(Ec2Error::RouteServerEndpointInUse(endpoint_id.to_string()));
9297 }
9298 let mut ep = self.route_server_endpoints.remove(endpoint_id).unwrap();
9299 ep.state = "deleting".to_string();
9300 Ok(ep)
9301 }
9302
9303 pub fn create_route_server_peer(
9304 &mut self,
9305 endpoint_id: &str,
9306 peer_address: String,
9307 peer_asn: i64,
9308 peer_liveness_detection: Option<String>,
9309 tags: Tags,
9310 ) -> Result<&RouteServerPeer, Ec2Error> {
9311 let endpoint = self
9312 .route_server_endpoints
9313 .get(endpoint_id)
9314 .ok_or_else(|| Ec2Error::InvalidRouteServerEndpointNotFound(endpoint_id.to_string()))?;
9315 let route_server_id = endpoint.route_server_id.clone();
9316 let vpc_id = endpoint.vpc_id.clone();
9317 let subnet_id = endpoint.subnet_id.clone();
9318 let endpoint_eni_id = Some(endpoint.eni_id.clone());
9319 let endpoint_eni_address = endpoint.eni_address.clone();
9320 let id = self.next_route_server_peer_id();
9321 let liveness = peer_liveness_detection.unwrap_or_else(|| "bgp-keepalive".to_string());
9322 let bgp_options = RouteServerBgpOptions {
9323 peer_asn: Some(peer_asn),
9324 peer_liveness_detection: Some(liveness.clone()),
9325 };
9326 let options = RouteServerPeerOptions {
9327 peer_asn,
9328 peer_liveness_detection: liveness,
9329 bgp_options: Some(bgp_options),
9330 };
9331 let peer = RouteServerPeer {
9332 route_server_peer_id: id.clone(),
9333 route_server_endpoint_id: endpoint_id.to_string(),
9334 route_server_id,
9335 vpc_id,
9336 subnet_id,
9337 peer_address,
9338 state: "available".to_string(),
9339 failure_reason: None,
9340 options,
9341 endpoint_eni_id,
9342 endpoint_eni_address,
9343 tags,
9344 };
9345 self.route_server_peers.insert(id.clone(), peer);
9346 Ok(self.route_server_peers.get(&id).unwrap())
9347 }
9348
9349 pub fn delete_route_server_peer(&mut self, peer_id: &str) -> Result<RouteServerPeer, Ec2Error> {
9350 let mut p = self
9351 .route_server_peers
9352 .remove(peer_id)
9353 .ok_or_else(|| Ec2Error::InvalidRouteServerPeerNotFound(peer_id.to_string()))?;
9354 p.state = "deleting".to_string();
9355 Ok(p)
9356 }
9357
9358 fn next_verified_access_instance_id(&mut self) -> String {
9361 self.counters.verified_access_instance += 1;
9362 format!("vai-{:08x}", self.counters.verified_access_instance)
9363 }
9364
9365 fn next_verified_access_trust_provider_id(&mut self) -> String {
9366 self.counters.verified_access_trust_provider += 1;
9367 format!("vatp-{:08x}", self.counters.verified_access_trust_provider)
9368 }
9369
9370 fn next_verified_access_group_id(&mut self) -> String {
9371 self.counters.verified_access_group += 1;
9372 format!("vagr-{:08x}", self.counters.verified_access_group)
9373 }
9374
9375 fn next_verified_access_endpoint_id(&mut self) -> String {
9376 self.counters.verified_access_endpoint += 1;
9377 format!("vae-{:08x}", self.counters.verified_access_endpoint)
9378 }
9379
9380 pub fn create_verified_access_instance(
9381 &mut self,
9382 description: Option<String>,
9383 fips_enabled: bool,
9384 cidr_endpoints_custom_subdomain: Option<String>,
9385 name: Option<String>,
9386 tags: Tags,
9387 ) -> &VerifiedAccessInstance {
9388 let id = self.next_verified_access_instance_id();
9389 let now = self.now_iso();
9390 let inst = VerifiedAccessInstance {
9391 verified_access_instance_id: id.clone(),
9392 description,
9393 creation_time: now.clone(),
9394 last_updated_time: now,
9395 fips_enabled,
9396 cidr_endpoints_custom_subdomain,
9397 name,
9398 trust_provider_ids: Vec::new(),
9399 tags,
9400 };
9401 self.verified_access_instances.insert(id.clone(), inst);
9402 self.verified_access_instances.get(&id).unwrap()
9403 }
9404
9405 #[allow(clippy::too_many_arguments)]
9406 pub fn create_verified_access_trust_provider(
9407 &mut self,
9408 description: Option<String>,
9409 trust_provider_type: String,
9410 user_trust_provider_type: Option<String>,
9411 device_trust_provider_type: Option<String>,
9412 oidc_options: Option<VerifiedAccessOidcOptions>,
9413 device_options: Option<VerifiedAccessDeviceOptions>,
9414 native_application_oidc_options: Option<VerifiedAccessNativeApplicationOidcOptions>,
9415 policy_reference_name: String,
9416 sse_specification: VerifiedAccessSseSpecification,
9417 tags: Tags,
9418 ) -> &VerifiedAccessTrustProvider {
9419 let id = self.next_verified_access_trust_provider_id();
9420 let now = self.now_iso();
9421 let tp = VerifiedAccessTrustProvider {
9422 verified_access_trust_provider_id: id.clone(),
9423 description,
9424 trust_provider_type,
9425 user_trust_provider_type,
9426 device_trust_provider_type,
9427 oidc_options,
9428 device_options,
9429 native_application_oidc_options,
9430 policy_reference_name,
9431 creation_time: now.clone(),
9432 last_updated_time: now,
9433 sse_specification,
9434 tags,
9435 };
9436 self.verified_access_trust_providers.insert(id.clone(), tp);
9437 self.verified_access_trust_providers.get(&id).unwrap()
9438 }
9439
9440 pub fn create_verified_access_group(
9441 &mut self,
9442 instance_id: String,
9443 owner: String,
9444 description: Option<String>,
9445 sse_specification: VerifiedAccessSseSpecification,
9446 policy_document: Option<String>,
9447 tags: Tags,
9448 ) -> Result<&VerifiedAccessGroup, Ec2Error> {
9449 if !self.verified_access_instances.contains_key(&instance_id) {
9450 return Err(Ec2Error::InvalidVerifiedAccessInstanceNotFound(
9451 instance_id.clone(),
9452 ));
9453 }
9454 let id = self.next_verified_access_group_id();
9455 let arn = format!(
9456 "arn:aws:ec2:us-east-1:{owner}:verified-access-group/{id}",
9457 owner = owner,
9458 id = id,
9459 );
9460 let now = self.now_iso();
9461 let policy_enabled = policy_document.is_some();
9462 let group = VerifiedAccessGroup {
9463 verified_access_group_id: id.clone(),
9464 verified_access_group_arn: arn,
9465 verified_access_instance_id: instance_id,
9466 owner,
9467 description,
9468 creation_time: now.clone(),
9469 last_updated_time: now,
9470 deletion_time: None,
9471 sse_specification,
9472 policy_document,
9473 policy_enabled,
9474 tags,
9475 };
9476 self.verified_access_groups.insert(id.clone(), group);
9477 Ok(self.verified_access_groups.get(&id).unwrap())
9478 }
9479
9480 #[allow(clippy::too_many_arguments)]
9481 pub fn create_verified_access_endpoint(
9482 &mut self,
9483 verified_access_group_id: String,
9484 application_domain: Option<String>,
9485 endpoint_type: String,
9486 attachment_type: String,
9487 domain_certificate_arn: Option<String>,
9488 endpoint_domain_prefix: Option<String>,
9489 security_group_ids: Vec<String>,
9490 load_balancer_options: Option<VerifiedAccessEndpointLoadBalancerOptions>,
9491 network_interface_options: Option<VerifiedAccessEndpointEniOptions>,
9492 cidr_options: Option<VerifiedAccessEndpointCidrOptions>,
9493 rds_options: Option<VerifiedAccessEndpointRdsOptions>,
9494 description: Option<String>,
9495 policy_document: Option<String>,
9496 sse_specification: VerifiedAccessSseSpecification,
9497 tags: Tags,
9498 ) -> Result<&VerifiedAccessEndpoint, Ec2Error> {
9499 let group = self
9500 .verified_access_groups
9501 .get(&verified_access_group_id)
9502 .ok_or_else(|| {
9503 Ec2Error::InvalidVerifiedAccessGroupNotFound(verified_access_group_id.clone())
9504 })?;
9505 let instance_id = group.verified_access_instance_id.clone();
9506 let id = self.next_verified_access_endpoint_id();
9507 let now = self.now_iso();
9508 let endpoint_domain = match (
9509 endpoint_domain_prefix.as_deref(),
9510 application_domain.as_deref(),
9511 ) {
9512 (Some(p), Some(d)) => Some(format!("{p}.{d}")),
9513 (Some(p), None) => Some(p.to_string()),
9514 (None, Some(d)) => Some(d.to_string()),
9515 (None, None) => None,
9516 };
9517 let policy_enabled = policy_document.is_some();
9518 let ep = VerifiedAccessEndpoint {
9519 verified_access_endpoint_id: id.clone(),
9520 verified_access_instance_id: instance_id,
9521 verified_access_group_id,
9522 application_domain,
9523 endpoint_type,
9524 attachment_type,
9525 domain_certificate_arn,
9526 endpoint_domain,
9527 device_validation_domain: None,
9528 security_group_ids,
9529 load_balancer_options,
9530 network_interface_options,
9531 cidr_options,
9532 rds_options,
9533 status_code: "active".to_string(),
9534 status_message: None,
9535 description,
9536 creation_time: now.clone(),
9537 last_updated_time: now,
9538 deletion_time: None,
9539 sse_specification,
9540 policy_document,
9541 policy_enabled,
9542 tags,
9543 };
9544 self.verified_access_endpoints.insert(id.clone(), ep);
9545 Ok(self.verified_access_endpoints.get(&id).unwrap())
9546 }
9547
9548 pub fn attach_verified_access_trust_provider(
9549 &mut self,
9550 instance_id: &str,
9551 trust_provider_id: &str,
9552 ) -> Result<(VerifiedAccessTrustProvider, VerifiedAccessInstance), Ec2Error> {
9553 if !self.verified_access_instances.contains_key(instance_id) {
9554 return Err(Ec2Error::InvalidVerifiedAccessInstanceNotFound(
9555 instance_id.to_string(),
9556 ));
9557 }
9558 if !self
9559 .verified_access_trust_providers
9560 .contains_key(trust_provider_id)
9561 {
9562 return Err(Ec2Error::InvalidVerifiedAccessTrustProviderNotFound(
9563 trust_provider_id.to_string(),
9564 ));
9565 }
9566 let key = (instance_id.to_string(), trust_provider_id.to_string());
9567 self.verified_access_trust_provider_attachments.insert(
9568 key,
9569 VerifiedAccessTrustProviderAttachment {
9570 instance_id: instance_id.to_string(),
9571 trust_provider_id: trust_provider_id.to_string(),
9572 },
9573 );
9574 let now = self.now_iso();
9577 if let Some(inst) = self.verified_access_instances.get_mut(instance_id) {
9578 if !inst
9579 .trust_provider_ids
9580 .iter()
9581 .any(|x| x == trust_provider_id)
9582 {
9583 inst.trust_provider_ids.push(trust_provider_id.to_string());
9584 }
9585 inst.last_updated_time = now.clone();
9586 }
9587 if let Some(tp) = self
9588 .verified_access_trust_providers
9589 .get_mut(trust_provider_id)
9590 {
9591 tp.last_updated_time = now;
9592 }
9593 let inst = self
9594 .verified_access_instances
9595 .get(instance_id)
9596 .unwrap()
9597 .clone();
9598 let tp = self
9599 .verified_access_trust_providers
9600 .get(trust_provider_id)
9601 .unwrap()
9602 .clone();
9603 Ok((tp, inst))
9604 }
9605
9606 pub fn detach_verified_access_trust_provider(
9607 &mut self,
9608 instance_id: &str,
9609 trust_provider_id: &str,
9610 ) -> Result<(VerifiedAccessTrustProvider, VerifiedAccessInstance), Ec2Error> {
9611 if !self.verified_access_instances.contains_key(instance_id) {
9612 return Err(Ec2Error::InvalidVerifiedAccessInstanceNotFound(
9613 instance_id.to_string(),
9614 ));
9615 }
9616 if !self
9617 .verified_access_trust_providers
9618 .contains_key(trust_provider_id)
9619 {
9620 return Err(Ec2Error::InvalidVerifiedAccessTrustProviderNotFound(
9621 trust_provider_id.to_string(),
9622 ));
9623 }
9624 let key = (instance_id.to_string(), trust_provider_id.to_string());
9625 if self
9626 .verified_access_trust_provider_attachments
9627 .remove(&key)
9628 .is_none()
9629 {
9630 return Err(
9631 Ec2Error::InvalidVerifiedAccessTrustProviderAttachmentNotFound(
9632 instance_id.to_string(),
9633 trust_provider_id.to_string(),
9634 ),
9635 );
9636 }
9637 let now = self.now_iso();
9638 if let Some(inst) = self.verified_access_instances.get_mut(instance_id) {
9639 inst.trust_provider_ids.retain(|x| x != trust_provider_id);
9640 inst.last_updated_time = now.clone();
9641 }
9642 if let Some(tp) = self
9643 .verified_access_trust_providers
9644 .get_mut(trust_provider_id)
9645 {
9646 tp.last_updated_time = now;
9647 }
9648 let inst = self
9649 .verified_access_instances
9650 .get(instance_id)
9651 .unwrap()
9652 .clone();
9653 let tp = self
9654 .verified_access_trust_providers
9655 .get(trust_provider_id)
9656 .unwrap()
9657 .clone();
9658 Ok((tp, inst))
9659 }
9660
9661 pub fn delete_verified_access_endpoint(
9662 &mut self,
9663 endpoint_id: &str,
9664 ) -> Result<VerifiedAccessEndpoint, Ec2Error> {
9665 let mut ep = self
9666 .verified_access_endpoints
9667 .remove(endpoint_id)
9668 .ok_or_else(|| {
9669 Ec2Error::InvalidVerifiedAccessEndpointNotFound(endpoint_id.to_string())
9670 })?;
9671 let now = self.now_iso();
9672 ep.status_code = "deleted".to_string();
9673 ep.deletion_time = Some(now.clone());
9674 ep.last_updated_time = now;
9675 Ok(ep)
9676 }
9677
9678 pub fn delete_verified_access_group(
9679 &mut self,
9680 group_id: &str,
9681 ) -> Result<VerifiedAccessGroup, Ec2Error> {
9682 if !self.verified_access_groups.contains_key(group_id) {
9683 return Err(Ec2Error::InvalidVerifiedAccessGroupNotFound(
9684 group_id.to_string(),
9685 ));
9686 }
9687 let in_use = self
9688 .verified_access_endpoints
9689 .values()
9690 .any(|e| e.verified_access_group_id == group_id);
9691 if in_use {
9692 return Err(Ec2Error::VerifiedAccessGroupInUse(group_id.to_string()));
9693 }
9694 let mut group = self.verified_access_groups.remove(group_id).unwrap();
9695 let now = self.now_iso();
9696 group.deletion_time = Some(now.clone());
9697 group.last_updated_time = now;
9698 Ok(group)
9699 }
9700
9701 pub fn delete_verified_access_instance(
9702 &mut self,
9703 instance_id: &str,
9704 ) -> Result<VerifiedAccessInstance, Ec2Error> {
9705 if !self.verified_access_instances.contains_key(instance_id) {
9706 return Err(Ec2Error::InvalidVerifiedAccessInstanceNotFound(
9707 instance_id.to_string(),
9708 ));
9709 }
9710 let has_attachments = self
9711 .verified_access_trust_provider_attachments
9712 .keys()
9713 .any(|(iid, _)| iid == instance_id);
9714 let has_groups = self
9715 .verified_access_groups
9716 .values()
9717 .any(|g| g.verified_access_instance_id == instance_id);
9718 if has_attachments || has_groups {
9719 return Err(Ec2Error::VerifiedAccessInstanceInUse(
9720 instance_id.to_string(),
9721 ));
9722 }
9723 self.verified_access_instance_logging_configurations
9725 .remove(instance_id);
9726 Ok(self.verified_access_instances.remove(instance_id).unwrap())
9727 }
9728
9729 pub fn delete_verified_access_trust_provider(
9730 &mut self,
9731 trust_provider_id: &str,
9732 ) -> Result<VerifiedAccessTrustProvider, Ec2Error> {
9733 if !self
9734 .verified_access_trust_providers
9735 .contains_key(trust_provider_id)
9736 {
9737 return Err(Ec2Error::InvalidVerifiedAccessTrustProviderNotFound(
9738 trust_provider_id.to_string(),
9739 ));
9740 }
9741 let attached = self
9742 .verified_access_trust_provider_attachments
9743 .keys()
9744 .any(|(_, tp)| tp == trust_provider_id);
9745 if attached {
9746 return Err(Ec2Error::VerifiedAccessTrustProviderInUse(
9747 trust_provider_id.to_string(),
9748 ));
9749 }
9750 Ok(self
9751 .verified_access_trust_providers
9752 .remove(trust_provider_id)
9753 .unwrap())
9754 }
9755
9756 pub fn modify_verified_access_instance(
9757 &mut self,
9758 instance_id: &str,
9759 description: Option<String>,
9760 cidr_endpoints_custom_subdomain: Option<String>,
9761 ) -> Result<&VerifiedAccessInstance, Ec2Error> {
9762 let now = self.now_iso();
9763 let inst = self
9764 .verified_access_instances
9765 .get_mut(instance_id)
9766 .ok_or_else(|| {
9767 Ec2Error::InvalidVerifiedAccessInstanceNotFound(instance_id.to_string())
9768 })?;
9769 if let Some(d) = description {
9770 inst.description = Some(d);
9771 }
9772 if let Some(s) = cidr_endpoints_custom_subdomain {
9773 inst.cidr_endpoints_custom_subdomain = Some(s);
9774 }
9775 inst.last_updated_time = now;
9776 Ok(self.verified_access_instances.get(instance_id).unwrap())
9777 }
9778
9779 #[allow(clippy::too_many_arguments)]
9780 pub fn modify_verified_access_trust_provider(
9781 &mut self,
9782 trust_provider_id: &str,
9783 description: Option<String>,
9784 oidc_options: Option<VerifiedAccessOidcOptions>,
9785 device_options: Option<VerifiedAccessDeviceOptions>,
9786 sse_specification: Option<VerifiedAccessSseSpecification>,
9787 ) -> Result<&VerifiedAccessTrustProvider, Ec2Error> {
9788 let now = self.now_iso();
9789 let tp = self
9790 .verified_access_trust_providers
9791 .get_mut(trust_provider_id)
9792 .ok_or_else(|| {
9793 Ec2Error::InvalidVerifiedAccessTrustProviderNotFound(trust_provider_id.to_string())
9794 })?;
9795 if let Some(d) = description {
9796 tp.description = Some(d);
9797 }
9798 if let Some(o) = oidc_options {
9799 let mut existing = tp.oidc_options.clone().unwrap_or_default();
9801 if o.issuer.is_some() {
9802 existing.issuer = o.issuer;
9803 }
9804 if o.authorization_endpoint.is_some() {
9805 existing.authorization_endpoint = o.authorization_endpoint;
9806 }
9807 if o.token_endpoint.is_some() {
9808 existing.token_endpoint = o.token_endpoint;
9809 }
9810 if o.user_info_endpoint.is_some() {
9811 existing.user_info_endpoint = o.user_info_endpoint;
9812 }
9813 if o.client_id.is_some() {
9814 existing.client_id = o.client_id;
9815 }
9816 if o.client_secret.is_some() {
9817 existing.client_secret = o.client_secret;
9818 }
9819 if o.scope.is_some() {
9820 existing.scope = o.scope;
9821 }
9822 tp.oidc_options = Some(existing);
9823 }
9824 if let Some(d) = device_options {
9825 let mut existing = tp.device_options.clone().unwrap_or_default();
9826 if d.public_signing_key_url.is_some() {
9827 existing.public_signing_key_url = d.public_signing_key_url;
9828 }
9829 tp.device_options = Some(existing);
9830 }
9831 if let Some(s) = sse_specification {
9832 tp.sse_specification = s;
9833 }
9834 tp.last_updated_time = now;
9835 Ok(self
9836 .verified_access_trust_providers
9837 .get(trust_provider_id)
9838 .unwrap())
9839 }
9840
9841 pub fn modify_verified_access_group(
9842 &mut self,
9843 group_id: &str,
9844 description: Option<String>,
9845 verified_access_instance_id: Option<String>,
9846 ) -> Result<&VerifiedAccessGroup, Ec2Error> {
9847 if let Some(ref iid) = verified_access_instance_id {
9848 if !self.verified_access_instances.contains_key(iid) {
9849 return Err(Ec2Error::InvalidVerifiedAccessInstanceNotFound(iid.clone()));
9850 }
9851 }
9852 let now = self.now_iso();
9853 let group = self
9854 .verified_access_groups
9855 .get_mut(group_id)
9856 .ok_or_else(|| Ec2Error::InvalidVerifiedAccessGroupNotFound(group_id.to_string()))?;
9857 if let Some(d) = description {
9858 group.description = Some(d);
9859 }
9860 if let Some(iid) = verified_access_instance_id {
9861 group.verified_access_instance_id = iid;
9862 }
9863 group.last_updated_time = now;
9864 Ok(self.verified_access_groups.get(group_id).unwrap())
9865 }
9866
9867 pub fn modify_verified_access_group_policy(
9868 &mut self,
9869 group_id: &str,
9870 policy_document: Option<String>,
9871 policy_enabled: Option<bool>,
9872 sse_specification: Option<VerifiedAccessSseSpecification>,
9873 ) -> Result<&VerifiedAccessGroup, Ec2Error> {
9874 let now = self.now_iso();
9875 let group = self
9876 .verified_access_groups
9877 .get_mut(group_id)
9878 .ok_or_else(|| Ec2Error::InvalidVerifiedAccessGroupNotFound(group_id.to_string()))?;
9879 if let Some(d) = policy_document {
9880 group.policy_document = Some(d);
9881 }
9882 if let Some(b) = policy_enabled {
9883 group.policy_enabled = b;
9884 }
9885 if let Some(s) = sse_specification {
9886 group.sse_specification = s;
9887 }
9888 group.last_updated_time = now;
9889 Ok(self.verified_access_groups.get(group_id).unwrap())
9890 }
9891
9892 #[allow(clippy::too_many_arguments)]
9893 pub fn modify_verified_access_endpoint(
9894 &mut self,
9895 endpoint_id: &str,
9896 description: Option<String>,
9897 verified_access_group_id: Option<String>,
9898 load_balancer_options: Option<VerifiedAccessEndpointLoadBalancerOptions>,
9899 network_interface_options: Option<VerifiedAccessEndpointEniOptions>,
9900 cidr_options: Option<VerifiedAccessEndpointCidrOptions>,
9901 rds_options: Option<VerifiedAccessEndpointRdsOptions>,
9902 ) -> Result<&VerifiedAccessEndpoint, Ec2Error> {
9903 if let Some(ref gid) = verified_access_group_id {
9904 if !self.verified_access_groups.contains_key(gid) {
9905 return Err(Ec2Error::InvalidVerifiedAccessGroupNotFound(gid.clone()));
9906 }
9907 }
9908 let now = self.now_iso();
9909 let ep = self
9910 .verified_access_endpoints
9911 .get_mut(endpoint_id)
9912 .ok_or_else(|| {
9913 Ec2Error::InvalidVerifiedAccessEndpointNotFound(endpoint_id.to_string())
9914 })?;
9915 if let Some(d) = description {
9916 ep.description = Some(d);
9917 }
9918 if let Some(gid) = verified_access_group_id {
9919 ep.verified_access_group_id = gid;
9920 }
9921 if let Some(o) = load_balancer_options {
9922 ep.load_balancer_options = Some(o);
9923 }
9924 if let Some(o) = network_interface_options {
9925 ep.network_interface_options = Some(o);
9926 }
9927 if let Some(o) = cidr_options {
9928 ep.cidr_options = Some(o);
9929 }
9930 if let Some(o) = rds_options {
9931 ep.rds_options = Some(o);
9932 }
9933 ep.last_updated_time = now;
9934 Ok(self.verified_access_endpoints.get(endpoint_id).unwrap())
9935 }
9936
9937 pub fn modify_verified_access_endpoint_policy(
9938 &mut self,
9939 endpoint_id: &str,
9940 policy_document: Option<String>,
9941 policy_enabled: Option<bool>,
9942 sse_specification: Option<VerifiedAccessSseSpecification>,
9943 ) -> Result<&VerifiedAccessEndpoint, Ec2Error> {
9944 let now = self.now_iso();
9945 let ep = self
9946 .verified_access_endpoints
9947 .get_mut(endpoint_id)
9948 .ok_or_else(|| {
9949 Ec2Error::InvalidVerifiedAccessEndpointNotFound(endpoint_id.to_string())
9950 })?;
9951 if let Some(d) = policy_document {
9952 ep.policy_document = Some(d);
9953 }
9954 if let Some(b) = policy_enabled {
9955 ep.policy_enabled = b;
9956 }
9957 if let Some(s) = sse_specification {
9958 ep.sse_specification = s;
9959 }
9960 ep.last_updated_time = now;
9961 Ok(self.verified_access_endpoints.get(endpoint_id).unwrap())
9962 }
9963
9964 pub fn modify_verified_access_instance_logging_configuration(
9965 &mut self,
9966 instance_id: &str,
9967 logs: VerifiedAccessLogs,
9968 ) -> Result<&VerifiedAccessLogs, Ec2Error> {
9969 if !self.verified_access_instances.contains_key(instance_id) {
9970 return Err(Ec2Error::InvalidVerifiedAccessInstanceNotFound(
9971 instance_id.to_string(),
9972 ));
9973 }
9974 self.verified_access_instance_logging_configurations
9975 .insert(instance_id.to_string(), logs);
9976 let now = self.now_iso();
9978 if let Some(inst) = self.verified_access_instances.get_mut(instance_id) {
9979 inst.last_updated_time = now;
9980 }
9981 Ok(self
9982 .verified_access_instance_logging_configurations
9983 .get(instance_id)
9984 .unwrap())
9985 }
9986
9987 fn next_capacity_manager_data_export_id(&mut self) -> String {
9990 self.counters.capacity_manager_data_export += 1;
9991 format!("cmde-{:08x}", self.counters.capacity_manager_data_export)
9992 }
9993
9994 fn next_interruptible_capacity_reservation_allocation_id(&mut self) -> String {
9995 self.counters.interruptible_capacity_reservation_allocation += 1;
9996 format!(
9997 "icra-{:08x}",
9998 self.counters.interruptible_capacity_reservation_allocation
9999 )
10000 }
10001
10002 fn next_capacity_block_id(&mut self) -> String {
10003 self.counters.capacity_block += 1;
10004 format!("cb-{:08x}", self.counters.capacity_block)
10005 }
10006
10007 fn next_capacity_block_extension_id(&mut self) -> String {
10008 self.counters.capacity_block_extension += 1;
10009 format!("cbe-{:08x}", self.counters.capacity_block_extension)
10010 }
10011
10012 pub fn associate_capacity_reservation_billing_owner(
10014 &mut self,
10015 capacity_reservation_id: &str,
10016 unused_reservation_billing_owner_id: &str,
10017 requester_account_id: &str,
10018 ) -> Result<&BillingOwnershipOffer, Ec2Error> {
10019 if !self
10020 .capacity_reservations
10021 .contains_key(capacity_reservation_id)
10022 {
10023 return Err(Ec2Error::CapacityReservationNotFound(
10024 capacity_reservation_id.to_string(),
10025 ));
10026 }
10027 let key = (
10028 capacity_reservation_id.to_string(),
10029 unused_reservation_billing_owner_id.to_string(),
10030 );
10031 if let Some(existing) = self.billing_ownership_offers.get(&key)
10032 && existing.status == "pending"
10033 {
10034 return Err(Ec2Error::BillingOwnershipOfferAlreadyExists(
10035 capacity_reservation_id.to_string(),
10036 unused_reservation_billing_owner_id.to_string(),
10037 ));
10038 }
10039 let now = self.now_iso();
10040 let offer = BillingOwnershipOffer {
10041 capacity_reservation_id: capacity_reservation_id.to_string(),
10042 unused_reservation_billing_owner_id: unused_reservation_billing_owner_id.to_string(),
10043 requested_by: requester_account_id.to_string(),
10044 status: "pending".to_string(),
10045 status_message: None,
10046 last_update_time: now,
10047 };
10048 if let Some(cr) = self.capacity_reservations.get_mut(capacity_reservation_id) {
10049 cr.pending_billing_owner_account_id =
10050 Some(unused_reservation_billing_owner_id.to_string());
10051 }
10052 self.billing_ownership_offers.insert(key.clone(), offer);
10053 Ok(self.billing_ownership_offers.get(&key).unwrap())
10054 }
10055
10056 pub fn accept_capacity_reservation_billing_ownership(
10058 &mut self,
10059 capacity_reservation_id: &str,
10060 accepter_account_id: &str,
10061 ) -> Result<&BillingOwnershipOffer, Ec2Error> {
10062 let key = (
10063 capacity_reservation_id.to_string(),
10064 accepter_account_id.to_string(),
10065 );
10066 let now = self.now_iso();
10067 let offer = self.billing_ownership_offers.get_mut(&key).ok_or_else(|| {
10068 Ec2Error::InvalidBillingOwnershipOfferNotFound(
10069 capacity_reservation_id.to_string(),
10070 accepter_account_id.to_string(),
10071 )
10072 })?;
10073 offer.status = "accepted".to_string();
10074 offer.last_update_time = now;
10075 if let Some(cr) = self.capacity_reservations.get_mut(capacity_reservation_id) {
10076 cr.billing_owner_account_id = Some(accepter_account_id.to_string());
10077 cr.pending_billing_owner_account_id = None;
10078 }
10079 Ok(self.billing_ownership_offers.get(&key).unwrap())
10080 }
10081
10082 pub fn disassociate_capacity_reservation_billing_owner(
10084 &mut self,
10085 capacity_reservation_id: &str,
10086 unused_reservation_billing_owner_id: &str,
10087 ) -> Result<&BillingOwnershipOffer, Ec2Error> {
10088 let key = (
10089 capacity_reservation_id.to_string(),
10090 unused_reservation_billing_owner_id.to_string(),
10091 );
10092 let now = self.now_iso();
10093 let offer = self.billing_ownership_offers.get_mut(&key).ok_or_else(|| {
10094 Ec2Error::InvalidBillingOwnershipOfferNotFound(
10095 capacity_reservation_id.to_string(),
10096 unused_reservation_billing_owner_id.to_string(),
10097 )
10098 })?;
10099 offer.status = "cancelled".to_string();
10100 offer.last_update_time = now;
10101 if let Some(cr) = self.capacity_reservations.get_mut(capacity_reservation_id) {
10102 cr.pending_billing_owner_account_id = None;
10103 }
10104 Ok(self.billing_ownership_offers.get(&key).unwrap())
10105 }
10106
10107 pub fn reject_capacity_reservation_billing_ownership(
10109 &mut self,
10110 capacity_reservation_id: &str,
10111 rejecter_account_id: &str,
10112 ) -> Result<&BillingOwnershipOffer, Ec2Error> {
10113 let key = (
10114 capacity_reservation_id.to_string(),
10115 rejecter_account_id.to_string(),
10116 );
10117 let now = self.now_iso();
10118 let offer = self.billing_ownership_offers.get_mut(&key).ok_or_else(|| {
10119 Ec2Error::InvalidBillingOwnershipOfferNotFound(
10120 capacity_reservation_id.to_string(),
10121 rejecter_account_id.to_string(),
10122 )
10123 })?;
10124 offer.status = "rejected".to_string();
10125 offer.last_update_time = now;
10126 if let Some(cr) = self.capacity_reservations.get_mut(capacity_reservation_id) {
10127 cr.pending_billing_owner_account_id = None;
10128 }
10129 Ok(self.billing_ownership_offers.get(&key).unwrap())
10130 }
10131
10132 #[allow(clippy::too_many_arguments)]
10134 pub fn create_capacity_manager_data_export(
10135 &mut self,
10136 schedule: String,
10137 organization_account_ids: Vec<String>,
10138 output_format: String,
10139 s3_destination: S3DestinationOptions,
10140 tags: Tags,
10141 ) -> Result<&CapacityManagerDataExport, Ec2Error> {
10142 if !matches!(
10143 schedule.as_str(),
10144 "ondemand" | "daily" | "weekly" | "monthly" | "hourly"
10145 ) {
10146 return Err(Ec2Error::InvalidParameterValue(format!(
10147 "Invalid Schedule: {schedule}"
10148 )));
10149 }
10150 if !matches!(output_format.as_str(), "parquet" | "csv") {
10151 return Err(Ec2Error::InvalidParameterValue(format!(
10152 "Invalid OutputFormat: {output_format}"
10153 )));
10154 }
10155 let id = self.next_capacity_manager_data_export_id();
10156 let now = self.now_iso();
10157 let export = CapacityManagerDataExport {
10158 data_export_id: id.clone(),
10159 schedule,
10160 organization_account_ids,
10161 output_format,
10162 s3_destination,
10163 status: "active".to_string(),
10164 status_message: None,
10165 last_export_time: None,
10166 next_export_time: None,
10167 create_time: now,
10168 tags,
10169 };
10170 self.capacity_manager_data_exports
10171 .insert(id.clone(), export);
10172 Ok(self.capacity_manager_data_exports.get(&id).unwrap())
10173 }
10174
10175 pub fn delete_capacity_manager_data_export(
10177 &mut self,
10178 data_export_id: &str,
10179 ) -> Result<CapacityManagerDataExport, Ec2Error> {
10180 self.capacity_manager_data_exports
10181 .remove(data_export_id)
10182 .ok_or_else(|| {
10183 Ec2Error::InvalidCapacityManagerDataExportNotFound(data_export_id.to_string())
10184 })
10185 }
10186
10187 pub fn update_capacity_manager_organizations_access(
10189 &mut self,
10190 organizations_access: bool,
10191 ) -> &CapacityManagerOrganizationsAccess {
10192 let now = self.now_iso();
10193 let new_state = if organizations_access {
10194 "enabled"
10195 } else {
10196 "disabled"
10197 };
10198 self.capacity_manager_organizations_access = Some(CapacityManagerOrganizationsAccess {
10199 state: new_state.to_string(),
10200 last_updated_time: now,
10201 });
10202 self.capacity_manager_organizations_access.as_ref().unwrap()
10203 }
10204
10205 #[allow(clippy::too_many_arguments)]
10208 pub fn create_capacity_reservation_by_splitting(
10209 &mut self,
10210 source_capacity_reservation_id: &str,
10211 instance_count: i32,
10212 account_id: &str,
10213 region: &str,
10214 tags: Tags,
10215 ) -> Result<(CapacityReservation, CapacityReservation), Ec2Error> {
10216 if instance_count <= 0 {
10217 return Err(Ec2Error::InvalidParameterValue(
10218 "InstanceCount must be > 0".to_string(),
10219 ));
10220 }
10221 let source = self
10222 .capacity_reservations
10223 .get(source_capacity_reservation_id)
10224 .ok_or_else(|| {
10225 Ec2Error::CapacityReservationNotFound(source_capacity_reservation_id.to_string())
10226 })?
10227 .clone();
10228 if source.available_instance_count < instance_count {
10229 return Err(Ec2Error::InsufficientCapacityReservationCapacity(
10230 source_capacity_reservation_id.to_string(),
10231 source.available_instance_count,
10232 instance_count,
10233 ));
10234 }
10235 let src_mut = self
10237 .capacity_reservations
10238 .get_mut(source_capacity_reservation_id)
10239 .unwrap();
10240 src_mut.total_instance_count -= instance_count;
10241 src_mut.available_instance_count -= instance_count;
10242 let updated_source = src_mut.clone();
10243 let new_id = self.next_capacity_reservation_id();
10246 let arn = format!("arn:aws:ec2:{region}:{account_id}:capacity-reservation/{new_id}");
10247 let new_cr = CapacityReservation {
10248 capacity_reservation_id: new_id.clone(),
10249 capacity_reservation_arn: arn,
10250 owner_id: account_id.to_string(),
10251 instance_type: source.instance_type.clone(),
10252 instance_platform: source.instance_platform.clone(),
10253 availability_zone: source.availability_zone.clone(),
10254 tenancy: source.tenancy.clone(),
10255 total_instance_count: instance_count,
10256 available_instance_count: instance_count,
10257 ebs_optimized: source.ebs_optimized,
10258 ephemeral_storage: source.ephemeral_storage,
10259 state: "active".to_string(),
10260 start_date: self.now_iso(),
10261 end_date: source.end_date.clone(),
10262 end_date_type: source.end_date_type.clone(),
10263 instance_match_criteria: source.instance_match_criteria.clone(),
10264 create_date: self.now_iso(),
10265 outpost_arn: source.outpost_arn.clone(),
10266 placement_group_arn: source.placement_group_arn.clone(),
10267 tags,
10268 pending_billing_owner_account_id: None,
10269 billing_owner_account_id: Some(account_id.to_string()),
10270 target_capacity_reservation_id: Some(source_capacity_reservation_id.to_string()),
10271 reservation_type: source.reservation_type.clone(),
10272 commitment_info: None,
10273 };
10274 self.capacity_reservations
10275 .insert(new_id.clone(), new_cr.clone());
10276 Ok((updated_source, new_cr))
10277 }
10278
10279 pub fn move_capacity_reservation_instances(
10281 &mut self,
10282 source_capacity_reservation_id: &str,
10283 destination_capacity_reservation_id: &str,
10284 instance_count: i32,
10285 ) -> Result<(CapacityReservation, CapacityReservation), Ec2Error> {
10286 if instance_count <= 0 {
10287 return Err(Ec2Error::InvalidParameterValue(
10288 "InstanceCount must be > 0".to_string(),
10289 ));
10290 }
10291 if !self
10292 .capacity_reservations
10293 .contains_key(destination_capacity_reservation_id)
10294 {
10295 return Err(Ec2Error::CapacityReservationNotFound(
10296 destination_capacity_reservation_id.to_string(),
10297 ));
10298 }
10299 let src = self
10300 .capacity_reservations
10301 .get(source_capacity_reservation_id)
10302 .ok_or_else(|| {
10303 Ec2Error::CapacityReservationNotFound(source_capacity_reservation_id.to_string())
10304 })?
10305 .clone();
10306 if src.available_instance_count < instance_count {
10307 return Err(Ec2Error::InsufficientCapacityReservationCapacity(
10308 source_capacity_reservation_id.to_string(),
10309 src.available_instance_count,
10310 instance_count,
10311 ));
10312 }
10313 let src_mut = self
10314 .capacity_reservations
10315 .get_mut(source_capacity_reservation_id)
10316 .unwrap();
10317 src_mut.total_instance_count -= instance_count;
10318 src_mut.available_instance_count -= instance_count;
10319 let new_src = src_mut.clone();
10320 let dest_mut = self
10321 .capacity_reservations
10322 .get_mut(destination_capacity_reservation_id)
10323 .unwrap();
10324 dest_mut.total_instance_count += instance_count;
10325 dest_mut.available_instance_count += instance_count;
10326 let new_dest = dest_mut.clone();
10327 Ok((new_src, new_dest))
10328 }
10329
10330 pub fn modify_instance_capacity_reservation_attributes(
10332 &mut self,
10333 instance_id: &str,
10334 spec: CapacityReservationSpecificationResponse,
10335 ) -> Result<(), Ec2Error> {
10336 let inst = self
10337 .instances
10338 .get_mut(instance_id)
10339 .ok_or_else(|| Ec2Error::InstanceNotFound(instance_id.to_string()))?;
10340 inst.capacity_reservation_specification = Some(spec);
10341 Ok(())
10342 }
10343
10344 #[allow(clippy::too_many_arguments)]
10346 pub fn create_interruptible_capacity_reservation_allocation(
10347 &mut self,
10348 capacity_reservation_id: &str,
10349 instance_count: i32,
10350 start_date_time: String,
10351 end_date_time: String,
10352 allocation_type: String,
10353 tags: Tags,
10354 ) -> Result<&InterruptibleCapacityReservationAllocation, Ec2Error> {
10355 if !self
10356 .capacity_reservations
10357 .contains_key(capacity_reservation_id)
10358 {
10359 return Err(Ec2Error::CapacityReservationNotFound(
10360 capacity_reservation_id.to_string(),
10361 ));
10362 }
10363 if instance_count <= 0 {
10364 return Err(Ec2Error::InvalidParameterValue(
10365 "InstanceCount must be > 0".to_string(),
10366 ));
10367 }
10368 if !matches!(allocation_type.as_str(), "scheduled" | "on-demand") {
10369 return Err(Ec2Error::InvalidParameterValue(format!(
10370 "Invalid AllocationType: {allocation_type}"
10371 )));
10372 }
10373 let id = self.next_interruptible_capacity_reservation_allocation_id();
10374 let alloc = InterruptibleCapacityReservationAllocation {
10375 allocation_id: id.clone(),
10376 capacity_reservation_id: capacity_reservation_id.to_string(),
10377 instance_count,
10378 start_date_time,
10379 end_date_time,
10380 state: "scheduled".to_string(),
10381 allocation_type,
10382 tags,
10383 };
10384 self.interruptible_capacity_reservation_allocations
10385 .insert(id.clone(), alloc);
10386 Ok(self
10387 .interruptible_capacity_reservation_allocations
10388 .get(&id)
10389 .unwrap())
10390 }
10391
10392 pub fn update_interruptible_capacity_reservation_allocation(
10394 &mut self,
10395 allocation_id: &str,
10396 instance_count: Option<i32>,
10397 start_date_time: Option<String>,
10398 end_date_time: Option<String>,
10399 allocation_type: Option<String>,
10400 ) -> Result<&InterruptibleCapacityReservationAllocation, Ec2Error> {
10401 let alloc = self
10402 .interruptible_capacity_reservation_allocations
10403 .get_mut(allocation_id)
10404 .ok_or_else(|| {
10405 Ec2Error::InvalidInterruptibleCapacityReservationAllocationNotFound(
10406 allocation_id.to_string(),
10407 )
10408 })?;
10409 if let Some(c) = instance_count {
10410 if c <= 0 {
10411 return Err(Ec2Error::InvalidParameterValue(
10412 "InstanceCount must be > 0".to_string(),
10413 ));
10414 }
10415 alloc.instance_count = c;
10416 }
10417 if let Some(s) = start_date_time {
10418 alloc.start_date_time = s;
10419 }
10420 if let Some(e) = end_date_time {
10421 alloc.end_date_time = e;
10422 }
10423 if let Some(t) = allocation_type {
10424 if !matches!(t.as_str(), "scheduled" | "on-demand") {
10425 return Err(Ec2Error::InvalidParameterValue(format!(
10426 "Invalid AllocationType: {t}"
10427 )));
10428 }
10429 alloc.allocation_type = t;
10430 }
10431 Ok(self
10432 .interruptible_capacity_reservation_allocations
10433 .get(allocation_id)
10434 .unwrap())
10435 }
10436
10437 #[allow(clippy::too_many_arguments)]
10440 pub fn purchase_capacity_block(
10441 &mut self,
10442 capacity_block_offering_id: &str,
10443 instance_platform: &str,
10444 instance_type: &str,
10445 instance_count: i32,
10446 availability_zone: &str,
10447 start_date: String,
10448 end_date: String,
10449 tenancy: String,
10450 upfront_fee: String,
10451 commitment_duration_in_seconds: i64,
10452 account_id: &str,
10453 region: &str,
10454 tags: Tags,
10455 ) -> Result<(CapacityBlock, CapacityReservation), Ec2Error> {
10456 if instance_count <= 0 {
10457 return Err(Ec2Error::InvalidParameterValue(
10458 "InstanceCount must be > 0".to_string(),
10459 ));
10460 }
10461 let cr_id = self.next_capacity_reservation_id();
10463 let cr_arn = format!("arn:aws:ec2:{region}:{account_id}:capacity-reservation/{cr_id}");
10464 let cr = CapacityReservation {
10465 capacity_reservation_id: cr_id.clone(),
10466 capacity_reservation_arn: cr_arn.clone(),
10467 owner_id: account_id.to_string(),
10468 instance_type: instance_type.to_string(),
10469 instance_platform: instance_platform.to_string(),
10470 availability_zone: availability_zone.to_string(),
10471 tenancy: tenancy.clone(),
10472 total_instance_count: instance_count,
10473 available_instance_count: instance_count,
10474 ebs_optimized: false,
10475 ephemeral_storage: false,
10476 state: "active".to_string(),
10477 start_date: start_date.clone(),
10478 end_date: Some(end_date.clone()),
10479 end_date_type: "limited".to_string(),
10480 instance_match_criteria: "targeted".to_string(),
10481 create_date: self.now_iso(),
10482 outpost_arn: None,
10483 placement_group_arn: None,
10484 tags: tags.clone(),
10485 pending_billing_owner_account_id: None,
10486 billing_owner_account_id: Some(account_id.to_string()),
10487 target_capacity_reservation_id: None,
10488 reservation_type: Some("capacity-block".to_string()),
10489 commitment_info: Some(CapacityReservationCommitmentInfo {
10490 commitment_end_date: Some(end_date.clone()),
10491 committed_instance_count: Some(instance_count),
10492 }),
10493 };
10494 self.capacity_reservations.insert(cr_id.clone(), cr.clone());
10495
10496 let block_id = self.next_capacity_block_id();
10497 let block = CapacityBlock {
10498 capacity_block_id: block_id.clone(),
10499 capacity_reservation_id: cr_id,
10500 capacity_block_offering_id: capacity_block_offering_id.to_string(),
10501 instance_type: instance_type.to_string(),
10502 instance_count,
10503 availability_zone: availability_zone.to_string(),
10504 start_date,
10505 end_date,
10506 tenancy,
10507 currency_code: "USD".to_string(),
10508 upfront_fee,
10509 commitment_duration_in_seconds,
10510 capacity_reservation_arn: cr_arn,
10511 tags,
10512 };
10513 self.capacity_blocks.insert(block_id, block.clone());
10514 Ok((block, cr))
10515 }
10516
10517 #[allow(clippy::too_many_arguments)]
10520 pub fn purchase_capacity_block_extension(
10521 &mut self,
10522 capacity_block_extension_offering_id: &str,
10523 capacity_reservation_id: &str,
10524 capacity_block_extension_duration_hours: i32,
10525 upfront_fee: String,
10526 account_id: &str,
10527 region: &str,
10528 ) -> Result<CapacityBlockExtension, Ec2Error> {
10529 let cr = self
10530 .capacity_reservations
10531 .get(capacity_reservation_id)
10532 .ok_or_else(|| {
10533 Ec2Error::CapacityReservationNotFound(capacity_reservation_id.to_string())
10534 })?
10535 .clone();
10536 if capacity_block_extension_duration_hours <= 0 {
10537 return Err(Ec2Error::InvalidParameterValue(
10538 "CapacityBlockExtensionDurationHours must be > 0".to_string(),
10539 ));
10540 }
10541 let id = self.next_capacity_block_extension_id();
10542 let now = self.now_iso();
10543 let start_date = cr.end_date.clone().unwrap_or_else(|| now.clone());
10544 let end_date = match chrono::DateTime::parse_from_rfc3339(&start_date) {
10548 Ok(start) => {
10549 let new_end =
10550 start + chrono::Duration::hours(capacity_block_extension_duration_hours as i64);
10551 new_end
10552 .with_timezone(&chrono::Utc)
10553 .format("%Y-%m-%dT%H:%M:%S.000Z")
10554 .to_string()
10555 }
10556 Err(_) => self.now_iso(),
10557 };
10558 let arn = format!("arn:aws:ec2:{region}:{account_id}:capacity-block-extension/{id}");
10559 let ext = CapacityBlockExtension {
10560 capacity_block_extension_id: id.clone(),
10561 capacity_reservation_id: capacity_reservation_id.to_string(),
10562 instance_type: cr.instance_type.clone(),
10563 availability_zone: cr.availability_zone.clone(),
10564 instance_count: cr.total_instance_count,
10565 availability_zone_id: None,
10566 start_date: start_date.clone(),
10567 end_date: end_date.clone(),
10568 capacity_block_extension_offering_id: capacity_block_extension_offering_id.to_string(),
10569 capacity_block_extension_status: "payment-succeeded".to_string(),
10570 capacity_block_extension_purchase_date: now.clone(),
10571 capacity_block_extension_duration_hours,
10572 currency_code: "USD".to_string(),
10573 upfront_fee,
10574 capacity_reservation_arn: Some(cr.capacity_reservation_arn.clone()),
10575 capacity_block_extension_arn: Some(arn),
10576 };
10577 self.capacity_block_extensions
10578 .insert(id.clone(), ext.clone());
10579 if let Some(cr_mut) = self.capacity_reservations.get_mut(capacity_reservation_id) {
10581 cr_mut.end_date = Some(end_date);
10582 }
10583 Ok(ext)
10584 }
10585
10586 pub fn create_tgw_multicast_domain(
10589 &mut self,
10590 tgw_id: &str,
10591 igmpv2_support: &str,
10592 static_sources_support: &str,
10593 auto_accept_shared_associations: &str,
10594 owner_id: &str,
10595 region: &str,
10596 tags: Tags,
10597 ) -> Result<&TransitGatewayMulticastDomain, Ec2Error> {
10598 if !self.transit_gateways.contains_key(tgw_id) {
10599 return Err(Ec2Error::TransitGatewayNotFound(tgw_id.to_string()));
10600 }
10601 let domain_id = self.next_tgw_multicast_domain_id();
10602 let arn =
10603 format!("arn:aws:ec2:{region}:{owner_id}:transit-gateway-multicast-domain/{domain_id}");
10604 let domain = TransitGatewayMulticastDomain {
10605 transit_gateway_multicast_domain_id: domain_id.clone(),
10606 transit_gateway_id: tgw_id.to_string(),
10607 transit_gateway_multicast_domain_arn: arn,
10608 owner_id: owner_id.to_string(),
10609 igmpv2_support: igmpv2_support.to_string(),
10610 static_sources_support: static_sources_support.to_string(),
10611 auto_accept_shared_associations: auto_accept_shared_associations.to_string(),
10612 state: "available".to_string(),
10613 creation_time: "2024-01-01T00:00:00Z".to_string(),
10614 tags,
10615 };
10616 self.tgw_multicast_domains.insert(domain_id.clone(), domain);
10617 Ok(self.tgw_multicast_domains.get(&domain_id).unwrap())
10618 }
10619
10620 pub fn delete_tgw_multicast_domain(&mut self, domain_id: &str) -> Result<(), Ec2Error> {
10621 if !self.tgw_multicast_domains.contains_key(domain_id) {
10622 return Err(Ec2Error::InvalidTgwMulticastDomainNotFound(
10623 domain_id.to_string(),
10624 ));
10625 }
10626 let has_assoc = self
10627 .tgw_multicast_domain_associations
10628 .keys()
10629 .any(|(d, _)| d == domain_id);
10630 let has_member = self
10631 .tgw_multicast_group_members
10632 .keys()
10633 .any(|(d, _, _)| d == domain_id);
10634 let has_source = self
10635 .tgw_multicast_group_sources
10636 .keys()
10637 .any(|(d, _, _)| d == domain_id);
10638 if has_assoc || has_member || has_source {
10639 return Err(Ec2Error::TgwMulticastDomainInUse(domain_id.to_string()));
10640 }
10641 if let Some(d) = self.tgw_multicast_domains.get_mut(domain_id) {
10642 d.state = "deleted".to_string();
10643 }
10644 Ok(())
10645 }
10646
10647 pub fn associate_tgw_multicast_domain(
10648 &mut self,
10649 domain_id: &str,
10650 attachment_id: &str,
10651 subnet_ids: Vec<String>,
10652 ) -> Result<&TransitGatewayMulticastDomainAssociation, Ec2Error> {
10653 let auto_accept = self
10654 .tgw_multicast_domains
10655 .get(domain_id)
10656 .ok_or_else(|| Ec2Error::InvalidTgwMulticastDomainNotFound(domain_id.to_string()))?
10657 .auto_accept_shared_associations
10658 .clone();
10659 let resource_id = self
10660 .tgw_vpc_attachments
10661 .get(attachment_id)
10662 .map(|a| a.vpc_id.clone())
10663 .unwrap_or_default();
10664 let subnet_state = if auto_accept == "enable" {
10665 "associated"
10666 } else {
10667 "pendingAcceptance"
10668 };
10669 let subnets: Vec<MulticastSubnetAssociation> = subnet_ids
10670 .into_iter()
10671 .map(|s| MulticastSubnetAssociation {
10672 subnet_id: s,
10673 state: subnet_state.to_string(),
10674 })
10675 .collect();
10676 let key = (domain_id.to_string(), attachment_id.to_string());
10677 let assoc = TransitGatewayMulticastDomainAssociation {
10678 transit_gateway_multicast_domain_id: domain_id.to_string(),
10679 transit_gateway_attachment_id: attachment_id.to_string(),
10680 resource_id,
10681 resource_type: "vpc".to_string(),
10682 subnets,
10683 };
10684 self.tgw_multicast_domain_associations
10685 .insert(key.clone(), assoc);
10686 Ok(self.tgw_multicast_domain_associations.get(&key).unwrap())
10687 }
10688
10689 pub fn accept_tgw_multicast_domain_associations(
10690 &mut self,
10691 domain_id: &str,
10692 attachment_id: &str,
10693 subnet_ids: &[String],
10694 ) -> Result<&TransitGatewayMulticastDomainAssociation, Ec2Error> {
10695 let key = (domain_id.to_string(), attachment_id.to_string());
10696 let assoc = self
10697 .tgw_multicast_domain_associations
10698 .get_mut(&key)
10699 .ok_or_else(|| {
10700 Ec2Error::InvalidTgwMulticastDomainAssociationNotFound(
10701 domain_id.to_string(),
10702 attachment_id.to_string(),
10703 )
10704 })?;
10705 for s in &mut assoc.subnets {
10706 if subnet_ids.is_empty() || subnet_ids.contains(&s.subnet_id) {
10707 s.state = "associated".to_string();
10708 }
10709 }
10710 Ok(self.tgw_multicast_domain_associations.get(&key).unwrap())
10711 }
10712
10713 pub fn reject_tgw_multicast_domain_associations(
10714 &mut self,
10715 domain_id: &str,
10716 attachment_id: &str,
10717 subnet_ids: &[String],
10718 ) -> Result<&TransitGatewayMulticastDomainAssociation, Ec2Error> {
10719 let key = (domain_id.to_string(), attachment_id.to_string());
10720 let assoc = self
10721 .tgw_multicast_domain_associations
10722 .get_mut(&key)
10723 .ok_or_else(|| {
10724 Ec2Error::InvalidTgwMulticastDomainAssociationNotFound(
10725 domain_id.to_string(),
10726 attachment_id.to_string(),
10727 )
10728 })?;
10729 for s in &mut assoc.subnets {
10730 if subnet_ids.is_empty() || subnet_ids.contains(&s.subnet_id) {
10731 s.state = "rejected".to_string();
10732 }
10733 }
10734 Ok(self.tgw_multicast_domain_associations.get(&key).unwrap())
10735 }
10736
10737 pub fn disassociate_tgw_multicast_domain(
10738 &mut self,
10739 domain_id: &str,
10740 attachment_id: &str,
10741 ) -> Result<TransitGatewayMulticastDomainAssociation, Ec2Error> {
10742 let key = (domain_id.to_string(), attachment_id.to_string());
10743 let mut assoc = self
10744 .tgw_multicast_domain_associations
10745 .remove(&key)
10746 .ok_or_else(|| {
10747 Ec2Error::InvalidTgwMulticastDomainAssociationNotFound(
10748 domain_id.to_string(),
10749 attachment_id.to_string(),
10750 )
10751 })?;
10752 for s in &mut assoc.subnets {
10753 s.state = "disassociated".to_string();
10754 }
10755 Ok(assoc)
10756 }
10757
10758 pub fn register_tgw_multicast_group_members(
10759 &mut self,
10760 domain_id: &str,
10761 group_ip: &str,
10762 nic_ids: Vec<String>,
10763 ) -> Result<Vec<String>, Ec2Error> {
10764 if !self.tgw_multicast_domains.contains_key(domain_id) {
10765 return Err(Ec2Error::InvalidTgwMulticastDomainNotFound(
10766 domain_id.to_string(),
10767 ));
10768 }
10769 let registered = nic_ids.clone();
10770 for nic in nic_ids {
10771 let key = (domain_id.to_string(), group_ip.to_string(), nic.clone());
10772 self.tgw_multicast_group_members.insert(
10773 key,
10774 TransitGatewayMulticastGroupMember {
10775 transit_gateway_multicast_domain_id: domain_id.to_string(),
10776 group_ip_address: group_ip.to_string(),
10777 transit_gateway_attachment_id: None,
10778 subnet_id: None,
10779 resource_id: None,
10780 resource_type: "vpc".to_string(),
10781 network_interface_id: nic,
10782 member_type: "igmp".to_string(),
10783 source_type: "igmp".to_string(),
10784 },
10785 );
10786 }
10787 Ok(registered)
10788 }
10789
10790 pub fn deregister_tgw_multicast_group_members(
10791 &mut self,
10792 domain_id: &str,
10793 group_ip: &str,
10794 nic_ids: Vec<String>,
10795 ) -> Result<Vec<String>, Ec2Error> {
10796 if !self.tgw_multicast_domains.contains_key(domain_id) {
10797 return Err(Ec2Error::InvalidTgwMulticastDomainNotFound(
10798 domain_id.to_string(),
10799 ));
10800 }
10801 let mut removed = Vec::new();
10802 for nic in nic_ids {
10803 let key = (domain_id.to_string(), group_ip.to_string(), nic.clone());
10804 if self.tgw_multicast_group_members.remove(&key).is_some() {
10805 removed.push(nic);
10806 }
10807 }
10808 Ok(removed)
10809 }
10810
10811 pub fn register_tgw_multicast_group_sources(
10812 &mut self,
10813 domain_id: &str,
10814 group_ip: &str,
10815 nic_ids: Vec<String>,
10816 ) -> Result<Vec<String>, Ec2Error> {
10817 if !self.tgw_multicast_domains.contains_key(domain_id) {
10818 return Err(Ec2Error::InvalidTgwMulticastDomainNotFound(
10819 domain_id.to_string(),
10820 ));
10821 }
10822 let registered = nic_ids.clone();
10823 for nic in nic_ids {
10824 let key = (domain_id.to_string(), group_ip.to_string(), nic.clone());
10825 self.tgw_multicast_group_sources.insert(
10826 key,
10827 TransitGatewayMulticastGroupSource {
10828 transit_gateway_multicast_domain_id: domain_id.to_string(),
10829 group_ip_address: group_ip.to_string(),
10830 transit_gateway_attachment_id: None,
10831 subnet_id: None,
10832 resource_id: None,
10833 resource_type: "vpc".to_string(),
10834 network_interface_id: nic,
10835 member_type: "static".to_string(),
10836 source_type: "static".to_string(),
10837 },
10838 );
10839 }
10840 Ok(registered)
10841 }
10842
10843 pub fn deregister_tgw_multicast_group_sources(
10844 &mut self,
10845 domain_id: &str,
10846 group_ip: &str,
10847 nic_ids: Vec<String>,
10848 ) -> Result<Vec<String>, Ec2Error> {
10849 if !self.tgw_multicast_domains.contains_key(domain_id) {
10850 return Err(Ec2Error::InvalidTgwMulticastDomainNotFound(
10851 domain_id.to_string(),
10852 ));
10853 }
10854 let mut removed = Vec::new();
10855 for nic in nic_ids {
10856 let key = (domain_id.to_string(), group_ip.to_string(), nic.clone());
10857 if self.tgw_multicast_group_sources.remove(&key).is_some() {
10858 removed.push(nic);
10859 }
10860 }
10861 Ok(removed)
10862 }
10863
10864 pub fn create_tgw_connect(
10865 &mut self,
10866 transport_attachment_id: &str,
10867 protocol: &str,
10868 tags: Tags,
10869 ) -> Result<&TransitGatewayConnect, Ec2Error> {
10870 let tgw_id = self
10871 .tgw_vpc_attachments
10872 .get(transport_attachment_id)
10873 .map(|a| a.transit_gateway_id.clone())
10874 .ok_or_else(|| {
10875 Ec2Error::TgwVpcAttachmentNotFound(transport_attachment_id.to_string())
10876 })?;
10877 let attach_id = self.next_tgw_connect_id();
10878 let connect = TransitGatewayConnect {
10879 transit_gateway_attachment_id: attach_id.clone(),
10880 transport_transit_gateway_attachment_id: transport_attachment_id.to_string(),
10881 transit_gateway_id: tgw_id,
10882 state: "available".to_string(),
10883 creation_time: "2024-01-01T00:00:00Z".to_string(),
10884 protocol: protocol.to_string(),
10885 tags,
10886 };
10887 self.tgw_connects.insert(attach_id.clone(), connect);
10888 Ok(self.tgw_connects.get(&attach_id).unwrap())
10889 }
10890
10891 pub fn delete_tgw_connect(&mut self, attach_id: &str) -> Result<(), Ec2Error> {
10892 if !self.tgw_connects.contains_key(attach_id) {
10893 return Err(Ec2Error::InvalidTgwConnectNotFound(attach_id.to_string()));
10894 }
10895 let has_peers = self
10896 .tgw_connect_peers
10897 .values()
10898 .any(|p| p.transit_gateway_attachment_id == attach_id);
10899 if has_peers {
10900 return Err(Ec2Error::TgwConnectInUse(attach_id.to_string()));
10901 }
10902 if let Some(c) = self.tgw_connects.get_mut(attach_id) {
10903 c.state = "deleted".to_string();
10904 }
10905 Ok(())
10906 }
10907
10908 #[allow(clippy::too_many_arguments)]
10909 pub fn create_tgw_connect_peer(
10910 &mut self,
10911 attachment_id: &str,
10912 peer_address: &str,
10913 transit_gateway_address: Option<&str>,
10914 inside_cidr_blocks: Vec<String>,
10915 peer_asn: i64,
10916 tags: Tags,
10917 ) -> Result<&TransitGatewayConnectPeer, Ec2Error> {
10918 if !self.tgw_connects.contains_key(attachment_id) {
10919 return Err(Ec2Error::InvalidTgwConnectNotFound(
10920 attachment_id.to_string(),
10921 ));
10922 }
10923 let peer_id = self.next_tgw_connect_peer_id();
10924 let tgw_addr = transit_gateway_address.unwrap_or("169.254.6.1").to_string();
10925 let bgp = TransitGatewayAttachmentBgpConfiguration {
10926 transit_gateway_asn: 64512,
10927 peer_asn,
10928 transit_gateway_address: tgw_addr.clone(),
10929 peer_address: peer_address.to_string(),
10930 bgp_status: "up".to_string(),
10931 };
10932 let peer = TransitGatewayConnectPeer {
10933 transit_gateway_attachment_id: attachment_id.to_string(),
10934 transit_gateway_connect_peer_id: peer_id.clone(),
10935 state: "available".to_string(),
10936 creation_time: "2024-01-01T00:00:00Z".to_string(),
10937 transit_gateway_address: tgw_addr,
10938 peer_address: peer_address.to_string(),
10939 inside_cidr_blocks,
10940 protocol: "gre".to_string(),
10941 bgp_configurations: vec![bgp],
10942 tags,
10943 };
10944 self.tgw_connect_peers.insert(peer_id.clone(), peer);
10945 Ok(self.tgw_connect_peers.get(&peer_id).unwrap())
10946 }
10947
10948 pub fn delete_tgw_connect_peer(&mut self, peer_id: &str) -> Result<(), Ec2Error> {
10949 if !self.tgw_connect_peers.contains_key(peer_id) {
10952 return Err(Ec2Error::InvalidTgwConnectPeerNotFound(peer_id.to_string()));
10953 }
10954 self.tgw_connect_peers.remove(peer_id);
10955 Ok(())
10956 }
10957
10958 pub fn create_tgw_metering_policy(
10959 &mut self,
10960 tgw_id: &str,
10961 name: &str,
10962 description: Option<String>,
10963 owner_id: &str,
10964 region: &str,
10965 tags: Tags,
10966 ) -> Result<&TransitGatewayMeteringPolicy, Ec2Error> {
10967 if !self.transit_gateways.contains_key(tgw_id) {
10968 return Err(Ec2Error::TransitGatewayNotFound(tgw_id.to_string()));
10969 }
10970 let policy_id = self.next_tgw_metering_policy_id();
10971 let arn =
10972 format!("arn:aws:ec2:{region}:{owner_id}:transit-gateway-metering-policy/{policy_id}");
10973 let policy = TransitGatewayMeteringPolicy {
10974 transit_gateway_metering_policy_id: policy_id.clone(),
10975 transit_gateway_metering_policy_arn: arn,
10976 transit_gateway_id: tgw_id.to_string(),
10977 name: name.to_string(),
10978 description,
10979 state: "available".to_string(),
10980 tags,
10981 last_updated_time: "2024-01-01T00:00:00Z".to_string(),
10982 version: 1,
10983 middlebox_attachment_ids: Vec::new(),
10984 };
10985 self.tgw_metering_policies.insert(policy_id.clone(), policy);
10986 Ok(self.tgw_metering_policies.get(&policy_id).unwrap())
10987 }
10988
10989 pub fn delete_tgw_metering_policy(&mut self, policy_id: &str) -> Result<(), Ec2Error> {
10990 if !self.tgw_metering_policies.contains_key(policy_id) {
10991 return Err(Ec2Error::InvalidTgwMeteringPolicyNotFound(
10992 policy_id.to_string(),
10993 ));
10994 }
10995 self.tgw_metering_policy_entries
10997 .retain(|(p, _), _| p != policy_id);
10998 if let Some(p) = self.tgw_metering_policies.get_mut(policy_id) {
10999 p.state = "deleted".to_string();
11000 }
11001 Ok(())
11002 }
11003
11004 pub fn modify_tgw_metering_policy(
11005 &mut self,
11006 policy_id: &str,
11007 new_name: Option<String>,
11008 new_description: Option<String>,
11009 ) -> Result<&TransitGatewayMeteringPolicy, Ec2Error> {
11010 let p = self
11011 .tgw_metering_policies
11012 .get_mut(policy_id)
11013 .ok_or_else(|| Ec2Error::InvalidTgwMeteringPolicyNotFound(policy_id.to_string()))?;
11014 if let Some(n) = new_name {
11015 p.name = n;
11016 }
11017 if let Some(d) = new_description {
11018 p.description = Some(d);
11019 }
11020 p.version += 1;
11021 p.last_updated_time = "2024-01-01T00:00:01Z".to_string();
11022 Ok(self.tgw_metering_policies.get(policy_id).unwrap())
11023 }
11024
11025 #[allow(clippy::too_many_arguments)]
11026 pub fn create_tgw_metering_policy_entry(
11027 &mut self,
11028 policy_id: &str,
11029 sequence_number: i32,
11030 action: &str,
11031 source_cidr_block: Option<String>,
11032 destination_cidr_block: Option<String>,
11033 protocol: Option<String>,
11034 source_port: Option<String>,
11035 destination_port: Option<String>,
11036 dimensions: Vec<String>,
11037 ) -> Result<&TransitGatewayMeteringPolicyEntry, Ec2Error> {
11038 if !self.tgw_metering_policies.contains_key(policy_id) {
11039 return Err(Ec2Error::InvalidTgwMeteringPolicyNotFound(
11040 policy_id.to_string(),
11041 ));
11042 }
11043 let entry_id = self.next_tgw_metering_policy_entry_id();
11044 let entry = TransitGatewayMeteringPolicyEntry {
11045 transit_gateway_metering_policy_entry_id: entry_id.clone(),
11046 transit_gateway_metering_policy_id: policy_id.to_string(),
11047 sequence_number,
11048 action: action.to_string(),
11049 source_cidr_block,
11050 destination_cidr_block,
11051 protocol,
11052 source_port,
11053 destination_port,
11054 dimensions,
11055 state: "available".to_string(),
11056 };
11057 let key = (policy_id.to_string(), entry_id.clone());
11058 self.tgw_metering_policy_entries.insert(key.clone(), entry);
11059 Ok(self.tgw_metering_policy_entries.get(&key).unwrap())
11060 }
11061
11062 pub fn delete_tgw_metering_policy_entry(
11063 &mut self,
11064 policy_id: &str,
11065 entry_id: &str,
11066 ) -> Result<(), Ec2Error> {
11067 let key = (policy_id.to_string(), entry_id.to_string());
11068 if self.tgw_metering_policy_entries.remove(&key).is_none() {
11069 return Err(Ec2Error::InvalidTgwMeteringPolicyEntryNotFound(
11070 entry_id.to_string(),
11071 ));
11072 }
11073 Ok(())
11074 }
11075
11076 pub fn create_tgw_policy_table(
11077 &mut self,
11078 tgw_id: &str,
11079 tags: Tags,
11080 ) -> Result<&TransitGatewayPolicyTable, Ec2Error> {
11081 if !self.transit_gateways.contains_key(tgw_id) {
11082 return Err(Ec2Error::TransitGatewayNotFound(tgw_id.to_string()));
11083 }
11084 let id = self.next_tgw_policy_table_id();
11085 let pt = TransitGatewayPolicyTable {
11086 transit_gateway_policy_table_id: id.clone(),
11087 transit_gateway_id: tgw_id.to_string(),
11088 state: "available".to_string(),
11089 creation_time: "2024-01-01T00:00:00Z".to_string(),
11090 tags,
11091 };
11092 self.tgw_policy_tables.insert(id.clone(), pt);
11093 Ok(self.tgw_policy_tables.get(&id).unwrap())
11094 }
11095
11096 pub fn delete_tgw_policy_table(&mut self, id: &str) -> Result<(), Ec2Error> {
11097 let pt = self
11098 .tgw_policy_tables
11099 .get_mut(id)
11100 .ok_or_else(|| Ec2Error::InvalidTgwPolicyTableNotFound(id.to_string()))?;
11101 pt.state = "deleted".to_string();
11102 self.tgw_policy_table_associations
11104 .retain(|(p, _), _| p != id);
11105 Ok(())
11106 }
11107
11108 pub fn associate_tgw_policy_table(
11109 &mut self,
11110 policy_table_id: &str,
11111 attachment_id: &str,
11112 ) -> Result<&TransitGatewayPolicyTableAssociation, Ec2Error> {
11113 if !self.tgw_policy_tables.contains_key(policy_table_id) {
11114 return Err(Ec2Error::InvalidTgwPolicyTableNotFound(
11115 policy_table_id.to_string(),
11116 ));
11117 }
11118 let resource_id = self
11119 .tgw_vpc_attachments
11120 .get(attachment_id)
11121 .map(|a| a.vpc_id.clone())
11122 .unwrap_or_default();
11123 let key = (policy_table_id.to_string(), attachment_id.to_string());
11124 let assoc = TransitGatewayPolicyTableAssociation {
11125 transit_gateway_policy_table_id: policy_table_id.to_string(),
11126 transit_gateway_attachment_id: attachment_id.to_string(),
11127 resource_type: "vpc".to_string(),
11128 resource_id,
11129 state: "associated".to_string(),
11130 };
11131 self.tgw_policy_table_associations
11132 .insert(key.clone(), assoc);
11133 Ok(self.tgw_policy_table_associations.get(&key).unwrap())
11134 }
11135
11136 pub fn disassociate_tgw_policy_table(
11137 &mut self,
11138 policy_table_id: &str,
11139 attachment_id: &str,
11140 ) -> Result<TransitGatewayPolicyTableAssociation, Ec2Error> {
11141 let key = (policy_table_id.to_string(), attachment_id.to_string());
11142 let mut assoc = self
11143 .tgw_policy_table_associations
11144 .remove(&key)
11145 .ok_or_else(|| {
11146 Ec2Error::InvalidTgwPolicyTableAssociationNotFound(
11147 policy_table_id.to_string(),
11148 attachment_id.to_string(),
11149 )
11150 })?;
11151 assoc.state = "disassociated".to_string();
11152 Ok(assoc)
11153 }
11154
11155 pub fn create_tgw_prefix_list_reference(
11156 &mut self,
11157 route_table_id: &str,
11158 prefix_list_id: &str,
11159 owner_id: &str,
11160 blackhole: bool,
11161 attachment_id: Option<String>,
11162 ) -> Result<&TransitGatewayPrefixListReference, Ec2Error> {
11163 if !self.tgw_route_tables.contains_key(route_table_id) {
11164 return Err(Ec2Error::TgwRouteTableNotFound(route_table_id.to_string()));
11165 }
11166 let (resource_id, resource_type) = if let Some(att_id) = &attachment_id {
11167 let r = self
11168 .tgw_vpc_attachments
11169 .get(att_id)
11170 .map(|a| (a.vpc_id.clone(), "vpc".to_string()));
11171 r.unwrap_or((String::new(), String::new()))
11172 } else {
11173 (String::new(), String::new())
11174 };
11175 let key = (route_table_id.to_string(), prefix_list_id.to_string());
11176 let r = TransitGatewayPrefixListReference {
11177 transit_gateway_route_table_id: route_table_id.to_string(),
11178 prefix_list_id: prefix_list_id.to_string(),
11179 prefix_list_owner_id: owner_id.to_string(),
11180 state: "available".to_string(),
11181 blackhole,
11182 transit_gateway_attachment_id: attachment_id,
11183 resource_id: if resource_id.is_empty() {
11184 None
11185 } else {
11186 Some(resource_id)
11187 },
11188 resource_type: if resource_type.is_empty() {
11189 None
11190 } else {
11191 Some(resource_type)
11192 },
11193 };
11194 self.tgw_prefix_list_references.insert(key.clone(), r);
11195 Ok(self.tgw_prefix_list_references.get(&key).unwrap())
11196 }
11197
11198 pub fn delete_tgw_prefix_list_reference(
11199 &mut self,
11200 route_table_id: &str,
11201 prefix_list_id: &str,
11202 ) -> Result<TransitGatewayPrefixListReference, Ec2Error> {
11203 let key = (route_table_id.to_string(), prefix_list_id.to_string());
11204 let mut r = self
11205 .tgw_prefix_list_references
11206 .remove(&key)
11207 .ok_or_else(|| {
11208 Ec2Error::InvalidTgwPrefixListReferenceNotFound(
11209 route_table_id.to_string(),
11210 prefix_list_id.to_string(),
11211 )
11212 })?;
11213 r.state = "deleted".to_string();
11214 Ok(r)
11215 }
11216
11217 pub fn modify_tgw_prefix_list_reference(
11218 &mut self,
11219 route_table_id: &str,
11220 prefix_list_id: &str,
11221 blackhole: Option<bool>,
11222 attachment_id: Option<String>,
11223 ) -> Result<&TransitGatewayPrefixListReference, Ec2Error> {
11224 let key = (route_table_id.to_string(), prefix_list_id.to_string());
11225 if !self.tgw_prefix_list_references.contains_key(&key) {
11226 return Err(Ec2Error::InvalidTgwPrefixListReferenceNotFound(
11227 route_table_id.to_string(),
11228 prefix_list_id.to_string(),
11229 ));
11230 }
11231 let new_resource = attachment_id.as_ref().and_then(|a| {
11233 self.tgw_vpc_attachments
11234 .get(a)
11235 .map(|att| (att.vpc_id.clone(), "vpc".to_string()))
11236 });
11237 let r = self.tgw_prefix_list_references.get_mut(&key).unwrap();
11238 if let Some(b) = blackhole {
11239 r.blackhole = b;
11240 }
11241 if let Some(a) = attachment_id {
11242 r.transit_gateway_attachment_id = Some(a);
11243 if let Some((rid, rt)) = new_resource {
11244 r.resource_id = Some(rid);
11245 r.resource_type = Some(rt);
11246 }
11247 }
11248 r.state = "available".to_string();
11249 Ok(self.tgw_prefix_list_references.get(&key).unwrap())
11250 }
11251
11252 #[allow(clippy::too_many_arguments)]
11253 pub fn create_tgw_route_table_announcement(
11254 &mut self,
11255 route_table_id: &str,
11256 peering_attachment_id: &str,
11257 announcement_direction: &str,
11258 peer_core_network_id: Option<String>,
11259 ) -> Result<&TransitGatewayRouteTableAnnouncement, Ec2Error> {
11260 let tgw_id = self
11261 .tgw_route_tables
11262 .get(route_table_id)
11263 .map(|r| r.transit_gateway_id.clone())
11264 .ok_or_else(|| Ec2Error::TgwRouteTableNotFound(route_table_id.to_string()))?;
11265 let peer_tgw_id = self
11266 .tgw_peering_attachments
11267 .get(peering_attachment_id)
11268 .map(|p| p.peer_transit_gateway_id.clone())
11269 .ok_or_else(|| {
11270 Ec2Error::TgwPeeringAttachmentNotFound(peering_attachment_id.to_string())
11271 })?;
11272 let id = self.next_tgw_route_table_announcement_id();
11273 let ann = TransitGatewayRouteTableAnnouncement {
11274 transit_gateway_route_table_announcement_id: id.clone(),
11275 transit_gateway_id: tgw_id,
11276 core_network_id: String::new(),
11277 peer_transit_gateway_id: peer_tgw_id,
11278 peer_core_network_id,
11279 peering_attachment_id: peering_attachment_id.to_string(),
11280 announcement_direction: announcement_direction.to_string(),
11281 transit_gateway_route_table_id: route_table_id.to_string(),
11282 state: "available".to_string(),
11283 creation_time: "2024-01-01T00:00:00Z".to_string(),
11284 tags: Tags::new(),
11285 };
11286 self.tgw_route_table_announcements.insert(id.clone(), ann);
11287 Ok(self.tgw_route_table_announcements.get(&id).unwrap())
11288 }
11289
11290 pub fn delete_tgw_route_table_announcement(&mut self, id: &str) -> Result<(), Ec2Error> {
11291 let ann = self
11292 .tgw_route_table_announcements
11293 .get_mut(id)
11294 .ok_or_else(|| Ec2Error::InvalidTgwRouteTableAnnouncementNotFound(id.to_string()))?;
11295 ann.state = "deleted".to_string();
11296 Ok(())
11297 }
11298
11299 pub fn accept_tgw_vpc_attachment(
11300 &mut self,
11301 attach_id: &str,
11302 ) -> Result<&TransitGatewayVpcAttachment, Ec2Error> {
11303 let att = self
11304 .tgw_vpc_attachments
11305 .get_mut(attach_id)
11306 .ok_or_else(|| Ec2Error::TgwVpcAttachmentNotFound(attach_id.to_string()))?;
11307 if att.state != "pendingAcceptance" {
11308 return Err(Ec2Error::TgwAttachmentNotPendingAcceptance(
11309 attach_id.to_string(),
11310 ));
11311 }
11312 att.state = "available".to_string();
11313 Ok(self.tgw_vpc_attachments.get(attach_id).unwrap())
11314 }
11315
11316 pub fn reject_tgw_vpc_attachment(
11317 &mut self,
11318 attach_id: &str,
11319 ) -> Result<&TransitGatewayVpcAttachment, Ec2Error> {
11320 let att = self
11321 .tgw_vpc_attachments
11322 .get_mut(attach_id)
11323 .ok_or_else(|| Ec2Error::TgwVpcAttachmentNotFound(attach_id.to_string()))?;
11324 if att.state != "pendingAcceptance" {
11325 return Err(Ec2Error::TgwAttachmentNotPendingAcceptance(
11326 attach_id.to_string(),
11327 ));
11328 }
11329 att.state = "rejected".to_string();
11330 Ok(self.tgw_vpc_attachments.get(attach_id).unwrap())
11331 }
11332
11333 pub fn replace_tgw_route(
11334 &mut self,
11335 route_table_id: &str,
11336 cidr: &str,
11337 new_attachment_id: Option<String>,
11338 blackhole: bool,
11339 ) -> Result<TransitGatewayRoute, Ec2Error> {
11340 let routes = self
11341 .tgw_routes
11342 .get_mut(route_table_id)
11343 .ok_or_else(|| Ec2Error::TgwRouteTableNotFound(route_table_id.to_string()))?;
11344 let pos = routes
11345 .iter()
11346 .position(|r| r.destination_cidr_block == cidr)
11347 .ok_or_else(|| {
11348 Ec2Error::TgwRouteNotFound(route_table_id.to_string(), cidr.to_string())
11349 })?;
11350 let r = &mut routes[pos];
11351 r.attachment_id = new_attachment_id;
11352 r.state = if blackhole { "blackhole" } else { "active" }.to_string();
11353 Ok(r.clone())
11354 }
11355
11356 pub fn next_ipam_id(&mut self) -> String {
11359 self.counters.ipam += 1;
11360 format!("ipam-{:08x}", self.counters.ipam)
11361 }
11362
11363 pub fn next_ipam_scope_id(&mut self) -> String {
11364 self.counters.ipam_scope += 1;
11365 format!("ipam-scope-{:08x}", self.counters.ipam_scope)
11366 }
11367
11368 pub fn next_ipam_pool_id(&mut self) -> String {
11369 self.counters.ipam_pool += 1;
11370 format!("ipam-pool-{:08x}", self.counters.ipam_pool)
11371 }
11372
11373 pub fn next_ipam_pool_cidr_id(&mut self) -> String {
11374 self.counters.ipam_pool_cidr += 1;
11375 format!("ipam-pool-cidr-{:08x}", self.counters.ipam_pool_cidr)
11376 }
11377
11378 pub fn next_ipam_pool_allocation_id(&mut self) -> String {
11379 self.counters.ipam_pool_allocation += 1;
11380 format!("ipam-pool-alloc-{:08x}", self.counters.ipam_pool_allocation)
11381 }
11382
11383 pub fn next_ipam_resource_discovery_id(&mut self) -> String {
11384 self.counters.ipam_resource_discovery += 1;
11385 format!(
11386 "ipam-res-disco-{:08x}",
11387 self.counters.ipam_resource_discovery
11388 )
11389 }
11390
11391 pub fn next_ipam_resource_discovery_assoc_id(&mut self) -> String {
11392 self.counters.ipam_resource_discovery_association += 1;
11393 format!(
11394 "ipam-res-disco-assoc-{:08x}",
11395 self.counters.ipam_resource_discovery_association
11396 )
11397 }
11398
11399 pub fn next_ipam_external_resource_verification_token_id(&mut self) -> String {
11400 self.counters.ipam_external_resource_verification_token += 1;
11401 format!(
11402 "ipam-ext-res-ver-token-{:08x}",
11403 self.counters.ipam_external_resource_verification_token
11404 )
11405 }
11406
11407 pub fn next_ipam_policy_id(&mut self) -> String {
11408 self.counters.ipam_policy += 1;
11409 format!("ipam-policy-{:08x}", self.counters.ipam_policy)
11410 }
11411
11412 pub fn next_ipam_prefix_list_resolver_id(&mut self) -> String {
11413 self.counters.ipam_prefix_list_resolver += 1;
11414 format!(
11415 "ipam-pl-resolver-{:08x}",
11416 self.counters.ipam_prefix_list_resolver
11417 )
11418 }
11419
11420 pub fn next_ipam_prefix_list_resolver_target_id(&mut self) -> String {
11421 self.counters.ipam_prefix_list_resolver_target += 1;
11422 format!(
11423 "ipam-pl-resolver-tgt-{:08x}",
11424 self.counters.ipam_prefix_list_resolver_target
11425 )
11426 }
11427
11428 #[allow(clippy::too_many_arguments)]
11430 pub fn create_ipam(
11431 &mut self,
11432 region: &str,
11433 owner_id: &str,
11434 description: Option<String>,
11435 operating_regions: Vec<IpamOperatingRegion>,
11436 tier: String,
11437 enable_private_gua: bool,
11438 metered_account: String,
11439 tags: Tags,
11440 ) -> &Ipam {
11441 let ipam_id = self.next_ipam_id();
11442 let ipam_arn = format!(
11443 "arn:aws:ec2::{owner_id}:ipam/{ipam_id}",
11444 owner_id = owner_id,
11445 ipam_id = ipam_id,
11446 );
11447 let private_scope_id = self.next_ipam_scope_id();
11449 let public_scope_id = self.next_ipam_scope_id();
11450 let private_scope_arn = format!(
11451 "arn:aws:ec2::{owner_id}:ipam-scope/{id}",
11452 owner_id = owner_id,
11453 id = private_scope_id,
11454 );
11455 let public_scope_arn = format!(
11456 "arn:aws:ec2::{owner_id}:ipam-scope/{id}",
11457 owner_id = owner_id,
11458 id = public_scope_id,
11459 );
11460 for (sid, sarn, stype) in [
11461 (
11462 private_scope_id.clone(),
11463 private_scope_arn.clone(),
11464 "private",
11465 ),
11466 (public_scope_id.clone(), public_scope_arn.clone(), "public"),
11467 ] {
11468 let scope = IpamScope {
11469 ipam_scope_id: sid.clone(),
11470 ipam_scope_arn: sarn,
11471 ipam_arn: ipam_arn.clone(),
11472 ipam_region: region.to_string(),
11473 ipam_scope_type: stype.to_string(),
11474 is_default: true,
11475 description: None,
11476 pool_count: 0,
11477 state: "create-complete".to_string(),
11478 tags: Tags::new(),
11479 owner_id: owner_id.to_string(),
11480 };
11481 self.ipam_scopes.insert(sid, scope);
11482 }
11483 let ipam = Ipam {
11484 ipam_id: ipam_id.clone(),
11485 ipam_arn,
11486 ipam_region: region.to_string(),
11487 public_default_scope_id: public_scope_id,
11488 private_default_scope_id: private_scope_id,
11489 scope_count: 2,
11490 description,
11491 operating_regions,
11492 state: "create-complete".to_string(),
11493 owner_id: owner_id.to_string(),
11494 default_resource_discovery_id: None,
11495 default_resource_discovery_association_id: None,
11496 resource_discovery_association_count: 0,
11497 tier,
11498 enable_private_gua,
11499 metered_account,
11500 tags,
11501 };
11502 self.ipams.insert(ipam_id.clone(), ipam);
11503 self.ipams.get(&ipam_id).unwrap()
11504 }
11505
11506 pub fn delete_ipam(&mut self, ipam_id: &str, cascade: bool) -> Result<Ipam, Ec2Error> {
11507 if !self.ipams.contains_key(ipam_id) {
11508 return Err(Ec2Error::InvalidIpamNotFound(ipam_id.to_string()));
11509 }
11510 let scope_ids: Vec<String> = self
11512 .ipam_scopes
11513 .values()
11514 .filter(|s| {
11515 self.ipams
11516 .get(ipam_id)
11517 .map(|i| s.ipam_arn == i.ipam_arn)
11518 .unwrap_or(false)
11519 })
11520 .map(|s| s.ipam_scope_id.clone())
11521 .collect();
11522 let pool_ids: Vec<String> = self
11524 .ipam_pools
11525 .values()
11526 .filter(|p| {
11527 self.ipam_scopes.values().any(|s| {
11528 scope_ids.contains(&s.ipam_scope_id) && s.ipam_scope_arn == p.ipam_scope_arn
11529 })
11530 })
11531 .map(|p| p.ipam_pool_id.clone())
11532 .collect();
11533 if !pool_ids.is_empty() && !cascade {
11534 return Err(Ec2Error::IpamInUse(ipam_id.to_string()));
11535 }
11536 if cascade {
11537 for pid in &pool_ids {
11538 self.ipam_pools.remove(pid);
11539 self.ipam_pool_cidrs.retain(|(pool, _), _| pool != pid);
11540 self.ipam_pool_allocations
11541 .retain(|(pool, _), _| pool != pid);
11542 }
11543 for sid in &scope_ids {
11544 self.ipam_scopes.remove(sid);
11545 }
11546 } else {
11547 for sid in &scope_ids {
11549 self.ipam_scopes.remove(sid);
11550 }
11551 }
11552 let mut ipam = self.ipams.remove(ipam_id).unwrap();
11553 ipam.state = "delete-complete".to_string();
11554 Ok(ipam)
11555 }
11556
11557 pub fn modify_ipam(
11558 &mut self,
11559 ipam_id: &str,
11560 description: Option<String>,
11561 add_operating_regions: Vec<IpamOperatingRegion>,
11562 remove_operating_regions: Vec<String>,
11563 tier: Option<String>,
11564 enable_private_gua: Option<bool>,
11565 metered_account: Option<String>,
11566 ) -> Result<&Ipam, Ec2Error> {
11567 let ipam = self
11568 .ipams
11569 .get_mut(ipam_id)
11570 .ok_or_else(|| Ec2Error::InvalidIpamNotFound(ipam_id.to_string()))?;
11571 if let Some(d) = description {
11572 ipam.description = Some(d);
11573 }
11574 if !remove_operating_regions.is_empty() {
11575 ipam.operating_regions
11576 .retain(|r| !remove_operating_regions.contains(&r.region_name));
11577 }
11578 for r in add_operating_regions {
11579 if !ipam
11580 .operating_regions
11581 .iter()
11582 .any(|x| x.region_name == r.region_name)
11583 {
11584 ipam.operating_regions.push(r);
11585 }
11586 }
11587 if let Some(t) = tier {
11588 ipam.tier = t;
11589 }
11590 if let Some(v) = enable_private_gua {
11591 ipam.enable_private_gua = v;
11592 }
11593 if let Some(m) = metered_account {
11594 ipam.metered_account = m;
11595 }
11596 ipam.state = "modify-complete".to_string();
11597 Ok(ipam)
11598 }
11599
11600 pub fn create_ipam_scope(
11601 &mut self,
11602 ipam_id: &str,
11603 description: Option<String>,
11604 tags: Tags,
11605 ) -> Result<&IpamScope, Ec2Error> {
11606 let ipam = self
11607 .ipams
11608 .get(ipam_id)
11609 .ok_or_else(|| Ec2Error::InvalidIpamNotFound(ipam_id.to_string()))?;
11610 let owner = ipam.owner_id.clone();
11611 let region = ipam.ipam_region.clone();
11612 let ipam_arn = ipam.ipam_arn.clone();
11613 let scope_id = self.next_ipam_scope_id();
11614 let scope_arn = format!("arn:aws:ec2::{owner}:ipam-scope/{scope_id}",);
11615 let scope = IpamScope {
11616 ipam_scope_id: scope_id.clone(),
11617 ipam_scope_arn: scope_arn,
11618 ipam_arn,
11619 ipam_region: region,
11620 ipam_scope_type: "private".to_string(),
11621 is_default: false,
11622 description,
11623 pool_count: 0,
11624 state: "create-complete".to_string(),
11625 tags,
11626 owner_id: owner,
11627 };
11628 self.ipam_scopes.insert(scope_id.clone(), scope);
11629 if let Some(i) = self.ipams.get_mut(ipam_id) {
11631 i.scope_count += 1;
11632 }
11633 Ok(self.ipam_scopes.get(&scope_id).unwrap())
11634 }
11635
11636 pub fn delete_ipam_scope(&mut self, scope_id: &str) -> Result<IpamScope, Ec2Error> {
11637 let scope = self
11638 .ipam_scopes
11639 .get(scope_id)
11640 .ok_or_else(|| Ec2Error::InvalidIpamScopeNotFound(scope_id.to_string()))?;
11641 if scope.is_default {
11642 return Err(Ec2Error::IpamScopeIsDefault(scope_id.to_string()));
11643 }
11644 let scope_arn = scope.ipam_scope_arn.clone();
11645 let ipam_arn = scope.ipam_arn.clone();
11646 let has_pools = self
11647 .ipam_pools
11648 .values()
11649 .any(|p| p.ipam_scope_arn == scope_arn);
11650 if has_pools {
11651 return Err(Ec2Error::IpamScopeInUse(scope_id.to_string()));
11652 }
11653 let mut removed = self.ipam_scopes.remove(scope_id).unwrap();
11654 removed.state = "delete-complete".to_string();
11655 if let Some(ipam) = self.ipams.values_mut().find(|i| i.ipam_arn == ipam_arn) {
11657 if ipam.scope_count > 0 {
11658 ipam.scope_count -= 1;
11659 }
11660 }
11661 Ok(removed)
11662 }
11663
11664 pub fn modify_ipam_scope(
11665 &mut self,
11666 scope_id: &str,
11667 description: Option<String>,
11668 ) -> Result<&IpamScope, Ec2Error> {
11669 let scope = self
11670 .ipam_scopes
11671 .get_mut(scope_id)
11672 .ok_or_else(|| Ec2Error::InvalidIpamScopeNotFound(scope_id.to_string()))?;
11673 if let Some(d) = description {
11674 scope.description = Some(d);
11675 }
11676 scope.state = "modify-complete".to_string();
11677 Ok(scope)
11678 }
11679
11680 #[allow(clippy::too_many_arguments)]
11681 pub fn create_ipam_pool(
11682 &mut self,
11683 scope_id: &str,
11684 address_family: String,
11685 description: Option<String>,
11686 locale: Option<String>,
11687 publicly_advertisable: bool,
11688 auto_import: bool,
11689 allocation_min_netmask_length: Option<i32>,
11690 allocation_max_netmask_length: Option<i32>,
11691 allocation_default_netmask_length: Option<i32>,
11692 allocation_resource_tags: Vec<(String, String)>,
11693 aws_service: Option<String>,
11694 public_ip_source: Option<String>,
11695 source_ipam_pool_id: Option<String>,
11696 tags: Tags,
11697 ) -> Result<&IpamPool, Ec2Error> {
11698 let scope = self
11699 .ipam_scopes
11700 .get(scope_id)
11701 .ok_or_else(|| Ec2Error::InvalidIpamScopeNotFound(scope_id.to_string()))?;
11702 let scope_arn = scope.ipam_scope_arn.clone();
11703 let scope_type = scope.ipam_scope_type.clone();
11704 let ipam_arn = scope.ipam_arn.clone();
11705 let region = scope.ipam_region.clone();
11706 let owner = scope.owner_id.clone();
11707 let pool_id = self.next_ipam_pool_id();
11708 let pool_arn = format!("arn:aws:ec2::{owner}:ipam-pool/{pool_id}",);
11709 let pool = IpamPool {
11710 ipam_pool_id: pool_id.clone(),
11711 source_ipam_pool_id,
11712 ipam_pool_arn: pool_arn,
11713 ipam_scope_arn: scope_arn,
11714 ipam_scope_type: scope_type,
11715 ipam_arn,
11716 ipam_region: region.clone(),
11717 locale: locale.unwrap_or_else(|| region.clone()),
11718 pool_depth: 1,
11719 state: "create-complete".to_string(),
11720 state_message: None,
11721 description,
11722 auto_import,
11723 publicly_advertisable,
11724 address_family,
11725 allocation_min_netmask_length,
11726 allocation_max_netmask_length,
11727 allocation_default_netmask_length,
11728 allocation_resource_tags,
11729 aws_service,
11730 public_ip_source,
11731 source_resource_id: None,
11732 source_resource_type: None,
11733 source_resource_region: None,
11734 source_resource_owner: None,
11735 tags,
11736 owner_id: owner,
11737 allocation_count: 0,
11738 };
11739 self.ipam_pools.insert(pool_id.clone(), pool);
11740 if let Some(s) = self.ipam_scopes.get_mut(scope_id) {
11742 s.pool_count += 1;
11743 }
11744 Ok(self.ipam_pools.get(&pool_id).unwrap())
11745 }
11746
11747 pub fn delete_ipam_pool(&mut self, pool_id: &str) -> Result<IpamPool, Ec2Error> {
11748 let pool = self
11749 .ipam_pools
11750 .get(pool_id)
11751 .ok_or_else(|| Ec2Error::InvalidIpamPoolNotFound(pool_id.to_string()))?;
11752 let scope_arn = pool.ipam_scope_arn.clone();
11753 let has_allocations = self
11754 .ipam_pool_allocations
11755 .keys()
11756 .any(|(pid, _)| pid == pool_id);
11757 if has_allocations {
11758 return Err(Ec2Error::IpamPoolInUse(pool_id.to_string()));
11759 }
11760 self.ipam_pool_cidrs.retain(|(pid, _), _| pid != pool_id);
11762 let mut removed = self.ipam_pools.remove(pool_id).unwrap();
11763 removed.state = "delete-complete".to_string();
11764 if let Some(scope) = self
11766 .ipam_scopes
11767 .values_mut()
11768 .find(|s| s.ipam_scope_arn == scope_arn)
11769 {
11770 if scope.pool_count > 0 {
11771 scope.pool_count -= 1;
11772 }
11773 }
11774 Ok(removed)
11775 }
11776
11777 #[allow(clippy::too_many_arguments)]
11778 pub fn modify_ipam_pool(
11779 &mut self,
11780 pool_id: &str,
11781 description: Option<String>,
11782 auto_import: Option<bool>,
11783 allocation_min_netmask_length: Option<i32>,
11784 allocation_max_netmask_length: Option<i32>,
11785 allocation_default_netmask_length: Option<i32>,
11786 add_allocation_resource_tags: Vec<(String, String)>,
11787 remove_allocation_resource_tags: Vec<(String, String)>,
11788 ) -> Result<&IpamPool, Ec2Error> {
11789 let pool = self
11790 .ipam_pools
11791 .get_mut(pool_id)
11792 .ok_or_else(|| Ec2Error::InvalidIpamPoolNotFound(pool_id.to_string()))?;
11793 if let Some(d) = description {
11794 pool.description = Some(d);
11795 }
11796 if let Some(v) = auto_import {
11797 pool.auto_import = v;
11798 }
11799 if let Some(v) = allocation_min_netmask_length {
11800 pool.allocation_min_netmask_length = Some(v);
11801 }
11802 if let Some(v) = allocation_max_netmask_length {
11803 pool.allocation_max_netmask_length = Some(v);
11804 }
11805 if let Some(v) = allocation_default_netmask_length {
11806 pool.allocation_default_netmask_length = Some(v);
11807 }
11808 for tag in add_allocation_resource_tags {
11809 if !pool
11810 .allocation_resource_tags
11811 .iter()
11812 .any(|(k, _)| k == &tag.0)
11813 {
11814 pool.allocation_resource_tags.push(tag);
11815 }
11816 }
11817 for tag in remove_allocation_resource_tags {
11818 pool.allocation_resource_tags.retain(|(k, _)| k != &tag.0);
11819 }
11820 pool.state = "modify-complete".to_string();
11821 Ok(pool)
11822 }
11823
11824 pub fn provision_ipam_pool_cidr(
11825 &mut self,
11826 pool_id: &str,
11827 cidr: Option<String>,
11828 netmask_length: Option<i32>,
11829 ) -> Result<&IpamPoolCidr, Ec2Error> {
11830 if !self.ipam_pools.contains_key(pool_id) {
11831 return Err(Ec2Error::InvalidIpamPoolNotFound(pool_id.to_string()));
11832 }
11833 let cidr_str = cidr.unwrap_or_else(|| {
11834 format!(
11836 "10.{}.0.0/{}",
11837 self.counters.ipam_pool_cidr & 0xff,
11838 netmask_length.unwrap_or(16)
11839 )
11840 });
11841 let pool_cidr_id = self.next_ipam_pool_cidr_id();
11842 let entry = IpamPoolCidr {
11843 cidr: cidr_str.clone(),
11844 state: "provisioned".to_string(),
11845 failure_reason: None,
11846 ipam_pool_cidr_id: pool_cidr_id,
11847 netmask_length,
11848 };
11849 let key = (pool_id.to_string(), cidr_str.clone());
11850 self.ipam_pool_cidrs.insert(key.clone(), entry);
11851 Ok(self.ipam_pool_cidrs.get(&key).unwrap())
11852 }
11853
11854 pub fn deprovision_ipam_pool_cidr(
11855 &mut self,
11856 pool_id: &str,
11857 cidr: &str,
11858 ) -> Result<IpamPoolCidr, Ec2Error> {
11859 let key = (pool_id.to_string(), cidr.to_string());
11860 let mut entry = self.ipam_pool_cidrs.remove(&key).ok_or_else(|| {
11861 Ec2Error::InvalidIpamPoolCidrNotFound(pool_id.to_string(), cidr.to_string())
11862 })?;
11863 entry.state = "deprovisioned".to_string();
11864 Ok(entry)
11865 }
11866
11867 pub fn allocate_ipam_pool_cidr(
11868 &mut self,
11869 pool_id: &str,
11870 cidr: Option<String>,
11871 netmask_length: Option<i32>,
11872 description: Option<String>,
11873 ) -> Result<&IpamPoolAllocation, Ec2Error> {
11874 let pool = self
11875 .ipam_pools
11876 .get(pool_id)
11877 .ok_or_else(|| Ec2Error::InvalidIpamPoolNotFound(pool_id.to_string()))?;
11878 let region = pool.ipam_region.clone();
11879 let owner = pool.owner_id.clone();
11880 let allocation_id = self.next_ipam_pool_allocation_id();
11881 let cidr_str = cidr.unwrap_or_else(|| {
11882 format!(
11883 "10.{}.0.0/{}",
11884 self.counters.ipam_pool_allocation & 0xff,
11885 netmask_length.unwrap_or(28)
11886 )
11887 });
11888 let alloc = IpamPoolAllocation {
11889 ipam_pool_allocation_id: allocation_id.clone(),
11890 cidr: cidr_str,
11891 ipam_pool_id: pool_id.to_string(),
11892 description,
11893 resource_id: None,
11894 resource_type: "custom".to_string(),
11895 resource_region: Some(region),
11896 resource_owner: Some(owner),
11897 };
11898 let key = (pool_id.to_string(), allocation_id);
11899 self.ipam_pool_allocations.insert(key.clone(), alloc);
11900 if let Some(p) = self.ipam_pools.get_mut(pool_id) {
11901 p.allocation_count += 1;
11902 }
11903 Ok(self.ipam_pool_allocations.get(&key).unwrap())
11904 }
11905
11906 pub fn release_ipam_pool_allocation(
11907 &mut self,
11908 pool_id: &str,
11909 allocation_id: &str,
11910 ) -> Result<(), Ec2Error> {
11911 let key = (pool_id.to_string(), allocation_id.to_string());
11912 if self.ipam_pool_allocations.remove(&key).is_none() {
11913 return Err(Ec2Error::InvalidIpamPoolAllocationNotFound(
11914 pool_id.to_string(),
11915 allocation_id.to_string(),
11916 ));
11917 }
11918 if let Some(p) = self.ipam_pools.get_mut(pool_id) {
11919 if p.allocation_count > 0 {
11920 p.allocation_count -= 1;
11921 }
11922 }
11923 Ok(())
11924 }
11925
11926 pub fn create_ipam_resource_discovery(
11927 &mut self,
11928 region: &str,
11929 owner_id: &str,
11930 description: Option<String>,
11931 operating_regions: Vec<IpamOperatingRegion>,
11932 tags: Tags,
11933 ) -> &IpamResourceDiscovery {
11934 let id = self.next_ipam_resource_discovery_id();
11935 let arn = format!("arn:aws:ec2::{owner_id}:ipam-resource-discovery/{id}",);
11936 let rd = IpamResourceDiscovery {
11937 ipam_resource_discovery_id: id.clone(),
11938 ipam_resource_discovery_arn: arn,
11939 ipam_resource_discovery_region: region.to_string(),
11940 description,
11941 operating_regions,
11942 is_default: false,
11943 state: "create-complete".to_string(),
11944 owner_id: owner_id.to_string(),
11945 tags,
11946 };
11947 self.ipam_resource_discoveries.insert(id.clone(), rd);
11948 self.ipam_resource_discoveries.get(&id).unwrap()
11949 }
11950
11951 pub fn delete_ipam_resource_discovery(
11952 &mut self,
11953 id: &str,
11954 ) -> Result<IpamResourceDiscovery, Ec2Error> {
11955 let rd = self
11956 .ipam_resource_discoveries
11957 .get(id)
11958 .ok_or_else(|| Ec2Error::InvalidIpamResourceDiscoveryNotFound(id.to_string()))?;
11959 if rd.is_default {
11960 return Err(Ec2Error::IpamResourceDiscoveryInUse(id.to_string()));
11961 }
11962 let has_assoc = self
11963 .ipam_resource_discovery_associations
11964 .values()
11965 .any(|a| a.ipam_resource_discovery_id == id);
11966 if has_assoc {
11967 return Err(Ec2Error::IpamResourceDiscoveryInUse(id.to_string()));
11968 }
11969 let mut removed = self.ipam_resource_discoveries.remove(id).unwrap();
11970 removed.state = "delete-complete".to_string();
11971 Ok(removed)
11972 }
11973
11974 pub fn modify_ipam_resource_discovery(
11975 &mut self,
11976 id: &str,
11977 description: Option<String>,
11978 add_operating_regions: Vec<IpamOperatingRegion>,
11979 remove_operating_regions: Vec<String>,
11980 ) -> Result<&IpamResourceDiscovery, Ec2Error> {
11981 let rd = self
11982 .ipam_resource_discoveries
11983 .get_mut(id)
11984 .ok_or_else(|| Ec2Error::InvalidIpamResourceDiscoveryNotFound(id.to_string()))?;
11985 if let Some(d) = description {
11986 rd.description = Some(d);
11987 }
11988 if !remove_operating_regions.is_empty() {
11989 rd.operating_regions
11990 .retain(|r| !remove_operating_regions.contains(&r.region_name));
11991 }
11992 for r in add_operating_regions {
11993 if !rd
11994 .operating_regions
11995 .iter()
11996 .any(|x| x.region_name == r.region_name)
11997 {
11998 rd.operating_regions.push(r);
11999 }
12000 }
12001 rd.state = "modify-complete".to_string();
12002 Ok(rd)
12003 }
12004
12005 pub fn associate_ipam_resource_discovery(
12006 &mut self,
12007 ipam_id: &str,
12008 resource_discovery_id: &str,
12009 tags: Tags,
12010 ) -> Result<&IpamResourceDiscoveryAssociation, Ec2Error> {
12011 let ipam = self
12012 .ipams
12013 .get(ipam_id)
12014 .ok_or_else(|| Ec2Error::InvalidIpamNotFound(ipam_id.to_string()))?;
12015 if !self
12016 .ipam_resource_discoveries
12017 .contains_key(resource_discovery_id)
12018 {
12019 return Err(Ec2Error::InvalidIpamResourceDiscoveryNotFound(
12020 resource_discovery_id.to_string(),
12021 ));
12022 }
12023 let owner = ipam.owner_id.clone();
12024 let region = ipam.ipam_region.clone();
12025 let ipam_arn = ipam.ipam_arn.clone();
12026 let assoc_id = self.next_ipam_resource_discovery_assoc_id();
12027 let assoc_arn =
12028 format!("arn:aws:ec2::{owner}:ipam-resource-discovery-association/{assoc_id}",);
12029 let assoc = IpamResourceDiscoveryAssociation {
12030 ipam_resource_discovery_association_id: assoc_id.clone(),
12031 ipam_resource_discovery_association_arn: assoc_arn,
12032 ipam_arn,
12033 ipam_id: ipam_id.to_string(),
12034 ipam_region: region,
12035 ipam_resource_discovery_id: resource_discovery_id.to_string(),
12036 owner_id: owner,
12037 is_default: false,
12038 resource_discovery_status: "active".to_string(),
12039 state: "associate-complete".to_string(),
12040 tags,
12041 };
12042 self.ipam_resource_discovery_associations
12043 .insert(assoc_id.clone(), assoc);
12044 if let Some(i) = self.ipams.get_mut(ipam_id) {
12045 i.resource_discovery_association_count += 1;
12046 }
12047 Ok(self
12048 .ipam_resource_discovery_associations
12049 .get(&assoc_id)
12050 .unwrap())
12051 }
12052
12053 pub fn disassociate_ipam_resource_discovery(
12054 &mut self,
12055 assoc_id: &str,
12056 ) -> Result<IpamResourceDiscoveryAssociation, Ec2Error> {
12057 let assoc = self
12058 .ipam_resource_discovery_associations
12059 .get(assoc_id)
12060 .ok_or_else(|| {
12061 Ec2Error::InvalidIpamResourceDiscoveryAssociationNotFound(assoc_id.to_string())
12062 })?;
12063 let ipam_id = assoc.ipam_id.clone();
12064 let mut removed = self
12065 .ipam_resource_discovery_associations
12066 .remove(assoc_id)
12067 .unwrap();
12068 removed.state = "disassociate-complete".to_string();
12069 if let Some(i) = self.ipams.get_mut(&ipam_id) {
12070 if i.resource_discovery_association_count > 0 {
12071 i.resource_discovery_association_count -= 1;
12072 }
12073 }
12074 Ok(removed)
12075 }
12076
12077 pub fn provision_ipam_byoasn(
12078 &mut self,
12079 ipam_id: &str,
12080 asn: &str,
12081 description: Option<String>,
12082 ) -> Result<&IpamByoasn, Ec2Error> {
12083 if !self.ipams.contains_key(ipam_id) {
12084 return Err(Ec2Error::InvalidIpamNotFound(ipam_id.to_string()));
12085 }
12086 let entry = IpamByoasn {
12087 asn: asn.to_string(),
12088 ipam_id: ipam_id.to_string(),
12089 description,
12090 state: "provisioned".to_string(),
12091 status_message: None,
12092 };
12093 let key = (ipam_id.to_string(), asn.to_string());
12094 self.ipam_byoasns.insert(key.clone(), entry);
12095 Ok(self.ipam_byoasns.get(&key).unwrap())
12096 }
12097
12098 pub fn deprovision_ipam_byoasn(
12099 &mut self,
12100 ipam_id: &str,
12101 asn: &str,
12102 ) -> Result<IpamByoasn, Ec2Error> {
12103 let key = (ipam_id.to_string(), asn.to_string());
12104 let mut entry = self.ipam_byoasns.remove(&key).ok_or_else(|| {
12105 Ec2Error::InvalidIpamByoasnNotFound(ipam_id.to_string(), asn.to_string())
12106 })?;
12107 entry.state = "deprovisioned".to_string();
12108 Ok(entry)
12109 }
12110
12111 pub fn associate_ipam_byoasn(
12112 &mut self,
12113 asn: &str,
12114 cidr: &str,
12115 ) -> Result<AsnAssociation, Ec2Error> {
12116 let exists = self.ipam_byoasns.keys().any(|(_, a)| a == asn);
12118 if !exists {
12119 return Err(Ec2Error::InvalidIpamByoasnNotFound(
12120 String::new(),
12121 asn.to_string(),
12122 ));
12123 }
12124 let cidr_entry = self
12125 .byoip_cidrs
12126 .get_mut(cidr)
12127 .ok_or_else(|| Ec2Error::InvalidByoipCidrNotFound(cidr.to_string()))?;
12128 if cidr_entry.asn_association.is_some() {
12129 return Err(Ec2Error::IpamByoasnAlreadyAssociated(asn.to_string()));
12130 }
12131 let assoc = AsnAssociation {
12132 asn: asn.to_string(),
12133 cidr: cidr.to_string(),
12134 state: "associated".to_string(),
12135 status_message: None,
12136 };
12137 cidr_entry.asn_association = Some(assoc.clone());
12138 Ok(assoc)
12139 }
12140
12141 pub fn disassociate_ipam_byoasn(
12142 &mut self,
12143 asn: &str,
12144 cidr: &str,
12145 ) -> Result<AsnAssociation, Ec2Error> {
12146 let cidr_entry = self
12147 .byoip_cidrs
12148 .get_mut(cidr)
12149 .ok_or_else(|| Ec2Error::InvalidByoipCidrNotFound(cidr.to_string()))?;
12150 let mut assoc = cidr_entry
12151 .asn_association
12152 .take()
12153 .ok_or_else(|| Ec2Error::InvalidIpamByoasnNotFound(String::new(), asn.to_string()))?;
12154 assoc.state = "disassociated".to_string();
12155 Ok(assoc)
12156 }
12157
12158 pub fn create_ipam_external_resource_verification_token(
12159 &mut self,
12160 ipam_id: &str,
12161 tags: Tags,
12162 ) -> Result<&IpamExternalResourceVerificationToken, Ec2Error> {
12163 let ipam = self
12164 .ipams
12165 .get(ipam_id)
12166 .ok_or_else(|| Ec2Error::InvalidIpamNotFound(ipam_id.to_string()))?;
12167 let region = ipam.ipam_region.clone();
12168 let ipam_arn = ipam.ipam_arn.clone();
12169 let owner = ipam.owner_id.clone();
12170 let token_id = self.next_ipam_external_resource_verification_token_id();
12171 let token_arn =
12172 format!("arn:aws:ec2::{owner}:ipam-external-resource-verification-token/{token_id}",);
12173 let now = chrono::Utc::now()
12174 .format("%Y-%m-%dT%H:%M:%S.000Z")
12175 .to_string();
12176 let entry = IpamExternalResourceVerificationToken {
12177 ipam_external_resource_verification_token_id: token_id.clone(),
12178 ipam_external_resource_verification_token_arn: token_arn,
12179 ipam_id: ipam_id.to_string(),
12180 ipam_arn,
12181 ipam_region: region,
12182 token_value: format!("token-{token_id}"),
12183 token_name: format!("token-name-{token_id}"),
12184 not_after: now,
12185 status: "valid".to_string(),
12186 state: "create-complete".to_string(),
12187 tags,
12188 };
12189 self.ipam_external_resource_verification_tokens
12190 .insert(token_id.clone(), entry);
12191 Ok(self
12192 .ipam_external_resource_verification_tokens
12193 .get(&token_id)
12194 .unwrap())
12195 }
12196
12197 pub fn delete_ipam_external_resource_verification_token(
12198 &mut self,
12199 token_id: &str,
12200 ) -> Result<IpamExternalResourceVerificationToken, Ec2Error> {
12201 let mut entry = self
12202 .ipam_external_resource_verification_tokens
12203 .remove(token_id)
12204 .ok_or_else(|| {
12205 Ec2Error::InvalidIpamExternalResourceVerificationTokenNotFound(token_id.to_string())
12206 })?;
12207 entry.state = "delete-complete".to_string();
12208 Ok(entry)
12209 }
12210
12211 pub fn create_ipam_policy(
12212 &mut self,
12213 ipam_id: &str,
12214 policy_name: String,
12215 description: Option<String>,
12216 allocation_rules: Vec<IpamPolicyAllocationRule>,
12217 tags: Tags,
12218 ) -> Result<&IpamPolicy, Ec2Error> {
12219 let ipam = self
12220 .ipams
12221 .get(ipam_id)
12222 .ok_or_else(|| Ec2Error::InvalidIpamNotFound(ipam_id.to_string()))?;
12223 let region = ipam.ipam_region.clone();
12224 let ipam_arn = ipam.ipam_arn.clone();
12225 let owner = ipam.owner_id.clone();
12226 let policy_id = self.next_ipam_policy_id();
12227 let policy_arn = format!("arn:aws:ec2::{owner}:ipam-policy/{policy_id}",);
12228 let policy = IpamPolicy {
12229 ipam_policy_id: policy_id.clone(),
12230 ipam_policy_arn: policy_arn,
12231 ipam_arn,
12232 ipam_region: region,
12233 policy_name,
12234 policy_type: "allocation".to_string(),
12235 description,
12236 state: "create-complete".to_string(),
12237 allocation_rules,
12238 tags,
12239 owner_id: owner,
12240 };
12241 self.ipam_policies.insert(policy_id.clone(), policy);
12242 Ok(self.ipam_policies.get(&policy_id).unwrap())
12243 }
12244
12245 pub fn delete_ipam_policy(&mut self, policy_id: &str) -> Result<IpamPolicy, Ec2Error> {
12246 let mut removed = self
12247 .ipam_policies
12248 .remove(policy_id)
12249 .ok_or_else(|| Ec2Error::InvalidIpamPolicyNotFound(policy_id.to_string()))?;
12250 removed.state = "delete-complete".to_string();
12251 Ok(removed)
12252 }
12253
12254 pub fn modify_ipam_policy_allocation_rules(
12255 &mut self,
12256 policy_id: &str,
12257 add_rules: Vec<IpamPolicyAllocationRule>,
12258 remove_rules: Vec<IpamPolicyAllocationRule>,
12259 ) -> Result<&IpamPolicy, Ec2Error> {
12260 let policy = self
12261 .ipam_policies
12262 .get_mut(policy_id)
12263 .ok_or_else(|| Ec2Error::InvalidIpamPolicyNotFound(policy_id.to_string()))?;
12264 let remove_pool_ids: Vec<Option<String>> = remove_rules
12265 .into_iter()
12266 .map(|r| r.source_ipam_pool_id)
12267 .collect();
12268 if !remove_pool_ids.is_empty() {
12269 policy
12270 .allocation_rules
12271 .retain(|r| !remove_pool_ids.contains(&r.source_ipam_pool_id));
12272 }
12273 for r in add_rules {
12274 if !policy
12275 .allocation_rules
12276 .iter()
12277 .any(|x| x.source_ipam_pool_id == r.source_ipam_pool_id)
12278 {
12279 policy.allocation_rules.push(r);
12280 }
12281 }
12282 Ok(policy)
12283 }
12284
12285 pub fn create_ipam_prefix_list_resolver(
12286 &mut self,
12287 ipam_id: &str,
12288 name: String,
12289 description: Option<String>,
12290 tags: Tags,
12291 ) -> Result<&IpamPrefixListResolver, Ec2Error> {
12292 let ipam = self
12293 .ipams
12294 .get(ipam_id)
12295 .ok_or_else(|| Ec2Error::InvalidIpamNotFound(ipam_id.to_string()))?;
12296 let region = ipam.ipam_region.clone();
12297 let ipam_arn = ipam.ipam_arn.clone();
12298 let owner = ipam.owner_id.clone();
12299 let resolver_id = self.next_ipam_prefix_list_resolver_id();
12300 let resolver_arn = format!("arn:aws:ec2::{owner}:ipam-prefix-list-resolver/{resolver_id}",);
12301 let resolver = IpamPrefixListResolver {
12302 ipam_prefix_list_resolver_id: resolver_id.clone(),
12303 ipam_prefix_list_resolver_arn: resolver_arn,
12304 ipam_arn,
12305 ipam_region: region,
12306 name,
12307 description,
12308 state: "available".to_string(),
12309 owner_id: owner,
12310 target_count: 0,
12311 tags,
12312 };
12313 self.ipam_prefix_list_resolvers
12314 .insert(resolver_id.clone(), resolver);
12315 Ok(self.ipam_prefix_list_resolvers.get(&resolver_id).unwrap())
12316 }
12317
12318 pub fn delete_ipam_prefix_list_resolver(
12319 &mut self,
12320 resolver_id: &str,
12321 ) -> Result<IpamPrefixListResolver, Ec2Error> {
12322 if !self.ipam_prefix_list_resolvers.contains_key(resolver_id) {
12323 return Err(Ec2Error::InvalidIpamPrefixListResolverNotFound(
12324 resolver_id.to_string(),
12325 ));
12326 }
12327 let has_targets = self
12328 .ipam_prefix_list_resolver_targets
12329 .keys()
12330 .any(|(rid, _)| rid == resolver_id);
12331 if has_targets {
12332 return Err(Ec2Error::IpamPrefixListResolverInUse(
12333 resolver_id.to_string(),
12334 ));
12335 }
12336 let mut removed = self.ipam_prefix_list_resolvers.remove(resolver_id).unwrap();
12337 removed.state = "deleted".to_string();
12338 Ok(removed)
12339 }
12340
12341 pub fn modify_ipam_prefix_list_resolver(
12342 &mut self,
12343 resolver_id: &str,
12344 name: Option<String>,
12345 description: Option<String>,
12346 ) -> Result<&IpamPrefixListResolver, Ec2Error> {
12347 let resolver = self
12348 .ipam_prefix_list_resolvers
12349 .get_mut(resolver_id)
12350 .ok_or_else(|| {
12351 Ec2Error::InvalidIpamPrefixListResolverNotFound(resolver_id.to_string())
12352 })?;
12353 if let Some(n) = name {
12354 resolver.name = n;
12355 }
12356 if let Some(d) = description {
12357 resolver.description = Some(d);
12358 }
12359 resolver.state = "modifying".to_string();
12360 Ok(resolver)
12361 }
12362
12363 pub fn create_ipam_prefix_list_resolver_target(
12364 &mut self,
12365 resolver_id: &str,
12366 target_resource_arn: String,
12367 target_resource_type: String,
12368 target_resource_region: String,
12369 tags: Tags,
12370 ) -> Result<&IpamPrefixListResolverTarget, Ec2Error> {
12371 let resolver = self
12372 .ipam_prefix_list_resolvers
12373 .get(resolver_id)
12374 .ok_or_else(|| {
12375 Ec2Error::InvalidIpamPrefixListResolverNotFound(resolver_id.to_string())
12376 })?;
12377 let owner = resolver.owner_id.clone();
12378 let target_id = self.next_ipam_prefix_list_resolver_target_id();
12379 let target = IpamPrefixListResolverTarget {
12380 ipam_prefix_list_resolver_target_id: target_id.clone(),
12381 ipam_prefix_list_resolver_id: resolver_id.to_string(),
12382 target_resource_arn,
12383 target_resource_type,
12384 target_resource_region,
12385 owner_id: owner,
12386 state: "available".to_string(),
12387 tags,
12388 };
12389 let key = (resolver_id.to_string(), target_id);
12390 self.ipam_prefix_list_resolver_targets
12391 .insert(key.clone(), target);
12392 if let Some(r) = self.ipam_prefix_list_resolvers.get_mut(resolver_id) {
12393 r.target_count += 1;
12394 }
12395 Ok(self.ipam_prefix_list_resolver_targets.get(&key).unwrap())
12396 }
12397
12398 pub fn delete_ipam_prefix_list_resolver_target(
12399 &mut self,
12400 resolver_id: &str,
12401 target_id: &str,
12402 ) -> Result<IpamPrefixListResolverTarget, Ec2Error> {
12403 let key = (resolver_id.to_string(), target_id.to_string());
12404 let mut removed = self
12405 .ipam_prefix_list_resolver_targets
12406 .remove(&key)
12407 .ok_or_else(|| {
12408 Ec2Error::InvalidIpamPrefixListResolverTargetNotFound(
12409 resolver_id.to_string(),
12410 target_id.to_string(),
12411 )
12412 })?;
12413 removed.state = "deleted".to_string();
12414 if let Some(r) = self.ipam_prefix_list_resolvers.get_mut(resolver_id) {
12415 if r.target_count > 0 {
12416 r.target_count -= 1;
12417 }
12418 }
12419 Ok(removed)
12420 }
12421
12422 pub fn modify_ipam_prefix_list_resolver_target(
12423 &mut self,
12424 resolver_id: &str,
12425 target_id: &str,
12426 target_resource_region: Option<String>,
12427 ) -> Result<&IpamPrefixListResolverTarget, Ec2Error> {
12428 let key = (resolver_id.to_string(), target_id.to_string());
12429 let target = self
12430 .ipam_prefix_list_resolver_targets
12431 .get_mut(&key)
12432 .ok_or_else(|| {
12433 Ec2Error::InvalidIpamPrefixListResolverTargetNotFound(
12434 resolver_id.to_string(),
12435 target_id.to_string(),
12436 )
12437 })?;
12438 if let Some(r) = target_resource_region {
12439 target.target_resource_region = r;
12440 }
12441 target.state = "modifying".to_string();
12442 Ok(target)
12443 }
12444
12445 pub fn move_byoip_cidr_to_ipam(
12446 &mut self,
12447 cidr: &str,
12448 ipam_pool_id: &str,
12449 ) -> Result<&ByoipCidr, Ec2Error> {
12450 if !self.ipam_pools.contains_key(ipam_pool_id) {
12451 return Err(Ec2Error::InvalidIpamPoolNotFound(ipam_pool_id.to_string()));
12452 }
12453 let entry = self
12454 .byoip_cidrs
12455 .get_mut(cidr)
12456 .ok_or_else(|| Ec2Error::InvalidByoipCidrNotFound(cidr.to_string()))?;
12457 entry.ipam_pool_id = Some(ipam_pool_id.to_string());
12458 Ok(entry)
12459 }
12460
12461 pub fn next_bundle_task_id(&mut self) -> String {
12466 self.counters.bundle_task += 1;
12467 format!("bun-{:08x}", self.counters.bundle_task)
12468 }
12469
12470 pub fn next_import_volume_task_id(&mut self) -> String {
12471 self.counters.import_volume_task += 1;
12472 format!("import-vol-{:08x}", self.counters.import_volume_task)
12473 }
12474
12475 pub fn next_export_image_task_id(&mut self) -> String {
12476 self.counters.export_image_task += 1;
12477 format!("export-ami-{:08x}", self.counters.export_image_task)
12478 }
12479
12480 pub fn next_outpost_lag_id(&mut self) -> String {
12481 self.counters.outpost_lag += 1;
12482 format!("outpost-lag-{:08x}", self.counters.outpost_lag)
12483 }
12484
12485 #[allow(clippy::too_many_arguments)]
12487 pub fn modify_volume(
12488 &mut self,
12489 volume_id: &str,
12490 new_size: Option<i32>,
12491 new_volume_type: Option<String>,
12492 new_iops: Option<i32>,
12493 new_throughput: Option<i32>,
12494 new_multi_attach_enabled: Option<bool>,
12495 ) -> Result<&VolumeModification, Ec2Error> {
12496 let now = self.now_iso();
12497 let vol = self
12498 .volumes
12499 .get_mut(volume_id)
12500 .ok_or_else(|| Ec2Error::VolumeNotFound(volume_id.to_string()))?;
12501 let original_size = vol.size;
12502 let original_iops = vol.iops;
12503 let original_throughput = vol.throughput;
12504 let original_volume_type = vol.volume_type.clone();
12505 if let Some(s) = new_size {
12506 vol.size = s;
12507 }
12508 if let Some(ref vt) = new_volume_type {
12509 vol.volume_type = vt.clone();
12510 }
12511 if let Some(i) = new_iops {
12512 vol.iops = Some(i);
12513 }
12514 if let Some(t) = new_throughput {
12515 vol.throughput = Some(t);
12516 }
12517 let modification = VolumeModification {
12518 volume_id: volume_id.to_string(),
12519 modification_state: "completed".to_string(),
12520 status_message: None,
12521 target_size: new_size.or(Some(original_size)),
12522 target_iops: new_iops.or(original_iops),
12523 target_throughput: new_throughput.or(original_throughput),
12524 target_volume_type: new_volume_type.or(Some(original_volume_type.clone())),
12525 target_multi_attach_enabled: new_multi_attach_enabled,
12526 original_size: Some(original_size),
12527 original_iops,
12528 original_throughput,
12529 original_volume_type: Some(original_volume_type),
12530 original_multi_attach_enabled: None,
12531 progress: 100,
12532 start_time: now.clone(),
12533 end_time: Some(now),
12534 };
12535 self.counters.volume_modification += 1;
12536 self.volume_modifications
12537 .insert(volume_id.to_string(), modification);
12538 Ok(self.volume_modifications.get(volume_id).unwrap())
12539 }
12540
12541 #[allow(clippy::too_many_arguments)]
12543 pub fn import_volume(
12544 &mut self,
12545 availability_zone: String,
12546 image_format: String,
12547 image_size: i64,
12548 image_import_manifest_url: String,
12549 volume_size: i64,
12550 description: Option<String>,
12551 ) -> &ImportVolumeTask {
12552 let id = self.next_import_volume_task_id();
12553 let now = chrono::Utc::now();
12554 let expires = (now + chrono::Duration::days(7))
12555 .format("%Y-%m-%dT%H:%M:%S.000Z")
12556 .to_string();
12557 let volume_id = format!("vol-{:08x}", self.counters.vol);
12558 let task = ImportVolumeTask {
12559 conversion_task_id: id.clone(),
12560 expiration_time: expires,
12561 image: DiskImageDescription {
12562 format: image_format,
12563 size: image_size,
12564 import_manifest_url: image_import_manifest_url,
12565 checksum: None,
12566 },
12567 volume: DiskImageVolumeDescription {
12568 size: volume_size,
12569 id: volume_id,
12570 },
12571 availability_zone,
12572 bytes_converted: 0,
12573 description,
12574 status: "active".to_string(),
12575 status_message: None,
12576 };
12577 self.import_volume_tasks.insert(id.clone(), task);
12578 self.import_volume_tasks.get(&id).unwrap()
12579 }
12580
12581 pub fn cancel_import_volume_task(&mut self, task_id: &str) -> Result<(), Ec2Error> {
12582 let task = self
12583 .import_volume_tasks
12584 .get_mut(task_id)
12585 .ok_or_else(|| Ec2Error::InvalidImportVolumeTaskNotFound(task_id.to_string()))?;
12586 task.status = "cancelled".to_string();
12587 Ok(())
12588 }
12589
12590 pub fn bundle_instance(
12592 &mut self,
12593 instance_id: String,
12594 bucket: String,
12595 prefix: String,
12596 ) -> &BundleTask {
12597 let id = self.next_bundle_task_id();
12598 let now = self.now_iso();
12599 let task = BundleTask {
12600 bundle_id: id.clone(),
12601 instance_id,
12602 bucket,
12603 prefix,
12604 start_time: now.clone(),
12605 update_time: now,
12606 state: "pending".to_string(),
12607 progress: "0%".to_string(),
12608 error_code: None,
12609 error_message: None,
12610 };
12611 self.bundle_tasks.insert(id.clone(), task);
12612 self.bundle_tasks.get(&id).unwrap()
12613 }
12614
12615 pub fn cancel_bundle_task(&mut self, bundle_id: &str) -> Result<&BundleTask, Ec2Error> {
12616 let now = self.now_iso();
12617 let task = self
12618 .bundle_tasks
12619 .get_mut(bundle_id)
12620 .ok_or_else(|| Ec2Error::InvalidBundleTaskNotFound(bundle_id.to_string()))?;
12621 task.state = "cancelling".to_string();
12622 task.update_time = now;
12623 Ok(self.bundle_tasks.get(bundle_id).unwrap())
12624 }
12625
12626 pub fn describe_bundle_tasks(&self, ids: &[String]) -> Vec<BundleTask> {
12627 self.bundle_tasks
12628 .values()
12629 .filter(|t| ids.is_empty() || ids.contains(&t.bundle_id))
12630 .cloned()
12631 .collect()
12632 }
12633
12634 pub fn set_id_format(&mut self, resource: &str, use_long_ids: bool) {
12636 self.id_format.insert(
12637 resource.to_string(),
12638 IdFormatEntry {
12639 use_long_ids,
12640 deadline: None,
12641 },
12642 );
12643 }
12644
12645 #[allow(clippy::too_many_arguments)]
12647 pub fn enable_fast_launch(
12648 &mut self,
12649 image_id: &str,
12650 resource_type: String,
12651 target_resource_count: Option<i32>,
12652 max_parallel_launches: i32,
12653 launch_template_id: Option<String>,
12654 launch_template_name: Option<String>,
12655 version: Option<String>,
12656 owner_id: String,
12657 ) -> Result<&FastLaunchState, Ec2Error> {
12658 let now = self.now_iso();
12659 let img = self
12660 .images
12661 .get_mut(image_id)
12662 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
12663 img.fast_launch_state = Some(FastLaunchState {
12664 state: "enabled".to_string(),
12665 image_id: image_id.to_string(),
12666 resource_type,
12667 snapshot_configuration: SnapshotConfigurationRequest {
12668 target_resource_count,
12669 },
12670 launch_template: FastLaunchLaunchTemplateSpecification {
12671 launch_template_id,
12672 launch_template_name,
12673 version,
12674 },
12675 max_parallel_launches,
12676 owner_id,
12677 state_transition_time: now,
12678 });
12679 Ok(img.fast_launch_state.as_ref().unwrap())
12680 }
12681
12682 pub fn disable_fast_launch(&mut self, image_id: &str) -> Result<&FastLaunchState, Ec2Error> {
12683 let now = self.now_iso();
12684 let img = self
12685 .images
12686 .get_mut(image_id)
12687 .ok_or_else(|| Ec2Error::AmiNotFound(image_id.to_string()))?;
12688 match img.fast_launch_state.as_mut() {
12689 Some(fl) => {
12690 fl.state = "disabled".to_string();
12691 fl.state_transition_time = now;
12692 Ok(img.fast_launch_state.as_ref().unwrap())
12693 }
12694 None => {
12695 img.fast_launch_state = Some(FastLaunchState {
12698 state: "disabled".to_string(),
12699 image_id: image_id.to_string(),
12700 resource_type: "snapshot".to_string(),
12701 snapshot_configuration: SnapshotConfigurationRequest::default(),
12702 launch_template: FastLaunchLaunchTemplateSpecification::default(),
12703 max_parallel_launches: 0,
12704 owner_id: String::new(),
12705 state_transition_time: now,
12706 });
12707 Ok(img.fast_launch_state.as_ref().unwrap())
12708 }
12709 }
12710 }
12711
12712 #[allow(clippy::too_many_arguments)]
12714 pub fn export_image(
12715 &mut self,
12716 image_id: String,
12717 role_name: String,
12718 s3_bucket: String,
12719 s3_prefix: Option<String>,
12720 disk_image_format: String,
12721 description: Option<String>,
12722 tags: Tags,
12723 ) -> &ExportImageTask {
12724 let id = self.next_export_image_task_id();
12725 let task = ExportImageTask {
12726 export_image_task_id: id.clone(),
12727 description,
12728 image_id,
12729 role_name,
12730 status: "active".to_string(),
12731 status_message: None,
12732 progress: "0".to_string(),
12733 s3_export_location: ExportTaskS3Location {
12734 s3_bucket,
12735 s3_prefix,
12736 },
12737 disk_image_format,
12738 tags,
12739 };
12740 self.export_image_tasks.insert(id.clone(), task);
12741 self.export_image_tasks.get(&id).unwrap()
12742 }
12743}
12744
12745fn action_to_persist_routes_state(action: Option<&str>) -> String {
12749 match action.unwrap_or("disable") {
12750 "enable" => "enabled".to_string(),
12751 "disable" => "disabled".to_string(),
12752 "reset" => "resetting".to_string(),
12753 other => other.to_string(),
12754 }
12755}
12756
12757#[derive(Debug, Clone)]
12759pub struct InstanceFamilyCreditPair {
12760 pub instance_family: String,
12761 pub cpu_credits: String,
12762}
12763
12764#[derive(Debug, Clone)]
12766pub struct ScheduledInstancePurchaseRequest {
12767 pub instance_type: String,
12768 pub platform: Option<String>,
12769 pub network_platform: Option<String>,
12770 pub availability_zone: String,
12771 pub instance_count: i32,
12772 pub hourly_price: Option<String>,
12773 pub total_scheduled_instance_hours: i32,
12774 pub recurrence: ScheduledInstanceRecurrence,
12775 pub slot_duration_in_hours: i32,
12776}