1use std::collections::HashMap;
2
3use chrono::Utc;
4use thiserror::Error;
5use uuid::Uuid;
6
7use crate::types::*;
8
9#[derive(Debug, Default)]
10pub struct BackupState {
11 pub vaults: HashMap<String, BackupVault>,
12 pub backup_plans: HashMap<String, BackupPlanData>,
13 pub backup_selections: HashMap<String, BackupSelectionData>,
15 pub report_plans: HashMap<String, ReportPlanData>,
16 pub recovery_points: HashMap<String, RecoveryPointData>,
18 pub backup_jobs: HashMap<String, BackupJobData>,
20 pub resource_tags: HashMap<String, HashMap<String, String>>,
22 pub vault_access_policies: HashMap<String, VaultAccessPolicy>,
24 pub vault_notifications: HashMap<String, VaultNotificationConfig>,
26 pub frameworks: HashMap<String, FrameworkData>,
28 pub global_settings: GlobalSettings,
30 pub region_settings: RegionSettings,
32 pub report_jobs: HashMap<String, ReportJobData>,
34 pub scan_jobs: HashMap<String, ScanJobData>,
36 pub tiering_configs: HashMap<String, TieringConfigData>,
38 pub legal_holds: HashMap<String, LegalHoldData>,
40 pub copy_jobs: HashMap<String, CopyJobData>,
42 pub restore_jobs: HashMap<String, RestoreJobData>,
44 pub restore_testing_plans: HashMap<String, RestoreTestingPlanData>,
46 pub restore_testing_selections: HashMap<(String, String), RestoreTestingSelectionData>,
48}
49
50#[derive(Debug, Error)]
51pub enum BackupError {
52 #[error("Backup vault {0} already exists.")]
53 VaultAlreadyExists(String),
54
55 #[error("Backup vault {0} does not exist.")]
56 VaultNotFound(String),
57
58 #[error("Backup plan with name {0} already exists.")]
59 PlanAlreadyExists(String),
60
61 #[error("Backup plan {0} does not exist.")]
62 PlanNotFound(String),
63
64 #[error("Report plan {0} already exists.")]
65 ReportPlanAlreadyExists(String),
66
67 #[error("Report plan {0} does not exist.")]
68 ReportPlanNotFound(String),
69
70 #[error("Framework {0} already exists.")]
71 FrameworkAlreadyExists(String),
72
73 #[error("Framework {0} does not exist.")]
74 FrameworkNotFound(String),
75
76 #[error("Backup selection {0} does not exist.")]
77 SelectionNotFound(String),
78
79 #[error("Recovery point {0} does not exist.")]
80 RecoveryPointNotFound(String),
81
82 #[error("Backup vault access policy for {0} does not exist.")]
83 VaultAccessPolicyNotFound(String),
84
85 #[error("Notification configuration for vault {0} does not exist.")]
86 VaultNotificationsNotFound(String),
87
88 #[error("Backup job {0} does not exist.")]
89 BackupJobNotFound(String),
90
91 #[error("Report job {0} does not exist.")]
92 ReportJobNotFound(String),
93
94 #[error("Scan job {0} does not exist.")]
95 ScanJobNotFound(String),
96
97 #[error("Tiering configuration {0} already exists.")]
98 TieringConfigAlreadyExists(String),
99
100 #[error("Tiering configuration {0} does not exist.")]
101 TieringConfigNotFound(String),
102
103 #[error("Vault {0} does not have a lock configured.")]
104 VaultNotLocked(String),
105
106 #[error("Backup job {0} is not in a cancellable state.")]
107 BackupJobNotCancellable(String),
108
109 #[error("Legal hold {0} already exists.")]
110 LegalHoldAlreadyExists(String),
111
112 #[error("Legal hold {0} does not exist.")]
113 LegalHoldNotFound(String),
114
115 #[error("Copy job {0} does not exist.")]
116 CopyJobNotFound(String),
117
118 #[error("Restore job {0} does not exist.")]
119 RestoreJobNotFound(String),
120
121 #[error("Restore testing plan {0} already exists.")]
122 RestoreTestingPlanAlreadyExists(String),
123
124 #[error("Restore testing plan {0} does not exist.")]
125 RestoreTestingPlanNotFound(String),
126
127 #[error("Restore testing selection {0} already exists.")]
128 RestoreTestingSelectionAlreadyExists(String),
129
130 #[error("Restore testing selection {0} does not exist.")]
131 RestoreTestingSelectionNotFound(String),
132}
133
134impl BackupState {
135 pub fn create_backup_vault(
136 &mut self,
137 name: &str,
138 arn: &str,
139 tags: HashMap<String, String>,
140 ) -> Result<&BackupVault, BackupError> {
141 if self.vaults.contains_key(name) {
142 return Err(BackupError::VaultAlreadyExists(name.to_string()));
143 }
144
145 if !tags.is_empty() {
146 self.resource_tags.insert(arn.to_string(), tags.clone());
147 }
148
149 let vault = BackupVault {
150 backup_vault_name: name.to_string(),
151 backup_vault_arn: arn.to_string(),
152 creation_date: Utc::now(),
153 number_of_recovery_points: 0,
154 locked: false,
155 min_retention_days: None,
156 max_retention_days: None,
157 lock_date: None,
158 tags,
159 };
160
161 self.vaults.insert(name.to_string(), vault);
162 Ok(self.vaults.get(name).unwrap())
163 }
164
165 pub fn describe_backup_vault(&self, name: &str) -> Result<&BackupVault, BackupError> {
166 self.vaults
167 .get(name)
168 .ok_or_else(|| BackupError::VaultNotFound(name.to_string()))
169 }
170
171 pub fn delete_backup_vault(&mut self, name: &str) -> Result<(), BackupError> {
172 if self.vaults.remove(name).is_none() {
173 return Err(BackupError::VaultNotFound(name.to_string()));
174 }
175 Ok(())
176 }
177
178 pub fn list_backup_vaults(&self) -> Vec<&BackupVault> {
179 self.vaults.values().collect()
180 }
181
182 pub fn create_backup_plan(
185 &mut self,
186 name: &str,
187 plan_json: &serde_json::Value,
188 region: &str,
189 account_id: &str,
190 tags: HashMap<String, String>,
191 ) -> Result<&BackupPlanData, BackupError> {
192 let already_exists = self
194 .backup_plans
195 .values()
196 .any(|p| p.backup_plan_name == name);
197 if already_exists {
198 return Err(BackupError::PlanAlreadyExists(name.to_string()));
199 }
200
201 let plan_id = Uuid::new_v4().to_string();
202 let version_id = Uuid::new_v4().to_string();
203 let arn = format!("arn:aws:backup:{region}:{account_id}:backup-plan:{plan_id}");
204
205 let enriched_json =
207 if let Some(rules_arr) = plan_json.get("Rules").and_then(|v| v.as_array()) {
208 let enriched_rules: Vec<serde_json::Value> = rules_arr
209 .iter()
210 .map(|rule| {
211 let mut r = rule.clone();
212 if let serde_json::Value::Object(ref mut map) = r {
213 map.entry("RuleId").or_insert_with(|| {
214 serde_json::Value::String(Uuid::new_v4().to_string())
215 });
216 }
217 r
218 })
219 .collect();
220 let mut enriched = plan_json.clone();
221 if let serde_json::Value::Object(ref mut map) = enriched {
222 map.insert(
223 "Rules".to_string(),
224 serde_json::Value::Array(enriched_rules),
225 );
226 }
227 enriched
228 } else {
229 plan_json.clone()
230 };
231
232 let plan = BackupPlanData {
233 backup_plan_id: plan_id.clone(),
234 backup_plan_arn: arn.clone(),
235 backup_plan_name: name.to_string(),
236 version_id,
237 creation_date: Utc::now(),
238 backup_plan_json: enriched_json,
239 tags: tags.clone(),
240 };
241
242 if !tags.is_empty() {
243 self.resource_tags.insert(arn, tags);
244 }
245
246 self.backup_plans.insert(plan_id.clone(), plan);
247 Ok(self.backup_plans.get(&plan_id).unwrap())
248 }
249
250 pub fn get_backup_plan(&self, plan_id: &str) -> Result<&BackupPlanData, BackupError> {
251 self.backup_plans
252 .get(plan_id)
253 .ok_or_else(|| BackupError::PlanNotFound(plan_id.to_string()))
254 }
255
256 pub fn delete_backup_plan(&mut self, plan_id: &str) -> Result<BackupPlanData, BackupError> {
257 self.backup_plans
258 .remove(plan_id)
259 .ok_or_else(|| BackupError::PlanNotFound(plan_id.to_string()))
260 }
261
262 pub fn list_backup_plans(&self) -> Vec<&BackupPlanData> {
263 self.backup_plans.values().collect()
264 }
265
266 pub fn create_report_plan(
269 &mut self,
270 name: &str,
271 description: &str,
272 delivery_channel: &serde_json::Value,
273 report_setting: &serde_json::Value,
274 region: &str,
275 account_id: &str,
276 tags: HashMap<String, String>,
277 ) -> Result<&ReportPlanData, BackupError> {
278 if self.report_plans.contains_key(name) {
279 return Err(BackupError::ReportPlanAlreadyExists(name.to_string()));
280 }
281
282 let arn = format!("arn:aws:backup:{region}:{account_id}:report-plan:{name}");
283
284 let plan = ReportPlanData {
285 report_plan_name: name.to_string(),
286 report_plan_arn: arn.clone(),
287 report_plan_description: description.to_string(),
288 report_delivery_channel: delivery_channel.clone(),
289 report_setting: report_setting.clone(),
290 creation_time: Utc::now(),
291 deployment_status: "COMPLETED".to_string(),
292 tags: tags.clone(),
293 };
294
295 if !tags.is_empty() {
296 self.resource_tags.insert(arn, tags);
297 }
298
299 self.report_plans.insert(name.to_string(), plan);
300 Ok(self.report_plans.get(name).unwrap())
301 }
302
303 pub fn describe_report_plan(&self, name: &str) -> Result<&ReportPlanData, BackupError> {
304 self.report_plans
305 .get(name)
306 .ok_or_else(|| BackupError::ReportPlanNotFound(name.to_string()))
307 }
308
309 pub fn delete_report_plan(&mut self, name: &str) -> Result<(), BackupError> {
310 if self.report_plans.remove(name).is_none() {
311 return Err(BackupError::ReportPlanNotFound(name.to_string()));
312 }
313 Ok(())
314 }
315
316 pub fn list_report_plans(&self) -> Vec<&ReportPlanData> {
317 self.report_plans.values().collect()
318 }
319
320 pub fn put_backup_vault_lock_configuration(
323 &mut self,
324 vault_name: &str,
325 min_retention_days: Option<i64>,
326 max_retention_days: Option<i64>,
327 ) -> Result<(), BackupError> {
328 let vault = self
329 .vaults
330 .get_mut(vault_name)
331 .ok_or_else(|| BackupError::VaultNotFound(vault_name.to_string()))?;
332 vault.locked = true;
333 vault.min_retention_days = min_retention_days;
334 vault.max_retention_days = max_retention_days;
335 vault.lock_date = Some(Utc::now());
336 Ok(())
337 }
338
339 pub fn delete_backup_vault_lock_configuration(
340 &mut self,
341 vault_name: &str,
342 ) -> Result<(), BackupError> {
343 let vault = self
344 .vaults
345 .get_mut(vault_name)
346 .ok_or_else(|| BackupError::VaultNotFound(vault_name.to_string()))?;
347 if !vault.locked {
348 return Err(BackupError::VaultNotLocked(vault_name.to_string()));
349 }
350 vault.locked = false;
351 vault.min_retention_days = None;
352 vault.max_retention_days = None;
353 vault.lock_date = None;
354 Ok(())
355 }
356
357 pub fn tag_resource(
360 &mut self,
361 resource_arn: &str,
362 tags: HashMap<String, String>,
363 ) -> Result<(), BackupError> {
364 let entry = self
365 .resource_tags
366 .entry(resource_arn.to_string())
367 .or_default();
368 entry.extend(tags);
369 Ok(())
370 }
371
372 pub fn untag_resource(
373 &mut self,
374 resource_arn: &str,
375 tag_keys: &[String],
376 ) -> Result<(), BackupError> {
377 if let Some(tags) = self.resource_tags.get_mut(resource_arn) {
378 for key in tag_keys {
379 tags.remove(key);
380 }
381 }
382 Ok(())
383 }
384
385 pub fn list_tags(&self, resource_arn: &str) -> HashMap<String, String> {
386 self.resource_tags
387 .get(resource_arn)
388 .cloned()
389 .unwrap_or_default()
390 }
391
392 pub fn create_backup_selection(
395 &mut self,
396 plan_id: &str,
397 selection_name: &str,
398 iam_role_arn: &str,
399 resources: Vec<String>,
400 selection_json: serde_json::Value,
401 ) -> Result<&BackupSelectionData, BackupError> {
402 if !self.backup_plans.contains_key(plan_id) {
403 return Err(BackupError::PlanNotFound(plan_id.to_string()));
404 }
405
406 let selection_id = Uuid::new_v4().to_string();
407 let selection = BackupSelectionData {
408 selection_id: selection_id.clone(),
409 backup_plan_id: plan_id.to_string(),
410 selection_name: selection_name.to_string(),
411 iam_role_arn: iam_role_arn.to_string(),
412 resources,
413 creation_date: Utc::now(),
414 selection_json,
415 };
416
417 self.backup_selections
418 .insert(selection_id.clone(), selection);
419 Ok(self.backup_selections.get(&selection_id).unwrap())
420 }
421
422 pub fn get_backup_selection(
423 &self,
424 plan_id: &str,
425 selection_id: &str,
426 ) -> Result<&BackupSelectionData, BackupError> {
427 self.backup_selections
428 .get(selection_id)
429 .filter(|s| s.backup_plan_id == plan_id)
430 .ok_or_else(|| BackupError::SelectionNotFound(selection_id.to_string()))
431 }
432
433 pub fn delete_backup_selection(
434 &mut self,
435 plan_id: &str,
436 selection_id: &str,
437 ) -> Result<(), BackupError> {
438 let exists = self
439 .backup_selections
440 .get(selection_id)
441 .map(|s| s.backup_plan_id == plan_id)
442 .unwrap_or(false);
443 if !exists {
444 return Err(BackupError::SelectionNotFound(selection_id.to_string()));
445 }
446 self.backup_selections.remove(selection_id);
447 Ok(())
448 }
449
450 pub fn list_backup_selections(&self, plan_id: &str) -> Vec<&BackupSelectionData> {
451 self.backup_selections
452 .values()
453 .filter(|s| s.backup_plan_id == plan_id)
454 .collect()
455 }
456
457 pub fn create_recovery_point(
460 &mut self,
461 vault_name: &str,
462 vault_arn: &str,
463 resource_arn: &str,
464 resource_type: &str,
465 iam_role_arn: &str,
466 account_id: &str,
467 region: &str,
468 ) -> Result<&RecoveryPointData, BackupError> {
469 if !self.vaults.contains_key(vault_name) {
470 return Err(BackupError::VaultNotFound(vault_name.to_string()));
471 }
472
473 let recovery_point_id = Uuid::new_v4().to_string();
474 let recovery_point_arn =
475 format!("arn:aws:backup:{region}:{account_id}:recovery-point:{recovery_point_id}");
476
477 let rp = RecoveryPointData {
478 recovery_point_arn: recovery_point_arn.clone(),
479 backup_vault_name: vault_name.to_string(),
480 backup_vault_arn: vault_arn.to_string(),
481 resource_arn: resource_arn.to_string(),
482 resource_type: resource_type.to_string(),
483 iam_role_arn: iam_role_arn.to_string(),
484 status: "COMPLETED".to_string(),
485 creation_date: Utc::now(),
486 backup_size_bytes: 0,
487 account_id: account_id.to_string(),
488 };
489
490 if let Some(vault) = self.vaults.get_mut(vault_name) {
492 vault.number_of_recovery_points += 1;
493 }
494
495 self.recovery_points.insert(recovery_point_arn.clone(), rp);
496 Ok(self.recovery_points.get(&recovery_point_arn).unwrap())
497 }
498
499 pub fn describe_recovery_point(
500 &self,
501 vault_name: &str,
502 recovery_point_arn: &str,
503 ) -> Result<&RecoveryPointData, BackupError> {
504 self.recovery_points
505 .get(recovery_point_arn)
506 .filter(|rp| rp.backup_vault_name == vault_name)
507 .ok_or_else(|| BackupError::RecoveryPointNotFound(recovery_point_arn.to_string()))
508 }
509
510 pub fn delete_recovery_point(
511 &mut self,
512 vault_name: &str,
513 recovery_point_arn: &str,
514 ) -> Result<(), BackupError> {
515 let exists = self
516 .recovery_points
517 .get(recovery_point_arn)
518 .map(|rp| rp.backup_vault_name == vault_name)
519 .unwrap_or(false);
520 if !exists {
521 return Err(BackupError::RecoveryPointNotFound(
522 recovery_point_arn.to_string(),
523 ));
524 }
525 self.recovery_points.remove(recovery_point_arn);
526 if let Some(vault) = self.vaults.get_mut(vault_name) {
528 vault.number_of_recovery_points = vault.number_of_recovery_points.saturating_sub(1);
529 }
530 Ok(())
531 }
532
533 pub fn list_recovery_points_by_backup_vault(
534 &self,
535 vault_name: &str,
536 ) -> Vec<&RecoveryPointData> {
537 self.recovery_points
538 .values()
539 .filter(|rp| rp.backup_vault_name == vault_name)
540 .collect()
541 }
542
543 pub fn start_backup_job(
546 &mut self,
547 vault_name: &str,
548 vault_arn: &str,
549 resource_arn: &str,
550 resource_type: &str,
551 iam_role_arn: &str,
552 account_id: &str,
553 region: &str,
554 ) -> Result<&BackupJobData, BackupError> {
555 if !self.vaults.contains_key(vault_name) {
556 return Err(BackupError::VaultNotFound(vault_name.to_string()));
557 }
558
559 let job_id = Uuid::new_v4().to_string();
560 let recovery_point_id = Uuid::new_v4().to_string();
561 let recovery_point_arn =
562 format!("arn:aws:backup:{region}:{account_id}:recovery-point:{recovery_point_id}");
563
564 let job = BackupJobData {
565 backup_job_id: job_id.clone(),
566 backup_vault_name: vault_name.to_string(),
567 backup_vault_arn: vault_arn.to_string(),
568 recovery_point_arn: recovery_point_arn.clone(),
569 resource_arn: resource_arn.to_string(),
570 resource_type: resource_type.to_string(),
571 iam_role_arn: iam_role_arn.to_string(),
572 state: "RUNNING".to_string(),
573 creation_date: Utc::now(),
574 completion_date: None,
575 account_id: account_id.to_string(),
576 };
577
578 self.backup_jobs.insert(job_id.clone(), job);
579 Ok(self.backup_jobs.get(&job_id).unwrap())
580 }
581
582 pub fn describe_backup_job(&self, job_id: &str) -> Result<&BackupJobData, BackupError> {
583 self.backup_jobs
584 .get(job_id)
585 .ok_or_else(|| BackupError::BackupJobNotFound(job_id.to_string()))
586 }
587
588 pub fn stop_backup_job(&mut self, job_id: &str) -> Result<(), BackupError> {
589 let job = self
590 .backup_jobs
591 .get_mut(job_id)
592 .ok_or_else(|| BackupError::BackupJobNotFound(job_id.to_string()))?;
593 if job.state == "COMPLETED" || job.state == "ABORTED" {
594 return Err(BackupError::BackupJobNotCancellable(job_id.to_string()));
595 }
596 job.state = "ABORTED".to_string();
597 job.completion_date = Some(Utc::now());
598 Ok(())
599 }
600
601 pub fn list_backup_jobs(&self) -> Vec<&BackupJobData> {
602 self.backup_jobs.values().collect()
603 }
604
605 pub fn put_backup_vault_access_policy(
608 &mut self,
609 vault_name: &str,
610 vault_arn: &str,
611 policy: &str,
612 ) -> Result<(), BackupError> {
613 if !self.vaults.contains_key(vault_name) {
614 return Err(BackupError::VaultNotFound(vault_name.to_string()));
615 }
616 self.vault_access_policies.insert(
617 vault_name.to_string(),
618 VaultAccessPolicy {
619 backup_vault_name: vault_name.to_string(),
620 backup_vault_arn: vault_arn.to_string(),
621 policy: policy.to_string(),
622 },
623 );
624 Ok(())
625 }
626
627 pub fn get_backup_vault_access_policy(
628 &self,
629 vault_name: &str,
630 ) -> Result<&VaultAccessPolicy, BackupError> {
631 self.vault_access_policies
632 .get(vault_name)
633 .ok_or_else(|| BackupError::VaultAccessPolicyNotFound(vault_name.to_string()))
634 }
635
636 pub fn delete_backup_vault_access_policy(
637 &mut self,
638 vault_name: &str,
639 ) -> Result<(), BackupError> {
640 if !self.vaults.contains_key(vault_name) {
641 return Err(BackupError::VaultNotFound(vault_name.to_string()));
642 }
643 self.vault_access_policies.remove(vault_name);
644 Ok(())
645 }
646
647 pub fn put_backup_vault_notifications(
650 &mut self,
651 vault_name: &str,
652 vault_arn: &str,
653 sns_topic_arn: &str,
654 backup_vault_events: Vec<String>,
655 ) -> Result<(), BackupError> {
656 if !self.vaults.contains_key(vault_name) {
657 return Err(BackupError::VaultNotFound(vault_name.to_string()));
658 }
659 self.vault_notifications.insert(
660 vault_name.to_string(),
661 VaultNotificationConfig {
662 backup_vault_name: vault_name.to_string(),
663 backup_vault_arn: vault_arn.to_string(),
664 sns_topic_arn: sns_topic_arn.to_string(),
665 backup_vault_events,
666 },
667 );
668 Ok(())
669 }
670
671 pub fn get_backup_vault_notifications(
672 &self,
673 vault_name: &str,
674 ) -> Result<&VaultNotificationConfig, BackupError> {
675 self.vault_notifications
676 .get(vault_name)
677 .ok_or_else(|| BackupError::VaultNotificationsNotFound(vault_name.to_string()))
678 }
679
680 pub fn delete_backup_vault_notifications(
681 &mut self,
682 vault_name: &str,
683 ) -> Result<(), BackupError> {
684 if !self.vaults.contains_key(vault_name) {
685 return Err(BackupError::VaultNotFound(vault_name.to_string()));
686 }
687 self.vault_notifications.remove(vault_name);
688 Ok(())
689 }
690
691 #[allow(clippy::too_many_arguments)]
694 pub fn create_framework(
695 &mut self,
696 name: &str,
697 description: &str,
698 controls: serde_json::Value,
699 region: &str,
700 account_id: &str,
701 tags: HashMap<String, String>,
702 ) -> Result<&FrameworkData, BackupError> {
703 if self.frameworks.contains_key(name) {
704 return Err(BackupError::FrameworkAlreadyExists(name.to_string()));
705 }
706
707 let arn = format!("arn:aws:backup:{region}:{account_id}:framework:{name}");
708 let num_controls = controls.as_array().map(|a| a.len() as i32).unwrap_or(0);
709
710 let framework = FrameworkData {
711 framework_name: name.to_string(),
712 framework_arn: arn.clone(),
713 framework_description: description.to_string(),
714 framework_controls: controls,
715 creation_time: Utc::now(),
716 deployment_status: "COMPLETED".to_string(),
717 number_of_controls: num_controls,
718 };
719
720 if !tags.is_empty() {
721 self.resource_tags.insert(arn, tags);
722 }
723
724 self.frameworks.insert(name.to_string(), framework);
725 Ok(self.frameworks.get(name).unwrap())
726 }
727
728 pub fn describe_framework(&self, name: &str) -> Result<&FrameworkData, BackupError> {
729 self.frameworks
730 .get(name)
731 .ok_or_else(|| BackupError::FrameworkNotFound(name.to_string()))
732 }
733
734 pub fn delete_framework(&mut self, name: &str) -> Result<(), BackupError> {
735 if self.frameworks.remove(name).is_none() {
736 return Err(BackupError::FrameworkNotFound(name.to_string()));
737 }
738 Ok(())
739 }
740
741 pub fn update_framework(
742 &mut self,
743 name: &str,
744 description: Option<&str>,
745 controls: Option<serde_json::Value>,
746 ) -> Result<&FrameworkData, BackupError> {
747 let framework = self
748 .frameworks
749 .get_mut(name)
750 .ok_or_else(|| BackupError::FrameworkNotFound(name.to_string()))?;
751 if let Some(desc) = description {
752 framework.framework_description = desc.to_string();
753 }
754 if let Some(c) = controls {
755 framework.number_of_controls = c.as_array().map(|a| a.len() as i32).unwrap_or(0);
756 framework.framework_controls = c;
757 }
758 Ok(self.frameworks.get(name).unwrap())
759 }
760
761 pub fn list_frameworks(&self) -> Vec<&FrameworkData> {
762 self.frameworks.values().collect()
763 }
764
765 pub fn update_global_settings(&mut self, settings: HashMap<String, String>) {
768 self.global_settings.global_settings.extend(settings);
769 }
770
771 pub fn describe_global_settings(&self) -> &GlobalSettings {
772 &self.global_settings
773 }
774
775 pub fn update_region_settings(
778 &mut self,
779 opt_in: Option<HashMap<String, bool>>,
780 management: Option<HashMap<String, bool>>,
781 ) {
782 if let Some(p) = opt_in {
783 self.region_settings
784 .resource_type_opt_in_preference
785 .extend(p);
786 }
787 if let Some(p) = management {
788 self.region_settings
789 .resource_type_management_preference
790 .extend(p);
791 }
792 }
793
794 pub fn describe_region_settings(&self) -> &RegionSettings {
795 &self.region_settings
796 }
797
798 pub fn start_report_job(
801 &mut self,
802 report_plan_name: &str,
803 region: &str,
804 account_id: &str,
805 ) -> Result<&ReportJobData, BackupError> {
806 let plan = self
807 .report_plans
808 .get(report_plan_name)
809 .ok_or_else(|| BackupError::ReportPlanNotFound(report_plan_name.to_string()))?;
810 let report_plan_arn = plan.report_plan_arn.clone();
811 let report_template = plan
812 .report_setting
813 .get("ReportTemplate")
814 .and_then(|v| v.as_str())
815 .unwrap_or("")
816 .to_string();
817
818 let job_id = Uuid::new_v4().to_string();
819 let job = ReportJobData {
820 report_job_id: job_id.clone(),
821 report_plan_arn,
822 report_template,
823 creation_time: Utc::now(),
824 completion_time: Some(Utc::now()),
825 status: "COMPLETED".to_string(),
826 };
827
828 self.report_jobs.insert(job_id.clone(), job);
829 Ok(self.report_jobs.get(&job_id).unwrap())
830 }
831
832 pub fn describe_report_job(&self, job_id: &str) -> Result<&ReportJobData, BackupError> {
833 self.report_jobs
834 .get(job_id)
835 .ok_or_else(|| BackupError::ReportJobNotFound(job_id.to_string()))
836 }
837
838 pub fn list_report_jobs(&self, report_plan_name: Option<&str>) -> Vec<&ReportJobData> {
839 self.report_jobs
840 .values()
841 .filter(|j| {
842 if let Some(name) = report_plan_name {
843 j.report_plan_arn.ends_with(&format!(":{name}"))
844 } else {
845 true
846 }
847 })
848 .collect()
849 }
850
851 pub fn start_scan_job(
854 &mut self,
855 vault_name: &str,
856 vault_arn: &str,
857 recovery_point_arn: &str,
858 iam_role_arn: &str,
859 malware_scanner: &str,
860 scan_mode: &str,
861 scanner_role_arn: &str,
862 scan_base_recovery_point_arn: Option<String>,
863 account_id: &str,
864 region: &str,
865 ) -> Result<&ScanJobData, BackupError> {
866 let scan_job_id = Uuid::new_v4().to_string();
867 let job = ScanJobData {
868 scan_job_id: scan_job_id.clone(),
869 backup_vault_name: vault_name.to_string(),
870 backup_vault_arn: vault_arn.to_string(),
871 recovery_point_arn: recovery_point_arn.to_string(),
872 iam_role_arn: iam_role_arn.to_string(),
873 malware_scanner: malware_scanner.to_string(),
874 scan_mode: scan_mode.to_string(),
875 scanner_role_arn: scanner_role_arn.to_string(),
876 scan_base_recovery_point_arn,
877 state: "RUNNING".to_string(),
878 creation_date: Utc::now(),
879 completion_date: None,
880 account_id: account_id.to_string(),
881 };
882 self.scan_jobs.insert(scan_job_id.clone(), job);
883 Ok(self.scan_jobs.get(&scan_job_id).unwrap())
884 }
885
886 pub fn describe_scan_job(&self, scan_job_id: &str) -> Result<&ScanJobData, BackupError> {
887 self.scan_jobs
888 .get(scan_job_id)
889 .ok_or_else(|| BackupError::ScanJobNotFound(scan_job_id.to_string()))
890 }
891
892 pub fn list_scan_jobs(&self) -> Vec<&ScanJobData> {
893 self.scan_jobs.values().collect()
894 }
895
896 pub fn create_tiering_configuration(
899 &mut self,
900 name: &str,
901 vault_name: &str,
902 resource_selection: serde_json::Value,
903 creator_request_id: Option<String>,
904 region: &str,
905 account_id: &str,
906 tags: HashMap<String, String>,
907 ) -> Result<&TieringConfigData, BackupError> {
908 if self.tiering_configs.contains_key(name) {
909 return Err(BackupError::TieringConfigAlreadyExists(name.to_string()));
910 }
911
912 let arn = format!("arn:aws:backup:{region}:{account_id}:tiering-configuration:{name}");
913 let now = Utc::now();
914
915 let config = TieringConfigData {
916 tiering_configuration_name: name.to_string(),
917 tiering_configuration_arn: arn.clone(),
918 backup_vault_name: vault_name.to_string(),
919 resource_selection,
920 creation_time: now,
921 last_updated_time: now,
922 creator_request_id,
923 tags: tags.clone(),
924 };
925
926 if !tags.is_empty() {
927 self.resource_tags.insert(arn, tags);
928 }
929
930 self.tiering_configs.insert(name.to_string(), config);
931 Ok(self.tiering_configs.get(name).unwrap())
932 }
933
934 pub fn get_tiering_configuration(&self, name: &str) -> Result<&TieringConfigData, BackupError> {
935 self.tiering_configs
936 .get(name)
937 .ok_or_else(|| BackupError::TieringConfigNotFound(name.to_string()))
938 }
939
940 pub fn delete_tiering_configuration(&mut self, name: &str) -> Result<(), BackupError> {
941 if self.tiering_configs.remove(name).is_none() {
942 return Err(BackupError::TieringConfigNotFound(name.to_string()));
943 }
944 Ok(())
945 }
946
947 pub fn list_tiering_configurations(&self) -> Vec<&TieringConfigData> {
948 self.tiering_configs.values().collect()
949 }
950
951 pub fn update_tiering_configuration(
952 &mut self,
953 name: &str,
954 vault_name: Option<&str>,
955 resource_selection: Option<serde_json::Value>,
956 ) -> Result<&TieringConfigData, BackupError> {
957 let config = self
958 .tiering_configs
959 .get_mut(name)
960 .ok_or_else(|| BackupError::TieringConfigNotFound(name.to_string()))?;
961 if let Some(vn) = vault_name {
962 config.backup_vault_name = vn.to_string();
963 }
964 if let Some(rs) = resource_selection {
965 config.resource_selection = rs;
966 }
967 config.last_updated_time = Utc::now();
968 Ok(self.tiering_configs.get(name).unwrap())
969 }
970
971 pub fn update_report_plan(
974 &mut self,
975 name: &str,
976 description: Option<&str>,
977 delivery_channel: Option<serde_json::Value>,
978 report_setting: Option<serde_json::Value>,
979 ) -> Result<&ReportPlanData, BackupError> {
980 let plan = self
981 .report_plans
982 .get_mut(name)
983 .ok_or_else(|| BackupError::ReportPlanNotFound(name.to_string()))?;
984 if let Some(desc) = description {
985 plan.report_plan_description = desc.to_string();
986 }
987 if let Some(dc) = delivery_channel {
988 plan.report_delivery_channel = dc;
989 }
990 if let Some(rs) = report_setting {
991 plan.report_setting = rs;
992 }
993 Ok(self.report_plans.get(name).unwrap())
994 }
995
996 pub fn create_legal_hold(
999 &mut self,
1000 title: &str,
1001 description: &str,
1002 recovery_point_selection: serde_json::Value,
1003 region: &str,
1004 account_id: &str,
1005 tags: HashMap<String, String>,
1006 ) -> Result<&LegalHoldData, BackupError> {
1007 let legal_hold_id = Uuid::new_v4().to_string();
1008 let arn = format!("arn:aws:backup:{region}:{account_id}:legal-hold:{legal_hold_id}");
1009
1010 let hold = LegalHoldData {
1011 legal_hold_id: legal_hold_id.clone(),
1012 legal_hold_arn: arn.clone(),
1013 title: title.to_string(),
1014 description: description.to_string(),
1015 status: "ACTIVE".to_string(),
1016 creation_date: Utc::now(),
1017 cancellation_date: None,
1018 recovery_point_selection,
1019 tags: tags.clone(),
1020 };
1021
1022 if !tags.is_empty() {
1023 self.resource_tags.insert(arn, tags);
1024 }
1025
1026 self.legal_holds.insert(legal_hold_id.clone(), hold);
1027 Ok(self.legal_holds.get(&legal_hold_id).unwrap())
1028 }
1029
1030 pub fn cancel_legal_hold(&mut self, legal_hold_id: &str) -> Result<(), BackupError> {
1031 let hold = self
1032 .legal_holds
1033 .get_mut(legal_hold_id)
1034 .ok_or_else(|| BackupError::LegalHoldNotFound(legal_hold_id.to_string()))?;
1035 hold.status = "CANCELED".to_string();
1036 hold.cancellation_date = Some(Utc::now());
1037 Ok(())
1038 }
1039
1040 pub fn get_legal_hold(&self, legal_hold_id: &str) -> Result<&LegalHoldData, BackupError> {
1041 self.legal_holds
1042 .get(legal_hold_id)
1043 .ok_or_else(|| BackupError::LegalHoldNotFound(legal_hold_id.to_string()))
1044 }
1045
1046 pub fn list_legal_holds(&self) -> Vec<&LegalHoldData> {
1047 self.legal_holds.values().collect()
1048 }
1049
1050 pub fn start_copy_job(
1053 &mut self,
1054 source_backup_vault_name: &str,
1055 source_recovery_point_arn: &str,
1056 destination_backup_vault_arn: &str,
1057 iam_role_arn: &str,
1058 account_id: &str,
1059 region: &str,
1060 ) -> Result<&CopyJobData, BackupError> {
1061 let copy_job_id = Uuid::new_v4().to_string();
1062 let source_vault_arn =
1063 format!("arn:aws:backup:{region}:{account_id}:backup-vault:{source_backup_vault_name}");
1064 let dest_rp_id = Uuid::new_v4().to_string();
1065 let dest_rp_arn =
1066 format!("arn:aws:backup:{region}:{account_id}:recovery-point:{dest_rp_id}");
1067
1068 let job = CopyJobData {
1069 copy_job_id: copy_job_id.clone(),
1070 source_backup_vault_name: source_backup_vault_name.to_string(),
1071 source_backup_vault_arn: source_vault_arn,
1072 source_recovery_point_arn: source_recovery_point_arn.to_string(),
1073 destination_backup_vault_arn: destination_backup_vault_arn.to_string(),
1074 destination_recovery_point_arn: dest_rp_arn,
1075 resource_arn: String::new(),
1076 resource_type: String::new(),
1077 iam_role_arn: iam_role_arn.to_string(),
1078 state: "COMPLETED".to_string(),
1079 creation_date: Utc::now(),
1080 completion_date: Some(Utc::now()),
1081 account_id: account_id.to_string(),
1082 };
1083
1084 self.copy_jobs.insert(copy_job_id.clone(), job);
1085 Ok(self.copy_jobs.get(©_job_id).unwrap())
1086 }
1087
1088 pub fn describe_copy_job(&self, copy_job_id: &str) -> Result<&CopyJobData, BackupError> {
1089 self.copy_jobs
1090 .get(copy_job_id)
1091 .ok_or_else(|| BackupError::CopyJobNotFound(copy_job_id.to_string()))
1092 }
1093
1094 pub fn list_copy_jobs(&self) -> Vec<&CopyJobData> {
1095 self.copy_jobs.values().collect()
1096 }
1097
1098 pub fn start_restore_job(
1101 &mut self,
1102 recovery_point_arn: &str,
1103 iam_role_arn: &str,
1104 resource_type: &str,
1105 metadata: HashMap<String, String>,
1106 account_id: &str,
1107 ) -> Result<&RestoreJobData, BackupError> {
1108 let restore_job_id = Uuid::new_v4().to_string();
1109
1110 let job = RestoreJobData {
1111 restore_job_id: restore_job_id.clone(),
1112 recovery_point_arn: recovery_point_arn.to_string(),
1113 resource_type: resource_type.to_string(),
1114 iam_role_arn: iam_role_arn.to_string(),
1115 status: "COMPLETED".to_string(),
1116 creation_date: Utc::now(),
1117 completion_date: Some(Utc::now()),
1118 backup_size_in_bytes: 0,
1119 account_id: account_id.to_string(),
1120 metadata,
1121 validation_status: None,
1122 validation_status_message: None,
1123 };
1124
1125 self.restore_jobs.insert(restore_job_id.clone(), job);
1126 Ok(self.restore_jobs.get(&restore_job_id).unwrap())
1127 }
1128
1129 pub fn describe_restore_job(
1130 &self,
1131 restore_job_id: &str,
1132 ) -> Result<&RestoreJobData, BackupError> {
1133 self.restore_jobs
1134 .get(restore_job_id)
1135 .ok_or_else(|| BackupError::RestoreJobNotFound(restore_job_id.to_string()))
1136 }
1137
1138 pub fn list_restore_jobs(&self) -> Vec<&RestoreJobData> {
1139 self.restore_jobs.values().collect()
1140 }
1141
1142 pub fn list_restore_jobs_by_recovery_point(&self, resource_arn: &str) -> Vec<&RestoreJobData> {
1143 self.restore_jobs
1144 .values()
1145 .filter(|j| j.recovery_point_arn.contains(resource_arn))
1146 .collect()
1147 }
1148
1149 pub fn put_restore_validation_result(
1150 &mut self,
1151 restore_job_id: &str,
1152 validation_status: &str,
1153 validation_status_message: Option<&str>,
1154 ) -> Result<(), BackupError> {
1155 let job = self
1156 .restore_jobs
1157 .get_mut(restore_job_id)
1158 .ok_or_else(|| BackupError::RestoreJobNotFound(restore_job_id.to_string()))?;
1159 job.validation_status = Some(validation_status.to_string());
1160 job.validation_status_message = validation_status_message.map(|s| s.to_string());
1161 Ok(())
1162 }
1163
1164 pub fn get_restore_job_metadata(
1165 &self,
1166 restore_job_id: &str,
1167 ) -> Result<&RestoreJobData, BackupError> {
1168 self.restore_jobs
1169 .get(restore_job_id)
1170 .ok_or_else(|| BackupError::RestoreJobNotFound(restore_job_id.to_string()))
1171 }
1172
1173 #[allow(clippy::too_many_arguments)]
1176 pub fn create_restore_testing_plan(
1177 &mut self,
1178 name: &str,
1179 schedule_expression: &str,
1180 schedule_expression_timezone: Option<String>,
1181 start_window_hours: Option<i32>,
1182 recovery_point_selection: serde_json::Value,
1183 creator_request_id: Option<String>,
1184 region: &str,
1185 account_id: &str,
1186 tags: HashMap<String, String>,
1187 ) -> Result<&RestoreTestingPlanData, BackupError> {
1188 if self.restore_testing_plans.contains_key(name) {
1189 return Err(BackupError::RestoreTestingPlanAlreadyExists(
1190 name.to_string(),
1191 ));
1192 }
1193
1194 let arn = format!("arn:aws:backup:{region}:{account_id}:restore-testing-plan:{name}");
1195 let now = Utc::now();
1196 let plan = RestoreTestingPlanData {
1197 restore_testing_plan_name: name.to_string(),
1198 restore_testing_plan_arn: arn.clone(),
1199 schedule_expression: schedule_expression.to_string(),
1200 schedule_expression_timezone,
1201 start_window_hours,
1202 recovery_point_selection,
1203 creator_request_id,
1204 creation_time: now,
1205 last_update_time: now,
1206 tags: tags.clone(),
1207 };
1208
1209 if !tags.is_empty() {
1210 self.resource_tags.insert(arn, tags);
1211 }
1212
1213 self.restore_testing_plans.insert(name.to_string(), plan);
1214 Ok(self.restore_testing_plans.get(name).unwrap())
1215 }
1216
1217 pub fn get_restore_testing_plan(
1218 &self,
1219 name: &str,
1220 ) -> Result<&RestoreTestingPlanData, BackupError> {
1221 self.restore_testing_plans
1222 .get(name)
1223 .ok_or_else(|| BackupError::RestoreTestingPlanNotFound(name.to_string()))
1224 }
1225
1226 pub fn delete_restore_testing_plan(&mut self, name: &str) -> Result<(), BackupError> {
1227 if self.restore_testing_plans.remove(name).is_none() {
1228 return Err(BackupError::RestoreTestingPlanNotFound(name.to_string()));
1229 }
1230 self.restore_testing_selections
1232 .retain(|(pn, _), _| pn != name);
1233 Ok(())
1234 }
1235
1236 pub fn list_restore_testing_plans(&self) -> Vec<&RestoreTestingPlanData> {
1237 self.restore_testing_plans.values().collect()
1238 }
1239
1240 pub fn update_restore_testing_plan(
1241 &mut self,
1242 name: &str,
1243 schedule_expression: Option<&str>,
1244 schedule_expression_timezone: Option<String>,
1245 start_window_hours: Option<i32>,
1246 recovery_point_selection: Option<serde_json::Value>,
1247 ) -> Result<&RestoreTestingPlanData, BackupError> {
1248 let plan = self
1249 .restore_testing_plans
1250 .get_mut(name)
1251 .ok_or_else(|| BackupError::RestoreTestingPlanNotFound(name.to_string()))?;
1252 if let Some(se) = schedule_expression {
1253 plan.schedule_expression = se.to_string();
1254 }
1255 if let Some(tz) = schedule_expression_timezone {
1256 plan.schedule_expression_timezone = Some(tz);
1257 }
1258 if let Some(swh) = start_window_hours {
1259 plan.start_window_hours = Some(swh);
1260 }
1261 if let Some(rps) = recovery_point_selection {
1262 plan.recovery_point_selection = rps;
1263 }
1264 plan.last_update_time = Utc::now();
1265 Ok(self.restore_testing_plans.get(name).unwrap())
1266 }
1267
1268 #[allow(clippy::too_many_arguments)]
1271 pub fn create_restore_testing_selection(
1272 &mut self,
1273 plan_name: &str,
1274 selection_name: &str,
1275 iam_role_arn: &str,
1276 protected_resource_type: &str,
1277 protected_resource_arns: Vec<String>,
1278 protected_resource_conditions: serde_json::Value,
1279 restore_metadata_overrides: HashMap<String, String>,
1280 validation_window_hours: Option<i32>,
1281 creator_request_id: Option<String>,
1282 ) -> Result<&RestoreTestingSelectionData, BackupError> {
1283 let plan = self
1284 .restore_testing_plans
1285 .get(plan_name)
1286 .ok_or_else(|| BackupError::RestoreTestingPlanNotFound(plan_name.to_string()))?;
1287 let plan_arn = plan.restore_testing_plan_arn.clone();
1288
1289 let key = (plan_name.to_string(), selection_name.to_string());
1290 if self.restore_testing_selections.contains_key(&key) {
1291 return Err(BackupError::RestoreTestingSelectionAlreadyExists(
1292 selection_name.to_string(),
1293 ));
1294 }
1295
1296 let now = Utc::now();
1297 let sel = RestoreTestingSelectionData {
1298 restore_testing_selection_name: selection_name.to_string(),
1299 restore_testing_plan_name: plan_name.to_string(),
1300 restore_testing_plan_arn: plan_arn,
1301 iam_role_arn: iam_role_arn.to_string(),
1302 protected_resource_type: protected_resource_type.to_string(),
1303 protected_resource_arns,
1304 protected_resource_conditions,
1305 restore_metadata_overrides,
1306 validation_window_hours,
1307 creator_request_id,
1308 creation_time: now,
1309 last_update_time: now,
1310 };
1311
1312 self.restore_testing_selections.insert(key.clone(), sel);
1313 Ok(self.restore_testing_selections.get(&key).unwrap())
1314 }
1315
1316 pub fn get_restore_testing_selection(
1317 &self,
1318 plan_name: &str,
1319 selection_name: &str,
1320 ) -> Result<&RestoreTestingSelectionData, BackupError> {
1321 let key = (plan_name.to_string(), selection_name.to_string());
1322 self.restore_testing_selections
1323 .get(&key)
1324 .ok_or_else(|| BackupError::RestoreTestingSelectionNotFound(selection_name.to_string()))
1325 }
1326
1327 pub fn delete_restore_testing_selection(
1328 &mut self,
1329 plan_name: &str,
1330 selection_name: &str,
1331 ) -> Result<(), BackupError> {
1332 let key = (plan_name.to_string(), selection_name.to_string());
1333 if self.restore_testing_selections.remove(&key).is_none() {
1334 return Err(BackupError::RestoreTestingSelectionNotFound(
1335 selection_name.to_string(),
1336 ));
1337 }
1338 Ok(())
1339 }
1340
1341 pub fn list_restore_testing_selections(
1342 &self,
1343 plan_name: &str,
1344 ) -> Vec<&RestoreTestingSelectionData> {
1345 self.restore_testing_selections
1346 .values()
1347 .filter(|s| s.restore_testing_plan_name == plan_name)
1348 .collect()
1349 }
1350
1351 pub fn update_restore_testing_selection(
1352 &mut self,
1353 plan_name: &str,
1354 selection_name: &str,
1355 iam_role_arn: Option<&str>,
1356 protected_resource_arns: Option<Vec<String>>,
1357 protected_resource_conditions: Option<serde_json::Value>,
1358 restore_metadata_overrides: Option<HashMap<String, String>>,
1359 validation_window_hours: Option<i32>,
1360 ) -> Result<&RestoreTestingSelectionData, BackupError> {
1361 let key = (plan_name.to_string(), selection_name.to_string());
1362 let sel = self
1363 .restore_testing_selections
1364 .get_mut(&key)
1365 .ok_or_else(|| {
1366 BackupError::RestoreTestingSelectionNotFound(selection_name.to_string())
1367 })?;
1368 if let Some(role) = iam_role_arn {
1369 sel.iam_role_arn = role.to_string();
1370 }
1371 if let Some(arns) = protected_resource_arns {
1372 sel.protected_resource_arns = arns;
1373 }
1374 if let Some(conds) = protected_resource_conditions {
1375 sel.protected_resource_conditions = conds;
1376 }
1377 if let Some(overrides) = restore_metadata_overrides {
1378 sel.restore_metadata_overrides = overrides;
1379 }
1380 if let Some(vwh) = validation_window_hours {
1381 sel.validation_window_hours = Some(vwh);
1382 }
1383 sel.last_update_time = Utc::now();
1384 Ok(self.restore_testing_selections.get(&key).unwrap())
1385 }
1386
1387 pub fn update_backup_plan(
1390 &mut self,
1391 plan_id: &str,
1392 backup_plan_json: &serde_json::Value,
1393 ) -> Result<&BackupPlanData, BackupError> {
1394 let plan = self
1395 .backup_plans
1396 .get_mut(plan_id)
1397 .ok_or_else(|| BackupError::PlanNotFound(plan_id.to_string()))?;
1398
1399 let enriched_json =
1401 if let Some(rules_arr) = backup_plan_json.get("Rules").and_then(|v| v.as_array()) {
1402 let enriched_rules: Vec<serde_json::Value> = rules_arr
1403 .iter()
1404 .map(|rule| {
1405 let mut r = rule.clone();
1406 if let serde_json::Value::Object(ref mut map) = r {
1407 map.entry("RuleId").or_insert_with(|| {
1408 serde_json::Value::String(Uuid::new_v4().to_string())
1409 });
1410 }
1411 r
1412 })
1413 .collect();
1414 let mut enriched = backup_plan_json.clone();
1415 if let serde_json::Value::Object(ref mut map) = enriched {
1416 map.insert(
1417 "Rules".to_string(),
1418 serde_json::Value::Array(enriched_rules),
1419 );
1420 }
1421 enriched
1422 } else {
1423 backup_plan_json.clone()
1424 };
1425
1426 plan.backup_plan_json = enriched_json;
1427 plan.version_id = Uuid::new_v4().to_string();
1428 if let Some(name) = backup_plan_json
1429 .get("BackupPlanName")
1430 .and_then(|v| v.as_str())
1431 {
1432 plan.backup_plan_name = name.to_string();
1433 }
1434
1435 Ok(self.backup_plans.get(plan_id).unwrap())
1436 }
1437}