winterbaume_cloudcontrol/
views.rs1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
7
8use crate::handlers::CloudControlService;
9use crate::state::CloudControlState;
10use crate::types::{ManagedResource, OperationStatus, OperationType, ResourceRequest};
11
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14pub struct CloudControlStateView {
15 #[serde(default)]
17 pub resources: HashMap<String, ResourceView>,
18 #[serde(default)]
20 pub requests: HashMap<String, RequestView>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ResourceView {
26 pub type_name: String,
27 pub identifier: String,
28 pub resource_model: String,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct RequestView {
34 pub request_token: String,
35 pub type_name: String,
36 pub identifier: String,
37 pub operation: String,
38 pub operation_status: String,
39 pub event_time: String,
40 #[serde(default)]
41 pub resource_model: Option<String>,
42 #[serde(default)]
43 pub status_message: Option<String>,
44 #[serde(default)]
45 pub error_code: Option<String>,
46}
47
48impl From<&CloudControlState> for CloudControlStateView {
49 fn from(state: &CloudControlState) -> Self {
50 Self {
51 resources: state
52 .resources
53 .iter()
54 .map(|((type_name, identifier), r)| {
55 let key = format!("{}|{}", type_name, identifier);
56 (
57 key,
58 ResourceView {
59 type_name: r.type_name.clone(),
60 identifier: r.identifier.clone(),
61 resource_model: r.resource_model.clone(),
62 },
63 )
64 })
65 .collect(),
66 requests: state
67 .requests
68 .iter()
69 .map(|(token, r)| {
70 (
71 token.clone(),
72 RequestView {
73 request_token: r.request_token.clone(),
74 type_name: r.type_name.clone(),
75 identifier: r.identifier.clone(),
76 operation: r.operation.as_str().to_string(),
77 operation_status: r.operation_status.as_str().to_string(),
78 event_time: r.event_time.to_rfc3339(),
79 resource_model: r.resource_model.clone(),
80 status_message: r.status_message.clone(),
81 error_code: r.error_code.clone(),
82 },
83 )
84 })
85 .collect(),
86 }
87 }
88}
89
90impl From<CloudControlStateView> for CloudControlState {
91 fn from(view: CloudControlStateView) -> Self {
92 let resources = view
93 .resources
94 .into_values()
95 .map(|rv| {
96 let key = (rv.type_name.clone(), rv.identifier.clone());
97 let resource = ManagedResource {
98 type_name: rv.type_name,
99 identifier: rv.identifier,
100 resource_model: rv.resource_model,
101 };
102 (key, resource)
103 })
104 .collect();
105
106 let requests = view
107 .requests
108 .into_values()
109 .map(|rv| {
110 let operation = match rv.operation.as_str() {
111 "DELETE" => OperationType::Delete,
112 "UPDATE" => OperationType::Update,
113 _ => OperationType::Create,
114 };
115 let operation_status = match rv.operation_status.as_str() {
116 "PENDING" => OperationStatus::Pending,
117 "IN_PROGRESS" => OperationStatus::InProgress,
118 "FAILED" => OperationStatus::Failed,
119 "CANCEL_IN_PROGRESS" => OperationStatus::CancelInProgress,
120 "CANCEL_COMPLETE" => OperationStatus::CancelComplete,
121 _ => OperationStatus::Success,
122 };
123 let event_time = chrono::DateTime::parse_from_rfc3339(&rv.event_time)
124 .map(|dt| dt.with_timezone(&chrono::Utc))
125 .unwrap_or_else(|_| chrono::Utc::now());
126
127 let request = ResourceRequest {
128 request_token: rv.request_token.clone(),
129 type_name: rv.type_name,
130 identifier: rv.identifier,
131 operation,
132 operation_status,
133 event_time,
134 resource_model: rv.resource_model,
135 status_message: rv.status_message,
136 error_code: rv.error_code,
137 };
138 (rv.request_token, request)
139 })
140 .collect();
141
142 CloudControlState {
143 resources,
144 requests,
145 }
146 }
147}
148
149impl StatefulService for CloudControlService {
150 type StateView = CloudControlStateView;
151
152 async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
153 let state = self.state.get(account_id, region);
154 let guard = state.read().await;
155 CloudControlStateView::from(&*guard)
156 }
157
158 async fn restore(
159 &self,
160 account_id: &str,
161 region: &str,
162 view: Self::StateView,
163 ) -> Result<(), StateViewError> {
164 let state = self.state.get(account_id, region);
165 {
166 let mut guard = state.write().await;
167 *guard = CloudControlState::from(view);
168 }
169 self.notify_state_changed(account_id, region).await;
170 Ok(())
171 }
172
173 async fn merge(
174 &self,
175 account_id: &str,
176 region: &str,
177 view: Self::StateView,
178 ) -> Result<(), StateViewError> {
179 let state = self.state.get(account_id, region);
180 {
181 let mut guard = state.write().await;
182 for rv in view.resources.into_values() {
183 let key = (rv.type_name.clone(), rv.identifier.clone());
184 guard.resources.insert(
185 key,
186 ManagedResource {
187 type_name: rv.type_name,
188 identifier: rv.identifier,
189 resource_model: rv.resource_model,
190 },
191 );
192 }
193 for rv in view.requests.into_values() {
195 let operation = match rv.operation.as_str() {
196 "DELETE" => OperationType::Delete,
197 "UPDATE" => OperationType::Update,
198 _ => OperationType::Create,
199 };
200 let operation_status = match rv.operation_status.as_str() {
201 "PENDING" => OperationStatus::Pending,
202 "IN_PROGRESS" => OperationStatus::InProgress,
203 "FAILED" => OperationStatus::Failed,
204 "CANCEL_IN_PROGRESS" => OperationStatus::CancelInProgress,
205 "CANCEL_COMPLETE" => OperationStatus::CancelComplete,
206 _ => OperationStatus::Success,
207 };
208 let event_time = chrono::DateTime::parse_from_rfc3339(&rv.event_time)
209 .map(|dt| dt.with_timezone(&chrono::Utc))
210 .unwrap_or_else(|_| chrono::Utc::now());
211
212 guard.requests.insert(
213 rv.request_token.clone(),
214 ResourceRequest {
215 request_token: rv.request_token,
216 type_name: rv.type_name,
217 identifier: rv.identifier,
218 operation,
219 operation_status,
220 event_time,
221 resource_model: rv.resource_model,
222 status_message: rv.status_message,
223 error_code: rv.error_code,
224 },
225 );
226 }
227 }
228 self.notify_state_changed(account_id, region).await;
229 Ok(())
230 }
231
232 fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
233 &self.notifier
234 }
235}