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::BTreeMap;
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
17/// On-disk snapshot envelope for EC2 state. Versioned so format changes fail
18/// loudly on upgrade rather than silently mis-parsing. Backing containers are
19/// not serialized -- on restore the server reconciles them via
20/// `Ec2Service::recover_persisted_containers`.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Ec2Snapshot {
23    pub schema_version: u32,
24    #[serde(default)]
25    pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<Ec2State>>,
26}
27
28pub const EC2_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
29
30impl fakecloud_core::multi_account::AccountState for Ec2State {
31    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
32        Self::new(account_id, region)
33    }
34}
35
36/// A single EC2 resource tag.
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38pub struct Tag {
39    pub key: String,
40    pub value: String,
41}
42
43/// A secondary CIDR-block association on a VPC.
44#[derive(Clone, Debug, Serialize, Deserialize)]
45pub struct VpcCidrAssoc {
46    pub association_id: String,
47    pub cidr_block: String,
48    /// `associated` | `disassociated`.
49    pub state: String,
50}
51
52/// A Virtual Private Cloud.
53#[derive(Clone, Debug, Serialize, Deserialize)]
54pub struct Vpc {
55    pub vpc_id: String,
56    pub cidr_block: String,
57    /// `pending` | `available`.
58    pub state: String,
59    pub dhcp_options_id: String,
60    /// `default` | `dedicated` | `host`.
61    pub instance_tenancy: String,
62    pub is_default: bool,
63    pub enable_dns_support: bool,
64    pub enable_dns_hostnames: bool,
65    #[serde(default)]
66    pub cidr_associations: Vec<VpcCidrAssoc>,
67    /// Amazon-provided IPv6 /56 CIDR, set when the VPC was created (or updated)
68    /// with `AmazonProvidedIpv6CidrBlock=true`. Reported in the
69    /// `Ipv6CidrBlockAssociationSet`; the `aws_vpc` resource reads
70    /// `ipv6_cidr_block` / `assign_generated_ipv6_cidr_block` from it.
71    #[serde(default)]
72    pub ipv6_cidr_block: Option<String>,
73}
74
75/// One `key -> values` entry in a DHCP options set.
76#[derive(Clone, Debug, Serialize, Deserialize)]
77pub struct DhcpConfig {
78    pub key: String,
79    pub values: Vec<String>,
80}
81
82/// A DHCP options set.
83#[derive(Clone, Debug, Serialize, Deserialize)]
84pub struct DhcpOptions {
85    pub dhcp_options_id: String,
86    pub configurations: Vec<DhcpConfig>,
87}
88
89/// A subnet within a VPC.
90#[derive(Clone, Debug, Serialize, Deserialize)]
91pub struct Subnet {
92    pub subnet_id: String,
93    pub vpc_id: String,
94    pub cidr_block: String,
95    pub availability_zone: String,
96    pub availability_zone_id: String,
97    /// `pending` | `available`.
98    pub state: String,
99    pub available_ip_address_count: i32,
100    pub default_for_az: bool,
101    pub map_public_ip_on_launch: bool,
102    pub assign_ipv6_address_on_creation: bool,
103    pub map_customer_owned_ip_on_launch: bool,
104    pub enable_dns64: bool,
105    /// `ip-name` | `resource-name`.
106    pub private_dns_hostname_type: String,
107    /// IPv6 /64 associated with the subnet (via CreateSubnet `Ipv6CidrBlock` or
108    /// AssociateSubnetCidrBlock). Reported in the `ipv6CidrBlockAssociationSet`;
109    /// the `aws_subnet` resource waits for this association.
110    #[serde(default)]
111    pub ipv6_cidr_block: Option<String>,
112}
113
114/// A CIDR reservation within a subnet.
115#[derive(Clone, Debug, Serialize, Deserialize)]
116pub struct SubnetCidrReservation {
117    pub subnet_cidr_reservation_id: String,
118    pub subnet_id: String,
119    pub cidr: String,
120    /// `prefix` | `explicit`.
121    pub reservation_type: String,
122    pub description: String,
123}
124
125/// A security-group rule (ingress or egress), stored flat.
126#[derive(Clone, Debug, Serialize, Deserialize)]
127pub struct SecurityGroupRule {
128    pub rule_id: String,
129    pub group_id: String,
130    pub is_egress: bool,
131    pub ip_protocol: String,
132    pub from_port: i64,
133    pub to_port: i64,
134    pub cidr_ipv4: Option<String>,
135    pub cidr_ipv6: Option<String>,
136    pub prefix_list_id: Option<String>,
137    pub referenced_group_id: Option<String>,
138    pub description: String,
139}
140
141/// A security group.
142#[derive(Clone, Debug, Serialize, Deserialize)]
143pub struct SecurityGroup {
144    pub group_id: String,
145    pub group_name: String,
146    pub description: String,
147    pub vpc_id: String,
148    #[serde(default)]
149    pub rules: Vec<SecurityGroupRule>,
150}
151
152/// A route within a route table.
153#[derive(Clone, Debug, Default, Serialize, Deserialize)]
154pub struct Route {
155    pub destination_cidr_block: Option<String>,
156    pub destination_ipv6_cidr_block: Option<String>,
157    pub destination_prefix_list_id: Option<String>,
158    pub gateway_id: Option<String>,
159    pub nat_gateway_id: Option<String>,
160    pub network_interface_id: Option<String>,
161    pub instance_id: Option<String>,
162    pub vpc_peering_connection_id: Option<String>,
163    pub transit_gateway_id: Option<String>,
164    pub egress_only_internet_gateway_id: Option<String>,
165    /// `active` | `blackhole`.
166    pub state: String,
167    /// `CreateRouteTable` | `CreateRoute`.
168    pub origin: String,
169}
170
171/// A route-table association (to a subnet or gateway, or the VPC main table).
172#[derive(Clone, Debug, Serialize, Deserialize)]
173pub struct RouteTableAssociation {
174    pub association_id: String,
175    pub route_table_id: String,
176    pub subnet_id: Option<String>,
177    pub gateway_id: Option<String>,
178    pub main: bool,
179}
180
181/// A route table.
182#[derive(Clone, Debug, Serialize, Deserialize)]
183pub struct RouteTable {
184    pub route_table_id: String,
185    pub vpc_id: String,
186    #[serde(default)]
187    pub routes: Vec<Route>,
188    #[serde(default)]
189    pub associations: Vec<RouteTableAssociation>,
190}
191
192/// An internet gateway (or egress-only IGW) with its VPC attachments.
193#[derive(Clone, Debug, Serialize, Deserialize)]
194pub struct InternetGateway {
195    pub internet_gateway_id: String,
196    /// (vpc_id, state) pairs.
197    #[serde(default)]
198    pub attachments: Vec<(String, String)>,
199}
200
201/// A NAT gateway.
202#[derive(Clone, Debug, Serialize, Deserialize)]
203pub struct NatGateway {
204    pub nat_gateway_id: String,
205    pub subnet_id: String,
206    pub vpc_id: String,
207    /// `pending` | `available` | `deleting` | `deleted`.
208    pub state: String,
209    /// `public` | `private`.
210    pub connectivity_type: String,
211    pub allocation_id: Option<String>,
212}
213
214/// An Elastic IP allocation.
215#[derive(Clone, Debug, Serialize, Deserialize)]
216pub struct ElasticIp {
217    pub allocation_id: String,
218    pub public_ip: String,
219    /// `vpc` | `standard`.
220    pub domain: String,
221    pub association_id: Option<String>,
222    pub instance_id: Option<String>,
223    pub network_interface_id: Option<String>,
224    pub private_ip_address: Option<String>,
225}
226
227/// An EC2 key pair (public-key metadata only).
228#[derive(Clone, Debug, Serialize, Deserialize)]
229pub struct KeyPair {
230    pub key_pair_id: String,
231    pub key_name: String,
232    /// `rsa` | `ed25519`.
233    pub key_type: String,
234    pub key_fingerprint: String,
235}
236
237/// A placement group.
238#[derive(Clone, Debug, Serialize, Deserialize)]
239pub struct PlacementGroup {
240    pub group_id: String,
241    pub group_name: String,
242    /// `cluster` | `spread` | `partition`.
243    pub strategy: String,
244    /// `available`.
245    pub state: String,
246    pub partition_count: Option<i64>,
247    pub spread_level: Option<String>,
248}
249
250/// An ENI attachment.
251#[derive(Clone, Debug, Serialize, Deserialize)]
252pub struct EniAttachment {
253    pub attachment_id: String,
254    pub instance_id: String,
255    pub device_index: i64,
256    /// `attaching` | `attached` | `detaching` | `detached`.
257    pub status: String,
258}
259
260/// An elastic network interface.
261#[derive(Clone, Debug, Serialize, Deserialize)]
262pub struct NetworkInterface {
263    pub network_interface_id: String,
264    pub subnet_id: String,
265    pub vpc_id: String,
266    pub availability_zone: String,
267    pub description: String,
268    pub mac_address: String,
269    pub private_ip_address: String,
270    /// `available` | `in-use`.
271    pub status: String,
272    pub interface_type: String,
273    pub source_dest_check: bool,
274    #[serde(default)]
275    pub group_ids: Vec<String>,
276    #[serde(default)]
277    pub private_ips: Vec<String>,
278    #[serde(default)]
279    pub ipv6_addresses: Vec<String>,
280    pub attachment: Option<EniAttachment>,
281    /// `publicIpDnsNameOptions.publicHostnameType` set by
282    /// `ModifyPublicIpDnsNameOptions`. AWS exposes no Describe that returns this
283    /// setting, so it is persisted (and the ENI's existence enforced) but not
284    /// reflected back through a Describe.
285    #[serde(default)]
286    pub public_ip_dns_hostname_type: Option<String>,
287}
288
289/// A network-interface permission grant.
290#[derive(Clone, Debug, Serialize, Deserialize)]
291pub struct NetworkInterfacePermission {
292    pub permission_id: String,
293    pub network_interface_id: String,
294    pub aws_account_id: String,
295    /// `INSTANCE-ATTACH` | `EIP-ASSOCIATE`.
296    pub permission: String,
297}
298
299/// An EC2 instance (metadata-faithful; a Docker-backed runtime layers on top).
300#[derive(Clone, Debug, Serialize, Deserialize)]
301pub struct Instance {
302    pub instance_id: String,
303    pub image_id: String,
304    pub instance_type: String,
305    /// EC2 state code: 0 pending, 16 running, 32 shutting-down, 48 terminated,
306    /// 64 stopping, 80 stopped.
307    pub state_code: i64,
308    pub state_name: String,
309    pub private_ip: String,
310    pub public_ip: Option<String>,
311    pub subnet_id: Option<String>,
312    pub vpc_id: Option<String>,
313    pub key_name: Option<String>,
314    #[serde(default)]
315    pub security_group_ids: Vec<String>,
316    pub reservation_id: String,
317    pub ami_launch_index: i64,
318    pub monitoring: bool,
319    pub az: String,
320    pub launch_time: String,
321    /// Id of the backing container/Pod, when this instance is backed by a
322    /// real container runtime. `None` in metadata-only mode.
323    #[serde(default)]
324    pub container_id: Option<String>,
325    // ---- modifiable instance attributes (ModifyInstanceAttribute et al.) ----
326    /// `disableApiTermination` — when true, TerminateInstances is rejected.
327    #[serde(default)]
328    pub disable_api_termination: bool,
329    /// `disableApiStop` — when true, StopInstances is rejected.
330    #[serde(default)]
331    pub disable_api_stop: bool,
332    /// `sourceDestCheck` — defaults to true on AWS.
333    #[serde(default = "default_true")]
334    pub source_dest_check: bool,
335    /// `ebsOptimized`.
336    #[serde(default)]
337    pub ebs_optimized: bool,
338    /// `instanceInitiatedShutdownBehavior` — `stop` (default) | `terminate`.
339    #[serde(default = "default_shutdown_behavior")]
340    pub instance_initiated_shutdown_behavior: String,
341    /// `userData` — base64-encoded, as supplied at launch / via Modify.
342    #[serde(default)]
343    pub user_data: Option<String>,
344    // ---- Modify*Options round-trip state ----
345    /// `metadataOptions` (`ModifyInstanceMetadataOptions`).
346    #[serde(default)]
347    pub metadata_options: MetadataOptions,
348    /// `cpuOptions` (`ModifyInstanceCpuOptions`).
349    #[serde(default)]
350    pub cpu_options: Option<CpuOptions>,
351    /// `bandwidthWeighting` (`ModifyInstanceNetworkPerformanceOptions`).
352    #[serde(default)]
353    pub bandwidth_weighting: Option<String>,
354    /// `maintenanceOptions` (`ModifyInstanceMaintenanceOptions`).
355    #[serde(default)]
356    pub maintenance_options: MaintenanceOptions,
357    /// `placement` tenancy/affinity overrides (`ModifyInstancePlacement`).
358    #[serde(default)]
359    pub placement_tenancy: Option<String>,
360    #[serde(default)]
361    pub placement_affinity: Option<String>,
362    #[serde(default)]
363    pub placement_group_name: Option<String>,
364    // ---- ModifyPrivateDnsNameOptions round-trip state ----
365    /// `privateDnsNameOptions.hostnameType` — `ip-name` | `resource-name`.
366    /// `None` reports the AWS default `ip-name`.
367    #[serde(default)]
368    pub private_dns_hostname_type: Option<String>,
369    /// `privateDnsNameOptions.enableResourceNameDnsARecord`.
370    #[serde(default)]
371    pub enable_resource_name_dns_a_record: bool,
372    /// `privateDnsNameOptions.enableResourceNameDnsAAAARecord`.
373    #[serde(default)]
374    pub enable_resource_name_dns_aaaa_record: bool,
375}
376
377fn default_true() -> bool {
378    true
379}
380
381fn default_shutdown_behavior() -> String {
382    "stop".to_string()
383}
384
385/// IMDS (instance-metadata service) options, round-tripped by
386/// `ModifyInstanceMetadataOptions` and reflected in DescribeInstances.
387#[derive(Clone, Debug, Serialize, Deserialize)]
388pub struct MetadataOptions {
389    /// `optional` | `required`.
390    pub http_tokens: String,
391    /// `disabled` | `enabled`.
392    pub http_endpoint: String,
393    pub http_put_response_hop_limit: i64,
394    /// `disabled` | `enabled`.
395    pub http_protocol_ipv6: String,
396    /// `disabled` | `enabled`.
397    pub instance_metadata_tags: String,
398}
399
400impl Default for MetadataOptions {
401    fn default() -> Self {
402        Self {
403            http_tokens: "optional".to_string(),
404            http_endpoint: "enabled".to_string(),
405            http_put_response_hop_limit: 1,
406            http_protocol_ipv6: "disabled".to_string(),
407            instance_metadata_tags: "disabled".to_string(),
408        }
409    }
410}
411
412/// CPU options round-tripped by `ModifyInstanceCpuOptions`.
413#[derive(Clone, Debug, Serialize, Deserialize)]
414pub struct CpuOptions {
415    pub core_count: i64,
416    pub threads_per_core: i64,
417}
418
419/// Maintenance options round-tripped by `ModifyInstanceMaintenanceOptions`.
420#[derive(Clone, Debug, Serialize, Deserialize)]
421pub struct MaintenanceOptions {
422    /// `disabled` | `default`.
423    pub auto_recovery: String,
424    /// `disabled` | `default`.
425    pub reboot_migration: String,
426}
427
428impl Default for MaintenanceOptions {
429    fn default() -> Self {
430        Self {
431            auto_recovery: "default".to_string(),
432            reboot_migration: "default".to_string(),
433        }
434    }
435}
436
437/// An EBS volume attachment.
438#[derive(Clone, Debug, Serialize, Deserialize)]
439pub struct VolumeAttachment {
440    pub volume_id: String,
441    pub instance_id: String,
442    pub device: String,
443    /// `attaching` | `attached` | `detaching` | `detached`.
444    pub status: String,
445    pub delete_on_termination: bool,
446}
447
448/// An EBS volume.
449#[derive(Clone, Debug, Serialize, Deserialize)]
450pub struct Volume {
451    pub volume_id: String,
452    pub size: i64,
453    pub snapshot_id: Option<String>,
454    pub availability_zone: String,
455    /// `creating` | `available` | `in-use` | `deleting` | `deleted`.
456    pub state: String,
457    pub volume_type: String,
458    pub iops: Option<i64>,
459    pub throughput: Option<i64>,
460    pub encrypted: bool,
461    pub kms_key_id: Option<String>,
462    pub multi_attach_enabled: bool,
463    pub auto_enable_io: bool,
464    #[serde(default)]
465    pub attachments: Vec<VolumeAttachment>,
466    #[serde(default)]
467    pub in_recycle_bin: bool,
468}
469
470/// An EBS snapshot.
471#[derive(Clone, Debug, Serialize, Deserialize)]
472pub struct Snapshot {
473    pub snapshot_id: String,
474    pub volume_id: String,
475    /// `pending` | `completed` | `error`.
476    pub state: String,
477    pub volume_size: i64,
478    pub description: String,
479    pub encrypted: bool,
480    /// `standard` | `archive`.
481    pub storage_tier: String,
482    #[serde(default)]
483    pub in_recycle_bin: bool,
484    #[serde(default)]
485    pub locked: bool,
486    pub lock_mode: Option<String>,
487}
488
489/// An AMI (machine image).
490#[derive(Clone, Debug, Serialize, Deserialize)]
491pub struct Image {
492    pub image_id: String,
493    pub name: String,
494    pub description: String,
495    /// `pending` | `available` | `disabled` | `deregistered`.
496    pub state: String,
497    pub architecture: String,
498    pub public: bool,
499    pub source_instance_id: Option<String>,
500    #[serde(default)]
501    pub in_recycle_bin: bool,
502    pub deprecation_time: Option<String>,
503    #[serde(default)]
504    pub deregistration_protection: bool,
505    /// `launchPermission` — AWS account ids the AMI is explicitly shared with
506    /// (cross-account share via `ModifyImageAttribute`).
507    #[serde(default)]
508    pub launch_permission_users: Vec<String>,
509    /// `launchPermission` groups — only `all` is valid in AWS (public share).
510    #[serde(default)]
511    pub launch_permission_groups: Vec<String>,
512    /// `bootMode` — `legacy-bios` | `uefi` | `uefi-preferred`. `None` reports
513    /// the default `uefi`; settable via `ModifyImageAttribute`.
514    #[serde(default)]
515    pub boot_mode: Option<String>,
516    /// `imageOwnerId` — the AWS account that owns the AMI. `None` reports the
517    /// requesting account (a user-registered AMI is owned by its creator); the
518    /// seeded public AMIs set this to the real Amazon/Canonical/etc. owner so
519    /// `aws_ami` data sources filtering by `owners`/`owner-id` resolve them.
520    #[serde(default)]
521    pub owner_id: Option<String>,
522    /// `imageOwnerAlias` — `amazon` | `aws-marketplace` | `self` etc. Set on the
523    /// seeded public AMIs so `owner-alias` filters and `owners = ["amazon"]`
524    /// resolve them; `None` for user-registered AMIs.
525    #[serde(default)]
526    pub owner_alias: Option<String>,
527    /// `creationDate`. `None` reports the fixed fallback; the seeded catalogue
528    /// sets distinct dates so Terraform's `most_recent = true` ordering is
529    /// deterministic.
530    #[serde(default)]
531    pub creation_date: Option<String>,
532    /// `rootDeviceName` (e.g. `/dev/xvda` for Linux, `/dev/sda1` for Windows).
533    /// `None` reports the Linux default.
534    #[serde(default)]
535    pub root_device_name: Option<String>,
536    /// `platformDetails` / Windows `platform`. `None` = Linux/UNIX.
537    #[serde(default)]
538    pub platform: Option<String>,
539}
540
541/// A network ACL entry (rule).
542#[derive(Clone, Debug, Serialize, Deserialize)]
543pub struct NetworkAclEntry {
544    pub rule_number: i64,
545    pub protocol: String,
546    /// `allow` | `deny`.
547    pub rule_action: String,
548    pub egress: bool,
549    pub cidr_block: Option<String>,
550    pub ipv6_cidr_block: Option<String>,
551    /// TCP/UDP port range (from, to).
552    pub port_range: Option<(i64, i64)>,
553    /// ICMP (type, code).
554    pub icmp_type_code: Option<(i64, i64)>,
555}
556
557/// A network ACL <-> subnet association.
558#[derive(Clone, Debug, Serialize, Deserialize)]
559pub struct NetworkAclAssoc {
560    pub association_id: String,
561    pub subnet_id: String,
562}
563
564/// A network ACL.
565#[derive(Clone, Debug, Serialize, Deserialize)]
566pub struct NetworkAcl {
567    pub network_acl_id: String,
568    pub vpc_id: String,
569    pub is_default: bool,
570    #[serde(default)]
571    pub entries: Vec<NetworkAclEntry>,
572    #[serde(default)]
573    pub associations: Vec<NetworkAclAssoc>,
574}
575
576/// A VPC peering connection.
577#[derive(Clone, Debug, Serialize, Deserialize)]
578pub struct VpcPeering {
579    pub id: String,
580    pub requester_vpc_id: String,
581    pub accepter_vpc_id: String,
582    /// `pending-acceptance` | `active` | `rejected` | `deleted`.
583    pub status: String,
584    /// Requester-side DNS-resolution-from-remote-VPC option.
585    #[serde(default)]
586    pub requester_allow_dns: bool,
587    /// Accepter-side DNS-resolution-from-remote-VPC option.
588    #[serde(default)]
589    pub accepter_allow_dns: bool,
590}
591
592/// A VPC endpoint.
593#[derive(Clone, Debug, Serialize, Deserialize)]
594pub struct VpcEndpoint {
595    pub id: String,
596    /// `Interface` | `Gateway` | `GatewayLoadBalancer` | ...
597    pub endpoint_type: String,
598    pub vpc_id: String,
599    pub service_name: String,
600    pub state: String,
601    #[serde(default)]
602    pub subnet_ids: Vec<String>,
603    #[serde(default)]
604    pub route_table_ids: Vec<String>,
605    #[serde(default)]
606    pub private_dns_enabled: bool,
607}
608
609/// A VPC endpoint service configuration (PrivateLink provider side).
610#[derive(Clone, Debug, Serialize, Deserialize)]
611pub struct EndpointService {
612    pub service_id: String,
613    pub service_name: String,
614    pub state: String,
615    pub acceptance_required: bool,
616    pub payer_responsibility: String,
617    #[serde(default)]
618    pub nlb_arns: Vec<String>,
619}
620
621/// A VPC endpoint connection notification.
622#[derive(Clone, Debug, Serialize, Deserialize)]
623pub struct ConnectionNotification {
624    pub id: String,
625    pub arn: String,
626    pub service_id: Option<String>,
627    #[serde(default)]
628    pub events: Vec<String>,
629}
630
631/// A VPC flow log.
632#[derive(Clone, Debug, Serialize, Deserialize)]
633pub struct FlowLog {
634    pub id: String,
635    pub resource_id: String,
636    pub traffic_type: String,
637    pub log_destination_type: String,
638    pub log_group_name: Option<String>,
639    /// Destination ARN for `s3` / `kinesis-data-firehose` deliveries.
640    pub log_destination: Option<String>,
641    /// IAM role ARN used to deliver logs to CloudWatch Logs
642    /// (`iam_role_arn` on the Terraform resource).
643    #[serde(default)]
644    pub deliver_logs_permission_arn: Option<String>,
645    /// Max log aggregation interval in seconds. AWS accepts 60 or 600 and
646    /// defaults to 600 when unspecified.
647    #[serde(default = "default_max_aggregation_interval")]
648    pub max_aggregation_interval: i64,
649}
650
651fn default_max_aggregation_interval() -> i64 {
652    600
653}
654
655/// A launch template (versions tracked as monotonic counters).
656#[derive(Clone, Debug, Serialize, Deserialize)]
657pub struct LaunchTemplate {
658    pub id: String,
659    pub name: String,
660    pub default_version: i64,
661    pub latest_version: i64,
662}
663
664/// A Spot instance request.
665#[derive(Clone, Debug, Serialize, Deserialize)]
666pub struct SpotRequest {
667    pub id: String,
668    /// `open` | `active` | `cancelled` | `closed`.
669    pub state: String,
670    pub request_type: String,
671    pub spot_price: String,
672}
673
674/// A Spot fleet request.
675#[derive(Clone, Debug, Serialize, Deserialize)]
676pub struct SpotFleet {
677    pub id: String,
678    pub state: String,
679}
680
681/// An EC2 fleet.
682#[derive(Clone, Debug, Serialize, Deserialize)]
683pub struct Fleet {
684    pub id: String,
685    pub state: String,
686    pub fleet_type: String,
687}
688
689/// An on-demand capacity reservation.
690#[derive(Clone, Debug, Serialize, Deserialize)]
691pub struct CapacityReservation {
692    pub id: String,
693    pub instance_type: String,
694    pub instance_platform: String,
695    pub availability_zone: String,
696    pub tenancy: String,
697    pub total_instance_count: i64,
698    pub available_instance_count: i64,
699    /// `active` | `expired` | `cancelled` | `pending` | `failed`.
700    pub state: String,
701    pub end_date_type: String,
702    pub instance_match_criteria: String,
703}
704
705/// A Reserved Instance purchase.
706#[derive(Clone, Debug, Serialize, Deserialize)]
707pub struct ReservedInstances {
708    pub id: String,
709    pub instance_type: String,
710    pub availability_zone: String,
711    pub instance_count: i64,
712    pub product_description: String,
713    pub state: String,
714    pub duration: i64,
715    pub fixed_price: String,
716    pub usage_price: String,
717}
718
719/// A Reserved Instances listing in the Reserved Instance Marketplace.
720#[derive(Clone, Debug, Serialize, Deserialize)]
721pub struct ReservedInstancesListing {
722    pub listing_id: String,
723    pub reserved_instances_id: String,
724    pub instance_count: i64,
725    pub client_token: String,
726    /// `active` | `cancelled` | `closed`.
727    pub status: String,
728    pub status_message: String,
729}
730
731/// A Reserved Instances modification request.
732#[derive(Clone, Debug, Serialize, Deserialize)]
733pub struct ReservedInstancesModification {
734    pub modification_id: String,
735    pub reserved_instances_ids: Vec<String>,
736    /// `processing` | `fulfilled` | `failed`.
737    pub status: String,
738    pub client_token: String,
739}
740
741/// A Dedicated Host.
742#[derive(Clone, Debug, Serialize, Deserialize)]
743pub struct DedicatedHost {
744    pub id: String,
745    pub auto_placement: String,
746    pub availability_zone: String,
747    pub instance_type: String,
748    pub state: String,
749    pub host_recovery: String,
750    pub host_maintenance: String,
751}
752
753/// A Transit Gateway.
754#[derive(Clone, Debug, Serialize, Deserialize)]
755pub struct TransitGateway {
756    pub id: String,
757    pub description: String,
758    /// `pending` | `available` | `modifying` | `deleting` | `deleted`.
759    #[serde(default = "tgw_default_state")]
760    pub state: String,
761}
762
763fn tgw_default_state() -> String {
764    "available".to_string()
765}
766
767/// A Transit Gateway attachment (VPC and others).
768#[derive(Clone, Debug, Serialize, Deserialize)]
769pub struct TgwAttachment {
770    pub id: String,
771    pub tgw_id: String,
772    pub resource_id: String,
773    pub resource_type: String,
774    #[serde(default)]
775    pub subnet_ids: Vec<String>,
776    pub state: String,
777}
778
779/// A Transit Gateway route table.
780#[derive(Clone, Debug, Serialize, Deserialize)]
781pub struct TgwRouteTable {
782    pub id: String,
783    pub tgw_id: String,
784}
785
786/// A static Transit Gateway route within a route table.
787#[derive(Clone, Debug, Serialize, Deserialize)]
788pub struct TgwRoute {
789    pub cidr: String,
790    pub attachment_id: String,
791    pub state: String,
792}
793
794/// A Transit Gateway multicast domain.
795#[derive(Clone, Debug, Serialize, Deserialize)]
796pub struct TgwMulticastDomain {
797    pub id: String,
798    pub tgw_id: String,
799}
800
801/// A Transit Gateway metering policy.
802#[derive(Clone, Debug, Serialize, Deserialize)]
803pub struct TgwMeteringPolicy {
804    pub id: String,
805    pub tgw_id: String,
806}
807
808/// A customer gateway (on-prem side of a VPN).
809#[derive(Clone, Debug, Serialize, Deserialize)]
810pub struct CustomerGateway {
811    pub id: String,
812    pub state: String,
813    pub ip_address: String,
814    pub bgp_asn: String,
815}
816
817/// A virtual private gateway.
818#[derive(Clone, Debug, Serialize, Deserialize)]
819pub struct VpnGateway {
820    pub id: String,
821    pub state: String,
822    #[serde(default)]
823    pub attachments: Vec<String>,
824}
825
826/// A Site-to-Site VPN connection.
827#[derive(Clone, Debug, Serialize, Deserialize)]
828pub struct VpnConnection {
829    pub id: String,
830    pub state: String,
831    pub customer_gateway_id: String,
832    pub vpn_gateway_id: Option<String>,
833    #[serde(default)]
834    pub routes: Vec<String>,
835}
836
837/// A VPN concentrator.
838#[derive(Clone, Debug, Serialize, Deserialize)]
839pub struct VpnConcentrator {
840    pub id: String,
841    pub state: String,
842}
843
844/// An IPAM (IP Address Manager).
845#[derive(Clone, Debug, Serialize, Deserialize)]
846pub struct Ipam {
847    pub id: String,
848    pub public_scope_id: String,
849    pub private_scope_id: String,
850    pub tier: String,
851    #[serde(default)]
852    pub description: String,
853}
854
855/// An IPAM scope.
856#[derive(Clone, Debug, Serialize, Deserialize)]
857pub struct IpamScope {
858    pub id: String,
859    pub ipam_id: String,
860    /// "public" or "private".
861    #[serde(default)]
862    pub scope_type: String,
863    #[serde(default)]
864    pub description: String,
865}
866
867/// An IPAM pool.
868#[derive(Clone, Debug, Serialize, Deserialize)]
869pub struct IpamPool {
870    pub id: String,
871    pub scope_id: String,
872    pub address_family: String,
873    #[serde(default)]
874    pub description: String,
875}
876
877/// An IPAM resource discovery.
878#[derive(Clone, Debug, Serialize, Deserialize)]
879pub struct IpamResourceDiscovery {
880    pub id: String,
881    #[serde(default)]
882    pub description: String,
883}
884
885/// An IPAM policy.
886#[derive(Clone, Debug, Serialize, Deserialize)]
887pub struct IpamPolicy {
888    pub id: String,
889    pub ipam_id: String,
890}
891
892/// An IPAM prefix-list resolver.
893#[derive(Clone, Debug, Serialize, Deserialize)]
894pub struct IpamPrefixListResolver {
895    pub id: String,
896    pub ipam_id: String,
897    pub address_family: String,
898    #[serde(default)]
899    pub description: String,
900}
901
902/// An IPAM prefix-list resolver target.
903#[derive(Clone, Debug, Serialize, Deserialize)]
904pub struct IpamPrefixListResolverTarget {
905    pub id: String,
906    pub resolver_id: String,
907    pub prefix_list_id: String,
908    pub prefix_list_region: String,
909    #[serde(default)]
910    pub track_latest_version: bool,
911}
912
913/// A Verified Access instance.
914#[derive(Clone, Debug, Serialize, Deserialize)]
915pub struct VerifiedAccessInstance {
916    pub id: String,
917    pub description: String,
918    #[serde(default)]
919    pub trust_providers: Vec<String>,
920}
921
922/// A Verified Access trust provider.
923#[derive(Clone, Debug, Serialize, Deserialize)]
924pub struct VerifiedAccessTrustProvider {
925    pub id: String,
926    pub trust_provider_type: String,
927    pub policy_reference_name: String,
928    pub description: String,
929}
930
931/// A Verified Access group.
932#[derive(Clone, Debug, Serialize, Deserialize)]
933pub struct VerifiedAccessGroup {
934    pub id: String,
935    pub instance_id: String,
936    pub description: String,
937}
938
939/// A Verified Access endpoint.
940#[derive(Clone, Debug, Serialize, Deserialize)]
941pub struct VerifiedAccessEndpoint {
942    pub id: String,
943    pub group_id: String,
944    pub instance_id: String,
945    pub endpoint_type: String,
946    pub attachment_type: String,
947}
948
949/// A Network Insights reachability path.
950#[derive(Clone, Debug, Serialize, Deserialize)]
951pub struct NetworkInsightsPath {
952    pub id: String,
953    pub source: String,
954    pub destination: String,
955    pub protocol: String,
956}
957
958/// A Network Insights path analysis.
959#[derive(Clone, Debug, Serialize, Deserialize)]
960pub struct NetworkInsightsAnalysis {
961    pub id: String,
962    pub path_id: String,
963}
964
965/// A Network Insights access scope.
966#[derive(Clone, Debug, Serialize, Deserialize)]
967pub struct NetworkInsightsAccessScope {
968    pub id: String,
969}
970
971/// A Network Insights access-scope analysis.
972#[derive(Clone, Debug, Serialize, Deserialize)]
973pub struct NetworkInsightsAccessScopeAnalysis {
974    pub id: String,
975    pub scope_id: String,
976}
977
978/// A carrier gateway (Wavelength).
979#[derive(Clone, Debug, Serialize, Deserialize)]
980pub struct CarrierGateway {
981    pub id: String,
982    pub vpc_id: String,
983}
984
985/// An EC2 Instance Connect endpoint.
986#[derive(Clone, Debug, Serialize, Deserialize)]
987pub struct InstanceConnectEndpoint {
988    pub id: String,
989    pub subnet_id: String,
990}
991
992/// A customer-owned IP (CoIP) pool.
993#[derive(Clone, Debug, Serialize, Deserialize)]
994pub struct CoipPool {
995    pub id: String,
996    pub route_table_id: String,
997}
998
999/// A local-gateway route table.
1000#[derive(Clone, Debug, Serialize, Deserialize)]
1001pub struct LocalGatewayRouteTable {
1002    pub id: String,
1003    pub local_gateway_id: String,
1004    pub mode: String,
1005}
1006
1007/// A local-gateway route-table <-> VPC association.
1008#[derive(Clone, Debug, Serialize, Deserialize)]
1009pub struct LocalGatewayRouteTableVpcAssoc {
1010    pub id: String,
1011    pub route_table_id: String,
1012    pub vpc_id: String,
1013}
1014
1015/// A local-gateway virtual interface.
1016#[derive(Clone, Debug, Serialize, Deserialize)]
1017pub struct LocalGatewayVif {
1018    pub id: String,
1019    pub group_id: String,
1020    pub vlan: String,
1021    pub local_address: String,
1022    pub peer_address: String,
1023}
1024
1025/// A local-gateway virtual-interface group.
1026#[derive(Clone, Debug, Serialize, Deserialize)]
1027pub struct LocalGatewayVifGroup {
1028    pub id: String,
1029    pub local_gateway_id: String,
1030}
1031
1032/// A local-gateway route-table <-> virtual-interface-group association.
1033#[derive(Clone, Debug, Serialize, Deserialize)]
1034pub struct LocalGatewayRouteTableVifgAssoc {
1035    pub id: String,
1036    pub route_table_id: String,
1037    pub vif_group_id: String,
1038}
1039
1040/// A Client VPN endpoint.
1041#[derive(Clone, Debug, Serialize, Deserialize)]
1042pub struct ClientVpnEndpoint {
1043    pub id: String,
1044    pub description: String,
1045    pub status: String,
1046    pub server_cert_arn: String,
1047    pub transport_protocol: String,
1048    pub client_cidr: String,
1049    #[serde(default)]
1050    pub routes: Vec<String>,
1051    /// (association id, subnet id) for each associated target network.
1052    #[serde(default)]
1053    pub target_networks: Vec<(String, String)>,
1054    /// Ingress authorization rule target CIDRs.
1055    #[serde(default)]
1056    pub auth_rules: Vec<String>,
1057}
1058
1059/// A Transit Gateway peering attachment.
1060#[derive(Clone, Debug, Serialize, Deserialize)]
1061pub struct TgwPeering {
1062    pub id: String,
1063    pub tgw_id: String,
1064    pub peer_tgw_id: String,
1065    pub peer_account: String,
1066    pub peer_region: String,
1067    pub state: String,
1068}
1069
1070/// One entry in a customer-managed prefix list.
1071#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1072pub struct PrefixListEntry {
1073    pub cidr: String,
1074    #[serde(default)]
1075    pub description: Option<String>,
1076}
1077
1078/// A customer-managed prefix list (`CreateManagedPrefixList`). Versions are a
1079/// monotonic counter; each entry-mutating Modify bumps `version` and snapshots
1080/// the prior entries into `version_history` so `RestoreManagedPrefixListVersion`
1081/// and `GetManagedPrefixListEntries(TargetVersion)` round-trip.
1082#[derive(Clone, Debug, Serialize, Deserialize)]
1083pub struct ManagedPrefixList {
1084    pub prefix_list_id: String,
1085    pub prefix_list_name: String,
1086    pub address_family: String,
1087    pub max_entries: i64,
1088    pub version: i64,
1089    /// `create-complete` | `modify-complete`.
1090    pub state: String,
1091    #[serde(default)]
1092    pub entries: Vec<PrefixListEntry>,
1093    /// version -> entries snapshot at that version.
1094    #[serde(default)]
1095    pub version_history: BTreeMap<i64, Vec<PrefixListEntry>>,
1096}
1097
1098/// A weekly time range within an instance event window.
1099#[derive(Clone, Debug, Serialize, Deserialize)]
1100pub struct EventWindowTimeRange {
1101    pub start_week_day: String,
1102    pub start_hour: i64,
1103    pub end_week_day: String,
1104    pub end_hour: i64,
1105}
1106
1107/// An instance event window (`CreateInstanceEventWindow`).
1108#[derive(Clone, Debug, Serialize, Deserialize)]
1109pub struct InstanceEventWindow {
1110    pub id: String,
1111    #[serde(default)]
1112    pub name: Option<String>,
1113    #[serde(default)]
1114    pub cron_expression: Option<String>,
1115    #[serde(default)]
1116    pub time_ranges: Vec<EventWindowTimeRange>,
1117    /// `creating` | `active` | `deleting` | `deleted`.
1118    pub state: String,
1119    /// Association target — instance ids, dedicated-host ids, or tags
1120    /// (`AssociateInstanceEventWindow` / `DisassociateInstanceEventWindow`).
1121    #[serde(default)]
1122    pub assoc_instance_ids: Vec<String>,
1123    #[serde(default)]
1124    pub assoc_dedicated_host_ids: Vec<String>,
1125    #[serde(default)]
1126    pub assoc_tags: Vec<Tag>,
1127}
1128
1129/// A traffic-mirror target (`CreateTrafficMirrorTarget`).
1130#[derive(Clone, Debug, Serialize, Deserialize)]
1131pub struct TrafficMirrorTarget {
1132    pub id: String,
1133    pub network_interface_id: Option<String>,
1134    pub network_load_balancer_arn: Option<String>,
1135    pub gateway_lb_endpoint_id: Option<String>,
1136    /// `network-interface` | `network-load-balancer` | `gateway-load-balancer-endpoint`.
1137    pub target_type: String,
1138    pub description: Option<String>,
1139}
1140
1141/// A traffic-mirror filter (`CreateTrafficMirrorFilter`).
1142#[derive(Clone, Debug, Serialize, Deserialize)]
1143pub struct TrafficMirrorFilter {
1144    pub id: String,
1145    pub description: Option<String>,
1146    #[serde(default)]
1147    pub network_services: Vec<String>,
1148}
1149
1150/// A traffic-mirror filter rule (`CreateTrafficMirrorFilterRule`).
1151#[derive(Clone, Debug, Serialize, Deserialize)]
1152pub struct TrafficMirrorFilterRule {
1153    pub id: String,
1154    pub filter_id: String,
1155    pub traffic_direction: String,
1156    pub rule_number: i64,
1157    pub rule_action: String,
1158    pub protocol: Option<i64>,
1159    pub destination_cidr_block: Option<String>,
1160    pub source_cidr_block: Option<String>,
1161    /// (from, to) port ranges.
1162    pub destination_port_range: Option<(i64, i64)>,
1163    pub source_port_range: Option<(i64, i64)>,
1164    pub description: Option<String>,
1165}
1166
1167/// A traffic-mirror session (`CreateTrafficMirrorSession`).
1168#[derive(Clone, Debug, Serialize, Deserialize)]
1169pub struct TrafficMirrorSession {
1170    pub id: String,
1171    pub target_id: String,
1172    pub filter_id: String,
1173    pub network_interface_id: String,
1174    pub packet_length: Option<i64>,
1175    pub session_number: i64,
1176    pub virtual_network_id: Option<i64>,
1177    pub description: Option<String>,
1178}
1179
1180/// A route server (`CreateRouteServer`).
1181#[derive(Clone, Debug, Serialize, Deserialize)]
1182pub struct RouteServer {
1183    pub id: String,
1184    pub amazon_side_asn: i64,
1185    /// `available` (lowercase per the AWS state enum is uppercase; we store the
1186    /// wire value).
1187    pub state: String,
1188    /// `ENABLED` | `DISABLED` | `RESETTING` | ...
1189    pub persist_routes_state: String,
1190    pub persist_routes_duration: Option<i64>,
1191    pub sns_notifications_enabled: bool,
1192}
1193
1194/// A VPC encryption control (`CreateVpcEncryptionControl`).
1195#[derive(Clone, Debug, Serialize, Deserialize)]
1196pub struct VpcEncryptionControl {
1197    pub id: String,
1198    pub vpc_id: String,
1199    /// `monitor` | `enforce`.
1200    pub mode: String,
1201    /// `available` | `monitor_in_progress` | `enforce_in_progress` | ...
1202    pub state: String,
1203    /// Per-resource-type exclusion state: resource -> `enabled` | `disabled`.
1204    #[serde(default)]
1205    pub exclusions: BTreeMap<String, String>,
1206}
1207
1208/// A VPC block-public-access exclusion (`CreateVpcBlockPublicAccessExclusion`).
1209#[derive(Clone, Debug, Serialize, Deserialize)]
1210pub struct VpcBpaExclusion {
1211    pub id: String,
1212    /// `allow-bidirectional` | `allow-egress`.
1213    pub internet_gateway_exclusion_mode: String,
1214    pub resource_arn: Option<String>,
1215    /// `create-complete` | `update-complete` | `delete-complete`.
1216    pub state: String,
1217}
1218
1219/// An Amazon FPGA image (`CreateFpgaImage` / `CopyFpgaImage`).
1220#[derive(Clone, Debug, Serialize, Deserialize)]
1221pub struct FpgaImage {
1222    pub id: String,
1223    #[serde(default)]
1224    pub name: String,
1225    #[serde(default)]
1226    pub description: String,
1227    /// `loadPermission` users the image is shared with.
1228    #[serde(default)]
1229    pub load_permission_users: Vec<String>,
1230    /// `loadPermission` groups (`all` for public).
1231    #[serde(default)]
1232    pub load_permission_groups: Vec<String>,
1233}
1234
1235/// Per-account, per-region EC2 state. Resource families are added to this
1236/// struct as their batches land.
1237#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1238pub struct Ec2State {
1239    pub account_id: String,
1240    pub region: String,
1241    /// resource-id -> tags. Shared by every Describe* `tag:` filter.
1242    #[serde(default)]
1243    pub tags: BTreeMap<String, Vec<Tag>>,
1244    #[serde(default)]
1245    pub vpcs: BTreeMap<String, Vpc>,
1246    #[serde(default)]
1247    pub dhcp_options: BTreeMap<String, DhcpOptions>,
1248    #[serde(default)]
1249    pub subnets: BTreeMap<String, Subnet>,
1250    #[serde(default)]
1251    pub subnet_cidr_reservations: BTreeMap<String, SubnetCidrReservation>,
1252    #[serde(default)]
1253    pub security_groups: BTreeMap<String, SecurityGroup>,
1254    #[serde(default)]
1255    pub route_tables: BTreeMap<String, RouteTable>,
1256    #[serde(default)]
1257    pub internet_gateways: BTreeMap<String, InternetGateway>,
1258    #[serde(default)]
1259    pub egress_only_igws: BTreeMap<String, InternetGateway>,
1260    #[serde(default)]
1261    pub nat_gateways: BTreeMap<String, NatGateway>,
1262    /// keyed by allocation id.
1263    #[serde(default)]
1264    pub elastic_ips: BTreeMap<String, ElasticIp>,
1265    /// keyed by key name.
1266    #[serde(default)]
1267    pub key_pairs: BTreeMap<String, KeyPair>,
1268    /// keyed by group name.
1269    #[serde(default)]
1270    pub placement_groups: BTreeMap<String, PlacementGroup>,
1271    #[serde(default)]
1272    pub network_interfaces: BTreeMap<String, NetworkInterface>,
1273    /// keyed by permission id.
1274    #[serde(default)]
1275    pub eni_permissions: BTreeMap<String, NetworkInterfacePermission>,
1276    #[serde(default)]
1277    pub instances: BTreeMap<String, Instance>,
1278    #[serde(default)]
1279    pub volumes: BTreeMap<String, Volume>,
1280    /// Account-level EBS default encryption toggle.
1281    #[serde(default)]
1282    pub ebs_encryption_default: bool,
1283    /// Account-level EBS default KMS key (None = `alias/aws/ebs`).
1284    #[serde(default)]
1285    pub ebs_default_kms_key_id: Option<String>,
1286    #[serde(default)]
1287    pub snapshots: BTreeMap<String, Snapshot>,
1288    /// Account-level snapshot block-public-access state.
1289    #[serde(default)]
1290    pub snapshot_block_public_access: String,
1291    #[serde(default)]
1292    pub images: BTreeMap<String, Image>,
1293    /// Watermarks attached to AMIs: image_id -> watermark_key -> watermark_name.
1294    #[serde(default)]
1295    pub image_watermarks: BTreeMap<String, BTreeMap<String, String>>,
1296    /// Account-level image block-public-access state.
1297    #[serde(default)]
1298    pub image_block_public_access: String,
1299    /// Account-level allowed-images settings state.
1300    #[serde(default)]
1301    pub allowed_images_settings: String,
1302    /// Allowed-images `imageCriterionSet`: each criterion is its list of
1303    /// `ImageProvider`s, persisted by ReplaceImageCriteriaInAllowedImagesSettings
1304    /// and reported by GetAllowedImagesSettings.
1305    #[serde(default)]
1306    pub allowed_image_criteria: Vec<Vec<String>>,
1307    #[serde(default)]
1308    pub network_acls: BTreeMap<String, NetworkAcl>,
1309    #[serde(default)]
1310    pub vpc_peerings: BTreeMap<String, VpcPeering>,
1311    #[serde(default)]
1312    pub vpc_endpoints: BTreeMap<String, VpcEndpoint>,
1313    #[serde(default)]
1314    pub endpoint_services: BTreeMap<String, EndpointService>,
1315    #[serde(default)]
1316    pub connection_notifications: BTreeMap<String, ConnectionNotification>,
1317    #[serde(default)]
1318    pub flow_logs: BTreeMap<String, FlowLog>,
1319    #[serde(default)]
1320    pub launch_templates: BTreeMap<String, LaunchTemplate>,
1321    #[serde(default)]
1322    pub spot_requests: BTreeMap<String, SpotRequest>,
1323    #[serde(default)]
1324    pub spot_fleets: BTreeMap<String, SpotFleet>,
1325    #[serde(default)]
1326    pub fleets: BTreeMap<String, Fleet>,
1327    /// Account-level spot datafeed subscription (bucket, prefix).
1328    #[serde(default)]
1329    pub spot_datafeed: Option<(String, String)>,
1330    #[serde(default)]
1331    pub capacity_reservations: BTreeMap<String, CapacityReservation>,
1332    /// Capacity reservation fleet ids (metadata-only).
1333    #[serde(default)]
1334    pub capacity_reservation_fleets: BTreeMap<String, String>,
1335    #[serde(default)]
1336    pub reserved_instances: BTreeMap<String, ReservedInstances>,
1337    #[serde(default)]
1338    pub reserved_instances_listings: BTreeMap<String, ReservedInstancesListing>,
1339    #[serde(default)]
1340    pub reserved_instances_modifications: BTreeMap<String, ReservedInstancesModification>,
1341    #[serde(default)]
1342    pub dedicated_hosts: BTreeMap<String, DedicatedHost>,
1343    #[serde(default)]
1344    pub transit_gateways: BTreeMap<String, TransitGateway>,
1345    #[serde(default)]
1346    pub tgw_attachments: BTreeMap<String, TgwAttachment>,
1347    #[serde(default)]
1348    pub tgw_route_tables: BTreeMap<String, TgwRouteTable>,
1349    /// route-table-id -> static routes.
1350    #[serde(default)]
1351    pub tgw_routes: BTreeMap<String, Vec<TgwRoute>>,
1352    /// route-table-id -> associated attachment ids.
1353    #[serde(default)]
1354    pub tgw_rt_associations: BTreeMap<String, Vec<String>>,
1355    /// route-table-id -> propagated attachment ids.
1356    #[serde(default)]
1357    pub tgw_rt_propagations: BTreeMap<String, Vec<String>>,
1358    /// route-table-id -> prefix-list ids referenced.
1359    #[serde(default)]
1360    pub tgw_prefix_list_refs: BTreeMap<String, Vec<String>>,
1361    #[serde(default)]
1362    pub tgw_peerings: BTreeMap<String, TgwPeering>,
1363    /// connect-attachment-id -> (transport attachment id, tgw id).
1364    #[serde(default)]
1365    pub tgw_connects: BTreeMap<String, (String, String)>,
1366    /// connect-peer-id -> attachment id.
1367    #[serde(default)]
1368    pub tgw_connect_peers: BTreeMap<String, String>,
1369    /// policy-table-id -> tgw id.
1370    #[serde(default)]
1371    pub tgw_policy_tables: BTreeMap<String, String>,
1372    /// policy-table-id -> associated attachment ids.
1373    #[serde(default)]
1374    pub tgw_policy_table_associations: BTreeMap<String, Vec<String>>,
1375    /// announcement-id -> (route-table id, peering-attachment id).
1376    #[serde(default)]
1377    pub tgw_announcements: BTreeMap<String, (String, String)>,
1378    #[serde(default)]
1379    pub tgw_multicast_domains: BTreeMap<String, TgwMulticastDomain>,
1380    #[serde(default)]
1381    pub tgw_metering_policies: BTreeMap<String, TgwMeteringPolicy>,
1382    #[serde(default)]
1383    pub customer_gateways: BTreeMap<String, CustomerGateway>,
1384    #[serde(default)]
1385    pub vpn_gateways: BTreeMap<String, VpnGateway>,
1386    #[serde(default)]
1387    pub vpn_connections: BTreeMap<String, VpnConnection>,
1388    #[serde(default)]
1389    pub vpn_concentrators: BTreeMap<String, VpnConcentrator>,
1390    #[serde(default)]
1391    pub client_vpn_endpoints: BTreeMap<String, ClientVpnEndpoint>,
1392    #[serde(default)]
1393    pub ipams: BTreeMap<String, Ipam>,
1394    #[serde(default)]
1395    pub ipam_scopes: BTreeMap<String, IpamScope>,
1396    #[serde(default)]
1397    pub ipam_pools: BTreeMap<String, IpamPool>,
1398    /// pool-id -> provisioned (cidr, cidr-id).
1399    #[serde(default)]
1400    pub ipam_pool_cidrs: BTreeMap<String, Vec<(String, String)>>,
1401    /// pool-id -> allocations (cidr, allocation-id).
1402    #[serde(default)]
1403    pub ipam_pool_allocations: BTreeMap<String, Vec<(String, String)>>,
1404    #[serde(default)]
1405    pub ipam_resource_discoveries: BTreeMap<String, IpamResourceDiscovery>,
1406    /// association-id -> (discovery-id, ipam-id).
1407    #[serde(default)]
1408    pub ipam_rd_associations: BTreeMap<String, (String, String)>,
1409    /// asn -> associated cidr.
1410    #[serde(default)]
1411    pub ipam_byoasns: BTreeMap<String, String>,
1412    /// external-token-id -> ipam-id.
1413    #[serde(default)]
1414    pub ipam_ext_tokens: BTreeMap<String, String>,
1415    #[serde(default)]
1416    pub ipam_policies: BTreeMap<String, IpamPolicy>,
1417    #[serde(default)]
1418    pub ipam_pl_resolvers: BTreeMap<String, IpamPrefixListResolver>,
1419    #[serde(default)]
1420    pub ipam_pl_resolver_targets: BTreeMap<String, IpamPrefixListResolverTarget>,
1421    /// policy-id -> (locale, resource-type) allocation-rule documents.
1422    #[serde(default)]
1423    pub ipam_policy_alloc_rules: BTreeMap<String, Vec<(String, String)>>,
1424    /// The single enabled IPAM policy id, if any.
1425    #[serde(default)]
1426    pub ipam_enabled_policy: Option<String>,
1427    #[serde(default)]
1428    pub va_instances: BTreeMap<String, VerifiedAccessInstance>,
1429    #[serde(default)]
1430    pub va_trust_providers: BTreeMap<String, VerifiedAccessTrustProvider>,
1431    #[serde(default)]
1432    pub va_groups: BTreeMap<String, VerifiedAccessGroup>,
1433    #[serde(default)]
1434    pub va_endpoints: BTreeMap<String, VerifiedAccessEndpoint>,
1435    /// group-id -> policy document.
1436    #[serde(default)]
1437    pub va_group_policies: BTreeMap<String, String>,
1438    /// endpoint-id -> policy document.
1439    #[serde(default)]
1440    pub va_endpoint_policies: BTreeMap<String, String>,
1441    #[serde(default)]
1442    pub ni_paths: BTreeMap<String, NetworkInsightsPath>,
1443    #[serde(default)]
1444    pub ni_analyses: BTreeMap<String, NetworkInsightsAnalysis>,
1445    #[serde(default)]
1446    pub ni_access_scopes: BTreeMap<String, NetworkInsightsAccessScope>,
1447    #[serde(default)]
1448    pub ni_scope_analyses: BTreeMap<String, NetworkInsightsAccessScopeAnalysis>,
1449    #[serde(default)]
1450    pub carrier_gateways: BTreeMap<String, CarrierGateway>,
1451    #[serde(default)]
1452    pub coip_pools: BTreeMap<String, CoipPool>,
1453    /// coip-pool-id -> CIDRs.
1454    #[serde(default)]
1455    pub coip_pool_cidrs: BTreeMap<String, Vec<String>>,
1456    #[serde(default)]
1457    pub lg_route_tables: BTreeMap<String, LocalGatewayRouteTable>,
1458    /// route-table-id -> destination CIDRs.
1459    #[serde(default)]
1460    pub lg_routes: BTreeMap<String, Vec<String>>,
1461    #[serde(default)]
1462    pub lg_rt_vpc_assocs: BTreeMap<String, LocalGatewayRouteTableVpcAssoc>,
1463    #[serde(default)]
1464    pub lg_virtual_interfaces: BTreeMap<String, LocalGatewayVif>,
1465    #[serde(default)]
1466    pub lg_vif_groups: BTreeMap<String, LocalGatewayVifGroup>,
1467    #[serde(default)]
1468    pub lg_rt_vifg_assocs: BTreeMap<String, LocalGatewayRouteTableVifgAssoc>,
1469    #[serde(default)]
1470    pub instance_connect_endpoints: BTreeMap<String, InstanceConnectEndpoint>,
1471    /// Image ids with fast-launch enabled.
1472    #[serde(default)]
1473    pub fast_launch_images: std::collections::HashSet<String>,
1474    #[serde(default)]
1475    pub serial_console_access: bool,
1476    // ---- account/region-scoped id-format settings (ModifyIdFormat) ----
1477    /// resource type -> use-long-ids (account default).
1478    #[serde(default)]
1479    pub id_format: BTreeMap<String, bool>,
1480    /// principal ARN -> (resource type -> use-long-ids).
1481    #[serde(default)]
1482    pub identity_id_format: BTreeMap<String, BTreeMap<String, bool>>,
1483    /// burstable instance family -> CpuCredits (`standard` | `unlimited`),
1484    /// set by ModifyDefaultCreditSpecification, read by GetDefaultCreditSpecification.
1485    #[serde(default)]
1486    pub default_credit_specs: BTreeMap<String, String>,
1487    /// VPC block-public-access `InternetGatewayBlockMode` (account/region
1488    /// singleton). `None` reports the default `off`.
1489    #[serde(default)]
1490    pub vpc_bpa_internet_gateway_block_mode: Option<String>,
1491    /// Managed-resource `DefaultVisibility` (`hidden` | `visible`). `None`
1492    /// reports the default `visible`.
1493    #[serde(default)]
1494    pub managed_resource_default_visibility: Option<String>,
1495    /// Availability-zone group -> opt-in status (`opted-in` | `not-opted-in`),
1496    /// set by ModifyAvailabilityZoneGroup, reflected in DescribeAvailabilityZones.
1497    #[serde(default)]
1498    pub az_group_optin: BTreeMap<String, String>,
1499    #[serde(default)]
1500    pub managed_prefix_lists: BTreeMap<String, ManagedPrefixList>,
1501    #[serde(default)]
1502    pub instance_event_windows: BTreeMap<String, InstanceEventWindow>,
1503    #[serde(default)]
1504    pub traffic_mirror_targets: BTreeMap<String, TrafficMirrorTarget>,
1505    #[serde(default)]
1506    pub traffic_mirror_filters: BTreeMap<String, TrafficMirrorFilter>,
1507    #[serde(default)]
1508    pub traffic_mirror_filter_rules: BTreeMap<String, TrafficMirrorFilterRule>,
1509    #[serde(default)]
1510    pub traffic_mirror_sessions: BTreeMap<String, TrafficMirrorSession>,
1511    #[serde(default)]
1512    pub route_servers: BTreeMap<String, RouteServer>,
1513    #[serde(default)]
1514    pub vpc_encryption_controls: BTreeMap<String, VpcEncryptionControl>,
1515    #[serde(default)]
1516    pub vpc_bpa_exclusions: BTreeMap<String, VpcBpaExclusion>,
1517    #[serde(default)]
1518    pub fpga_images: BTreeMap<String, FpgaImage>,
1519    /// IPAM pool allocation id -> description (ModifyIpamPoolAllocation), read
1520    /// back by DescribeIpamPoolAllocations.
1521    #[serde(default)]
1522    pub ipam_allocation_descriptions: BTreeMap<String, String>,
1523}
1524
1525impl Ec2State {
1526    pub fn new(account_id: &str, region: &str) -> Self {
1527        let mut state = Self {
1528            account_id: account_id.to_string(),
1529            region: region.to_string(),
1530            ..Default::default()
1531        };
1532        // Seed the default VPC topology (VPC, IGW, subnets, route table,
1533        // security group, NACL) the way every AWS account+region ships one, so
1534        // callers that never touch the VPC APIs still launch into a real,
1535        // isolatable network. Ids are deterministic, so the throwaway empty
1536        // states the read paths build report the same ids as this one.
1537        crate::defaults::bootstrap_default_network(&mut state);
1538        // Seed the public AMI catalogue (Amazon Linux, Ubuntu, Windows) so
1539        // `aws_ami` data sources resolve, matching how every real account sees
1540        // Amazon/Canonical-owned public images.
1541        crate::defaults::seed_public_images(&mut state);
1542        state
1543    }
1544
1545    /// Idempotently (re)seed the public AMI catalogue into this account. Used on
1546    /// snapshot restore so accounts persisted by a binary that predated the
1547    /// catalogue (#1964) still get it after an upgrade+restart — without it,
1548    /// `aws_ami { owners=["amazon"] }` returns empty for legacy accounts. Seeds
1549    /// have deterministic ids, so re-seeding an already-seeded account is a no-op.
1550    pub fn ensure_public_images_seeded(&mut self) {
1551        crate::defaults::seed_public_images(self);
1552    }
1553
1554    /// Replace the tag set for `resource_id` with `tags` merged over any
1555    /// existing tags (CreateTags is upsert-by-key, matching AWS).
1556    pub fn upsert_tags(&mut self, resource_id: &str, new_tags: &[Tag]) {
1557        let entry = self.tags.entry(resource_id.to_string()).or_default();
1558        for t in new_tags {
1559            if let Some(existing) = entry.iter_mut().find(|e| e.key == t.key) {
1560                existing.value = t.value.clone();
1561            } else {
1562                entry.push(t.clone());
1563            }
1564        }
1565    }
1566
1567    /// Remove tags for `resource_id`. When a tag's value is `None`, the key is
1568    /// removed regardless of value; when `Some`, only a key+value match is
1569    /// removed (AWS DeleteTags semantics).
1570    pub fn remove_tags(&mut self, resource_id: &str, to_remove: &[(String, Option<String>)]) {
1571        if let Some(entry) = self.tags.get_mut(resource_id) {
1572            for (key, value) in to_remove {
1573                entry.retain(|e| {
1574                    if &e.key != key {
1575                        return true;
1576                    }
1577                    match value {
1578                        Some(v) => &e.value != v,
1579                        None => false,
1580                    }
1581                });
1582            }
1583            if entry.is_empty() {
1584                self.tags.remove(resource_id);
1585            }
1586        }
1587    }
1588
1589    /// Tags for `resource_id`, or an empty slice when none.
1590    pub fn tags_for(&self, resource_id: &str) -> &[Tag] {
1591        self.tags.get(resource_id).map(Vec::as_slice).unwrap_or(&[])
1592    }
1593}
1594
1595#[cfg(test)]
1596mod tests {
1597    use super::*;
1598
1599    fn tag(k: &str, v: &str) -> Tag {
1600        Tag {
1601            key: k.to_string(),
1602            value: v.to_string(),
1603        }
1604    }
1605
1606    #[test]
1607    fn upsert_tags_inserts_then_overwrites_by_key() {
1608        let mut s = Ec2State::new("123456789012", "us-east-1");
1609        s.upsert_tags("vpc-1", &[tag("Name", "a"), tag("env", "dev")]);
1610        s.upsert_tags("vpc-1", &[tag("Name", "b")]);
1611        let tags = s.tags_for("vpc-1");
1612        assert_eq!(tags.len(), 2);
1613        assert_eq!(tags.iter().find(|t| t.key == "Name").unwrap().value, "b");
1614    }
1615
1616    #[test]
1617    fn remove_tags_by_key_and_by_key_value() {
1618        let mut s = Ec2State::new("123456789012", "us-east-1");
1619        s.upsert_tags(
1620            "i-1",
1621            &[tag("Name", "x"), tag("env", "prod"), tag("team", "a")],
1622        );
1623        // key-only removal
1624        s.remove_tags("i-1", &[("Name".to_string(), None)]);
1625        // key+value removal that does NOT match -> kept
1626        s.remove_tags("i-1", &[("env".to_string(), Some("dev".to_string()))]);
1627        // key+value removal that matches -> removed
1628        s.remove_tags("i-1", &[("team".to_string(), Some("a".to_string()))]);
1629        let tags = s.tags_for("i-1");
1630        assert_eq!(tags.len(), 1);
1631        assert_eq!(tags[0].key, "env");
1632    }
1633
1634    #[test]
1635    fn empty_tag_set_drops_resource_entry() {
1636        let mut s = Ec2State::new("123456789012", "us-east-1");
1637        s.upsert_tags("sg-1", &[tag("Name", "x")]);
1638        s.remove_tags("sg-1", &[("Name".to_string(), None)]);
1639        assert!(!s.tags.contains_key("sg-1"));
1640    }
1641
1642    fn sample_instance() -> Instance {
1643        Instance {
1644            instance_id: "i-1".to_string(),
1645            image_id: "ami-1".to_string(),
1646            instance_type: "t3.micro".to_string(),
1647            state_code: 16,
1648            state_name: "running".to_string(),
1649            private_ip: "10.0.0.1".to_string(),
1650            public_ip: Some("52.0.0.1".to_string()),
1651            subnet_id: Some("subnet-1".to_string()),
1652            vpc_id: Some("vpc-1".to_string()),
1653            key_name: None,
1654            security_group_ids: vec!["sg-1".to_string()],
1655            reservation_id: "r-1".to_string(),
1656            ami_launch_index: 0,
1657            monitoring: false,
1658            az: "us-east-1a".to_string(),
1659            launch_time: "2024-01-01T00:00:00.000Z".to_string(),
1660            container_id: Some("abc".to_string()),
1661            disable_api_termination: true,
1662            disable_api_stop: true,
1663            source_dest_check: false,
1664            ebs_optimized: true,
1665            instance_initiated_shutdown_behavior: "terminate".to_string(),
1666            user_data: Some("ZWNobyBoaQ==".to_string()),
1667            metadata_options: MetadataOptions {
1668                http_tokens: "required".to_string(),
1669                ..MetadataOptions::default()
1670            },
1671            cpu_options: Some(CpuOptions {
1672                core_count: 4,
1673                threads_per_core: 2,
1674            }),
1675            bandwidth_weighting: Some("vpc-1".to_string()),
1676            maintenance_options: MaintenanceOptions::default(),
1677            placement_tenancy: Some("dedicated".to_string()),
1678            placement_affinity: None,
1679            placement_group_name: Some("cluster-1".to_string()),
1680            private_dns_hostname_type: Some("resource-name".to_string()),
1681            enable_resource_name_dns_a_record: true,
1682            enable_resource_name_dns_aaaa_record: false,
1683        }
1684    }
1685
1686    #[test]
1687    fn instance_attributes_round_trip_through_serde() {
1688        let inst = sample_instance();
1689        let json = serde_json::to_string(&inst).unwrap();
1690        let back: Instance = serde_json::from_str(&json).unwrap();
1691        assert!(back.disable_api_termination);
1692        assert!(back.disable_api_stop);
1693        assert!(!back.source_dest_check);
1694        assert!(back.ebs_optimized);
1695        assert_eq!(back.instance_initiated_shutdown_behavior, "terminate");
1696        assert_eq!(back.user_data.as_deref(), Some("ZWNobyBoaQ=="));
1697        assert_eq!(back.metadata_options.http_tokens, "required");
1698        assert_eq!(back.cpu_options.as_ref().unwrap().core_count, 4);
1699        assert_eq!(back.bandwidth_weighting.as_deref(), Some("vpc-1"));
1700        assert_eq!(back.placement_tenancy.as_deref(), Some("dedicated"));
1701        assert_eq!(back.placement_group_name.as_deref(), Some("cluster-1"));
1702    }
1703
1704    #[test]
1705    fn instance_attribute_defaults_load_from_legacy_snapshot() {
1706        // A snapshot written before the attribute fields existed (only the
1707        // pre-existing members) must deserialize, with AWS defaults filled in.
1708        let legacy = r#"{
1709            "instance_id":"i-1","image_id":"ami-1","instance_type":"t3.micro",
1710            "state_code":16,"state_name":"running","private_ip":"10.0.0.1",
1711            "public_ip":null,"subnet_id":null,"vpc_id":null,"key_name":null,
1712            "reservation_id":"r-1","ami_launch_index":0,"monitoring":false,
1713            "az":"us-east-1a","launch_time":"2024-01-01T00:00:00.000Z"
1714        }"#;
1715        let inst: Instance = serde_json::from_str(legacy).unwrap();
1716        assert!(!inst.disable_api_termination);
1717        assert!(inst.source_dest_check, "sourceDestCheck defaults to true");
1718        assert_eq!(inst.instance_initiated_shutdown_behavior, "stop");
1719        assert_eq!(inst.metadata_options.http_tokens, "optional");
1720        assert!(inst.cpu_options.is_none());
1721    }
1722}