coil_ops/recovery/
catalog.rs1use std::collections::BTreeSet;
2use std::time::Duration;
3
4use coil_auth::Capability;
5use coil_jobs::RetryPolicy;
6
7use crate::error::OpsModelError;
8use crate::identifiers::RecoveryWorkflowId;
9
10use super::{RecoveryStage, RecoveryWorkflowDefinition};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct RecoveryCatalog {
14 definitions: Vec<RecoveryWorkflowDefinition>,
15}
16
17impl RecoveryCatalog {
18 pub fn new(definitions: Vec<RecoveryWorkflowDefinition>) -> Self {
19 Self { definitions }
20 }
21
22 pub fn standard() -> Self {
23 Self::new(vec![
24 RecoveryWorkflowDefinition::new(
25 RecoveryWorkflowId::new("recovery.customer-app.full-restore")
26 .expect("constant workflow id is valid"),
27 "Full customer-app restore",
28 Some(
29 "Restores source-of-truth state first, then rebuilds disposable layers and redeploys assets."
30 .to_string(),
31 ),
32 Capability::SystemModuleManage,
33 default_recovery_retry_policy(),
34 true,
35 true,
36 vec![
37 RecoveryStage::RestoreDatabase,
38 RecoveryStage::ReattachManagedObjectStore,
39 RecoveryStage::RestoreLocalOnlySensitive,
40 RecoveryStage::RebuildDerivedState,
41 RecoveryStage::RedeployStaticAssets,
42 RecoveryStage::ValidateReadiness,
43 ],
44 )
45 .expect("constant recovery workflow is valid"),
46 RecoveryWorkflowDefinition::new(
47 RecoveryWorkflowId::new("recovery.customer-app.derived-state")
48 .expect("constant workflow id is valid"),
49 "Derived-state rebuild",
50 Some(
51 "Rebuilds caches, search indexes, report snapshots, and redeploys static assets without restoring source-of-truth storage."
52 .to_string(),
53 ),
54 Capability::SystemModuleManage,
55 default_recovery_retry_policy(),
56 true,
57 false,
58 vec![
59 RecoveryStage::RebuildDerivedState,
60 RecoveryStage::RedeployStaticAssets,
61 RecoveryStage::ValidateReadiness,
62 ],
63 )
64 .expect("constant recovery workflow is valid"),
65 ])
66 }
67
68 pub fn definitions(&self) -> &[RecoveryWorkflowDefinition] {
69 &self.definitions
70 }
71
72 pub fn definition(&self, id: &RecoveryWorkflowId) -> Option<&RecoveryWorkflowDefinition> {
73 self.definitions
74 .iter()
75 .find(|definition| &definition.id == id)
76 }
77
78 pub fn validate(&self) -> Result<(), OpsModelError> {
79 let mut ids = BTreeSet::new();
80 for definition in &self.definitions {
81 if !ids.insert(definition.id.as_str().to_string()) {
82 return Err(OpsModelError::DuplicateIdentifier {
83 kind: "recovery workflow",
84 id: definition.id.to_string(),
85 });
86 }
87 }
88 Ok(())
89 }
90}
91
92impl Default for RecoveryCatalog {
93 fn default() -> Self {
94 Self::standard()
95 }
96}
97
98fn default_recovery_retry_policy() -> RetryPolicy {
99 RetryPolicy::new(3, Duration::from_secs(30), Duration::from_secs(900))
100 .expect("constant recovery retry policy is valid")
101}