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
400/// Account/region-level IMDS defaults (ModifyInstanceMetadataDefaults). Each
401/// field is `None` until explicitly set; a set of `no-preference` clears it
402/// back to `None` (AWS drops the default rather than storing the sentinel).
403#[derive(Clone, Debug, Default, Serialize, Deserialize)]
404pub struct InstanceMetadataDefaults {
405    #[serde(default)]
406    pub http_tokens: Option<String>,
407    #[serde(default)]
408    pub http_endpoint: Option<String>,
409    #[serde(default)]
410    pub http_put_response_hop_limit: Option<i64>,
411    #[serde(default)]
412    pub instance_metadata_tags: Option<String>,
413    #[serde(default)]
414    pub http_tokens_enforced: Option<String>,
415}
416
417impl Default for MetadataOptions {
418    fn default() -> Self {
419        Self {
420            http_tokens: "optional".to_string(),
421            http_endpoint: "enabled".to_string(),
422            http_put_response_hop_limit: 1,
423            http_protocol_ipv6: "disabled".to_string(),
424            instance_metadata_tags: "disabled".to_string(),
425        }
426    }
427}
428
429/// CPU options round-tripped by `ModifyInstanceCpuOptions`.
430#[derive(Clone, Debug, Serialize, Deserialize)]
431pub struct CpuOptions {
432    pub core_count: i64,
433    pub threads_per_core: i64,
434}
435
436/// Maintenance options round-tripped by `ModifyInstanceMaintenanceOptions`.
437#[derive(Clone, Debug, Serialize, Deserialize)]
438pub struct MaintenanceOptions {
439    /// `disabled` | `default`.
440    pub auto_recovery: String,
441    /// `disabled` | `default`.
442    pub reboot_migration: String,
443}
444
445impl Default for MaintenanceOptions {
446    fn default() -> Self {
447        Self {
448            auto_recovery: "default".to_string(),
449            reboot_migration: "default".to_string(),
450        }
451    }
452}
453
454/// An EBS volume attachment.
455#[derive(Clone, Debug, Serialize, Deserialize)]
456pub struct VolumeAttachment {
457    pub volume_id: String,
458    pub instance_id: String,
459    pub device: String,
460    /// `attaching` | `attached` | `detaching` | `detached`.
461    pub status: String,
462    pub delete_on_termination: bool,
463}
464
465/// An EBS volume.
466#[derive(Clone, Debug, Serialize, Deserialize)]
467pub struct Volume {
468    pub volume_id: String,
469    pub size: i64,
470    pub snapshot_id: Option<String>,
471    pub availability_zone: String,
472    /// `creating` | `available` | `in-use` | `deleting` | `deleted`.
473    pub state: String,
474    pub volume_type: String,
475    pub iops: Option<i64>,
476    pub throughput: Option<i64>,
477    pub encrypted: bool,
478    pub kms_key_id: Option<String>,
479    pub multi_attach_enabled: bool,
480    pub auto_enable_io: bool,
481    #[serde(default)]
482    pub attachments: Vec<VolumeAttachment>,
483    #[serde(default)]
484    pub in_recycle_bin: bool,
485}
486
487/// An EBS snapshot.
488#[derive(Clone, Debug, Serialize, Deserialize)]
489pub struct Snapshot {
490    pub snapshot_id: String,
491    pub volume_id: String,
492    /// `pending` | `completed` | `error`.
493    pub state: String,
494    pub volume_size: i64,
495    pub description: String,
496    pub encrypted: bool,
497    /// `standard` | `archive`.
498    pub storage_tier: String,
499    #[serde(default)]
500    pub in_recycle_bin: bool,
501    #[serde(default)]
502    pub locked: bool,
503    pub lock_mode: Option<String>,
504    /// createVolumePermission grantees: account IDs, or the literal `all` for
505    /// the public group. Managed by ModifySnapshotAttribute.
506    #[serde(default)]
507    pub create_volume_permissions: Vec<String>,
508}
509
510/// An AMI (machine image).
511#[derive(Clone, Debug, Serialize, Deserialize)]
512pub struct Image {
513    pub image_id: String,
514    pub name: String,
515    pub description: String,
516    /// `pending` | `available` | `disabled` | `deregistered`.
517    pub state: String,
518    pub architecture: String,
519    pub public: bool,
520    pub source_instance_id: Option<String>,
521    #[serde(default)]
522    pub in_recycle_bin: bool,
523    pub deprecation_time: Option<String>,
524    #[serde(default)]
525    pub deregistration_protection: bool,
526    /// `launchPermission` — AWS account ids the AMI is explicitly shared with
527    /// (cross-account share via `ModifyImageAttribute`).
528    #[serde(default)]
529    pub launch_permission_users: Vec<String>,
530    /// `launchPermission` groups — only `all` is valid in AWS (public share).
531    #[serde(default)]
532    pub launch_permission_groups: Vec<String>,
533    /// `bootMode` — `legacy-bios` | `uefi` | `uefi-preferred`. `None` reports
534    /// the default `uefi`; settable via `ModifyImageAttribute`.
535    #[serde(default)]
536    pub boot_mode: Option<String>,
537    /// `imageOwnerId` — the AWS account that owns the AMI. `None` reports the
538    /// requesting account (a user-registered AMI is owned by its creator); the
539    /// seeded public AMIs set this to the real Amazon/Canonical/etc. owner so
540    /// `aws_ami` data sources filtering by `owners`/`owner-id` resolve them.
541    #[serde(default)]
542    pub owner_id: Option<String>,
543    /// `imageOwnerAlias` — `amazon` | `aws-marketplace` | `self` etc. Set on the
544    /// seeded public AMIs so `owner-alias` filters and `owners = ["amazon"]`
545    /// resolve them; `None` for user-registered AMIs.
546    #[serde(default)]
547    pub owner_alias: Option<String>,
548    /// `creationDate`. `None` reports the fixed fallback; the seeded catalogue
549    /// sets distinct dates so Terraform's `most_recent = true` ordering is
550    /// deterministic.
551    #[serde(default)]
552    pub creation_date: Option<String>,
553    /// `rootDeviceName` (e.g. `/dev/xvda` for Linux, `/dev/sda1` for Windows).
554    /// `None` reports the Linux default.
555    #[serde(default)]
556    pub root_device_name: Option<String>,
557    /// `platformDetails` / Windows `platform`. `None` = Linux/UNIX.
558    #[serde(default)]
559    pub platform: Option<String>,
560}
561
562/// A network ACL entry (rule).
563#[derive(Clone, Debug, Serialize, Deserialize)]
564pub struct NetworkAclEntry {
565    pub rule_number: i64,
566    pub protocol: String,
567    /// `allow` | `deny`.
568    pub rule_action: String,
569    pub egress: bool,
570    pub cidr_block: Option<String>,
571    pub ipv6_cidr_block: Option<String>,
572    /// TCP/UDP port range (from, to).
573    pub port_range: Option<(i64, i64)>,
574    /// ICMP (type, code).
575    pub icmp_type_code: Option<(i64, i64)>,
576}
577
578/// A network ACL <-> subnet association.
579#[derive(Clone, Debug, Serialize, Deserialize)]
580pub struct NetworkAclAssoc {
581    pub association_id: String,
582    pub subnet_id: String,
583}
584
585/// A network ACL.
586#[derive(Clone, Debug, Serialize, Deserialize)]
587pub struct NetworkAcl {
588    pub network_acl_id: String,
589    pub vpc_id: String,
590    pub is_default: bool,
591    #[serde(default)]
592    pub entries: Vec<NetworkAclEntry>,
593    #[serde(default)]
594    pub associations: Vec<NetworkAclAssoc>,
595}
596
597/// A VPC peering connection.
598#[derive(Clone, Debug, Serialize, Deserialize)]
599pub struct VpcPeering {
600    pub id: String,
601    pub requester_vpc_id: String,
602    pub accepter_vpc_id: String,
603    /// `pending-acceptance` | `active` | `rejected` | `deleted`.
604    pub status: String,
605    /// Requester-side DNS-resolution-from-remote-VPC option.
606    #[serde(default)]
607    pub requester_allow_dns: bool,
608    /// Accepter-side DNS-resolution-from-remote-VPC option.
609    #[serde(default)]
610    pub accepter_allow_dns: bool,
611}
612
613/// A VPC endpoint.
614#[derive(Clone, Debug, Serialize, Deserialize)]
615pub struct VpcEndpoint {
616    pub id: String,
617    /// `Interface` | `Gateway` | `GatewayLoadBalancer` | ...
618    pub endpoint_type: String,
619    pub vpc_id: String,
620    pub service_name: String,
621    pub state: String,
622    #[serde(default)]
623    pub subnet_ids: Vec<String>,
624    #[serde(default)]
625    pub route_table_ids: Vec<String>,
626    #[serde(default)]
627    pub private_dns_enabled: bool,
628    /// Security groups attached to an Interface endpoint's ENIs.
629    #[serde(default)]
630    pub security_group_ids: Vec<String>,
631}
632
633/// A VPC endpoint service configuration (PrivateLink provider side).
634#[derive(Clone, Debug, Serialize, Deserialize)]
635pub struct EndpointService {
636    pub service_id: String,
637    pub service_name: String,
638    pub state: String,
639    pub acceptance_required: bool,
640    pub payer_responsibility: String,
641    #[serde(default)]
642    pub nlb_arns: Vec<String>,
643}
644
645/// A VPC endpoint connection notification.
646#[derive(Clone, Debug, Serialize, Deserialize)]
647pub struct ConnectionNotification {
648    pub id: String,
649    pub arn: String,
650    pub service_id: Option<String>,
651    #[serde(default)]
652    pub events: Vec<String>,
653}
654
655/// A VPC flow log.
656#[derive(Clone, Debug, Serialize, Deserialize)]
657pub struct FlowLog {
658    pub id: String,
659    pub resource_id: String,
660    pub traffic_type: String,
661    pub log_destination_type: String,
662    pub log_group_name: Option<String>,
663    /// Destination ARN for `s3` / `kinesis-data-firehose` deliveries.
664    pub log_destination: Option<String>,
665    /// IAM role ARN used to deliver logs to CloudWatch Logs
666    /// (`iam_role_arn` on the Terraform resource).
667    #[serde(default)]
668    pub deliver_logs_permission_arn: Option<String>,
669    /// Max log aggregation interval in seconds. AWS accepts 60 or 600 and
670    /// defaults to 600 when unspecified.
671    #[serde(default = "default_max_aggregation_interval")]
672    pub max_aggregation_interval: i64,
673}
674
675fn default_max_aggregation_interval() -> i64 {
676    600
677}
678
679/// A launch template (versions tracked as monotonic counters).
680#[derive(Clone, Debug, Serialize, Deserialize)]
681pub struct LaunchTemplate {
682    pub id: String,
683    pub name: String,
684    pub default_version: i64,
685    pub latest_version: i64,
686}
687
688/// A Spot instance request.
689#[derive(Clone, Debug, Serialize, Deserialize)]
690pub struct SpotRequest {
691    pub id: String,
692    /// `open` | `active` | `cancelled` | `closed`.
693    pub state: String,
694    pub request_type: String,
695    pub spot_price: String,
696}
697
698/// A Spot fleet request.
699#[derive(Clone, Debug, Serialize, Deserialize)]
700pub struct SpotFleet {
701    pub id: String,
702    pub state: String,
703}
704
705/// An EC2 fleet.
706#[derive(Clone, Debug, Serialize, Deserialize)]
707pub struct Fleet {
708    pub id: String,
709    pub state: String,
710    pub fleet_type: String,
711}
712
713/// An on-demand capacity reservation.
714#[derive(Clone, Debug, Serialize, Deserialize)]
715pub struct CapacityReservation {
716    pub id: String,
717    pub instance_type: String,
718    pub instance_platform: String,
719    pub availability_zone: String,
720    pub tenancy: String,
721    pub total_instance_count: i64,
722    pub available_instance_count: i64,
723    /// `active` | `expired` | `cancelled` | `pending` | `failed`.
724    pub state: String,
725    pub end_date_type: String,
726    pub instance_match_criteria: String,
727}
728
729/// A Reserved Instance purchase.
730#[derive(Clone, Debug, Serialize, Deserialize)]
731pub struct ReservedInstances {
732    pub id: String,
733    pub instance_type: String,
734    pub availability_zone: String,
735    pub instance_count: i64,
736    pub product_description: String,
737    pub state: String,
738    pub duration: i64,
739    pub fixed_price: String,
740    pub usage_price: String,
741}
742
743/// A Reserved Instances listing in the Reserved Instance Marketplace.
744#[derive(Clone, Debug, Serialize, Deserialize)]
745pub struct ReservedInstancesListing {
746    pub listing_id: String,
747    pub reserved_instances_id: String,
748    pub instance_count: i64,
749    pub client_token: String,
750    /// `active` | `cancelled` | `closed`.
751    pub status: String,
752    pub status_message: String,
753}
754
755/// A Reserved Instances modification request.
756#[derive(Clone, Debug, Serialize, Deserialize)]
757pub struct ReservedInstancesModification {
758    pub modification_id: String,
759    pub reserved_instances_ids: Vec<String>,
760    /// `processing` | `fulfilled` | `failed`.
761    pub status: String,
762    pub client_token: String,
763}
764
765/// A Dedicated Host.
766#[derive(Clone, Debug, Serialize, Deserialize)]
767pub struct DedicatedHost {
768    pub id: String,
769    pub auto_placement: String,
770    pub availability_zone: String,
771    pub instance_type: String,
772    pub state: String,
773    pub host_recovery: String,
774    pub host_maintenance: String,
775}
776
777/// A Transit Gateway.
778#[derive(Clone, Debug, Serialize, Deserialize)]
779pub struct TransitGateway {
780    pub id: String,
781    pub description: String,
782    /// `pending` | `available` | `modifying` | `deleting` | `deleted`.
783    #[serde(default = "tgw_default_state")]
784    pub state: String,
785}
786
787fn tgw_default_state() -> String {
788    "available".to_string()
789}
790
791/// A Transit Gateway attachment (VPC and others).
792#[derive(Clone, Debug, Serialize, Deserialize)]
793pub struct TgwAttachment {
794    pub id: String,
795    pub tgw_id: String,
796    pub resource_id: String,
797    pub resource_type: String,
798    #[serde(default)]
799    pub subnet_ids: Vec<String>,
800    pub state: String,
801}
802
803/// A Transit Gateway route table.
804#[derive(Clone, Debug, Serialize, Deserialize)]
805pub struct TgwRouteTable {
806    pub id: String,
807    pub tgw_id: String,
808}
809
810/// A static Transit Gateway route within a route table.
811#[derive(Clone, Debug, Serialize, Deserialize)]
812pub struct TgwRoute {
813    pub cidr: String,
814    pub attachment_id: String,
815    pub state: String,
816}
817
818/// A Transit Gateway multicast domain.
819#[derive(Clone, Debug, Serialize, Deserialize)]
820pub struct TgwMulticastDomain {
821    pub id: String,
822    pub tgw_id: String,
823}
824
825/// A Transit Gateway metering policy.
826#[derive(Clone, Debug, Serialize, Deserialize)]
827pub struct TgwMeteringPolicy {
828    pub id: String,
829    pub tgw_id: String,
830}
831
832/// A customer gateway (on-prem side of a VPN).
833#[derive(Clone, Debug, Serialize, Deserialize)]
834pub struct CustomerGateway {
835    pub id: String,
836    pub state: String,
837    pub ip_address: String,
838    pub bgp_asn: String,
839}
840
841/// A virtual private gateway.
842#[derive(Clone, Debug, Serialize, Deserialize)]
843pub struct VpnGateway {
844    pub id: String,
845    pub state: String,
846    #[serde(default)]
847    pub attachments: Vec<String>,
848}
849
850/// A Site-to-Site VPN connection.
851#[derive(Clone, Debug, Serialize, Deserialize)]
852pub struct VpnConnection {
853    pub id: String,
854    pub state: String,
855    pub customer_gateway_id: String,
856    pub vpn_gateway_id: Option<String>,
857    #[serde(default)]
858    pub routes: Vec<String>,
859}
860
861/// A VPN concentrator.
862#[derive(Clone, Debug, Serialize, Deserialize)]
863pub struct VpnConcentrator {
864    pub id: String,
865    pub state: String,
866}
867
868/// An IPAM (IP Address Manager).
869#[derive(Clone, Debug, Serialize, Deserialize)]
870pub struct Ipam {
871    pub id: String,
872    pub public_scope_id: String,
873    pub private_scope_id: String,
874    pub tier: String,
875    #[serde(default)]
876    pub description: String,
877}
878
879/// An IPAM scope.
880#[derive(Clone, Debug, Serialize, Deserialize)]
881pub struct IpamScope {
882    pub id: String,
883    pub ipam_id: String,
884    /// "public" or "private".
885    #[serde(default)]
886    pub scope_type: String,
887    #[serde(default)]
888    pub description: String,
889}
890
891/// An IPAM pool.
892#[derive(Clone, Debug, Serialize, Deserialize)]
893pub struct IpamPool {
894    pub id: String,
895    pub scope_id: String,
896    pub address_family: String,
897    #[serde(default)]
898    pub description: String,
899}
900
901/// An IPAM resource discovery.
902#[derive(Clone, Debug, Serialize, Deserialize)]
903pub struct IpamResourceDiscovery {
904    pub id: String,
905    #[serde(default)]
906    pub description: String,
907}
908
909/// An IPAM policy.
910#[derive(Clone, Debug, Serialize, Deserialize)]
911pub struct IpamPolicy {
912    pub id: String,
913    pub ipam_id: String,
914}
915
916/// An IPAM prefix-list resolver.
917#[derive(Clone, Debug, Serialize, Deserialize)]
918pub struct IpamPrefixListResolver {
919    pub id: String,
920    pub ipam_id: String,
921    pub address_family: String,
922    #[serde(default)]
923    pub description: String,
924}
925
926/// An IPAM prefix-list resolver target.
927#[derive(Clone, Debug, Serialize, Deserialize)]
928pub struct IpamPrefixListResolverTarget {
929    pub id: String,
930    pub resolver_id: String,
931    pub prefix_list_id: String,
932    pub prefix_list_region: String,
933    #[serde(default)]
934    pub track_latest_version: bool,
935}
936
937/// A Verified Access instance.
938#[derive(Clone, Debug, Serialize, Deserialize)]
939pub struct VerifiedAccessInstance {
940    pub id: String,
941    pub description: String,
942    #[serde(default)]
943    pub trust_providers: Vec<String>,
944}
945
946/// A Verified Access trust provider.
947#[derive(Clone, Debug, Serialize, Deserialize)]
948pub struct VerifiedAccessTrustProvider {
949    pub id: String,
950    pub trust_provider_type: String,
951    pub policy_reference_name: String,
952    pub description: String,
953}
954
955/// A Verified Access group.
956#[derive(Clone, Debug, Serialize, Deserialize)]
957pub struct VerifiedAccessGroup {
958    pub id: String,
959    pub instance_id: String,
960    pub description: String,
961}
962
963/// A Verified Access endpoint.
964#[derive(Clone, Debug, Serialize, Deserialize)]
965pub struct VerifiedAccessEndpoint {
966    pub id: String,
967    pub group_id: String,
968    pub instance_id: String,
969    pub endpoint_type: String,
970    pub attachment_type: String,
971}
972
973/// A Network Insights reachability path.
974#[derive(Clone, Debug, Serialize, Deserialize)]
975pub struct NetworkInsightsPath {
976    pub id: String,
977    pub source: String,
978    pub destination: String,
979    pub protocol: String,
980}
981
982/// A Network Insights path analysis.
983#[derive(Clone, Debug, Serialize, Deserialize)]
984pub struct NetworkInsightsAnalysis {
985    pub id: String,
986    pub path_id: String,
987}
988
989/// A Network Insights access scope.
990#[derive(Clone, Debug, Serialize, Deserialize)]
991pub struct NetworkInsightsAccessScope {
992    pub id: String,
993}
994
995/// A Network Insights access-scope analysis.
996#[derive(Clone, Debug, Serialize, Deserialize)]
997pub struct NetworkInsightsAccessScopeAnalysis {
998    pub id: String,
999    pub scope_id: String,
1000}
1001
1002/// A carrier gateway (Wavelength).
1003#[derive(Clone, Debug, Serialize, Deserialize)]
1004pub struct CarrierGateway {
1005    pub id: String,
1006    pub vpc_id: String,
1007}
1008
1009/// An EC2 Instance Connect endpoint.
1010#[derive(Clone, Debug, Serialize, Deserialize)]
1011pub struct InstanceConnectEndpoint {
1012    pub id: String,
1013    pub subnet_id: String,
1014}
1015
1016/// A customer-owned IP (CoIP) pool.
1017#[derive(Clone, Debug, Serialize, Deserialize)]
1018pub struct CoipPool {
1019    pub id: String,
1020    pub route_table_id: String,
1021}
1022
1023/// A local-gateway route table.
1024#[derive(Clone, Debug, Serialize, Deserialize)]
1025pub struct LocalGatewayRouteTable {
1026    pub id: String,
1027    pub local_gateway_id: String,
1028    pub mode: String,
1029}
1030
1031/// A local-gateway route-table <-> VPC association.
1032#[derive(Clone, Debug, Serialize, Deserialize)]
1033pub struct LocalGatewayRouteTableVpcAssoc {
1034    pub id: String,
1035    pub route_table_id: String,
1036    pub vpc_id: String,
1037}
1038
1039/// A local-gateway virtual interface.
1040#[derive(Clone, Debug, Serialize, Deserialize)]
1041pub struct LocalGatewayVif {
1042    pub id: String,
1043    pub group_id: String,
1044    pub vlan: String,
1045    pub local_address: String,
1046    pub peer_address: String,
1047}
1048
1049/// A local-gateway virtual-interface group.
1050#[derive(Clone, Debug, Serialize, Deserialize)]
1051pub struct LocalGatewayVifGroup {
1052    pub id: String,
1053    pub local_gateway_id: String,
1054}
1055
1056/// A local-gateway route-table <-> virtual-interface-group association.
1057#[derive(Clone, Debug, Serialize, Deserialize)]
1058pub struct LocalGatewayRouteTableVifgAssoc {
1059    pub id: String,
1060    pub route_table_id: String,
1061    pub vif_group_id: String,
1062}
1063
1064/// A Client VPN endpoint.
1065#[derive(Clone, Debug, Serialize, Deserialize)]
1066pub struct ClientVpnEndpoint {
1067    pub id: String,
1068    pub description: String,
1069    pub status: String,
1070    pub server_cert_arn: String,
1071    pub transport_protocol: String,
1072    pub client_cidr: String,
1073    #[serde(default)]
1074    pub routes: Vec<String>,
1075    /// (association id, subnet id) for each associated target network.
1076    #[serde(default)]
1077    pub target_networks: Vec<(String, String)>,
1078    /// Ingress authorization rule target CIDRs.
1079    #[serde(default)]
1080    pub auth_rules: Vec<String>,
1081}
1082
1083/// A Transit Gateway peering attachment.
1084#[derive(Clone, Debug, Serialize, Deserialize)]
1085pub struct TgwPeering {
1086    pub id: String,
1087    pub tgw_id: String,
1088    pub peer_tgw_id: String,
1089    pub peer_account: String,
1090    pub peer_region: String,
1091    pub state: String,
1092}
1093
1094/// One entry in a customer-managed prefix list.
1095#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1096pub struct PrefixListEntry {
1097    pub cidr: String,
1098    #[serde(default)]
1099    pub description: Option<String>,
1100}
1101
1102/// A customer-managed prefix list (`CreateManagedPrefixList`). Versions are a
1103/// monotonic counter; each entry-mutating Modify bumps `version` and snapshots
1104/// the prior entries into `version_history` so `RestoreManagedPrefixListVersion`
1105/// and `GetManagedPrefixListEntries(TargetVersion)` round-trip.
1106#[derive(Clone, Debug, Serialize, Deserialize)]
1107pub struct ManagedPrefixList {
1108    pub prefix_list_id: String,
1109    pub prefix_list_name: String,
1110    pub address_family: String,
1111    pub max_entries: i64,
1112    pub version: i64,
1113    /// `create-complete` | `modify-complete`.
1114    pub state: String,
1115    #[serde(default)]
1116    pub entries: Vec<PrefixListEntry>,
1117    /// version -> entries snapshot at that version.
1118    #[serde(default)]
1119    pub version_history: BTreeMap<i64, Vec<PrefixListEntry>>,
1120}
1121
1122/// A weekly time range within an instance event window.
1123#[derive(Clone, Debug, Serialize, Deserialize)]
1124pub struct EventWindowTimeRange {
1125    pub start_week_day: String,
1126    pub start_hour: i64,
1127    pub end_week_day: String,
1128    pub end_hour: i64,
1129}
1130
1131/// An instance event window (`CreateInstanceEventWindow`).
1132#[derive(Clone, Debug, Serialize, Deserialize)]
1133pub struct InstanceEventWindow {
1134    pub id: String,
1135    #[serde(default)]
1136    pub name: Option<String>,
1137    #[serde(default)]
1138    pub cron_expression: Option<String>,
1139    #[serde(default)]
1140    pub time_ranges: Vec<EventWindowTimeRange>,
1141    /// `creating` | `active` | `deleting` | `deleted`.
1142    pub state: String,
1143    /// Association target — instance ids, dedicated-host ids, or tags
1144    /// (`AssociateInstanceEventWindow` / `DisassociateInstanceEventWindow`).
1145    #[serde(default)]
1146    pub assoc_instance_ids: Vec<String>,
1147    #[serde(default)]
1148    pub assoc_dedicated_host_ids: Vec<String>,
1149    #[serde(default)]
1150    pub assoc_tags: Vec<Tag>,
1151}
1152
1153/// A traffic-mirror target (`CreateTrafficMirrorTarget`).
1154#[derive(Clone, Debug, Serialize, Deserialize)]
1155pub struct TrafficMirrorTarget {
1156    pub id: String,
1157    pub network_interface_id: Option<String>,
1158    pub network_load_balancer_arn: Option<String>,
1159    pub gateway_lb_endpoint_id: Option<String>,
1160    /// `network-interface` | `network-load-balancer` | `gateway-load-balancer-endpoint`.
1161    pub target_type: String,
1162    pub description: Option<String>,
1163}
1164
1165/// A traffic-mirror filter (`CreateTrafficMirrorFilter`).
1166#[derive(Clone, Debug, Serialize, Deserialize)]
1167pub struct TrafficMirrorFilter {
1168    pub id: String,
1169    pub description: Option<String>,
1170    #[serde(default)]
1171    pub network_services: Vec<String>,
1172}
1173
1174/// A traffic-mirror filter rule (`CreateTrafficMirrorFilterRule`).
1175#[derive(Clone, Debug, Serialize, Deserialize)]
1176pub struct TrafficMirrorFilterRule {
1177    pub id: String,
1178    pub filter_id: String,
1179    pub traffic_direction: String,
1180    pub rule_number: i64,
1181    pub rule_action: String,
1182    pub protocol: Option<i64>,
1183    pub destination_cidr_block: Option<String>,
1184    pub source_cidr_block: Option<String>,
1185    /// (from, to) port ranges.
1186    pub destination_port_range: Option<(i64, i64)>,
1187    pub source_port_range: Option<(i64, i64)>,
1188    pub description: Option<String>,
1189}
1190
1191/// A traffic-mirror session (`CreateTrafficMirrorSession`).
1192#[derive(Clone, Debug, Serialize, Deserialize)]
1193pub struct TrafficMirrorSession {
1194    pub id: String,
1195    pub target_id: String,
1196    pub filter_id: String,
1197    pub network_interface_id: String,
1198    pub packet_length: Option<i64>,
1199    pub session_number: i64,
1200    pub virtual_network_id: Option<i64>,
1201    pub description: Option<String>,
1202}
1203
1204/// A route server (`CreateRouteServer`).
1205#[derive(Clone, Debug, Serialize, Deserialize)]
1206pub struct RouteServer {
1207    pub id: String,
1208    pub amazon_side_asn: i64,
1209    /// `available` (lowercase per the AWS state enum is uppercase; we store the
1210    /// wire value).
1211    pub state: String,
1212    /// `ENABLED` | `DISABLED` | `RESETTING` | ...
1213    pub persist_routes_state: String,
1214    pub persist_routes_duration: Option<i64>,
1215    pub sns_notifications_enabled: bool,
1216}
1217
1218/// A VPC encryption control (`CreateVpcEncryptionControl`).
1219#[derive(Clone, Debug, Serialize, Deserialize)]
1220pub struct VpcEncryptionControl {
1221    pub id: String,
1222    pub vpc_id: String,
1223    /// `monitor` | `enforce`.
1224    pub mode: String,
1225    /// `available` | `monitor_in_progress` | `enforce_in_progress` | ...
1226    pub state: String,
1227    /// Per-resource-type exclusion state: resource -> `enabled` | `disabled`.
1228    #[serde(default)]
1229    pub exclusions: BTreeMap<String, String>,
1230}
1231
1232/// A VPC block-public-access exclusion (`CreateVpcBlockPublicAccessExclusion`).
1233#[derive(Clone, Debug, Serialize, Deserialize)]
1234pub struct VpcBpaExclusion {
1235    pub id: String,
1236    /// `allow-bidirectional` | `allow-egress`.
1237    pub internet_gateway_exclusion_mode: String,
1238    pub resource_arn: Option<String>,
1239    /// `create-complete` | `update-complete` | `delete-complete`.
1240    pub state: String,
1241}
1242
1243/// An Amazon FPGA image (`CreateFpgaImage` / `CopyFpgaImage`).
1244#[derive(Clone, Debug, Serialize, Deserialize)]
1245pub struct FpgaImage {
1246    pub id: String,
1247    #[serde(default)]
1248    pub name: String,
1249    #[serde(default)]
1250    pub description: String,
1251    /// `loadPermission` users the image is shared with.
1252    #[serde(default)]
1253    pub load_permission_users: Vec<String>,
1254    /// `loadPermission` groups (`all` for public).
1255    #[serde(default)]
1256    pub load_permission_groups: Vec<String>,
1257}
1258
1259/// Per-account, per-region EC2 state. Resource families are added to this
1260/// struct as their batches land.
1261#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1262pub struct Ec2State {
1263    pub account_id: String,
1264    pub region: String,
1265    /// resource-id -> tags. Shared by every Describe* `tag:` filter.
1266    #[serde(default)]
1267    pub tags: BTreeMap<String, Vec<Tag>>,
1268    #[serde(default)]
1269    pub vpcs: BTreeMap<String, Vpc>,
1270    #[serde(default)]
1271    pub dhcp_options: BTreeMap<String, DhcpOptions>,
1272    #[serde(default)]
1273    pub subnets: BTreeMap<String, Subnet>,
1274    #[serde(default)]
1275    pub subnet_cidr_reservations: BTreeMap<String, SubnetCidrReservation>,
1276    #[serde(default)]
1277    pub security_groups: BTreeMap<String, SecurityGroup>,
1278    #[serde(default)]
1279    pub route_tables: BTreeMap<String, RouteTable>,
1280    #[serde(default)]
1281    pub internet_gateways: BTreeMap<String, InternetGateway>,
1282    #[serde(default)]
1283    pub egress_only_igws: BTreeMap<String, InternetGateway>,
1284    #[serde(default)]
1285    pub nat_gateways: BTreeMap<String, NatGateway>,
1286    /// keyed by allocation id.
1287    #[serde(default)]
1288    pub elastic_ips: BTreeMap<String, ElasticIp>,
1289    /// keyed by key name.
1290    #[serde(default)]
1291    pub key_pairs: BTreeMap<String, KeyPair>,
1292    /// keyed by group name.
1293    #[serde(default)]
1294    pub placement_groups: BTreeMap<String, PlacementGroup>,
1295    #[serde(default)]
1296    pub network_interfaces: BTreeMap<String, NetworkInterface>,
1297    /// keyed by permission id.
1298    #[serde(default)]
1299    pub eni_permissions: BTreeMap<String, NetworkInterfacePermission>,
1300    #[serde(default)]
1301    pub instances: BTreeMap<String, Instance>,
1302    #[serde(default)]
1303    pub volumes: BTreeMap<String, Volume>,
1304    /// Account-level EBS default encryption toggle.
1305    #[serde(default)]
1306    pub ebs_encryption_default: bool,
1307    /// Account-level EBS default KMS key (None = `alias/aws/ebs`).
1308    #[serde(default)]
1309    pub ebs_default_kms_key_id: Option<String>,
1310    #[serde(default)]
1311    pub snapshots: BTreeMap<String, Snapshot>,
1312    /// Account-level snapshot block-public-access state.
1313    #[serde(default)]
1314    pub snapshot_block_public_access: String,
1315    #[serde(default)]
1316    pub images: BTreeMap<String, Image>,
1317    /// Watermarks attached to AMIs: image_id -> watermark_key -> watermark_name.
1318    #[serde(default)]
1319    pub image_watermarks: BTreeMap<String, BTreeMap<String, String>>,
1320    /// Account-level image block-public-access state.
1321    #[serde(default)]
1322    pub image_block_public_access: String,
1323    /// Account-level allowed-images settings state.
1324    #[serde(default)]
1325    pub allowed_images_settings: String,
1326    /// Allowed-images `imageCriterionSet`: each criterion is its list of
1327    /// `ImageProvider`s, persisted by ReplaceImageCriteriaInAllowedImagesSettings
1328    /// and reported by GetAllowedImagesSettings.
1329    #[serde(default)]
1330    pub allowed_image_criteria: Vec<Vec<String>>,
1331    #[serde(default)]
1332    pub network_acls: BTreeMap<String, NetworkAcl>,
1333    #[serde(default)]
1334    pub vpc_peerings: BTreeMap<String, VpcPeering>,
1335    #[serde(default)]
1336    pub vpc_endpoints: BTreeMap<String, VpcEndpoint>,
1337    #[serde(default)]
1338    pub endpoint_services: BTreeMap<String, EndpointService>,
1339    #[serde(default)]
1340    pub connection_notifications: BTreeMap<String, ConnectionNotification>,
1341    #[serde(default)]
1342    pub flow_logs: BTreeMap<String, FlowLog>,
1343    #[serde(default)]
1344    pub launch_templates: BTreeMap<String, LaunchTemplate>,
1345    #[serde(default)]
1346    pub spot_requests: BTreeMap<String, SpotRequest>,
1347    #[serde(default)]
1348    pub spot_fleets: BTreeMap<String, SpotFleet>,
1349    #[serde(default)]
1350    pub fleets: BTreeMap<String, Fleet>,
1351    /// Account-level spot datafeed subscription (bucket, prefix).
1352    #[serde(default)]
1353    pub spot_datafeed: Option<(String, String)>,
1354    #[serde(default)]
1355    pub capacity_reservations: BTreeMap<String, CapacityReservation>,
1356    /// Capacity reservation fleet ids (metadata-only).
1357    #[serde(default)]
1358    pub capacity_reservation_fleets: BTreeMap<String, String>,
1359    #[serde(default)]
1360    pub reserved_instances: BTreeMap<String, ReservedInstances>,
1361    #[serde(default)]
1362    pub reserved_instances_listings: BTreeMap<String, ReservedInstancesListing>,
1363    #[serde(default)]
1364    pub reserved_instances_modifications: BTreeMap<String, ReservedInstancesModification>,
1365    #[serde(default)]
1366    pub dedicated_hosts: BTreeMap<String, DedicatedHost>,
1367    #[serde(default)]
1368    pub transit_gateways: BTreeMap<String, TransitGateway>,
1369    #[serde(default)]
1370    pub tgw_attachments: BTreeMap<String, TgwAttachment>,
1371    #[serde(default)]
1372    pub tgw_route_tables: BTreeMap<String, TgwRouteTable>,
1373    /// route-table-id -> static routes.
1374    #[serde(default)]
1375    pub tgw_routes: BTreeMap<String, Vec<TgwRoute>>,
1376    /// route-table-id -> associated attachment ids.
1377    #[serde(default)]
1378    pub tgw_rt_associations: BTreeMap<String, Vec<String>>,
1379    /// route-table-id -> propagated attachment ids.
1380    #[serde(default)]
1381    pub tgw_rt_propagations: BTreeMap<String, Vec<String>>,
1382    /// route-table-id -> prefix-list ids referenced.
1383    #[serde(default)]
1384    pub tgw_prefix_list_refs: BTreeMap<String, Vec<String>>,
1385    #[serde(default)]
1386    pub tgw_peerings: BTreeMap<String, TgwPeering>,
1387    /// connect-attachment-id -> (transport attachment id, tgw id).
1388    #[serde(default)]
1389    pub tgw_connects: BTreeMap<String, (String, String)>,
1390    /// connect-peer-id -> attachment id.
1391    #[serde(default)]
1392    pub tgw_connect_peers: BTreeMap<String, String>,
1393    /// policy-table-id -> tgw id.
1394    #[serde(default)]
1395    pub tgw_policy_tables: BTreeMap<String, String>,
1396    /// policy-table-id -> associated attachment ids.
1397    #[serde(default)]
1398    pub tgw_policy_table_associations: BTreeMap<String, Vec<String>>,
1399    /// announcement-id -> (route-table id, peering-attachment id).
1400    #[serde(default)]
1401    pub tgw_announcements: BTreeMap<String, (String, String)>,
1402    #[serde(default)]
1403    pub tgw_multicast_domains: BTreeMap<String, TgwMulticastDomain>,
1404    #[serde(default)]
1405    pub tgw_metering_policies: BTreeMap<String, TgwMeteringPolicy>,
1406    #[serde(default)]
1407    pub customer_gateways: BTreeMap<String, CustomerGateway>,
1408    #[serde(default)]
1409    pub vpn_gateways: BTreeMap<String, VpnGateway>,
1410    #[serde(default)]
1411    pub vpn_connections: BTreeMap<String, VpnConnection>,
1412    #[serde(default)]
1413    pub vpn_concentrators: BTreeMap<String, VpnConcentrator>,
1414    #[serde(default)]
1415    pub client_vpn_endpoints: BTreeMap<String, ClientVpnEndpoint>,
1416    #[serde(default)]
1417    pub ipams: BTreeMap<String, Ipam>,
1418    #[serde(default)]
1419    pub ipam_scopes: BTreeMap<String, IpamScope>,
1420    #[serde(default)]
1421    pub ipam_pools: BTreeMap<String, IpamPool>,
1422    /// pool-id -> provisioned (cidr, cidr-id).
1423    #[serde(default)]
1424    pub ipam_pool_cidrs: BTreeMap<String, Vec<(String, String)>>,
1425    /// pool-id -> allocations (cidr, allocation-id).
1426    #[serde(default)]
1427    pub ipam_pool_allocations: BTreeMap<String, Vec<(String, String)>>,
1428    #[serde(default)]
1429    pub ipam_resource_discoveries: BTreeMap<String, IpamResourceDiscovery>,
1430    /// association-id -> (discovery-id, ipam-id).
1431    #[serde(default)]
1432    pub ipam_rd_associations: BTreeMap<String, (String, String)>,
1433    /// asn -> associated cidr.
1434    #[serde(default)]
1435    pub ipam_byoasns: BTreeMap<String, String>,
1436    /// external-token-id -> ipam-id.
1437    #[serde(default)]
1438    pub ipam_ext_tokens: BTreeMap<String, String>,
1439    #[serde(default)]
1440    pub ipam_policies: BTreeMap<String, IpamPolicy>,
1441    #[serde(default)]
1442    pub ipam_pl_resolvers: BTreeMap<String, IpamPrefixListResolver>,
1443    #[serde(default)]
1444    pub ipam_pl_resolver_targets: BTreeMap<String, IpamPrefixListResolverTarget>,
1445    /// policy-id -> (locale, resource-type) allocation-rule documents.
1446    #[serde(default)]
1447    pub ipam_policy_alloc_rules: BTreeMap<String, Vec<(String, String)>>,
1448    /// The single enabled IPAM policy id, if any.
1449    #[serde(default)]
1450    pub ipam_enabled_policy: Option<String>,
1451    #[serde(default)]
1452    pub va_instances: BTreeMap<String, VerifiedAccessInstance>,
1453    #[serde(default)]
1454    pub va_trust_providers: BTreeMap<String, VerifiedAccessTrustProvider>,
1455    #[serde(default)]
1456    pub va_groups: BTreeMap<String, VerifiedAccessGroup>,
1457    #[serde(default)]
1458    pub va_endpoints: BTreeMap<String, VerifiedAccessEndpoint>,
1459    /// group-id -> policy document.
1460    #[serde(default)]
1461    pub va_group_policies: BTreeMap<String, String>,
1462    /// endpoint-id -> policy document.
1463    #[serde(default)]
1464    pub va_endpoint_policies: BTreeMap<String, String>,
1465    #[serde(default)]
1466    pub ni_paths: BTreeMap<String, NetworkInsightsPath>,
1467    #[serde(default)]
1468    pub ni_analyses: BTreeMap<String, NetworkInsightsAnalysis>,
1469    #[serde(default)]
1470    pub ni_access_scopes: BTreeMap<String, NetworkInsightsAccessScope>,
1471    #[serde(default)]
1472    pub ni_scope_analyses: BTreeMap<String, NetworkInsightsAccessScopeAnalysis>,
1473    #[serde(default)]
1474    pub carrier_gateways: BTreeMap<String, CarrierGateway>,
1475    #[serde(default)]
1476    pub coip_pools: BTreeMap<String, CoipPool>,
1477    /// coip-pool-id -> CIDRs.
1478    #[serde(default)]
1479    pub coip_pool_cidrs: BTreeMap<String, Vec<String>>,
1480    #[serde(default)]
1481    pub lg_route_tables: BTreeMap<String, LocalGatewayRouteTable>,
1482    /// route-table-id -> destination CIDRs.
1483    #[serde(default)]
1484    pub lg_routes: BTreeMap<String, Vec<String>>,
1485    #[serde(default)]
1486    pub lg_rt_vpc_assocs: BTreeMap<String, LocalGatewayRouteTableVpcAssoc>,
1487    #[serde(default)]
1488    pub lg_virtual_interfaces: BTreeMap<String, LocalGatewayVif>,
1489    #[serde(default)]
1490    pub lg_vif_groups: BTreeMap<String, LocalGatewayVifGroup>,
1491    #[serde(default)]
1492    pub lg_rt_vifg_assocs: BTreeMap<String, LocalGatewayRouteTableVifgAssoc>,
1493    #[serde(default)]
1494    pub instance_connect_endpoints: BTreeMap<String, InstanceConnectEndpoint>,
1495    /// Image ids with fast-launch enabled.
1496    #[serde(default)]
1497    pub fast_launch_images: std::collections::HashSet<String>,
1498    #[serde(default)]
1499    pub serial_console_access: bool,
1500    // ---- account/region-scoped id-format settings (ModifyIdFormat) ----
1501    /// resource type -> use-long-ids (account default).
1502    #[serde(default)]
1503    pub id_format: BTreeMap<String, bool>,
1504    /// principal ARN -> (resource type -> use-long-ids).
1505    #[serde(default)]
1506    pub identity_id_format: BTreeMap<String, BTreeMap<String, bool>>,
1507    /// burstable instance family -> CpuCredits (`standard` | `unlimited`),
1508    /// set by ModifyDefaultCreditSpecification, read by GetDefaultCreditSpecification.
1509    #[serde(default)]
1510    pub default_credit_specs: BTreeMap<String, String>,
1511    /// instance id -> CpuCredits (`standard` | `unlimited`), set by
1512    /// ModifyInstanceCreditSpecification, read by DescribeInstanceCreditSpecifications.
1513    #[serde(default)]
1514    pub instance_credit_specs: BTreeMap<String, String>,
1515    /// Account/region IMDS defaults (ModifyInstanceMetadataDefaults /
1516    /// GetInstanceMetadataDefaults). `None` reports the AWS defaults.
1517    #[serde(default)]
1518    pub instance_metadata_defaults: Option<InstanceMetadataDefaults>,
1519    /// Instance-tag keys surfaced in instance-state-change events
1520    /// (Register/Describe InstanceEventNotificationAttributes).
1521    #[serde(default)]
1522    pub event_notification_tag_keys: Vec<String>,
1523    /// When true, all instance tags are included in event notifications
1524    /// (`IncludeAllTagsOfInstance`).
1525    #[serde(default)]
1526    pub event_notification_include_all_tags: bool,
1527    /// VPC block-public-access `InternetGatewayBlockMode` (account/region
1528    /// singleton). `None` reports the default `off`.
1529    #[serde(default)]
1530    pub vpc_bpa_internet_gateway_block_mode: Option<String>,
1531    /// Managed-resource `DefaultVisibility` (`hidden` | `visible`). `None`
1532    /// reports the default `visible`.
1533    #[serde(default)]
1534    pub managed_resource_default_visibility: Option<String>,
1535    /// Availability-zone group -> opt-in status (`opted-in` | `not-opted-in`),
1536    /// set by ModifyAvailabilityZoneGroup, reflected in DescribeAvailabilityZones.
1537    #[serde(default)]
1538    pub az_group_optin: BTreeMap<String, String>,
1539    #[serde(default)]
1540    pub managed_prefix_lists: BTreeMap<String, ManagedPrefixList>,
1541    #[serde(default)]
1542    pub instance_event_windows: BTreeMap<String, InstanceEventWindow>,
1543    #[serde(default)]
1544    pub traffic_mirror_targets: BTreeMap<String, TrafficMirrorTarget>,
1545    #[serde(default)]
1546    pub traffic_mirror_filters: BTreeMap<String, TrafficMirrorFilter>,
1547    #[serde(default)]
1548    pub traffic_mirror_filter_rules: BTreeMap<String, TrafficMirrorFilterRule>,
1549    #[serde(default)]
1550    pub traffic_mirror_sessions: BTreeMap<String, TrafficMirrorSession>,
1551    #[serde(default)]
1552    pub route_servers: BTreeMap<String, RouteServer>,
1553    #[serde(default)]
1554    pub vpc_encryption_controls: BTreeMap<String, VpcEncryptionControl>,
1555    #[serde(default)]
1556    pub vpc_bpa_exclusions: BTreeMap<String, VpcBpaExclusion>,
1557    #[serde(default)]
1558    pub fpga_images: BTreeMap<String, FpgaImage>,
1559    /// IPAM pool allocation id -> description (ModifyIpamPoolAllocation), read
1560    /// back by DescribeIpamPoolAllocations.
1561    #[serde(default)]
1562    pub ipam_allocation_descriptions: BTreeMap<String, String>,
1563}
1564
1565impl Ec2State {
1566    pub fn new(account_id: &str, region: &str) -> Self {
1567        let mut state = Self {
1568            account_id: account_id.to_string(),
1569            region: region.to_string(),
1570            ..Default::default()
1571        };
1572        // Seed the default VPC topology (VPC, IGW, subnets, route table,
1573        // security group, NACL) the way every AWS account+region ships one, so
1574        // callers that never touch the VPC APIs still launch into a real,
1575        // isolatable network. Ids are deterministic, so the throwaway empty
1576        // states the read paths build report the same ids as this one.
1577        crate::defaults::bootstrap_default_network(&mut state);
1578        // Seed the public AMI catalogue (Amazon Linux, Ubuntu, Windows) so
1579        // `aws_ami` data sources resolve, matching how every real account sees
1580        // Amazon/Canonical-owned public images.
1581        crate::defaults::seed_public_images(&mut state);
1582        state
1583    }
1584
1585    /// Idempotently (re)seed the public AMI catalogue into this account. Used on
1586    /// snapshot restore so accounts persisted by a binary that predated the
1587    /// catalogue (#1964) still get it after an upgrade+restart — without it,
1588    /// `aws_ami { owners=["amazon"] }` returns empty for legacy accounts. Seeds
1589    /// have deterministic ids, so re-seeding an already-seeded account is a no-op.
1590    pub fn ensure_public_images_seeded(&mut self) {
1591        crate::defaults::seed_public_images(self);
1592    }
1593
1594    /// Replace the tag set for `resource_id` with `tags` merged over any
1595    /// existing tags (CreateTags is upsert-by-key, matching AWS).
1596    pub fn upsert_tags(&mut self, resource_id: &str, new_tags: &[Tag]) {
1597        let entry = self.tags.entry(resource_id.to_string()).or_default();
1598        for t in new_tags {
1599            if let Some(existing) = entry.iter_mut().find(|e| e.key == t.key) {
1600                existing.value = t.value.clone();
1601            } else {
1602                entry.push(t.clone());
1603            }
1604        }
1605    }
1606
1607    /// Remove tags for `resource_id`. When a tag's value is `None`, the key is
1608    /// removed regardless of value; when `Some`, only a key+value match is
1609    /// removed (AWS DeleteTags semantics).
1610    pub fn remove_tags(&mut self, resource_id: &str, to_remove: &[(String, Option<String>)]) {
1611        if let Some(entry) = self.tags.get_mut(resource_id) {
1612            for (key, value) in to_remove {
1613                entry.retain(|e| {
1614                    if &e.key != key {
1615                        return true;
1616                    }
1617                    match value {
1618                        Some(v) => &e.value != v,
1619                        None => false,
1620                    }
1621                });
1622            }
1623            if entry.is_empty() {
1624                self.tags.remove(resource_id);
1625            }
1626        }
1627    }
1628
1629    /// Tags for `resource_id`, or an empty slice when none.
1630    pub fn tags_for(&self, resource_id: &str) -> &[Tag] {
1631        self.tags.get(resource_id).map(Vec::as_slice).unwrap_or(&[])
1632    }
1633}
1634
1635#[cfg(test)]
1636mod tests {
1637    use super::*;
1638
1639    fn tag(k: &str, v: &str) -> Tag {
1640        Tag {
1641            key: k.to_string(),
1642            value: v.to_string(),
1643        }
1644    }
1645
1646    #[test]
1647    fn upsert_tags_inserts_then_overwrites_by_key() {
1648        let mut s = Ec2State::new("123456789012", "us-east-1");
1649        s.upsert_tags("vpc-1", &[tag("Name", "a"), tag("env", "dev")]);
1650        s.upsert_tags("vpc-1", &[tag("Name", "b")]);
1651        let tags = s.tags_for("vpc-1");
1652        assert_eq!(tags.len(), 2);
1653        assert_eq!(tags.iter().find(|t| t.key == "Name").unwrap().value, "b");
1654    }
1655
1656    #[test]
1657    fn remove_tags_by_key_and_by_key_value() {
1658        let mut s = Ec2State::new("123456789012", "us-east-1");
1659        s.upsert_tags(
1660            "i-1",
1661            &[tag("Name", "x"), tag("env", "prod"), tag("team", "a")],
1662        );
1663        // key-only removal
1664        s.remove_tags("i-1", &[("Name".to_string(), None)]);
1665        // key+value removal that does NOT match -> kept
1666        s.remove_tags("i-1", &[("env".to_string(), Some("dev".to_string()))]);
1667        // key+value removal that matches -> removed
1668        s.remove_tags("i-1", &[("team".to_string(), Some("a".to_string()))]);
1669        let tags = s.tags_for("i-1");
1670        assert_eq!(tags.len(), 1);
1671        assert_eq!(tags[0].key, "env");
1672    }
1673
1674    #[test]
1675    fn empty_tag_set_drops_resource_entry() {
1676        let mut s = Ec2State::new("123456789012", "us-east-1");
1677        s.upsert_tags("sg-1", &[tag("Name", "x")]);
1678        s.remove_tags("sg-1", &[("Name".to_string(), None)]);
1679        assert!(!s.tags.contains_key("sg-1"));
1680    }
1681
1682    fn sample_instance() -> Instance {
1683        Instance {
1684            instance_id: "i-1".to_string(),
1685            image_id: "ami-1".to_string(),
1686            instance_type: "t3.micro".to_string(),
1687            state_code: 16,
1688            state_name: "running".to_string(),
1689            private_ip: "10.0.0.1".to_string(),
1690            public_ip: Some("52.0.0.1".to_string()),
1691            subnet_id: Some("subnet-1".to_string()),
1692            vpc_id: Some("vpc-1".to_string()),
1693            key_name: None,
1694            security_group_ids: vec!["sg-1".to_string()],
1695            reservation_id: "r-1".to_string(),
1696            ami_launch_index: 0,
1697            monitoring: false,
1698            az: "us-east-1a".to_string(),
1699            launch_time: "2024-01-01T00:00:00.000Z".to_string(),
1700            container_id: Some("abc".to_string()),
1701            disable_api_termination: true,
1702            disable_api_stop: true,
1703            source_dest_check: false,
1704            ebs_optimized: true,
1705            instance_initiated_shutdown_behavior: "terminate".to_string(),
1706            user_data: Some("ZWNobyBoaQ==".to_string()),
1707            metadata_options: MetadataOptions {
1708                http_tokens: "required".to_string(),
1709                ..MetadataOptions::default()
1710            },
1711            cpu_options: Some(CpuOptions {
1712                core_count: 4,
1713                threads_per_core: 2,
1714            }),
1715            bandwidth_weighting: Some("vpc-1".to_string()),
1716            maintenance_options: MaintenanceOptions::default(),
1717            placement_tenancy: Some("dedicated".to_string()),
1718            placement_affinity: None,
1719            placement_group_name: Some("cluster-1".to_string()),
1720            private_dns_hostname_type: Some("resource-name".to_string()),
1721            enable_resource_name_dns_a_record: true,
1722            enable_resource_name_dns_aaaa_record: false,
1723        }
1724    }
1725
1726    #[test]
1727    fn instance_attributes_round_trip_through_serde() {
1728        let inst = sample_instance();
1729        let json = serde_json::to_string(&inst).unwrap();
1730        let back: Instance = serde_json::from_str(&json).unwrap();
1731        assert!(back.disable_api_termination);
1732        assert!(back.disable_api_stop);
1733        assert!(!back.source_dest_check);
1734        assert!(back.ebs_optimized);
1735        assert_eq!(back.instance_initiated_shutdown_behavior, "terminate");
1736        assert_eq!(back.user_data.as_deref(), Some("ZWNobyBoaQ=="));
1737        assert_eq!(back.metadata_options.http_tokens, "required");
1738        assert_eq!(back.cpu_options.as_ref().unwrap().core_count, 4);
1739        assert_eq!(back.bandwidth_weighting.as_deref(), Some("vpc-1"));
1740        assert_eq!(back.placement_tenancy.as_deref(), Some("dedicated"));
1741        assert_eq!(back.placement_group_name.as_deref(), Some("cluster-1"));
1742    }
1743
1744    #[test]
1745    fn instance_attribute_defaults_load_from_legacy_snapshot() {
1746        // A snapshot written before the attribute fields existed (only the
1747        // pre-existing members) must deserialize, with AWS defaults filled in.
1748        let legacy = r#"{
1749            "instance_id":"i-1","image_id":"ami-1","instance_type":"t3.micro",
1750            "state_code":16,"state_name":"running","private_ip":"10.0.0.1",
1751            "public_ip":null,"subnet_id":null,"vpc_id":null,"key_name":null,
1752            "reservation_id":"r-1","ami_launch_index":0,"monitoring":false,
1753            "az":"us-east-1a","launch_time":"2024-01-01T00:00:00.000Z"
1754        }"#;
1755        let inst: Instance = serde_json::from_str(legacy).unwrap();
1756        assert!(!inst.disable_api_termination);
1757        assert!(inst.source_dest_check, "sourceDestCheck defaults to true");
1758        assert_eq!(inst.instance_initiated_shutdown_behavior, "stop");
1759        assert_eq!(inst.metadata_options.http_tokens, "optional");
1760        assert!(inst.cpu_options.is_none());
1761    }
1762}