Skip to main content

fakecloud_cloudformation/
state.rs

1use 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    /// For custom resources, the Lambda ARN (ServiceToken) used for invocation.
14    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    /// Generic stores keyed by `category` (change_sets, stack_sets, types,
39    /// generated_templates, resource_scans, refactors, etc.) so the
40    /// extras handlers can keep state alive without proliferating
41    /// per-category fields.
42    #[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}