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