Skip to main content

fakecloud_secretsmanager/
state.rs

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 Secret {
8    pub name: String,
9    pub arn: String,
10    pub description: Option<String>,
11    pub kms_key_id: Option<String>,
12    pub versions: BTreeMap<String, SecretVersion>,
13    pub current_version_id: Option<String>,
14    pub tags: Vec<(String, String)>,
15    pub tags_ever_set: bool,
16    pub deleted: bool,
17    pub deletion_date: Option<DateTime<Utc>>,
18    pub created_at: DateTime<Utc>,
19    pub last_changed_at: DateTime<Utc>,
20    pub last_accessed_at: Option<DateTime<Utc>>,
21    pub rotation_enabled: Option<bool>,
22    pub rotation_lambda_arn: Option<String>,
23    pub rotation_rules: Option<RotationRules>,
24    pub last_rotated_at: Option<DateTime<Utc>>,
25    pub resource_policy: Option<String>,
26    /// Replica regions added via ReplicateSecretToRegions. Reflected in
27    /// ReplicationStatus on describe/replicate responses.
28    #[serde(default)]
29    pub replica_regions: Vec<String>,
30}
31
32#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct RotationRules {
34    pub automatically_after_days: Option<i64>,
35    /// The length of the rotation window, e.g. "2h" / "1d". AWS's modern
36    /// rotation scheduling form alongside ScheduleExpression.
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub duration: Option<String>,
39    /// A cron() or rate() expression for the rotation schedule.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub schedule_expression: Option<String>,
42}
43
44#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
45pub struct SecretVersion {
46    pub version_id: String,
47    pub secret_string: Option<String>,
48    pub secret_binary: Option<Vec<u8>>,
49    pub stages: Vec<String>,
50    pub created_at: DateTime<Utc>,
51}
52
53#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
54pub struct SecretsManagerState {
55    pub account_id: String,
56    pub region: String,
57    pub secrets: BTreeMap<String, Secret>,
58}
59
60impl SecretsManagerState {
61    pub fn new(account_id: &str, region: &str) -> Self {
62        Self {
63            account_id: account_id.to_string(),
64            region: region.to_string(),
65            secrets: BTreeMap::new(),
66        }
67    }
68
69    pub fn reset(&mut self) {
70        self.secrets.clear();
71    }
72}
73
74pub type SharedSecretsManagerState =
75    Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<SecretsManagerState>>>;
76
77impl fakecloud_core::multi_account::AccountState for SecretsManagerState {
78    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
79        Self::new(account_id, region)
80    }
81}
82
83/// On-disk snapshot envelope for Secrets Manager state. Versioned so
84/// format changes fail loudly on upgrade.
85#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
86pub struct SecretsManagerSnapshot {
87    pub schema_version: u32,
88    #[serde(default)]
89    pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<SecretsManagerState>>,
90    #[serde(default)]
91    pub state: Option<SecretsManagerState>,
92}
93
94pub const SECRETSMANAGER_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn new_initializes_empty() {
102        let state = SecretsManagerState::new("123456789012", "us-east-1");
103        assert_eq!(state.account_id, "123456789012");
104        assert_eq!(state.region, "us-east-1");
105        assert!(state.secrets.is_empty());
106    }
107
108    #[test]
109    fn reset_clears_secrets() {
110        let mut state = SecretsManagerState::new("123456789012", "us-east-1");
111        state.secrets.insert(
112            "s1".to_string(),
113            Secret {
114                name: "s1".to_string(),
115                arn: "arn".to_string(),
116                description: None,
117                kms_key_id: None,
118                versions: BTreeMap::new(),
119                current_version_id: None,
120                tags: vec![],
121                tags_ever_set: false,
122                deleted: false,
123                deletion_date: None,
124                created_at: Utc::now(),
125                last_changed_at: Utc::now(),
126                last_accessed_at: None,
127                rotation_enabled: None,
128                rotation_lambda_arn: None,
129                rotation_rules: None,
130                last_rotated_at: None,
131                resource_policy: None,
132                replica_regions: Vec::new(),
133            },
134        );
135        state.reset();
136        assert!(state.secrets.is_empty());
137    }
138}