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