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