Skip to main content

fakecloud_ssm/
state.rs

1use chrono::{DateTime, Utc};
2use parking_lot::RwLock;
3use std::collections::{BTreeMap, HashMap};
4use std::sync::Arc;
5
6#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7pub struct SsmParameter {
8    pub name: String,
9    pub value: String,
10    pub param_type: String, // String, StringList, SecureString
11    pub version: i64,
12    pub arn: String,
13    pub last_modified: DateTime<Utc>,
14    pub history: Vec<SsmParameterVersion>,
15    pub tags: HashMap<String, String>,
16    pub labels: HashMap<i64, Vec<String>>, // version -> labels
17    pub description: Option<String>,
18    pub allowed_pattern: Option<String>,
19    pub key_id: Option<String>,
20    pub data_type: String, // "text" or "aws:ec2:image"
21    pub tier: String,      // "Standard", "Advanced", "Intelligent-Tiering"
22    pub policies: Option<String>,
23}
24
25#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
26pub struct SsmParameterVersion {
27    pub value: String,
28    pub version: i64,
29    pub last_modified: DateTime<Utc>,
30    pub param_type: String,
31    pub description: Option<String>,
32    pub key_id: Option<String>,
33    pub labels: Vec<String>,
34}
35
36#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
37pub struct SsmDocument {
38    pub name: String,
39    pub content: String,
40    pub document_type: String,
41    pub document_format: String,
42    pub target_type: Option<String>,
43    pub version_name: Option<String>,
44    pub tags: HashMap<String, String>,
45    pub versions: Vec<SsmDocumentVersion>,
46    pub default_version: String,
47    pub latest_version: String,
48    pub created_date: DateTime<Utc>,
49    pub owner: String,
50    pub status: String,
51    pub permissions: HashMap<String, Vec<String>>, // permission_type -> account_ids
52    #[serde(default)]
53    pub reviews: Vec<DocumentReview>,
54}
55
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct DocumentReview {
58    pub reviewer: String,
59    pub action: String, // SendForReview / Approve / Reject
60    pub comment: Vec<DocumentReviewComment>,
61    pub created_time: DateTime<Utc>,
62    pub updated_time: DateTime<Utc>,
63}
64
65#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
66pub struct DocumentReviewComment {
67    pub comment_type: String, // Comment
68    pub content: String,
69}
70
71#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
72pub struct SsmDocumentVersion {
73    pub content: String,
74    pub document_version: String,
75    pub version_name: Option<String>,
76    pub created_date: DateTime<Utc>,
77    pub status: String,
78    pub document_format: String,
79    pub is_default_version: bool,
80}
81
82#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
83pub struct SsmCommand {
84    pub command_id: String,
85    pub document_name: String,
86    pub instance_ids: Vec<String>,
87    pub parameters: HashMap<String, Vec<String>>,
88    pub status: String,
89    pub requested_date_time: DateTime<Utc>,
90    pub comment: Option<String>,
91    pub output_s3_bucket_name: Option<String>,
92    pub output_s3_key_prefix: Option<String>,
93    pub output_s3_region: Option<String>,
94    pub timeout_seconds: Option<i64>,
95    pub service_role_arn: Option<String>,
96    pub notification_config: Option<serde_json::Value>,
97    pub targets: Vec<serde_json::Value>,
98    pub document_hash: Option<String>,
99    pub document_hash_type: Option<String>,
100}
101
102#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
103pub struct MaintenanceWindowTarget {
104    pub window_target_id: String,
105    pub window_id: String,
106    pub resource_type: String,
107    pub targets: Vec<serde_json::Value>,
108    pub name: Option<String>,
109    pub description: Option<String>,
110    pub owner_information: Option<String>,
111}
112
113#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
114pub struct MaintenanceWindowTask {
115    pub window_task_id: String,
116    pub window_id: String,
117    pub task_arn: String,
118    pub task_type: String,
119    pub targets: Vec<serde_json::Value>,
120    pub max_concurrency: Option<String>,
121    pub max_errors: Option<String>,
122    pub priority: i64,
123    pub service_role_arn: Option<String>,
124    pub name: Option<String>,
125    pub description: Option<String>,
126}
127
128#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
129pub struct MaintenanceWindow {
130    pub id: String,
131    pub name: String,
132    pub schedule: String,
133    pub duration: i64,
134    pub cutoff: i64,
135    pub allow_unassociated_targets: bool,
136    pub enabled: bool,
137    pub description: Option<String>,
138    pub tags: HashMap<String, String>,
139    pub targets: Vec<MaintenanceWindowTarget>,
140    pub tasks: Vec<MaintenanceWindowTask>,
141    pub schedule_timezone: Option<String>,
142    pub schedule_offset: Option<i64>,
143    pub start_date: Option<String>,
144    pub end_date: Option<String>,
145    pub client_token: Option<String>,
146}
147
148#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
149pub struct PatchBaseline {
150    pub id: String,
151    pub name: String,
152    pub operating_system: String,
153    pub description: Option<String>,
154    pub approval_rules: Option<serde_json::Value>,
155    pub approved_patches: Vec<String>,
156    pub rejected_patches: Vec<String>,
157    pub tags: HashMap<String, String>,
158    pub approved_patches_compliance_level: String,
159    pub rejected_patches_action: String,
160    pub global_filters: Option<serde_json::Value>,
161    pub sources: Vec<serde_json::Value>,
162    pub approved_patches_enable_non_security: bool,
163    pub available_security_updates_compliance_status: Option<String>,
164    pub client_token: Option<String>,
165}
166
167#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
168pub struct PatchGroup {
169    pub baseline_id: String,
170    pub patch_group: String,
171}
172
173#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
174pub struct SsmAssociation {
175    pub association_id: String,
176    pub name: String, // document name
177    pub targets: Vec<serde_json::Value>,
178    pub schedule_expression: Option<String>,
179    pub parameters: HashMap<String, Vec<String>>,
180    pub association_name: Option<String>,
181    pub document_version: Option<String>,
182    pub output_location: Option<serde_json::Value>,
183    pub automation_target_parameter_name: Option<String>,
184    pub max_errors: Option<String>,
185    pub max_concurrency: Option<String>,
186    pub compliance_severity: Option<String>,
187    pub sync_compliance: Option<String>,
188    pub apply_only_at_cron_interval: bool,
189    pub calendar_names: Vec<String>,
190    pub target_locations: Vec<serde_json::Value>,
191    pub schedule_offset: Option<i64>,
192    pub target_maps: Vec<serde_json::Value>,
193    pub tags: HashMap<String, String>,
194    pub status: String,
195    pub status_date: DateTime<Utc>,
196    pub overview: serde_json::Value,
197    pub created_date: DateTime<Utc>,
198    pub last_update_association_date: DateTime<Utc>,
199    pub last_execution_date: Option<DateTime<Utc>>,
200    pub instance_id: Option<String>,
201    pub versions: Vec<SsmAssociationVersion>,
202}
203
204#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
205pub struct SsmAssociationVersion {
206    pub version: i64,
207    pub name: String,
208    pub targets: Vec<serde_json::Value>,
209    pub schedule_expression: Option<String>,
210    pub parameters: HashMap<String, Vec<String>>,
211    pub document_version: Option<String>,
212    pub created_date: DateTime<Utc>,
213    pub association_name: Option<String>,
214    pub max_errors: Option<String>,
215    pub max_concurrency: Option<String>,
216    pub compliance_severity: Option<String>,
217}
218
219#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
220pub struct SsmOpsItem {
221    pub ops_item_id: String,
222    pub title: String,
223    pub description: Option<String>,
224    pub source: String,
225    pub status: String,
226    pub priority: Option<i64>,
227    pub severity: Option<String>,
228    pub category: Option<String>,
229    pub operational_data: HashMap<String, serde_json::Value>,
230    pub notifications: Vec<serde_json::Value>,
231    pub related_ops_items: Vec<serde_json::Value>,
232    pub tags: HashMap<String, String>,
233    pub created_time: DateTime<Utc>,
234    pub last_modified_time: DateTime<Utc>,
235    pub created_by: String,
236    pub last_modified_by: String,
237    pub ops_item_type: Option<String>,
238    pub planned_start_time: Option<DateTime<Utc>>,
239    pub planned_end_time: Option<DateTime<Utc>>,
240    pub actual_start_time: Option<DateTime<Utc>>,
241    pub actual_end_time: Option<DateTime<Utc>>,
242}
243
244#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
245pub struct SsmResourcePolicy {
246    pub policy_id: String,
247    pub policy_hash: String,
248    pub policy: String,
249    pub resource_arn: String,
250}
251
252#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
253pub struct SsmServiceSetting {
254    pub setting_id: String,
255    pub setting_value: String,
256    pub last_modified_date: DateTime<Utc>,
257    pub last_modified_user: String,
258    pub status: String,
259}
260
261#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
262pub struct OpsItemRelatedItem {
263    pub association_id: String,
264    pub ops_item_id: String,
265    pub association_type: String,
266    pub resource_type: String,
267    pub resource_uri: String,
268    pub created_time: DateTime<Utc>,
269    pub created_by: String,
270    pub last_modified_time: DateTime<Utc>,
271    pub last_modified_by: String,
272}
273
274#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
275pub struct OpsItemEvent {
276    pub ops_item_id: String,
277    pub event_id: String,
278    pub source: String,
279    pub detail_type: String,
280    pub created_time: DateTime<Utc>,
281    pub created_by: String,
282}
283
284#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
285pub struct OpsMetadataEntry {
286    pub ops_metadata_arn: String,
287    pub resource_id: String,
288    pub metadata: HashMap<String, serde_json::Value>,
289    pub creation_date: DateTime<Utc>,
290}
291
292#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
293pub struct AutomationExecution {
294    pub automation_execution_id: String,
295    pub document_name: String,
296    pub document_version: Option<String>,
297    pub automation_execution_status: String,
298    pub execution_start_time: DateTime<Utc>,
299    pub execution_end_time: Option<DateTime<Utc>>,
300    pub parameters: HashMap<String, Vec<String>>,
301    pub outputs: HashMap<String, Vec<String>>,
302    pub mode: String,
303    pub target: Option<String>,
304    pub targets: Vec<serde_json::Value>,
305    pub max_concurrency: Option<String>,
306    pub max_errors: Option<String>,
307    pub executed_by: String,
308    pub step_executions: Vec<AutomationStepExecution>,
309    pub automation_subtype: Option<String>,
310    pub runbooks: Vec<serde_json::Value>,
311    pub change_request_name: Option<String>,
312    pub scheduled_time: Option<DateTime<Utc>>,
313}
314
315#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
316pub struct AutomationStepExecution {
317    pub step_name: String,
318    pub action: String,
319    pub step_status: String,
320    pub execution_start_time: Option<DateTime<Utc>>,
321    pub execution_end_time: Option<DateTime<Utc>>,
322    pub inputs: HashMap<String, String>,
323    pub outputs: HashMap<String, Vec<String>>,
324    pub step_execution_id: String,
325}
326
327#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
328pub struct SsmSession {
329    pub session_id: String,
330    pub target: String,
331    pub status: String,
332    pub start_date: DateTime<Utc>,
333    pub end_date: Option<DateTime<Utc>>,
334    pub owner: String,
335    pub reason: Option<String>,
336}
337
338#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
339pub struct SsmActivation {
340    pub activation_id: String,
341    pub iam_role: String,
342    pub registration_limit: i64,
343    pub registrations_count: i64,
344    pub expiration_date: Option<DateTime<Utc>>,
345    pub description: Option<String>,
346    pub default_instance_name: Option<String>,
347    pub created_date: DateTime<Utc>,
348    pub expired: bool,
349    pub tags: HashMap<String, String>,
350}
351
352#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
353pub struct ManagedInstance {
354    pub instance_id: String,
355    pub activation_id: Option<String>,
356    pub iam_role: String,
357    pub ping_status: String,
358    pub platform_type: String,
359    pub platform_name: String,
360    pub platform_version: String,
361    pub agent_version: String,
362    pub last_ping_date_time: DateTime<Utc>,
363    pub registration_date: DateTime<Utc>,
364    pub resource_type: String,
365    pub computer_name: String,
366    pub ip_address: String,
367    pub is_latest_version: bool,
368    pub association_status: Option<String>,
369    pub source_id: Option<String>,
370    pub source_type: Option<String>,
371}
372
373#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
374pub struct ExecutionPreview {
375    pub execution_preview_id: String,
376    pub document_name: String,
377    pub status: String,
378    pub created_time: DateTime<Utc>,
379}
380
381#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
382pub struct SsmState {
383    pub account_id: String,
384    pub region: String,
385    pub parameters: BTreeMap<String, SsmParameter>, // name -> param (BTreeMap for path queries)
386    pub documents: BTreeMap<String, SsmDocument>,
387    pub commands: Vec<SsmCommand>,
388    pub maintenance_windows: HashMap<String, MaintenanceWindow>,
389    pub patch_baselines: HashMap<String, PatchBaseline>,
390    pub patch_groups: Vec<PatchGroup>,
391    pub associations: HashMap<String, SsmAssociation>,
392    pub ops_items: HashMap<String, SsmOpsItem>,
393    pub resource_policies: Vec<SsmResourcePolicy>,
394    pub service_settings: HashMap<String, SsmServiceSetting>,
395    pub default_patch_baseline_id: Option<String>,
396    pub ops_item_counter: u64,
397    pub maintenance_window_executions: Vec<MaintenanceWindowExecution>,
398    pub inventory_entries: HashMap<String, InventoryEntry>, // instance_id -> entry
399    pub inventory_deletions: Vec<InventoryDeletion>,
400    pub compliance_items: Vec<ComplianceItem>,
401    pub resource_data_syncs: HashMap<String, ResourceDataSync>,
402    pub mw_execution_counter: u64,
403    pub inventory_deletion_counter: u64,
404    pub ops_item_related_items: Vec<OpsItemRelatedItem>,
405    pub ops_item_related_item_counter: u64,
406    pub ops_item_events: Vec<OpsItemEvent>,
407    pub ops_metadata: HashMap<String, OpsMetadataEntry>,
408    pub automation_executions: HashMap<String, AutomationExecution>,
409    pub automation_execution_counter: u64,
410    pub sessions: HashMap<String, SsmSession>,
411    pub session_counter: u64,
412    pub activations: HashMap<String, SsmActivation>,
413    pub activation_counter: u64,
414    pub managed_instances: HashMap<String, ManagedInstance>,
415    pub execution_previews: HashMap<String, ExecutionPreview>,
416    pub execution_preview_counter: u64,
417}
418
419impl SsmState {
420    pub fn new(account_id: &str, region: &str) -> Self {
421        let mut state = Self {
422            account_id: account_id.to_string(),
423            region: region.to_string(),
424            parameters: BTreeMap::new(),
425            documents: BTreeMap::new(),
426            commands: Vec::new(),
427            maintenance_windows: HashMap::new(),
428            patch_baselines: HashMap::new(),
429            patch_groups: Vec::new(),
430            associations: HashMap::new(),
431            ops_items: HashMap::new(),
432            resource_policies: Vec::new(),
433            service_settings: HashMap::new(),
434            default_patch_baseline_id: None,
435            ops_item_counter: 0,
436            maintenance_window_executions: Vec::new(),
437            inventory_entries: HashMap::new(),
438            inventory_deletions: Vec::new(),
439            compliance_items: Vec::new(),
440            resource_data_syncs: HashMap::new(),
441            mw_execution_counter: 0,
442            inventory_deletion_counter: 0,
443            ops_item_related_items: Vec::new(),
444            ops_item_related_item_counter: 0,
445            ops_item_events: Vec::new(),
446            ops_metadata: HashMap::new(),
447            automation_executions: HashMap::new(),
448            automation_execution_counter: 0,
449            sessions: HashMap::new(),
450            session_counter: 0,
451            activations: HashMap::new(),
452            activation_counter: 0,
453            managed_instances: HashMap::new(),
454            execution_previews: HashMap::new(),
455            execution_preview_counter: 0,
456        };
457        state.seed_defaults();
458        state
459    }
460
461    pub fn reset(&mut self) {
462        self.parameters.clear();
463        self.documents.clear();
464        self.commands.clear();
465        self.maintenance_windows.clear();
466        self.patch_baselines.clear();
467        self.patch_groups.clear();
468        self.associations.clear();
469        self.ops_items.clear();
470        self.resource_policies.clear();
471        self.service_settings.clear();
472        self.default_patch_baseline_id = None;
473        self.ops_item_counter = 0;
474        self.maintenance_window_executions.clear();
475        self.inventory_entries.clear();
476        self.inventory_deletions.clear();
477        self.compliance_items.clear();
478        self.resource_data_syncs.clear();
479        self.mw_execution_counter = 0;
480        self.inventory_deletion_counter = 0;
481        self.ops_item_related_items.clear();
482        self.ops_item_related_item_counter = 0;
483        self.ops_item_events.clear();
484        self.ops_metadata.clear();
485        self.automation_executions.clear();
486        self.automation_execution_counter = 0;
487        self.sessions.clear();
488        self.session_counter = 0;
489        self.activations.clear();
490        self.activation_counter = 0;
491        self.managed_instances.clear();
492        self.execution_previews.clear();
493        self.execution_preview_counter = 0;
494        self.seed_defaults();
495    }
496
497    fn seed_defaults(&mut self) {
498        let now = chrono::Utc::now();
499
500        // Seed region parameters
501        let regions: &[(&str, &str)] = &[
502            ("af-south-1", "Africa (Cape Town)"),
503            ("ap-east-1", "Asia Pacific (Hong Kong)"),
504            ("ap-northeast-1", "Asia Pacific (Tokyo)"),
505            ("ap-northeast-2", "Asia Pacific (Seoul)"),
506            ("ap-northeast-3", "Asia Pacific (Osaka)"),
507            ("ap-south-1", "Asia Pacific (Mumbai)"),
508            ("ap-south-2", "Asia Pacific (Hyderabad)"),
509            ("ap-southeast-1", "Asia Pacific (Singapore)"),
510            ("ap-southeast-2", "Asia Pacific (Sydney)"),
511            ("ap-southeast-3", "Asia Pacific (Jakarta)"),
512            ("ca-central-1", "Canada (Central)"),
513            ("eu-central-1", "Europe (Frankfurt)"),
514            ("eu-central-2", "Europe (Zurich)"),
515            ("eu-north-1", "Europe (Stockholm)"),
516            ("eu-south-1", "Europe (Milan)"),
517            ("eu-south-2", "Europe (Spain)"),
518            ("eu-west-1", "Europe (Ireland)"),
519            ("eu-west-2", "Europe (London)"),
520            ("eu-west-3", "Europe (Paris)"),
521            ("me-central-1", "Middle East (UAE)"),
522            ("me-south-1", "Middle East (Bahrain)"),
523            ("sa-east-1", "South America (Sao Paulo)"),
524            ("us-east-1", "US East (N. Virginia)"),
525            ("us-east-2", "US East (Ohio)"),
526            ("us-west-1", "US West (N. California)"),
527            ("us-west-2", "US West (Oregon)"),
528        ];
529
530        for (region_code, long_name) in regions {
531            let base_path = format!("/aws/service/global-infrastructure/regions/{region_code}");
532            self.insert_default_param(&base_path, region_code, now);
533            self.insert_default_param(&format!("{base_path}/longName"), long_name, now);
534            self.insert_default_param(&format!("{base_path}/domain"), "amazonaws.com", now);
535            self.insert_default_param(&format!("{base_path}/geolocationRegion"), region_code, now);
536            let country = match region_code.split('-').next().unwrap_or("") {
537                "us" => "US",
538                "eu" => "DE",
539                "ap" => "JP",
540                "sa" => "BR",
541                "ca" => "CA",
542                "me" => "BH",
543                "af" => "ZA",
544                "il" => "IL",
545                _ => "US",
546            };
547            self.insert_default_param(&format!("{base_path}/geolocationCountry"), country, now);
548            self.insert_default_param(&format!("{base_path}/partition"), "aws", now);
549        }
550
551        // Seed service parameters
552        let services = [
553            "acm",
554            "apigateway",
555            "autoscaling",
556            "cloudformation",
557            "cloudfront",
558            "cloudwatch",
559            "codebuild",
560            "codecommit",
561            "codedeploy",
562            "dynamodb",
563            "ec2",
564            "ecr",
565            "ecs",
566            "eks",
567            "elasticache",
568            "elasticbeanstalk",
569            "elasticloadbalancing",
570            "es",
571            "events",
572            "firehose",
573            "iam",
574            "kinesis",
575            "kms",
576            "lambda",
577            "logs",
578            "rds",
579            "redshift",
580            "route53",
581            "s3",
582            "ses",
583            "sns",
584            "sqs",
585            "ssm",
586            "sts",
587        ];
588        for svc in &services {
589            let name = format!("/aws/service/global-infrastructure/services/{svc}");
590            self.insert_default_param(&name, svc, now);
591        }
592
593        // Seed AMI parameters (10 entries per region)
594        let ami_names = [
595            "al2023-ami-kernel-default-x86_64",
596            "al2023-ami-kernel-default-arm64",
597            "al2023-ami-minimal-kernel-default-x86_64",
598            "al2023-ami-minimal-kernel-default-arm64",
599            "amzn2-ami-hvm-x86_64-gp2",
600            "amzn2-ami-hvm-arm64-gp2",
601            "amzn2-ami-kernel-5.10-hvm-x86_64-gp2",
602            "amzn2-ami-kernel-5.10-hvm-arm64-gp2",
603            "amzn2-ami-minimal-hvm-x86_64-ebs",
604            "amzn2-ami-minimal-hvm-arm64-ebs",
605        ];
606
607        // Generate region-specific AMI IDs using a simple hash
608        for (i, ami_name) in ami_names.iter().enumerate() {
609            let name = format!("/aws/service/ami-amazon-linux-latest/{ami_name}");
610            let ami_id = format!(
611                "ami-{:017x}",
612                // Simple region-specific hash
613                {
614                    let mut h: u64 = 0xcbf29ce484222325;
615                    for b in self.region.as_bytes() {
616                        h ^= *b as u64;
617                        h = h.wrapping_mul(0x100000001b3);
618                    }
619                    for b in ami_name.as_bytes() {
620                        h ^= *b as u64;
621                        h = h.wrapping_mul(0x100000001b3);
622                    }
623                    h.wrapping_add(i as u64)
624                }
625            );
626            self.insert_default_param(&name, &ami_id, now);
627        }
628    }
629
630    fn insert_default_param(&mut self, name: &str, value: &str, now: DateTime<Utc>) {
631        let arn = if name.starts_with('/') {
632            format!(
633                "arn:aws:ssm:{}:{}:parameter{}",
634                self.region, self.account_id, name
635            )
636        } else {
637            format!(
638                "arn:aws:ssm:{}:{}:parameter/{}",
639                self.region, self.account_id, name
640            )
641        };
642        self.parameters.insert(
643            name.to_string(),
644            SsmParameter {
645                name: name.to_string(),
646                value: value.to_string(),
647                param_type: "String".to_string(),
648                version: 1,
649                arn,
650                last_modified: now,
651                history: Vec::new(),
652                tags: HashMap::new(),
653                labels: HashMap::new(),
654                description: None,
655                allowed_pattern: None,
656                key_id: None,
657                data_type: "text".to_string(),
658                tier: "Standard".to_string(),
659                policies: None,
660            },
661        );
662    }
663}
664
665#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
666pub struct MaintenanceWindowExecution {
667    pub window_execution_id: String,
668    pub window_id: String,
669    pub status: String,
670    pub start_time: DateTime<Utc>,
671    pub end_time: Option<DateTime<Utc>>,
672    pub tasks: Vec<MaintenanceWindowExecutionTask>,
673}
674
675#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
676pub struct MaintenanceWindowExecutionTask {
677    pub task_execution_id: String,
678    pub window_execution_id: String,
679    pub task_arn: String,
680    pub task_type: String,
681    pub status: String,
682    pub start_time: DateTime<Utc>,
683    pub end_time: Option<DateTime<Utc>>,
684    pub invocations: Vec<MaintenanceWindowExecutionTaskInvocation>,
685}
686
687#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
688pub struct MaintenanceWindowExecutionTaskInvocation {
689    pub invocation_id: String,
690    pub task_execution_id: String,
691    pub window_execution_id: String,
692    pub execution_id: Option<String>,
693    pub status: String,
694    pub start_time: DateTime<Utc>,
695    pub end_time: Option<DateTime<Utc>>,
696    pub parameters: Option<String>,
697    pub owner_information: Option<String>,
698    pub window_target_id: Option<String>,
699    pub status_details: Option<String>,
700}
701
702#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
703pub struct InventoryItem {
704    pub type_name: String,
705    pub schema_version: String,
706    pub capture_time: String,
707    pub content: Vec<HashMap<String, String>>,
708    pub content_hash: Option<String>,
709    pub context: Option<HashMap<String, String>>,
710}
711
712#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
713pub struct InventoryEntry {
714    pub instance_id: String,
715    pub items: Vec<InventoryItem>,
716}
717
718#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
719pub struct InventoryDeletion {
720    pub deletion_id: String,
721    pub type_name: String,
722    pub deletion_start_time: DateTime<Utc>,
723    pub last_status: String,
724    pub last_status_message: String,
725    pub deletion_summary: serde_json::Value,
726    pub last_status_update_time: DateTime<Utc>,
727}
728
729#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
730pub struct ComplianceItem {
731    pub resource_id: String,
732    pub resource_type: String,
733    pub compliance_type: String,
734    pub severity: String,
735    pub status: String,
736    pub title: Option<String>,
737    pub id: Option<String>,
738    pub details: HashMap<String, String>,
739    pub execution_summary: serde_json::Value,
740}
741
742#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
743pub struct ResourceDataSync {
744    pub sync_name: String,
745    pub sync_type: Option<String>,
746    pub sync_source: Option<serde_json::Value>,
747    pub s3_destination: Option<serde_json::Value>,
748    pub created_date: DateTime<Utc>,
749    pub last_sync_time: Option<DateTime<Utc>>,
750    pub last_successful_sync_time: Option<DateTime<Utc>>,
751    pub last_status: String,
752    pub sync_last_modified_time: DateTime<Utc>,
753}
754
755pub type SharedSsmState = Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<SsmState>>>;
756
757impl fakecloud_core::multi_account::AccountState for SsmState {
758    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
759        Self::new(account_id, region)
760    }
761}
762
763/// On-disk snapshot envelope for SSM state. Versioned so format
764/// changes fail loudly on upgrade.
765#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
766pub struct SsmSnapshot {
767    pub schema_version: u32,
768    #[serde(default)]
769    pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<SsmState>>,
770    #[serde(default)]
771    pub state: Option<SsmState>,
772}
773
774pub const SSM_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
775
776#[cfg(test)]
777mod tests {
778    use super::*;
779
780    #[test]
781    fn new_initializes() {
782        let state = SsmState::new("123456789012", "us-east-1");
783        assert_eq!(state.account_id, "123456789012");
784        assert_eq!(state.region, "us-east-1");
785    }
786
787    #[test]
788    fn new_seeds_default_region_parameters() {
789        let state = SsmState::new("123456789012", "us-east-1");
790        let region_key = "/aws/service/global-infrastructure/regions/us-east-1";
791        assert!(state.parameters.contains_key(region_key));
792        let long_key = format!("{region_key}/longName");
793        assert!(state.parameters.contains_key(&long_key));
794    }
795
796    #[test]
797    fn new_seeds_default_service_parameters() {
798        let state = SsmState::new("123456789012", "us-east-1");
799        let key = "/aws/service/global-infrastructure/services/lambda";
800        assert!(state.parameters.contains_key(key));
801    }
802
803    #[test]
804    fn reset_reseeds_defaults() {
805        let mut state = SsmState::new("123456789012", "us-east-1");
806        state.parameters.clear();
807        state.documents.clear();
808        state.ops_item_counter = 42;
809        state.reset();
810        // Defaults re-seeded
811        let key = "/aws/service/global-infrastructure/services/s3";
812        assert!(state.parameters.contains_key(key));
813        assert_eq!(state.ops_item_counter, 0);
814    }
815
816    #[test]
817    fn reset_clears_ephemeral_counters() {
818        let mut state = SsmState::new("123456789012", "us-east-1");
819        state.mw_execution_counter = 7;
820        state.automation_execution_counter = 3;
821        state.session_counter = 9;
822        state.activation_counter = 2;
823        state.execution_preview_counter = 5;
824        state.reset();
825        assert_eq!(state.mw_execution_counter, 0);
826        assert_eq!(state.automation_execution_counter, 0);
827        assert_eq!(state.session_counter, 0);
828        assert_eq!(state.activation_counter, 0);
829        assert_eq!(state.execution_preview_counter, 0);
830    }
831}