fakecloud_cloudformation/
state.rs1use chrono::{DateTime, Utc};
2use parking_lot::RwLock;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct StackResource {
9 pub logical_id: String,
10 pub physical_id: String,
11 pub resource_type: String,
12 pub status: String,
13 pub service_token: Option<String>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Stack {
19 pub name: String,
20 pub stack_id: String,
21 pub template: String,
22 pub status: String,
23 pub resources: Vec<StackResource>,
24 pub parameters: HashMap<String, String>,
25 pub tags: HashMap<String, String>,
26 pub created_at: DateTime<Utc>,
27 pub updated_at: Option<DateTime<Utc>>,
28 pub description: Option<String>,
29 pub notification_arns: Vec<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct CloudFormationState {
34 pub account_id: String,
35 pub region: String,
36 #[serde(default)]
37 pub stacks: HashMap<String, Stack>,
38 #[serde(default)]
43 pub extras: HashMap<String, HashMap<String, serde_json::Value>>,
44 #[serde(default)]
45 pub events: HashMap<String, Vec<serde_json::Value>>,
46 #[serde(default)]
47 pub stack_policies: HashMap<String, String>,
48 #[serde(default)]
49 pub termination_protection: HashMap<String, bool>,
50 #[serde(default)]
51 pub orgs_access_enabled: bool,
52}
53
54impl CloudFormationState {
55 pub fn new(account_id: &str, region: &str) -> Self {
56 Self {
57 account_id: account_id.to_string(),
58 region: region.to_string(),
59 stacks: HashMap::new(),
60 extras: HashMap::new(),
61 events: HashMap::new(),
62 stack_policies: HashMap::new(),
63 termination_protection: HashMap::new(),
64 orgs_access_enabled: false,
65 }
66 }
67
68 pub fn reset(&mut self) {
69 self.stacks.clear();
70 self.extras.clear();
71 self.events.clear();
72 self.stack_policies.clear();
73 self.termination_protection.clear();
74 self.orgs_access_enabled = false;
75 }
76}
77
78pub type SharedCloudFormationState =
79 Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<CloudFormationState>>>;
80
81impl fakecloud_core::multi_account::AccountState for CloudFormationState {
82 fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
83 Self::new(account_id, region)
84 }
85}
86
87pub const CLOUDFORMATION_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
88
89#[derive(Debug, Serialize, Deserialize)]
90pub struct CloudFormationSnapshot {
91 pub schema_version: u32,
92 #[serde(default)]
93 pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<CloudFormationState>>,
94 #[serde(default)]
95 pub state: Option<CloudFormationState>,
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn new_initializes_empty() {
104 let state = CloudFormationState::new("123456789012", "us-east-1");
105 assert_eq!(state.account_id, "123456789012");
106 assert_eq!(state.region, "us-east-1");
107 assert!(state.stacks.is_empty());
108 }
109
110 #[test]
111 fn reset_clears_stacks() {
112 let mut state = CloudFormationState::new("123456789012", "us-east-1");
113 state.stacks.insert(
114 "s1".to_string(),
115 Stack {
116 name: "s1".to_string(),
117 stack_id: "id".to_string(),
118 template: "{}".to_string(),
119 status: "CREATE_COMPLETE".to_string(),
120 resources: vec![],
121 parameters: HashMap::new(),
122 tags: HashMap::new(),
123 created_at: Utc::now(),
124 updated_at: None,
125 description: None,
126 notification_arns: vec![],
127 },
128 );
129 state.reset();
130 assert!(state.stacks.is_empty());
131 }
132}