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}
245
246#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
247pub struct SsmAssociationVersion {
248 pub version: i64,
249 pub name: String,
250 pub targets: Vec<serde_json::Value>,
251 pub schedule_expression: Option<String>,
252 pub parameters: BTreeMap<String, Vec<String>>,
253 pub document_version: Option<String>,
254 pub created_date: DateTime<Utc>,
255 pub association_name: Option<String>,
256 pub max_errors: Option<String>,
257 pub max_concurrency: Option<String>,
258 pub compliance_severity: Option<String>,
259}
260
261#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
262pub struct SsmOpsItem {
263 pub ops_item_id: String,
264 pub title: String,
265 pub description: Option<String>,
266 pub source: String,
267 pub status: String,
268 pub priority: Option<i64>,
269 pub severity: Option<String>,
270 pub category: Option<String>,
271 pub operational_data: BTreeMap<String, serde_json::Value>,
272 pub notifications: Vec<serde_json::Value>,
273 pub related_ops_items: Vec<serde_json::Value>,
274 pub tags: BTreeMap<String, String>,
275 pub created_time: DateTime<Utc>,
276 pub last_modified_time: DateTime<Utc>,
277 pub created_by: String,
278 pub last_modified_by: String,
279 pub ops_item_type: Option<String>,
280 pub planned_start_time: Option<DateTime<Utc>>,
281 pub planned_end_time: Option<DateTime<Utc>>,
282 pub actual_start_time: Option<DateTime<Utc>>,
283 pub actual_end_time: Option<DateTime<Utc>>,
284}
285
286#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
287pub struct SsmResourcePolicy {
288 pub policy_id: String,
289 pub policy_hash: String,
290 pub policy: String,
291 pub resource_arn: String,
292}
293
294#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
295pub struct SsmServiceSetting {
296 pub setting_id: String,
297 pub setting_value: String,
298 pub last_modified_date: DateTime<Utc>,
299 pub last_modified_user: String,
300 pub status: String,
301}
302
303#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
304pub struct OpsItemRelatedItem {
305 pub association_id: String,
306 pub ops_item_id: String,
307 pub association_type: String,
308 pub resource_type: String,
309 pub resource_uri: String,
310 pub created_time: DateTime<Utc>,
311 pub created_by: String,
312 pub last_modified_time: DateTime<Utc>,
313 pub last_modified_by: String,
314}
315
316#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
317pub struct OpsItemEvent {
318 pub ops_item_id: String,
319 pub event_id: String,
320 pub source: String,
321 pub detail_type: String,
322 pub created_time: DateTime<Utc>,
323 pub created_by: String,
324}
325
326#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
327pub struct OpsMetadataEntry {
328 pub ops_metadata_arn: String,
329 pub resource_id: String,
330 pub metadata: BTreeMap<String, serde_json::Value>,
331 pub creation_date: DateTime<Utc>,
332}
333
334#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
335pub struct AutomationExecution {
336 pub automation_execution_id: String,
337 pub document_name: String,
338 pub document_version: Option<String>,
339 pub automation_execution_status: String,
340 pub execution_start_time: DateTime<Utc>,
341 pub execution_end_time: Option<DateTime<Utc>>,
342 pub parameters: BTreeMap<String, Vec<String>>,
343 pub outputs: BTreeMap<String, Vec<String>>,
344 pub mode: String,
345 pub target: Option<String>,
346 pub targets: Vec<serde_json::Value>,
347 pub max_concurrency: Option<String>,
348 pub max_errors: Option<String>,
349 pub executed_by: String,
350 pub step_executions: Vec<AutomationStepExecution>,
351 pub automation_subtype: Option<String>,
352 pub runbooks: Vec<serde_json::Value>,
353 pub change_request_name: Option<String>,
354 pub scheduled_time: Option<DateTime<Utc>>,
355}
356
357#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
358pub struct AutomationStepExecution {
359 pub step_name: String,
360 pub action: String,
361 pub step_status: String,
362 pub execution_start_time: Option<DateTime<Utc>>,
363 pub execution_end_time: Option<DateTime<Utc>>,
364 pub inputs: BTreeMap<String, String>,
365 pub outputs: BTreeMap<String, Vec<String>>,
366 pub step_execution_id: String,
367}
368
369#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
370pub struct SsmSession {
371 pub session_id: String,
372 pub target: String,
373 pub status: String,
374 pub start_date: DateTime<Utc>,
375 pub end_date: Option<DateTime<Utc>>,
376 pub owner: String,
377 pub reason: Option<String>,
378}
379
380#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
381pub struct SsmActivation {
382 pub activation_id: String,
383 pub iam_role: String,
384 pub registration_limit: i64,
385 pub registrations_count: i64,
386 pub expiration_date: Option<DateTime<Utc>>,
387 pub description: Option<String>,
388 pub default_instance_name: Option<String>,
389 pub created_date: DateTime<Utc>,
390 pub expired: bool,
391 pub tags: BTreeMap<String, String>,
392}
393
394#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
395pub struct ManagedInstance {
396 pub instance_id: String,
397 pub activation_id: Option<String>,
398 pub iam_role: String,
399 pub ping_status: String,
400 pub platform_type: String,
401 pub platform_name: String,
402 pub platform_version: String,
403 pub agent_version: String,
404 pub last_ping_date_time: DateTime<Utc>,
405 pub registration_date: DateTime<Utc>,
406 pub resource_type: String,
407 pub computer_name: String,
408 pub ip_address: String,
409 pub is_latest_version: bool,
410 pub association_status: Option<String>,
411 pub source_id: Option<String>,
412 pub source_type: Option<String>,
413}
414
415#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
416pub struct ExecutionPreview {
417 pub execution_preview_id: String,
418 pub document_name: String,
419 pub status: String,
420 pub created_time: DateTime<Utc>,
421}
422
423#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
424pub struct SsmState {
425 pub account_id: String,
426 pub region: String,
427 pub parameters: BTreeMap<String, SsmParameter>, pub documents: BTreeMap<String, SsmDocument>,
429 pub commands: Vec<SsmCommand>,
430 pub maintenance_windows: BTreeMap<String, MaintenanceWindow>,
431 pub patch_baselines: BTreeMap<String, PatchBaseline>,
432 pub patch_groups: Vec<PatchGroup>,
433 pub associations: BTreeMap<String, SsmAssociation>,
434 pub ops_items: BTreeMap<String, SsmOpsItem>,
435 pub resource_policies: Vec<SsmResourcePolicy>,
436 pub service_settings: BTreeMap<String, SsmServiceSetting>,
437 pub default_patch_baseline_id: Option<String>,
438 pub ops_item_counter: u64,
439 pub maintenance_window_executions: Vec<MaintenanceWindowExecution>,
440 pub inventory_entries: BTreeMap<String, InventoryEntry>, pub inventory_deletions: Vec<InventoryDeletion>,
442 pub compliance_items: Vec<ComplianceItem>,
443 pub resource_data_syncs: BTreeMap<String, ResourceDataSync>,
444 pub mw_execution_counter: u64,
445 pub inventory_deletion_counter: u64,
446 pub ops_item_related_items: Vec<OpsItemRelatedItem>,
447 pub ops_item_related_item_counter: u64,
448 pub ops_item_events: Vec<OpsItemEvent>,
449 pub ops_metadata: BTreeMap<String, OpsMetadataEntry>,
450 pub automation_executions: BTreeMap<String, AutomationExecution>,
451 pub automation_execution_counter: u64,
452 pub sessions: BTreeMap<String, SsmSession>,
453 pub session_counter: u64,
454 pub activations: BTreeMap<String, SsmActivation>,
455 pub activation_counter: u64,
456 pub managed_instances: BTreeMap<String, ManagedInstance>,
457 pub execution_previews: BTreeMap<String, ExecutionPreview>,
458 pub execution_preview_counter: u64,
459 #[serde(default)]
465 pub parameter_policy_events: Vec<ParameterPolicyEvent>,
466}
467
468#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
472pub struct ParameterPolicyEvent {
473 pub parameter_name: String,
474 pub parameter_arn: String,
475 pub event_type: String,
476 pub message: String,
477 pub created_at: DateTime<Utc>,
478}
479
480impl SsmState {
481 pub fn new(account_id: &str, region: &str) -> Self {
482 let mut state = Self {
483 account_id: account_id.to_string(),
484 region: region.to_string(),
485 parameters: BTreeMap::new(),
486 documents: BTreeMap::new(),
487 commands: Vec::new(),
488 maintenance_windows: BTreeMap::new(),
489 patch_baselines: BTreeMap::new(),
490 patch_groups: Vec::new(),
491 associations: BTreeMap::new(),
492 ops_items: BTreeMap::new(),
493 resource_policies: Vec::new(),
494 service_settings: BTreeMap::new(),
495 default_patch_baseline_id: None,
496 ops_item_counter: 0,
497 maintenance_window_executions: Vec::new(),
498 inventory_entries: BTreeMap::new(),
499 inventory_deletions: Vec::new(),
500 compliance_items: Vec::new(),
501 resource_data_syncs: BTreeMap::new(),
502 mw_execution_counter: 0,
503 inventory_deletion_counter: 0,
504 ops_item_related_items: Vec::new(),
505 ops_item_related_item_counter: 0,
506 ops_item_events: Vec::new(),
507 ops_metadata: BTreeMap::new(),
508 automation_executions: BTreeMap::new(),
509 automation_execution_counter: 0,
510 sessions: BTreeMap::new(),
511 session_counter: 0,
512 activations: BTreeMap::new(),
513 activation_counter: 0,
514 managed_instances: BTreeMap::new(),
515 execution_previews: BTreeMap::new(),
516 execution_preview_counter: 0,
517 parameter_policy_events: Vec::new(),
518 };
519 state.seed_defaults();
520 state
521 }
522
523 pub fn reset(&mut self) {
524 self.parameters.clear();
525 self.documents.clear();
526 self.commands.clear();
527 self.maintenance_windows.clear();
528 self.patch_baselines.clear();
529 self.patch_groups.clear();
530 self.associations.clear();
531 self.ops_items.clear();
532 self.resource_policies.clear();
533 self.service_settings.clear();
534 self.default_patch_baseline_id = None;
535 self.ops_item_counter = 0;
536 self.maintenance_window_executions.clear();
537 self.inventory_entries.clear();
538 self.inventory_deletions.clear();
539 self.compliance_items.clear();
540 self.resource_data_syncs.clear();
541 self.mw_execution_counter = 0;
542 self.inventory_deletion_counter = 0;
543 self.ops_item_related_items.clear();
544 self.ops_item_related_item_counter = 0;
545 self.ops_item_events.clear();
546 self.ops_metadata.clear();
547 self.automation_executions.clear();
548 self.automation_execution_counter = 0;
549 self.sessions.clear();
550 self.session_counter = 0;
551 self.activations.clear();
552 self.activation_counter = 0;
553 self.managed_instances.clear();
554 self.execution_previews.clear();
555 self.execution_preview_counter = 0;
556 self.parameter_policy_events.clear();
557 self.seed_defaults();
558 }
559
560 fn seed_defaults(&mut self) {
561 let now = chrono::Utc::now();
562
563 let regions: &[(&str, &str)] = &[
565 ("af-south-1", "Africa (Cape Town)"),
566 ("ap-east-1", "Asia Pacific (Hong Kong)"),
567 ("ap-northeast-1", "Asia Pacific (Tokyo)"),
568 ("ap-northeast-2", "Asia Pacific (Seoul)"),
569 ("ap-northeast-3", "Asia Pacific (Osaka)"),
570 ("ap-south-1", "Asia Pacific (Mumbai)"),
571 ("ap-south-2", "Asia Pacific (Hyderabad)"),
572 ("ap-southeast-1", "Asia Pacific (Singapore)"),
573 ("ap-southeast-2", "Asia Pacific (Sydney)"),
574 ("ap-southeast-3", "Asia Pacific (Jakarta)"),
575 ("ca-central-1", "Canada (Central)"),
576 ("eu-central-1", "Europe (Frankfurt)"),
577 ("eu-central-2", "Europe (Zurich)"),
578 ("eu-north-1", "Europe (Stockholm)"),
579 ("eu-south-1", "Europe (Milan)"),
580 ("eu-south-2", "Europe (Spain)"),
581 ("eu-west-1", "Europe (Ireland)"),
582 ("eu-west-2", "Europe (London)"),
583 ("eu-west-3", "Europe (Paris)"),
584 ("me-central-1", "Middle East (UAE)"),
585 ("me-south-1", "Middle East (Bahrain)"),
586 ("sa-east-1", "South America (Sao Paulo)"),
587 ("us-east-1", "US East (N. Virginia)"),
588 ("us-east-2", "US East (Ohio)"),
589 ("us-west-1", "US West (N. California)"),
590 ("us-west-2", "US West (Oregon)"),
591 ];
592
593 for (region_code, long_name) in regions {
594 let base_path = format!("/aws/service/global-infrastructure/regions/{region_code}");
595 self.insert_default_param(&base_path, region_code, now);
596 self.insert_default_param(&format!("{base_path}/longName"), long_name, now);
597 self.insert_default_param(&format!("{base_path}/domain"), "amazonaws.com", now);
598 self.insert_default_param(&format!("{base_path}/geolocationRegion"), region_code, now);
599 let country = match region_code.split('-').next().unwrap_or("") {
600 "us" => "US",
601 "eu" => "DE",
602 "ap" => "JP",
603 "sa" => "BR",
604 "ca" => "CA",
605 "me" => "BH",
606 "af" => "ZA",
607 "il" => "IL",
608 _ => "US",
609 };
610 self.insert_default_param(&format!("{base_path}/geolocationCountry"), country, now);
611 self.insert_default_param(&format!("{base_path}/partition"), "aws", now);
612 }
613
614 let services = [
616 "acm",
617 "apigateway",
618 "autoscaling",
619 "cloudformation",
620 "cloudfront",
621 "cloudwatch",
622 "codebuild",
623 "codecommit",
624 "codedeploy",
625 "dynamodb",
626 "ec2",
627 "ecr",
628 "ecs",
629 "eks",
630 "elasticache",
631 "elasticbeanstalk",
632 "elasticloadbalancing",
633 "es",
634 "events",
635 "firehose",
636 "iam",
637 "kinesis",
638 "kms",
639 "lambda",
640 "logs",
641 "rds",
642 "redshift",
643 "route53",
644 "s3",
645 "ses",
646 "sns",
647 "sqs",
648 "ssm",
649 "sts",
650 ];
651 for svc in &services {
652 let name = format!("/aws/service/global-infrastructure/services/{svc}");
653 self.insert_default_param(&name, svc, now);
654 }
655
656 let ami_names = [
658 "al2023-ami-kernel-default-x86_64",
659 "al2023-ami-kernel-default-arm64",
660 "al2023-ami-minimal-kernel-default-x86_64",
661 "al2023-ami-minimal-kernel-default-arm64",
662 "amzn2-ami-hvm-x86_64-gp2",
663 "amzn2-ami-hvm-arm64-gp2",
664 "amzn2-ami-kernel-5.10-hvm-x86_64-gp2",
665 "amzn2-ami-kernel-5.10-hvm-arm64-gp2",
666 "amzn2-ami-minimal-hvm-x86_64-ebs",
667 "amzn2-ami-minimal-hvm-arm64-ebs",
668 ];
669
670 for (i, ami_name) in ami_names.iter().enumerate() {
672 let name = format!("/aws/service/ami-amazon-linux-latest/{ami_name}");
673 let ami_id = format!(
674 "ami-{:017x}",
675 {
677 let mut h: u64 = 0xcbf29ce484222325;
678 for b in self.region.as_bytes() {
679 h ^= *b as u64;
680 h = h.wrapping_mul(0x100000001b3);
681 }
682 for b in ami_name.as_bytes() {
683 h ^= *b as u64;
684 h = h.wrapping_mul(0x100000001b3);
685 }
686 h.wrapping_add(i as u64)
687 }
688 );
689 self.insert_default_param(&name, &ami_id, now);
690 }
691 }
692
693 fn insert_default_param(&mut self, name: &str, value: &str, now: DateTime<Utc>) {
694 let arn = if name.starts_with('/') {
695 format!(
696 "arn:aws:ssm:{}:{}:parameter{}",
697 self.region, self.account_id, name
698 )
699 } else {
700 format!(
701 "arn:aws:ssm:{}:{}:parameter/{}",
702 self.region, self.account_id, name
703 )
704 };
705 self.parameters.insert(
706 name.to_string(),
707 SsmParameter {
708 name: name.to_string(),
709 value: value.to_string(),
710 param_type: "String".to_string(),
711 version: 1,
712 arn,
713 last_modified: now,
714 history: Vec::new(),
715 tags: BTreeMap::new(),
716 labels: BTreeMap::new(),
717 description: None,
718 allowed_pattern: None,
719 key_id: None,
720 data_type: "text".to_string(),
721 tier: "Standard".to_string(),
722 policies: None,
723 expiration_notified: false,
724 no_change_notified: false,
725 },
726 );
727 }
728}
729
730#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
731pub struct MaintenanceWindowExecution {
732 pub window_execution_id: String,
733 pub window_id: String,
734 pub status: String,
735 pub start_time: DateTime<Utc>,
736 pub end_time: Option<DateTime<Utc>>,
737 pub tasks: Vec<MaintenanceWindowExecutionTask>,
738}
739
740#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
741pub struct MaintenanceWindowExecutionTask {
742 pub task_execution_id: String,
743 pub window_execution_id: String,
744 pub task_arn: String,
745 pub task_type: String,
746 pub status: String,
747 pub start_time: DateTime<Utc>,
748 pub end_time: Option<DateTime<Utc>>,
749 pub invocations: Vec<MaintenanceWindowExecutionTaskInvocation>,
750}
751
752#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
753pub struct MaintenanceWindowExecutionTaskInvocation {
754 pub invocation_id: String,
755 pub task_execution_id: String,
756 pub window_execution_id: String,
757 pub execution_id: Option<String>,
758 pub status: String,
759 pub start_time: DateTime<Utc>,
760 pub end_time: Option<DateTime<Utc>>,
761 pub parameters: Option<String>,
762 pub owner_information: Option<String>,
763 pub window_target_id: Option<String>,
764 pub status_details: Option<String>,
765}
766
767#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
768pub struct InventoryItem {
769 pub type_name: String,
770 pub schema_version: String,
771 pub capture_time: String,
772 pub content: Vec<BTreeMap<String, String>>,
773 pub content_hash: Option<String>,
774 pub context: Option<BTreeMap<String, String>>,
775}
776
777#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
778pub struct InventoryEntry {
779 pub instance_id: String,
780 pub items: Vec<InventoryItem>,
781}
782
783#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
784pub struct InventoryDeletion {
785 pub deletion_id: String,
786 pub type_name: String,
787 pub deletion_start_time: DateTime<Utc>,
788 pub last_status: String,
789 pub last_status_message: String,
790 pub deletion_summary: serde_json::Value,
791 pub last_status_update_time: DateTime<Utc>,
792}
793
794#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
795pub struct ComplianceItem {
796 pub resource_id: String,
797 pub resource_type: String,
798 pub compliance_type: String,
799 pub severity: String,
800 pub status: String,
801 pub title: Option<String>,
802 pub id: Option<String>,
803 pub details: BTreeMap<String, String>,
804 pub execution_summary: serde_json::Value,
805}
806
807#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
808pub struct ResourceDataSync {
809 pub sync_name: String,
810 pub sync_type: Option<String>,
811 pub sync_source: Option<serde_json::Value>,
812 pub s3_destination: Option<serde_json::Value>,
813 pub created_date: DateTime<Utc>,
814 pub last_sync_time: Option<DateTime<Utc>>,
815 pub last_successful_sync_time: Option<DateTime<Utc>>,
816 pub last_status: String,
817 pub sync_last_modified_time: DateTime<Utc>,
818}
819
820pub type SharedSsmState = Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<SsmState>>>;
821
822impl fakecloud_core::multi_account::AccountState for SsmState {
823 fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
824 Self::new(account_id, region)
825 }
826}
827
828#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
831pub struct SsmSnapshot {
832 pub schema_version: u32,
833 #[serde(default)]
834 pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<SsmState>>,
835 #[serde(default)]
836 pub state: Option<SsmState>,
837}
838
839pub const SSM_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844
845 #[test]
846 fn new_initializes() {
847 let state = SsmState::new("123456789012", "us-east-1");
848 assert_eq!(state.account_id, "123456789012");
849 assert_eq!(state.region, "us-east-1");
850 }
851
852 #[test]
853 fn new_seeds_default_region_parameters() {
854 let state = SsmState::new("123456789012", "us-east-1");
855 let region_key = "/aws/service/global-infrastructure/regions/us-east-1";
856 assert!(state.parameters.contains_key(region_key));
857 let long_key = format!("{region_key}/longName");
858 assert!(state.parameters.contains_key(&long_key));
859 }
860
861 #[test]
862 fn new_seeds_default_service_parameters() {
863 let state = SsmState::new("123456789012", "us-east-1");
864 let key = "/aws/service/global-infrastructure/services/lambda";
865 assert!(state.parameters.contains_key(key));
866 }
867
868 #[test]
869 fn reset_reseeds_defaults() {
870 let mut state = SsmState::new("123456789012", "us-east-1");
871 state.parameters.clear();
872 state.documents.clear();
873 state.ops_item_counter = 42;
874 state.reset();
875 let key = "/aws/service/global-infrastructure/services/s3";
877 assert!(state.parameters.contains_key(key));
878 assert_eq!(state.ops_item_counter, 0);
879 }
880
881 #[test]
882 fn reset_clears_ephemeral_counters() {
883 let mut state = SsmState::new("123456789012", "us-east-1");
884 state.mw_execution_counter = 7;
885 state.automation_execution_counter = 3;
886 state.session_counter = 9;
887 state.activation_counter = 2;
888 state.execution_preview_counter = 5;
889 state.reset();
890 assert_eq!(state.mw_execution_counter, 0);
891 assert_eq!(state.automation_execution_counter, 0);
892 assert_eq!(state.session_counter, 0);
893 assert_eq!(state.activation_counter, 0);
894 assert_eq!(state.execution_preview_counter, 0);
895 }
896}