1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
7
8use crate::handlers::CloudFormationService;
9use crate::state::CloudFormationState;
10use crate::types::{
11 ChangeSet, Stack, StackEvent, StackExport, StackInstance, StackInstanceKey, StackParameter,
12 StackResource, StackSet, StackSetOperation, StackTag,
13};
14
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
20pub struct CloudFormationStateView {
21 #[serde(default)]
22 pub stacks: HashMap<String, StackView>,
23 #[serde(default)]
24 pub stack_sets: HashMap<String, StackSetView>,
25 #[serde(default)]
26 pub stack_instances: Vec<StackInstanceView>,
27 #[serde(default)]
28 pub registered_types: Vec<crate::types::RegisteredType>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct StackView {
33 pub stack_id: String,
34 pub stack_name: String,
35 pub stack_status: String,
36 pub creation_time: String,
37 pub last_updated_time: Option<String>,
38 pub deletion_time: Option<String>,
39 pub description: Option<String>,
40 pub template_body: Option<String>,
41 pub stack_policy_body: Option<String>,
42 pub parameters: Vec<StackParameter>,
43 pub outputs: Vec<crate::types::StackOutput>,
44 pub tags: Vec<StackTag>,
45 pub capabilities: Vec<String>,
46 pub resources: Vec<StackResource>,
47 pub events: Vec<StackEvent>,
48 pub change_sets: Vec<ChangeSet>,
49 pub exports: Vec<StackExport>,
50 pub role_arn: Option<String>,
51 pub timeout_in_minutes: Option<i32>,
52 pub disable_rollback: bool,
53 #[serde(default)]
54 pub enable_termination_protection: bool,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct StackSetView {
59 pub stack_set_id: String,
60 pub stack_set_name: String,
61 pub stack_set_arn: String,
62 pub status: String,
63 pub description: Option<String>,
64 pub template_body: Option<String>,
65 pub parameters: Vec<StackParameter>,
66 pub tags: Vec<StackTag>,
67 pub capabilities: Vec<String>,
68 pub operations: Vec<StackSetOperation>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct StackInstanceView {
73 pub stack_set_name: String,
74 pub account: String,
75 pub region: String,
76 pub stack_id: Option<String>,
77 pub status: String,
78 pub status_reason: Option<String>,
79 pub stack_set_id: String,
80 pub parameter_overrides: Vec<StackParameter>,
81}
82
83impl From<&Stack> for StackView {
88 fn from(s: &Stack) -> Self {
89 Self {
90 stack_id: s.stack_id.clone(),
91 stack_name: s.stack_name.clone(),
92 stack_status: s.stack_status.clone(),
93 creation_time: s.creation_time.clone(),
94 last_updated_time: s.last_updated_time.clone(),
95 deletion_time: s.deletion_time.clone(),
96 description: s.description.clone(),
97 template_body: s.template_body.clone(),
98 stack_policy_body: s.stack_policy_body.clone(),
99 parameters: s.parameters.clone(),
100 outputs: s.outputs.clone(),
101 tags: s.tags.clone(),
102 capabilities: s.capabilities.clone(),
103 resources: s.resources.clone(),
104 events: s.events.clone(),
105 change_sets: s.change_sets.clone(),
106 exports: s.exports.clone(),
107 role_arn: s.role_arn.clone(),
108 timeout_in_minutes: s.timeout_in_minutes,
109 disable_rollback: s.disable_rollback,
110 enable_termination_protection: s.enable_termination_protection,
111 }
112 }
113}
114
115impl From<StackView> for Stack {
116 fn from(v: StackView) -> Self {
117 Self {
118 stack_id: v.stack_id,
119 stack_name: v.stack_name,
120 stack_status: v.stack_status,
121 creation_time: v.creation_time,
122 last_updated_time: v.last_updated_time,
123 deletion_time: v.deletion_time,
124 description: v.description,
125 template_body: v.template_body,
126 stack_policy_body: v.stack_policy_body,
127 parameters: v.parameters,
128 outputs: v.outputs,
129 tags: v.tags,
130 capabilities: v.capabilities,
131 resources: v.resources,
132 events: v.events,
133 change_sets: v.change_sets,
134 exports: v.exports,
135 role_arn: v.role_arn,
136 timeout_in_minutes: v.timeout_in_minutes,
137 disable_rollback: v.disable_rollback,
138 enable_termination_protection: v.enable_termination_protection,
139 }
140 }
141}
142
143impl From<&StackSet> for StackSetView {
144 fn from(ss: &StackSet) -> Self {
145 Self {
146 stack_set_id: ss.stack_set_id.clone(),
147 stack_set_name: ss.stack_set_name.clone(),
148 stack_set_arn: ss.stack_set_arn.clone(),
149 status: ss.status.clone(),
150 description: ss.description.clone(),
151 template_body: ss.template_body.clone(),
152 parameters: ss.parameters.clone(),
153 tags: ss.tags.clone(),
154 capabilities: ss.capabilities.clone(),
155 operations: ss.operations.clone(),
156 }
157 }
158}
159
160impl From<StackSetView> for StackSet {
161 fn from(v: StackSetView) -> Self {
162 Self {
163 stack_set_id: v.stack_set_id,
164 stack_set_name: v.stack_set_name,
165 stack_set_arn: v.stack_set_arn,
166 status: v.status,
167 description: v.description,
168 template_body: v.template_body,
169 parameters: v.parameters,
170 tags: v.tags,
171 capabilities: v.capabilities,
172 operations: v.operations,
173 }
174 }
175}
176
177impl From<&StackInstance> for StackInstanceView {
178 fn from(i: &StackInstance) -> Self {
179 Self {
180 stack_set_name: i.stack_set_name.clone(),
181 account: i.account.clone(),
182 region: i.region.clone(),
183 stack_id: i.stack_id.clone(),
184 status: i.status.clone(),
185 status_reason: i.status_reason.clone(),
186 stack_set_id: i.stack_set_id.clone(),
187 parameter_overrides: i.parameter_overrides.clone(),
188 }
189 }
190}
191
192impl From<StackInstanceView> for StackInstance {
193 fn from(v: StackInstanceView) -> Self {
194 Self {
195 stack_set_name: v.stack_set_name,
196 account: v.account,
197 region: v.region,
198 stack_id: v.stack_id,
199 status: v.status,
200 status_reason: v.status_reason,
201 stack_set_id: v.stack_set_id,
202 parameter_overrides: v.parameter_overrides,
203 }
204 }
205}
206
207impl From<&CloudFormationState> for CloudFormationStateView {
208 fn from(state: &CloudFormationState) -> Self {
209 Self {
210 stacks: state
211 .stacks
212 .iter()
213 .map(|(k, v)| (k.clone(), StackView::from(v)))
214 .collect(),
215 stack_sets: state
216 .stack_sets
217 .iter()
218 .map(|(k, v)| (k.clone(), StackSetView::from(v)))
219 .collect(),
220 stack_instances: state
221 .stack_instances
222 .values()
223 .map(StackInstanceView::from)
224 .collect(),
225 registered_types: state.registered_types.clone(),
226 }
227 }
228}
229
230impl From<CloudFormationStateView> for CloudFormationState {
231 fn from(view: CloudFormationStateView) -> Self {
232 let stack_instances = view
233 .stack_instances
234 .into_iter()
235 .map(|v| {
236 let key = StackInstanceKey {
237 stack_set_name: v.stack_set_name.clone(),
238 account: v.account.clone(),
239 region: v.region.clone(),
240 };
241 (key, StackInstance::from(v))
242 })
243 .collect();
244 Self {
245 stacks: view
246 .stacks
247 .into_iter()
248 .map(|(k, v)| (k, Stack::from(v)))
249 .collect(),
250 stack_sets: view
251 .stack_sets
252 .into_iter()
253 .map(|(k, v)| (k, StackSet::from(v)))
254 .collect(),
255 stack_instances,
256 registered_types: view.registered_types,
257 }
258 }
259}
260
261impl StatefulService for CloudFormationService {
266 type StateView = CloudFormationStateView;
267
268 async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
269 let state = self.state.get(account_id, region);
270 let guard = state.read().await;
271 CloudFormationStateView::from(&*guard)
272 }
273
274 async fn restore(
275 &self,
276 account_id: &str,
277 region: &str,
278 view: Self::StateView,
279 ) -> Result<(), StateViewError> {
280 let state = self.state.get(account_id, region);
281 {
282 let mut guard = state.write().await;
283 *guard = CloudFormationState::from(view);
284 }
285 self.notify_state_changed(account_id, region).await;
286 Ok(())
287 }
288
289 async fn merge(
290 &self,
291 account_id: &str,
292 region: &str,
293 view: Self::StateView,
294 ) -> Result<(), StateViewError> {
295 let state = self.state.get(account_id, region);
296 {
297 let mut guard = state.write().await;
298 for (k, v) in view.stacks {
299 guard.stacks.insert(k, Stack::from(v));
300 }
301 for (k, v) in view.stack_sets {
302 guard.stack_sets.insert(k, StackSet::from(v));
303 }
304 for v in view.stack_instances {
305 let key = StackInstanceKey {
306 stack_set_name: v.stack_set_name.clone(),
307 account: v.account.clone(),
308 region: v.region.clone(),
309 };
310 guard.stack_instances.insert(key, StackInstance::from(v));
311 }
312 for rt in view.registered_types {
313 if !guard
314 .registered_types
315 .iter()
316 .any(|t| t.type_name == rt.type_name && t.type_kind == rt.type_kind)
317 {
318 guard.registered_types.push(rt);
319 }
320 }
321 }
322 self.notify_state_changed(account_id, region).await;
323 Ok(())
324 }
325
326 fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
327 &self.notifier
328 }
329}