Skip to main content

fakecloud_ec2/
state.rs

1//! EC2 service state.
2//!
3//! Partitioned per account+region via [`fakecloud_core::multi_account`]. The
4//! `tags` map is keyed by EC2 resource id (e.g. `vpc-…`, `i-…`, `sg-…`) and is
5//! the backing store for `CreateTags`/`DeleteTags`/`DescribeTags` plus the
6//! `tag:`/`tag-key` describe filters shared across every resource family.
7
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use parking_lot::RwLock;
12use serde::{Deserialize, Serialize};
13
14/// Shared, account-partitioned EC2 state handle.
15pub type SharedEc2State = Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<Ec2State>>>;
16
17impl fakecloud_core::multi_account::AccountState for Ec2State {
18    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
19        Self::new(account_id, region)
20    }
21}
22
23/// A single EC2 resource tag.
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
25pub struct Tag {
26    pub key: String,
27    pub value: String,
28}
29
30/// A secondary CIDR-block association on a VPC.
31#[derive(Clone, Debug, Serialize, Deserialize)]
32pub struct VpcCidrAssoc {
33    pub association_id: String,
34    pub cidr_block: String,
35    /// `associated` | `disassociated`.
36    pub state: String,
37}
38
39/// A Virtual Private Cloud.
40#[derive(Clone, Debug, Serialize, Deserialize)]
41pub struct Vpc {
42    pub vpc_id: String,
43    pub cidr_block: String,
44    /// `pending` | `available`.
45    pub state: String,
46    pub dhcp_options_id: String,
47    /// `default` | `dedicated` | `host`.
48    pub instance_tenancy: String,
49    pub is_default: bool,
50    pub enable_dns_support: bool,
51    pub enable_dns_hostnames: bool,
52    #[serde(default)]
53    pub cidr_associations: Vec<VpcCidrAssoc>,
54}
55
56/// One `key -> values` entry in a DHCP options set.
57#[derive(Clone, Debug, Serialize, Deserialize)]
58pub struct DhcpConfig {
59    pub key: String,
60    pub values: Vec<String>,
61}
62
63/// A DHCP options set.
64#[derive(Clone, Debug, Serialize, Deserialize)]
65pub struct DhcpOptions {
66    pub dhcp_options_id: String,
67    pub configurations: Vec<DhcpConfig>,
68}
69
70/// A subnet within a VPC.
71#[derive(Clone, Debug, Serialize, Deserialize)]
72pub struct Subnet {
73    pub subnet_id: String,
74    pub vpc_id: String,
75    pub cidr_block: String,
76    pub availability_zone: String,
77    pub availability_zone_id: String,
78    /// `pending` | `available`.
79    pub state: String,
80    pub available_ip_address_count: i32,
81    pub default_for_az: bool,
82    pub map_public_ip_on_launch: bool,
83    pub assign_ipv6_address_on_creation: bool,
84    pub map_customer_owned_ip_on_launch: bool,
85    pub enable_dns64: bool,
86    /// `ip-name` | `resource-name`.
87    pub private_dns_hostname_type: String,
88}
89
90/// A CIDR reservation within a subnet.
91#[derive(Clone, Debug, Serialize, Deserialize)]
92pub struct SubnetCidrReservation {
93    pub subnet_cidr_reservation_id: String,
94    pub subnet_id: String,
95    pub cidr: String,
96    /// `prefix` | `explicit`.
97    pub reservation_type: String,
98    pub description: String,
99}
100
101/// A security-group rule (ingress or egress), stored flat.
102#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct SecurityGroupRule {
104    pub rule_id: String,
105    pub group_id: String,
106    pub is_egress: bool,
107    pub ip_protocol: String,
108    pub from_port: i64,
109    pub to_port: i64,
110    pub cidr_ipv4: Option<String>,
111    pub cidr_ipv6: Option<String>,
112    pub prefix_list_id: Option<String>,
113    pub referenced_group_id: Option<String>,
114    pub description: String,
115}
116
117/// A security group.
118#[derive(Clone, Debug, Serialize, Deserialize)]
119pub struct SecurityGroup {
120    pub group_id: String,
121    pub group_name: String,
122    pub description: String,
123    pub vpc_id: String,
124    #[serde(default)]
125    pub rules: Vec<SecurityGroupRule>,
126}
127
128/// A route within a route table.
129#[derive(Clone, Debug, Default, Serialize, Deserialize)]
130pub struct Route {
131    pub destination_cidr_block: Option<String>,
132    pub destination_ipv6_cidr_block: Option<String>,
133    pub destination_prefix_list_id: Option<String>,
134    pub gateway_id: Option<String>,
135    pub nat_gateway_id: Option<String>,
136    pub network_interface_id: Option<String>,
137    pub instance_id: Option<String>,
138    pub vpc_peering_connection_id: Option<String>,
139    pub transit_gateway_id: Option<String>,
140    pub egress_only_internet_gateway_id: Option<String>,
141    /// `active` | `blackhole`.
142    pub state: String,
143    /// `CreateRouteTable` | `CreateRoute`.
144    pub origin: String,
145}
146
147/// A route-table association (to a subnet or gateway, or the VPC main table).
148#[derive(Clone, Debug, Serialize, Deserialize)]
149pub struct RouteTableAssociation {
150    pub association_id: String,
151    pub route_table_id: String,
152    pub subnet_id: Option<String>,
153    pub gateway_id: Option<String>,
154    pub main: bool,
155}
156
157/// A route table.
158#[derive(Clone, Debug, Serialize, Deserialize)]
159pub struct RouteTable {
160    pub route_table_id: String,
161    pub vpc_id: String,
162    #[serde(default)]
163    pub routes: Vec<Route>,
164    #[serde(default)]
165    pub associations: Vec<RouteTableAssociation>,
166}
167
168/// An internet gateway (or egress-only IGW) with its VPC attachments.
169#[derive(Clone, Debug, Serialize, Deserialize)]
170pub struct InternetGateway {
171    pub internet_gateway_id: String,
172    /// (vpc_id, state) pairs.
173    #[serde(default)]
174    pub attachments: Vec<(String, String)>,
175}
176
177/// A NAT gateway.
178#[derive(Clone, Debug, Serialize, Deserialize)]
179pub struct NatGateway {
180    pub nat_gateway_id: String,
181    pub subnet_id: String,
182    pub vpc_id: String,
183    /// `pending` | `available` | `deleting` | `deleted`.
184    pub state: String,
185    /// `public` | `private`.
186    pub connectivity_type: String,
187    pub allocation_id: Option<String>,
188}
189
190/// An Elastic IP allocation.
191#[derive(Clone, Debug, Serialize, Deserialize)]
192pub struct ElasticIp {
193    pub allocation_id: String,
194    pub public_ip: String,
195    /// `vpc` | `standard`.
196    pub domain: String,
197    pub association_id: Option<String>,
198    pub instance_id: Option<String>,
199    pub network_interface_id: Option<String>,
200    pub private_ip_address: Option<String>,
201}
202
203/// An EC2 key pair (public-key metadata only).
204#[derive(Clone, Debug, Serialize, Deserialize)]
205pub struct KeyPair {
206    pub key_pair_id: String,
207    pub key_name: String,
208    /// `rsa` | `ed25519`.
209    pub key_type: String,
210    pub key_fingerprint: String,
211}
212
213/// A placement group.
214#[derive(Clone, Debug, Serialize, Deserialize)]
215pub struct PlacementGroup {
216    pub group_id: String,
217    pub group_name: String,
218    /// `cluster` | `spread` | `partition`.
219    pub strategy: String,
220    /// `available`.
221    pub state: String,
222    pub partition_count: Option<i64>,
223    pub spread_level: Option<String>,
224}
225
226/// An ENI attachment.
227#[derive(Clone, Debug, Serialize, Deserialize)]
228pub struct EniAttachment {
229    pub attachment_id: String,
230    pub instance_id: String,
231    pub device_index: i64,
232    /// `attaching` | `attached` | `detaching` | `detached`.
233    pub status: String,
234}
235
236/// An elastic network interface.
237#[derive(Clone, Debug, Serialize, Deserialize)]
238pub struct NetworkInterface {
239    pub network_interface_id: String,
240    pub subnet_id: String,
241    pub vpc_id: String,
242    pub availability_zone: String,
243    pub description: String,
244    pub mac_address: String,
245    pub private_ip_address: String,
246    /// `available` | `in-use`.
247    pub status: String,
248    pub interface_type: String,
249    pub source_dest_check: bool,
250    #[serde(default)]
251    pub group_ids: Vec<String>,
252    #[serde(default)]
253    pub private_ips: Vec<String>,
254    #[serde(default)]
255    pub ipv6_addresses: Vec<String>,
256    pub attachment: Option<EniAttachment>,
257}
258
259/// A network-interface permission grant.
260#[derive(Clone, Debug, Serialize, Deserialize)]
261pub struct NetworkInterfacePermission {
262    pub permission_id: String,
263    pub network_interface_id: String,
264    pub aws_account_id: String,
265    /// `INSTANCE-ATTACH` | `EIP-ASSOCIATE`.
266    pub permission: String,
267}
268
269/// An EC2 instance (metadata-faithful; a Docker-backed runtime layers on top).
270#[derive(Clone, Debug, Serialize, Deserialize)]
271pub struct Instance {
272    pub instance_id: String,
273    pub image_id: String,
274    pub instance_type: String,
275    /// EC2 state code: 0 pending, 16 running, 32 shutting-down, 48 terminated,
276    /// 64 stopping, 80 stopped.
277    pub state_code: i64,
278    pub state_name: String,
279    pub private_ip: String,
280    pub public_ip: Option<String>,
281    pub subnet_id: Option<String>,
282    pub vpc_id: Option<String>,
283    pub key_name: Option<String>,
284    #[serde(default)]
285    pub security_group_ids: Vec<String>,
286    pub reservation_id: String,
287    pub ami_launch_index: i64,
288    pub monitoring: bool,
289    pub az: String,
290    pub launch_time: String,
291}
292
293/// An EBS volume attachment.
294#[derive(Clone, Debug, Serialize, Deserialize)]
295pub struct VolumeAttachment {
296    pub volume_id: String,
297    pub instance_id: String,
298    pub device: String,
299    /// `attaching` | `attached` | `detaching` | `detached`.
300    pub status: String,
301    pub delete_on_termination: bool,
302}
303
304/// An EBS volume.
305#[derive(Clone, Debug, Serialize, Deserialize)]
306pub struct Volume {
307    pub volume_id: String,
308    pub size: i64,
309    pub snapshot_id: Option<String>,
310    pub availability_zone: String,
311    /// `creating` | `available` | `in-use` | `deleting` | `deleted`.
312    pub state: String,
313    pub volume_type: String,
314    pub iops: Option<i64>,
315    pub throughput: Option<i64>,
316    pub encrypted: bool,
317    pub kms_key_id: Option<String>,
318    pub multi_attach_enabled: bool,
319    pub auto_enable_io: bool,
320    #[serde(default)]
321    pub attachments: Vec<VolumeAttachment>,
322    #[serde(default)]
323    pub in_recycle_bin: bool,
324}
325
326/// An EBS snapshot.
327#[derive(Clone, Debug, Serialize, Deserialize)]
328pub struct Snapshot {
329    pub snapshot_id: String,
330    pub volume_id: String,
331    /// `pending` | `completed` | `error`.
332    pub state: String,
333    pub volume_size: i64,
334    pub description: String,
335    pub encrypted: bool,
336    /// `standard` | `archive`.
337    pub storage_tier: String,
338    #[serde(default)]
339    pub in_recycle_bin: bool,
340    #[serde(default)]
341    pub locked: bool,
342    pub lock_mode: Option<String>,
343}
344
345/// An AMI (machine image).
346#[derive(Clone, Debug, Serialize, Deserialize)]
347pub struct Image {
348    pub image_id: String,
349    pub name: String,
350    pub description: String,
351    /// `pending` | `available` | `disabled` | `deregistered`.
352    pub state: String,
353    pub architecture: String,
354    pub public: bool,
355    pub source_instance_id: Option<String>,
356    #[serde(default)]
357    pub in_recycle_bin: bool,
358    pub deprecation_time: Option<String>,
359    #[serde(default)]
360    pub deregistration_protection: bool,
361    /// `launchPermission` — AWS account ids the AMI is explicitly shared with
362    /// (cross-account share via `ModifyImageAttribute`).
363    #[serde(default)]
364    pub launch_permission_users: Vec<String>,
365    /// `launchPermission` groups — only `all` is valid in AWS (public share).
366    #[serde(default)]
367    pub launch_permission_groups: Vec<String>,
368    /// `bootMode` — `legacy-bios` | `uefi` | `uefi-preferred`. `None` reports
369    /// the default `uefi`; settable via `ModifyImageAttribute`.
370    #[serde(default)]
371    pub boot_mode: Option<String>,
372}
373
374/// A network ACL entry (rule).
375#[derive(Clone, Debug, Serialize, Deserialize)]
376pub struct NetworkAclEntry {
377    pub rule_number: i64,
378    pub protocol: String,
379    /// `allow` | `deny`.
380    pub rule_action: String,
381    pub egress: bool,
382    pub cidr_block: Option<String>,
383    pub ipv6_cidr_block: Option<String>,
384    /// TCP/UDP port range (from, to).
385    pub port_range: Option<(i64, i64)>,
386    /// ICMP (type, code).
387    pub icmp_type_code: Option<(i64, i64)>,
388}
389
390/// A network ACL <-> subnet association.
391#[derive(Clone, Debug, Serialize, Deserialize)]
392pub struct NetworkAclAssoc {
393    pub association_id: String,
394    pub subnet_id: String,
395}
396
397/// A network ACL.
398#[derive(Clone, Debug, Serialize, Deserialize)]
399pub struct NetworkAcl {
400    pub network_acl_id: String,
401    pub vpc_id: String,
402    pub is_default: bool,
403    #[serde(default)]
404    pub entries: Vec<NetworkAclEntry>,
405    #[serde(default)]
406    pub associations: Vec<NetworkAclAssoc>,
407}
408
409/// A VPC peering connection.
410#[derive(Clone, Debug, Serialize, Deserialize)]
411pub struct VpcPeering {
412    pub id: String,
413    pub requester_vpc_id: String,
414    pub accepter_vpc_id: String,
415    /// `pending-acceptance` | `active` | `rejected` | `deleted`.
416    pub status: String,
417    /// Requester-side DNS-resolution-from-remote-VPC option.
418    #[serde(default)]
419    pub requester_allow_dns: bool,
420    /// Accepter-side DNS-resolution-from-remote-VPC option.
421    #[serde(default)]
422    pub accepter_allow_dns: bool,
423}
424
425/// A VPC endpoint.
426#[derive(Clone, Debug, Serialize, Deserialize)]
427pub struct VpcEndpoint {
428    pub id: String,
429    /// `Interface` | `Gateway` | `GatewayLoadBalancer` | ...
430    pub endpoint_type: String,
431    pub vpc_id: String,
432    pub service_name: String,
433    pub state: String,
434    #[serde(default)]
435    pub subnet_ids: Vec<String>,
436    #[serde(default)]
437    pub route_table_ids: Vec<String>,
438    #[serde(default)]
439    pub private_dns_enabled: bool,
440}
441
442/// A VPC endpoint service configuration (PrivateLink provider side).
443#[derive(Clone, Debug, Serialize, Deserialize)]
444pub struct EndpointService {
445    pub service_id: String,
446    pub service_name: String,
447    pub state: String,
448    pub acceptance_required: bool,
449    pub payer_responsibility: String,
450    #[serde(default)]
451    pub nlb_arns: Vec<String>,
452}
453
454/// A VPC endpoint connection notification.
455#[derive(Clone, Debug, Serialize, Deserialize)]
456pub struct ConnectionNotification {
457    pub id: String,
458    pub arn: String,
459    pub service_id: Option<String>,
460    #[serde(default)]
461    pub events: Vec<String>,
462}
463
464/// A VPC flow log.
465#[derive(Clone, Debug, Serialize, Deserialize)]
466pub struct FlowLog {
467    pub id: String,
468    pub resource_id: String,
469    pub traffic_type: String,
470    pub log_destination_type: String,
471    pub log_group_name: Option<String>,
472    /// Destination ARN for `s3` / `kinesis-data-firehose` deliveries.
473    pub log_destination: Option<String>,
474}
475
476/// A launch template (versions tracked as monotonic counters).
477#[derive(Clone, Debug, Serialize, Deserialize)]
478pub struct LaunchTemplate {
479    pub id: String,
480    pub name: String,
481    pub default_version: i64,
482    pub latest_version: i64,
483}
484
485/// A Spot instance request.
486#[derive(Clone, Debug, Serialize, Deserialize)]
487pub struct SpotRequest {
488    pub id: String,
489    /// `open` | `active` | `cancelled` | `closed`.
490    pub state: String,
491    pub request_type: String,
492    pub spot_price: String,
493}
494
495/// A Spot fleet request.
496#[derive(Clone, Debug, Serialize, Deserialize)]
497pub struct SpotFleet {
498    pub id: String,
499    pub state: String,
500}
501
502/// An EC2 fleet.
503#[derive(Clone, Debug, Serialize, Deserialize)]
504pub struct Fleet {
505    pub id: String,
506    pub state: String,
507    pub fleet_type: String,
508}
509
510/// An on-demand capacity reservation.
511#[derive(Clone, Debug, Serialize, Deserialize)]
512pub struct CapacityReservation {
513    pub id: String,
514    pub instance_type: String,
515    pub instance_platform: String,
516    pub availability_zone: String,
517    pub tenancy: String,
518    pub total_instance_count: i64,
519    pub available_instance_count: i64,
520    /// `active` | `expired` | `cancelled` | `pending` | `failed`.
521    pub state: String,
522    pub end_date_type: String,
523    pub instance_match_criteria: String,
524}
525
526/// A Reserved Instance purchase.
527#[derive(Clone, Debug, Serialize, Deserialize)]
528pub struct ReservedInstances {
529    pub id: String,
530    pub instance_type: String,
531    pub availability_zone: String,
532    pub instance_count: i64,
533    pub product_description: String,
534    pub state: String,
535    pub duration: i64,
536    pub fixed_price: String,
537    pub usage_price: String,
538}
539
540/// A Reserved Instances listing in the Reserved Instance Marketplace.
541#[derive(Clone, Debug, Serialize, Deserialize)]
542pub struct ReservedInstancesListing {
543    pub listing_id: String,
544    pub reserved_instances_id: String,
545    pub instance_count: i64,
546    pub client_token: String,
547    /// `active` | `cancelled` | `closed`.
548    pub status: String,
549    pub status_message: String,
550}
551
552/// A Reserved Instances modification request.
553#[derive(Clone, Debug, Serialize, Deserialize)]
554pub struct ReservedInstancesModification {
555    pub modification_id: String,
556    pub reserved_instances_ids: Vec<String>,
557    /// `processing` | `fulfilled` | `failed`.
558    pub status: String,
559    pub client_token: String,
560}
561
562/// A Dedicated Host.
563#[derive(Clone, Debug, Serialize, Deserialize)]
564pub struct DedicatedHost {
565    pub id: String,
566    pub auto_placement: String,
567    pub availability_zone: String,
568    pub instance_type: String,
569    pub state: String,
570    pub host_recovery: String,
571    pub host_maintenance: String,
572}
573
574/// A Transit Gateway.
575#[derive(Clone, Debug, Serialize, Deserialize)]
576pub struct TransitGateway {
577    pub id: String,
578    pub description: String,
579    /// `pending` | `available` | `modifying` | `deleting` | `deleted`.
580    #[serde(default = "tgw_default_state")]
581    pub state: String,
582}
583
584fn tgw_default_state() -> String {
585    "available".to_string()
586}
587
588/// A Transit Gateway attachment (VPC and others).
589#[derive(Clone, Debug, Serialize, Deserialize)]
590pub struct TgwAttachment {
591    pub id: String,
592    pub tgw_id: String,
593    pub resource_id: String,
594    pub resource_type: String,
595    #[serde(default)]
596    pub subnet_ids: Vec<String>,
597    pub state: String,
598}
599
600/// A Transit Gateway route table.
601#[derive(Clone, Debug, Serialize, Deserialize)]
602pub struct TgwRouteTable {
603    pub id: String,
604    pub tgw_id: String,
605}
606
607/// A static Transit Gateway route within a route table.
608#[derive(Clone, Debug, Serialize, Deserialize)]
609pub struct TgwRoute {
610    pub cidr: String,
611    pub attachment_id: String,
612    pub state: String,
613}
614
615/// A Transit Gateway multicast domain.
616#[derive(Clone, Debug, Serialize, Deserialize)]
617pub struct TgwMulticastDomain {
618    pub id: String,
619    pub tgw_id: String,
620}
621
622/// A Transit Gateway metering policy.
623#[derive(Clone, Debug, Serialize, Deserialize)]
624pub struct TgwMeteringPolicy {
625    pub id: String,
626    pub tgw_id: String,
627}
628
629/// A customer gateway (on-prem side of a VPN).
630#[derive(Clone, Debug, Serialize, Deserialize)]
631pub struct CustomerGateway {
632    pub id: String,
633    pub state: String,
634    pub ip_address: String,
635    pub bgp_asn: String,
636}
637
638/// A virtual private gateway.
639#[derive(Clone, Debug, Serialize, Deserialize)]
640pub struct VpnGateway {
641    pub id: String,
642    pub state: String,
643    #[serde(default)]
644    pub attachments: Vec<String>,
645}
646
647/// A Site-to-Site VPN connection.
648#[derive(Clone, Debug, Serialize, Deserialize)]
649pub struct VpnConnection {
650    pub id: String,
651    pub state: String,
652    pub customer_gateway_id: String,
653    pub vpn_gateway_id: Option<String>,
654    #[serde(default)]
655    pub routes: Vec<String>,
656}
657
658/// A VPN concentrator.
659#[derive(Clone, Debug, Serialize, Deserialize)]
660pub struct VpnConcentrator {
661    pub id: String,
662    pub state: String,
663}
664
665/// An IPAM (IP Address Manager).
666#[derive(Clone, Debug, Serialize, Deserialize)]
667pub struct Ipam {
668    pub id: String,
669    pub public_scope_id: String,
670    pub private_scope_id: String,
671    pub tier: String,
672    #[serde(default)]
673    pub description: String,
674}
675
676/// An IPAM scope.
677#[derive(Clone, Debug, Serialize, Deserialize)]
678pub struct IpamScope {
679    pub id: String,
680    pub ipam_id: String,
681    /// "public" or "private".
682    #[serde(default)]
683    pub scope_type: String,
684    #[serde(default)]
685    pub description: String,
686}
687
688/// An IPAM pool.
689#[derive(Clone, Debug, Serialize, Deserialize)]
690pub struct IpamPool {
691    pub id: String,
692    pub scope_id: String,
693    pub address_family: String,
694    #[serde(default)]
695    pub description: String,
696}
697
698/// An IPAM resource discovery.
699#[derive(Clone, Debug, Serialize, Deserialize)]
700pub struct IpamResourceDiscovery {
701    pub id: String,
702    #[serde(default)]
703    pub description: String,
704}
705
706/// An IPAM policy.
707#[derive(Clone, Debug, Serialize, Deserialize)]
708pub struct IpamPolicy {
709    pub id: String,
710    pub ipam_id: String,
711}
712
713/// An IPAM prefix-list resolver.
714#[derive(Clone, Debug, Serialize, Deserialize)]
715pub struct IpamPrefixListResolver {
716    pub id: String,
717    pub ipam_id: String,
718    pub address_family: String,
719    #[serde(default)]
720    pub description: String,
721}
722
723/// An IPAM prefix-list resolver target.
724#[derive(Clone, Debug, Serialize, Deserialize)]
725pub struct IpamPrefixListResolverTarget {
726    pub id: String,
727    pub resolver_id: String,
728    pub prefix_list_id: String,
729    pub prefix_list_region: String,
730    #[serde(default)]
731    pub track_latest_version: bool,
732}
733
734/// A Verified Access instance.
735#[derive(Clone, Debug, Serialize, Deserialize)]
736pub struct VerifiedAccessInstance {
737    pub id: String,
738    pub description: String,
739    #[serde(default)]
740    pub trust_providers: Vec<String>,
741}
742
743/// A Verified Access trust provider.
744#[derive(Clone, Debug, Serialize, Deserialize)]
745pub struct VerifiedAccessTrustProvider {
746    pub id: String,
747    pub trust_provider_type: String,
748    pub policy_reference_name: String,
749    pub description: String,
750}
751
752/// A Verified Access group.
753#[derive(Clone, Debug, Serialize, Deserialize)]
754pub struct VerifiedAccessGroup {
755    pub id: String,
756    pub instance_id: String,
757    pub description: String,
758}
759
760/// A Verified Access endpoint.
761#[derive(Clone, Debug, Serialize, Deserialize)]
762pub struct VerifiedAccessEndpoint {
763    pub id: String,
764    pub group_id: String,
765    pub instance_id: String,
766    pub endpoint_type: String,
767    pub attachment_type: String,
768}
769
770/// A Network Insights reachability path.
771#[derive(Clone, Debug, Serialize, Deserialize)]
772pub struct NetworkInsightsPath {
773    pub id: String,
774    pub source: String,
775    pub destination: String,
776    pub protocol: String,
777}
778
779/// A Network Insights path analysis.
780#[derive(Clone, Debug, Serialize, Deserialize)]
781pub struct NetworkInsightsAnalysis {
782    pub id: String,
783    pub path_id: String,
784}
785
786/// A Network Insights access scope.
787#[derive(Clone, Debug, Serialize, Deserialize)]
788pub struct NetworkInsightsAccessScope {
789    pub id: String,
790}
791
792/// A Network Insights access-scope analysis.
793#[derive(Clone, Debug, Serialize, Deserialize)]
794pub struct NetworkInsightsAccessScopeAnalysis {
795    pub id: String,
796    pub scope_id: String,
797}
798
799/// A carrier gateway (Wavelength).
800#[derive(Clone, Debug, Serialize, Deserialize)]
801pub struct CarrierGateway {
802    pub id: String,
803    pub vpc_id: String,
804}
805
806/// An EC2 Instance Connect endpoint.
807#[derive(Clone, Debug, Serialize, Deserialize)]
808pub struct InstanceConnectEndpoint {
809    pub id: String,
810    pub subnet_id: String,
811}
812
813/// A customer-owned IP (CoIP) pool.
814#[derive(Clone, Debug, Serialize, Deserialize)]
815pub struct CoipPool {
816    pub id: String,
817    pub route_table_id: String,
818}
819
820/// A local-gateway route table.
821#[derive(Clone, Debug, Serialize, Deserialize)]
822pub struct LocalGatewayRouteTable {
823    pub id: String,
824    pub local_gateway_id: String,
825    pub mode: String,
826}
827
828/// A local-gateway route-table <-> VPC association.
829#[derive(Clone, Debug, Serialize, Deserialize)]
830pub struct LocalGatewayRouteTableVpcAssoc {
831    pub id: String,
832    pub route_table_id: String,
833    pub vpc_id: String,
834}
835
836/// A local-gateway virtual interface.
837#[derive(Clone, Debug, Serialize, Deserialize)]
838pub struct LocalGatewayVif {
839    pub id: String,
840    pub group_id: String,
841    pub vlan: String,
842    pub local_address: String,
843    pub peer_address: String,
844}
845
846/// A local-gateway virtual-interface group.
847#[derive(Clone, Debug, Serialize, Deserialize)]
848pub struct LocalGatewayVifGroup {
849    pub id: String,
850    pub local_gateway_id: String,
851}
852
853/// A local-gateway route-table <-> virtual-interface-group association.
854#[derive(Clone, Debug, Serialize, Deserialize)]
855pub struct LocalGatewayRouteTableVifgAssoc {
856    pub id: String,
857    pub route_table_id: String,
858    pub vif_group_id: String,
859}
860
861/// A Client VPN endpoint.
862#[derive(Clone, Debug, Serialize, Deserialize)]
863pub struct ClientVpnEndpoint {
864    pub id: String,
865    pub description: String,
866    pub status: String,
867    pub server_cert_arn: String,
868    pub transport_protocol: String,
869    pub client_cidr: String,
870    #[serde(default)]
871    pub routes: Vec<String>,
872    /// (association id, subnet id) for each associated target network.
873    #[serde(default)]
874    pub target_networks: Vec<(String, String)>,
875    /// Ingress authorization rule target CIDRs.
876    #[serde(default)]
877    pub auth_rules: Vec<String>,
878}
879
880/// A Transit Gateway peering attachment.
881#[derive(Clone, Debug, Serialize, Deserialize)]
882pub struct TgwPeering {
883    pub id: String,
884    pub tgw_id: String,
885    pub peer_tgw_id: String,
886    pub peer_account: String,
887    pub peer_region: String,
888    pub state: String,
889}
890
891/// Per-account, per-region EC2 state. Resource families are added to this
892/// struct as their batches land.
893#[derive(Clone, Debug, Default, Serialize, Deserialize)]
894pub struct Ec2State {
895    pub account_id: String,
896    pub region: String,
897    /// resource-id -> tags. Shared by every Describe* `tag:` filter.
898    #[serde(default)]
899    pub tags: HashMap<String, Vec<Tag>>,
900    #[serde(default)]
901    pub vpcs: HashMap<String, Vpc>,
902    #[serde(default)]
903    pub dhcp_options: HashMap<String, DhcpOptions>,
904    #[serde(default)]
905    pub subnets: HashMap<String, Subnet>,
906    #[serde(default)]
907    pub subnet_cidr_reservations: HashMap<String, SubnetCidrReservation>,
908    #[serde(default)]
909    pub security_groups: HashMap<String, SecurityGroup>,
910    #[serde(default)]
911    pub route_tables: HashMap<String, RouteTable>,
912    #[serde(default)]
913    pub internet_gateways: HashMap<String, InternetGateway>,
914    #[serde(default)]
915    pub egress_only_igws: HashMap<String, InternetGateway>,
916    #[serde(default)]
917    pub nat_gateways: HashMap<String, NatGateway>,
918    /// keyed by allocation id.
919    #[serde(default)]
920    pub elastic_ips: HashMap<String, ElasticIp>,
921    /// keyed by key name.
922    #[serde(default)]
923    pub key_pairs: HashMap<String, KeyPair>,
924    /// keyed by group name.
925    #[serde(default)]
926    pub placement_groups: HashMap<String, PlacementGroup>,
927    #[serde(default)]
928    pub network_interfaces: HashMap<String, NetworkInterface>,
929    /// keyed by permission id.
930    #[serde(default)]
931    pub eni_permissions: HashMap<String, NetworkInterfacePermission>,
932    #[serde(default)]
933    pub instances: HashMap<String, Instance>,
934    #[serde(default)]
935    pub volumes: HashMap<String, Volume>,
936    /// Account-level EBS default encryption toggle.
937    #[serde(default)]
938    pub ebs_encryption_default: bool,
939    /// Account-level EBS default KMS key (None = `alias/aws/ebs`).
940    #[serde(default)]
941    pub ebs_default_kms_key_id: Option<String>,
942    #[serde(default)]
943    pub snapshots: HashMap<String, Snapshot>,
944    /// Account-level snapshot block-public-access state.
945    #[serde(default)]
946    pub snapshot_block_public_access: String,
947    #[serde(default)]
948    pub images: HashMap<String, Image>,
949    /// Account-level image block-public-access state.
950    #[serde(default)]
951    pub image_block_public_access: String,
952    /// Account-level allowed-images settings state.
953    #[serde(default)]
954    pub allowed_images_settings: String,
955    #[serde(default)]
956    pub network_acls: HashMap<String, NetworkAcl>,
957    #[serde(default)]
958    pub vpc_peerings: HashMap<String, VpcPeering>,
959    #[serde(default)]
960    pub vpc_endpoints: HashMap<String, VpcEndpoint>,
961    #[serde(default)]
962    pub endpoint_services: HashMap<String, EndpointService>,
963    #[serde(default)]
964    pub connection_notifications: HashMap<String, ConnectionNotification>,
965    #[serde(default)]
966    pub flow_logs: HashMap<String, FlowLog>,
967    #[serde(default)]
968    pub launch_templates: HashMap<String, LaunchTemplate>,
969    #[serde(default)]
970    pub spot_requests: HashMap<String, SpotRequest>,
971    #[serde(default)]
972    pub spot_fleets: HashMap<String, SpotFleet>,
973    #[serde(default)]
974    pub fleets: HashMap<String, Fleet>,
975    /// Account-level spot datafeed subscription (bucket, prefix).
976    #[serde(default)]
977    pub spot_datafeed: Option<(String, String)>,
978    #[serde(default)]
979    pub capacity_reservations: HashMap<String, CapacityReservation>,
980    /// Capacity reservation fleet ids (metadata-only).
981    #[serde(default)]
982    pub capacity_reservation_fleets: HashMap<String, String>,
983    #[serde(default)]
984    pub reserved_instances: HashMap<String, ReservedInstances>,
985    #[serde(default)]
986    pub reserved_instances_listings: HashMap<String, ReservedInstancesListing>,
987    #[serde(default)]
988    pub reserved_instances_modifications: HashMap<String, ReservedInstancesModification>,
989    #[serde(default)]
990    pub dedicated_hosts: HashMap<String, DedicatedHost>,
991    #[serde(default)]
992    pub transit_gateways: HashMap<String, TransitGateway>,
993    #[serde(default)]
994    pub tgw_attachments: HashMap<String, TgwAttachment>,
995    #[serde(default)]
996    pub tgw_route_tables: HashMap<String, TgwRouteTable>,
997    /// route-table-id -> static routes.
998    #[serde(default)]
999    pub tgw_routes: HashMap<String, Vec<TgwRoute>>,
1000    /// route-table-id -> associated attachment ids.
1001    #[serde(default)]
1002    pub tgw_rt_associations: HashMap<String, Vec<String>>,
1003    /// route-table-id -> propagated attachment ids.
1004    #[serde(default)]
1005    pub tgw_rt_propagations: HashMap<String, Vec<String>>,
1006    /// route-table-id -> prefix-list ids referenced.
1007    #[serde(default)]
1008    pub tgw_prefix_list_refs: HashMap<String, Vec<String>>,
1009    #[serde(default)]
1010    pub tgw_peerings: HashMap<String, TgwPeering>,
1011    /// connect-attachment-id -> (transport attachment id, tgw id).
1012    #[serde(default)]
1013    pub tgw_connects: HashMap<String, (String, String)>,
1014    /// connect-peer-id -> attachment id.
1015    #[serde(default)]
1016    pub tgw_connect_peers: HashMap<String, String>,
1017    /// policy-table-id -> tgw id.
1018    #[serde(default)]
1019    pub tgw_policy_tables: HashMap<String, String>,
1020    /// policy-table-id -> associated attachment ids.
1021    #[serde(default)]
1022    pub tgw_policy_table_associations: HashMap<String, Vec<String>>,
1023    /// announcement-id -> (route-table id, peering-attachment id).
1024    #[serde(default)]
1025    pub tgw_announcements: HashMap<String, (String, String)>,
1026    #[serde(default)]
1027    pub tgw_multicast_domains: HashMap<String, TgwMulticastDomain>,
1028    #[serde(default)]
1029    pub tgw_metering_policies: HashMap<String, TgwMeteringPolicy>,
1030    #[serde(default)]
1031    pub customer_gateways: HashMap<String, CustomerGateway>,
1032    #[serde(default)]
1033    pub vpn_gateways: HashMap<String, VpnGateway>,
1034    #[serde(default)]
1035    pub vpn_connections: HashMap<String, VpnConnection>,
1036    #[serde(default)]
1037    pub vpn_concentrators: HashMap<String, VpnConcentrator>,
1038    #[serde(default)]
1039    pub client_vpn_endpoints: HashMap<String, ClientVpnEndpoint>,
1040    #[serde(default)]
1041    pub ipams: HashMap<String, Ipam>,
1042    #[serde(default)]
1043    pub ipam_scopes: HashMap<String, IpamScope>,
1044    #[serde(default)]
1045    pub ipam_pools: HashMap<String, IpamPool>,
1046    /// pool-id -> provisioned (cidr, cidr-id).
1047    #[serde(default)]
1048    pub ipam_pool_cidrs: HashMap<String, Vec<(String, String)>>,
1049    /// pool-id -> allocations (cidr, allocation-id).
1050    #[serde(default)]
1051    pub ipam_pool_allocations: HashMap<String, Vec<(String, String)>>,
1052    #[serde(default)]
1053    pub ipam_resource_discoveries: HashMap<String, IpamResourceDiscovery>,
1054    /// association-id -> (discovery-id, ipam-id).
1055    #[serde(default)]
1056    pub ipam_rd_associations: HashMap<String, (String, String)>,
1057    /// asn -> associated cidr.
1058    #[serde(default)]
1059    pub ipam_byoasns: HashMap<String, String>,
1060    /// external-token-id -> ipam-id.
1061    #[serde(default)]
1062    pub ipam_ext_tokens: HashMap<String, String>,
1063    #[serde(default)]
1064    pub ipam_policies: HashMap<String, IpamPolicy>,
1065    #[serde(default)]
1066    pub ipam_pl_resolvers: HashMap<String, IpamPrefixListResolver>,
1067    #[serde(default)]
1068    pub ipam_pl_resolver_targets: HashMap<String, IpamPrefixListResolverTarget>,
1069    /// policy-id -> (locale, resource-type) allocation-rule documents.
1070    #[serde(default)]
1071    pub ipam_policy_alloc_rules: HashMap<String, Vec<(String, String)>>,
1072    /// The single enabled IPAM policy id, if any.
1073    #[serde(default)]
1074    pub ipam_enabled_policy: Option<String>,
1075    #[serde(default)]
1076    pub va_instances: HashMap<String, VerifiedAccessInstance>,
1077    #[serde(default)]
1078    pub va_trust_providers: HashMap<String, VerifiedAccessTrustProvider>,
1079    #[serde(default)]
1080    pub va_groups: HashMap<String, VerifiedAccessGroup>,
1081    #[serde(default)]
1082    pub va_endpoints: HashMap<String, VerifiedAccessEndpoint>,
1083    /// group-id -> policy document.
1084    #[serde(default)]
1085    pub va_group_policies: HashMap<String, String>,
1086    /// endpoint-id -> policy document.
1087    #[serde(default)]
1088    pub va_endpoint_policies: HashMap<String, String>,
1089    #[serde(default)]
1090    pub ni_paths: HashMap<String, NetworkInsightsPath>,
1091    #[serde(default)]
1092    pub ni_analyses: HashMap<String, NetworkInsightsAnalysis>,
1093    #[serde(default)]
1094    pub ni_access_scopes: HashMap<String, NetworkInsightsAccessScope>,
1095    #[serde(default)]
1096    pub ni_scope_analyses: HashMap<String, NetworkInsightsAccessScopeAnalysis>,
1097    #[serde(default)]
1098    pub carrier_gateways: HashMap<String, CarrierGateway>,
1099    #[serde(default)]
1100    pub coip_pools: HashMap<String, CoipPool>,
1101    /// coip-pool-id -> CIDRs.
1102    #[serde(default)]
1103    pub coip_pool_cidrs: HashMap<String, Vec<String>>,
1104    #[serde(default)]
1105    pub lg_route_tables: HashMap<String, LocalGatewayRouteTable>,
1106    /// route-table-id -> destination CIDRs.
1107    #[serde(default)]
1108    pub lg_routes: HashMap<String, Vec<String>>,
1109    #[serde(default)]
1110    pub lg_rt_vpc_assocs: HashMap<String, LocalGatewayRouteTableVpcAssoc>,
1111    #[serde(default)]
1112    pub lg_virtual_interfaces: HashMap<String, LocalGatewayVif>,
1113    #[serde(default)]
1114    pub lg_vif_groups: HashMap<String, LocalGatewayVifGroup>,
1115    #[serde(default)]
1116    pub lg_rt_vifg_assocs: HashMap<String, LocalGatewayRouteTableVifgAssoc>,
1117    #[serde(default)]
1118    pub instance_connect_endpoints: HashMap<String, InstanceConnectEndpoint>,
1119    /// Image ids with fast-launch enabled.
1120    #[serde(default)]
1121    pub fast_launch_images: std::collections::HashSet<String>,
1122    #[serde(default)]
1123    pub serial_console_access: bool,
1124}
1125
1126impl Ec2State {
1127    pub fn new(account_id: &str, region: &str) -> Self {
1128        Self {
1129            account_id: account_id.to_string(),
1130            region: region.to_string(),
1131            ..Default::default()
1132        }
1133    }
1134
1135    /// Replace the tag set for `resource_id` with `tags` merged over any
1136    /// existing tags (CreateTags is upsert-by-key, matching AWS).
1137    pub fn upsert_tags(&mut self, resource_id: &str, new_tags: &[Tag]) {
1138        let entry = self.tags.entry(resource_id.to_string()).or_default();
1139        for t in new_tags {
1140            if let Some(existing) = entry.iter_mut().find(|e| e.key == t.key) {
1141                existing.value = t.value.clone();
1142            } else {
1143                entry.push(t.clone());
1144            }
1145        }
1146    }
1147
1148    /// Remove tags for `resource_id`. When a tag's value is `None`, the key is
1149    /// removed regardless of value; when `Some`, only a key+value match is
1150    /// removed (AWS DeleteTags semantics).
1151    pub fn remove_tags(&mut self, resource_id: &str, to_remove: &[(String, Option<String>)]) {
1152        if let Some(entry) = self.tags.get_mut(resource_id) {
1153            for (key, value) in to_remove {
1154                entry.retain(|e| {
1155                    if &e.key != key {
1156                        return true;
1157                    }
1158                    match value {
1159                        Some(v) => &e.value != v,
1160                        None => false,
1161                    }
1162                });
1163            }
1164            if entry.is_empty() {
1165                self.tags.remove(resource_id);
1166            }
1167        }
1168    }
1169
1170    /// Tags for `resource_id`, or an empty slice when none.
1171    pub fn tags_for(&self, resource_id: &str) -> &[Tag] {
1172        self.tags.get(resource_id).map(Vec::as_slice).unwrap_or(&[])
1173    }
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178    use super::*;
1179
1180    fn tag(k: &str, v: &str) -> Tag {
1181        Tag {
1182            key: k.to_string(),
1183            value: v.to_string(),
1184        }
1185    }
1186
1187    #[test]
1188    fn upsert_tags_inserts_then_overwrites_by_key() {
1189        let mut s = Ec2State::new("123456789012", "us-east-1");
1190        s.upsert_tags("vpc-1", &[tag("Name", "a"), tag("env", "dev")]);
1191        s.upsert_tags("vpc-1", &[tag("Name", "b")]);
1192        let tags = s.tags_for("vpc-1");
1193        assert_eq!(tags.len(), 2);
1194        assert_eq!(tags.iter().find(|t| t.key == "Name").unwrap().value, "b");
1195    }
1196
1197    #[test]
1198    fn remove_tags_by_key_and_by_key_value() {
1199        let mut s = Ec2State::new("123456789012", "us-east-1");
1200        s.upsert_tags(
1201            "i-1",
1202            &[tag("Name", "x"), tag("env", "prod"), tag("team", "a")],
1203        );
1204        // key-only removal
1205        s.remove_tags("i-1", &[("Name".to_string(), None)]);
1206        // key+value removal that does NOT match -> kept
1207        s.remove_tags("i-1", &[("env".to_string(), Some("dev".to_string()))]);
1208        // key+value removal that matches -> removed
1209        s.remove_tags("i-1", &[("team".to_string(), Some("a".to_string()))]);
1210        let tags = s.tags_for("i-1");
1211        assert_eq!(tags.len(), 1);
1212        assert_eq!(tags[0].key, "env");
1213    }
1214
1215    #[test]
1216    fn empty_tag_set_drops_resource_entry() {
1217        let mut s = Ec2State::new("123456789012", "us-east-1");
1218        s.upsert_tags("sg-1", &[tag("Name", "x")]);
1219        s.remove_tags("sg-1", &[("Name".to_string(), None)]);
1220        assert!(!s.tags.contains_key("sg-1"));
1221    }
1222}