winterbaume_secretsmanager/
views.rs1use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
8
9mod opt_hex {
11 use super::*;
12
13 pub fn serialize<S>(value: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
14 where
15 S: Serializer,
16 {
17 match value {
18 Some(bytes) => {
19 let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
20 serializer.serialize_some(&hex)
21 }
22 None => serializer.serialize_none(),
23 }
24 }
25
26 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
27 where
28 D: Deserializer<'de>,
29 {
30 let opt: Option<String> = Option::deserialize(deserializer)?;
31 match opt {
32 None => Ok(None),
33 Some(hex) => {
34 if hex.len() % 2 != 0 {
35 return Err(serde::de::Error::custom("odd-length hex string"));
36 }
37 let bytes = (0..hex.len())
38 .step_by(2)
39 .map(|i| u8::from_str_radix(&hex[i..i + 2], 16))
40 .collect::<Result<Vec<u8>, _>>()
41 .map_err(|e| serde::de::Error::custom(format!("invalid hex: {e}")))?;
42 Ok(Some(bytes))
43 }
44 }
45 }
46}
47
48use crate::handlers::SecretsManagerService;
49use crate::state::SecretsManagerState;
50use crate::types::{ReplicationStatus, RotationRules, Secret, SecretVersion};
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct SecretsmanagerStateView {
55 #[serde(default)]
57 pub secrets: HashMap<String, SecretView>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct SecretView {
63 pub name: String,
64 pub arn: String,
65 pub description: String,
66 pub created_date: String,
67 pub last_changed_date: String,
68 #[serde(default)]
69 pub versions: HashMap<String, SecretVersionView>,
70 pub current_version_id: Option<String>,
71 pub deleted_date: Option<String>,
72 #[serde(default)]
73 pub tags: HashMap<String, String>,
74 pub resource_policy: Option<String>,
75 pub rotation_enabled: Option<bool>,
76 pub rotation_lambda_arn: Option<String>,
77 pub rotation_rules: Option<RotationRulesView>,
78 pub last_rotated_date: Option<String>,
79 #[serde(default)]
80 pub replication_status: Vec<ReplicationStatusView>,
81 pub primary_region: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct SecretVersionView {
87 pub version_id: String,
88 pub secret_string: Option<String>,
89 #[serde(default, with = "opt_hex")]
90 pub secret_binary: Option<Vec<u8>>,
91 pub created_date: String,
92 #[serde(default)]
93 pub version_stages: Vec<String>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct RotationRulesView {
99 pub automatically_after_days: Option<i64>,
100 pub duration: Option<String>,
101 pub schedule_expression: Option<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ReplicationStatusView {
107 pub region: String,
108 pub status: String,
109 pub status_message: Option<String>,
110 pub kms_key_id: Option<String>,
111 pub last_accessed_date: Option<String>,
112}
113
114impl From<&SecretsManagerState> for SecretsmanagerStateView {
117 fn from(state: &SecretsManagerState) -> Self {
118 SecretsmanagerStateView {
119 secrets: state
120 .secrets
121 .iter()
122 .map(|(k, v)| (k.clone(), SecretView::from(v)))
123 .collect(),
124 }
125 }
126}
127
128impl From<&Secret> for SecretView {
129 fn from(s: &Secret) -> Self {
130 SecretView {
131 name: s.name.clone(),
132 arn: s.arn.clone(),
133 description: s.description.clone(),
134 created_date: s.created_date.to_rfc3339(),
135 last_changed_date: s.last_changed_date.to_rfc3339(),
136 versions: s
137 .versions
138 .iter()
139 .map(|(k, v)| (k.clone(), SecretVersionView::from(v)))
140 .collect(),
141 current_version_id: s.current_version_id.clone(),
142 deleted_date: s.deleted_date.map(|d| d.to_rfc3339()),
143 tags: s.tags.clone(),
144 resource_policy: s.resource_policy.clone(),
145 rotation_enabled: s.rotation_enabled,
146 rotation_lambda_arn: s.rotation_lambda_arn.clone(),
147 rotation_rules: s.rotation_rules.as_ref().map(RotationRulesView::from),
148 last_rotated_date: s.last_rotated_date.map(|d| d.to_rfc3339()),
149 replication_status: s
150 .replication_status
151 .iter()
152 .map(ReplicationStatusView::from)
153 .collect(),
154 primary_region: s.primary_region.clone(),
155 }
156 }
157}
158
159impl From<&SecretVersion> for SecretVersionView {
160 fn from(v: &SecretVersion) -> Self {
161 SecretVersionView {
162 version_id: v.version_id.clone(),
163 secret_string: v.secret_string.clone(),
164 secret_binary: v.secret_binary.clone(),
165 created_date: v.created_date.to_rfc3339(),
166 version_stages: v.version_stages.clone(),
167 }
168 }
169}
170
171impl From<&RotationRules> for RotationRulesView {
172 fn from(r: &RotationRules) -> Self {
173 RotationRulesView {
174 automatically_after_days: r.automatically_after_days,
175 duration: r.duration.clone(),
176 schedule_expression: r.schedule_expression.clone(),
177 }
178 }
179}
180
181impl From<&ReplicationStatus> for ReplicationStatusView {
182 fn from(r: &ReplicationStatus) -> Self {
183 ReplicationStatusView {
184 region: r.region.clone(),
185 status: r.status.clone(),
186 status_message: r.status_message.clone(),
187 kms_key_id: r.kms_key_id.clone(),
188 last_accessed_date: r.last_accessed_date.map(|d| d.to_rfc3339()),
189 }
190 }
191}
192
193impl From<SecretsmanagerStateView> for SecretsManagerState {
196 fn from(view: SecretsmanagerStateView) -> Self {
197 SecretsManagerState {
198 secrets: view
199 .secrets
200 .into_iter()
201 .map(|(k, v)| (k, Secret::from(v)))
202 .collect(),
203 }
204 }
205}
206
207impl From<SecretView> for Secret {
208 fn from(v: SecretView) -> Self {
209 let parse_dt = |s: &str| -> DateTime<Utc> {
210 DateTime::parse_from_rfc3339(s)
211 .map(|dt| dt.with_timezone(&Utc))
212 .unwrap_or_else(|_| Utc::now())
213 };
214 Secret {
215 name: v.name,
216 arn: v.arn,
217 description: v.description,
218 created_date: parse_dt(&v.created_date),
219 last_changed_date: parse_dt(&v.last_changed_date),
220 versions: v
221 .versions
222 .into_iter()
223 .map(|(k, sv)| (k, SecretVersion::from(sv)))
224 .collect(),
225 current_version_id: v.current_version_id,
226 deleted_date: v.deleted_date.as_deref().map(parse_dt),
227 tags: v.tags,
228 resource_policy: v.resource_policy,
229 rotation_enabled: v.rotation_enabled,
230 rotation_lambda_arn: v.rotation_lambda_arn,
231 rotation_rules: v.rotation_rules.map(RotationRules::from),
232 last_rotated_date: v.last_rotated_date.as_deref().map(parse_dt),
233 replication_status: v
234 .replication_status
235 .into_iter()
236 .map(ReplicationStatus::from)
237 .collect(),
238 primary_region: v.primary_region,
239 }
240 }
241}
242
243impl From<SecretVersionView> for SecretVersion {
244 fn from(v: SecretVersionView) -> Self {
245 let created_date = DateTime::parse_from_rfc3339(&v.created_date)
246 .map(|dt| dt.with_timezone(&Utc))
247 .unwrap_or_else(|_| Utc::now());
248 SecretVersion {
249 version_id: v.version_id,
250 secret_string: v.secret_string,
251 secret_binary: v.secret_binary,
252 created_date,
253 version_stages: v.version_stages,
254 }
255 }
256}
257
258impl From<RotationRulesView> for RotationRules {
259 fn from(v: RotationRulesView) -> Self {
260 RotationRules {
261 automatically_after_days: v.automatically_after_days,
262 duration: v.duration,
263 schedule_expression: v.schedule_expression,
264 }
265 }
266}
267
268impl From<ReplicationStatusView> for ReplicationStatus {
269 fn from(v: ReplicationStatusView) -> Self {
270 let last_accessed_date = v.last_accessed_date.as_deref().and_then(|s| {
271 DateTime::parse_from_rfc3339(s)
272 .map(|dt| dt.with_timezone(&Utc))
273 .ok()
274 });
275 ReplicationStatus {
276 region: v.region,
277 status: v.status,
278 status_message: v.status_message,
279 kms_key_id: v.kms_key_id,
280 last_accessed_date,
281 }
282 }
283}
284
285impl StatefulService for SecretsManagerService {
288 type StateView = SecretsmanagerStateView;
289
290 async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
291 let state = self.state.get(account_id, region);
292 let guard = state.read().await;
293 SecretsmanagerStateView::from(&*guard)
294 }
295
296 async fn restore(
297 &self,
298 account_id: &str,
299 region: &str,
300 view: Self::StateView,
301 ) -> Result<(), StateViewError> {
302 let state = self.state.get(account_id, region);
303 {
304 let mut guard = state.write().await;
305 *guard = SecretsManagerState::from(view);
306 }
307 self.notify_state_changed(account_id, region).await;
308 Ok(())
309 }
310
311 async fn merge(
312 &self,
313 account_id: &str,
314 region: &str,
315 view: Self::StateView,
316 ) -> Result<(), StateViewError> {
317 let state = self.state.get(account_id, region);
318 {
319 let mut guard = state.write().await;
320 for (name, secret_view) in view.secrets {
321 guard.secrets.insert(name, Secret::from(secret_view));
322 }
323 }
324 self.notify_state_changed(account_id, region).await;
325 Ok(())
326 }
327
328 fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
329 &self.notifier
330 }
331}