1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
7
8use crate::handlers::WorkSpacesService;
9use crate::state::WorkSpacesState;
10use crate::types::{
11 ClientProperties, ConnectionAlias, ConnectionAliasAssociation, ConnectionAliasPermission,
12 IpGroup, IpRule, SelfservicePermissions, Tag, Workspace, WorkspaceBundle,
13 WorkspaceCreationProperties, WorkspaceDirectory, WorkspaceImage, WorkspacesPool,
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize, Default)]
18pub struct WorkSpacesStateView {
19 #[serde(default)]
20 pub workspaces: HashMap<String, WorkspaceView>,
21 #[serde(default)]
22 pub directories: HashMap<String, WorkspaceDirectoryView>,
23 #[serde(default)]
24 pub tags: HashMap<String, Vec<TagView>>,
25 #[serde(default)]
26 pub images: HashMap<String, WorkspaceImageView>,
27 #[serde(default)]
28 pub client_properties: HashMap<String, ClientPropertiesView>,
29 #[serde(default)]
30 pub selfservice_permissions: HashMap<String, SelfservicePermissionsView>,
31 #[serde(default)]
32 pub workspace_creation_properties: HashMap<String, WorkspaceCreationPropertiesView>,
33 #[serde(default)]
34 pub image_permissions: HashMap<String, Vec<String>>,
35 #[serde(default)]
36 pub ip_groups: HashMap<String, IpGroupView>,
37 #[serde(default)]
38 pub connection_aliases: HashMap<String, ConnectionAliasView>,
39 #[serde(default)]
40 pub connection_alias_permissions: HashMap<String, Vec<ConnectionAliasPermissionView>>,
41 #[serde(default)]
42 pub bundles: HashMap<String, WorkspaceBundleView>,
43 #[serde(default)]
44 pub pools: HashMap<String, WorkspacesPoolView>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct WorkspaceView {
49 pub workspace_id: String,
50 pub directory_id: String,
51 pub user_name: String,
52 pub bundle_id: String,
53 pub state: String,
54 pub ip_address: String,
55 pub computer_name: String,
56 pub subnet_id: String,
57 pub root_volume_size_gib: i32,
58 pub user_volume_size_gib: i32,
59 pub volume_encryption_key: Option<String>,
60 pub user_volume_encryption_enabled: bool,
61 pub root_volume_encryption_enabled: bool,
62 pub running_mode: String,
63 pub running_mode_auto_stop_timeout_in_minutes: i32,
64 #[serde(default)]
66 pub workspace_properties: Option<serde_json::Value>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct WorkspaceDirectoryView {
71 pub directory_id: String,
72 pub directory_name: String,
73 pub directory_type: String,
74 pub alias: String,
75 pub state: String,
76 pub registration_code: String,
77 pub workspace_security_group_id: String,
78 pub iam_role_id: String,
79 #[serde(default)]
81 pub self_service_permissions: Option<serde_json::Value>,
82 #[serde(default)]
84 pub workspace_access_properties: Option<serde_json::Value>,
85 #[serde(default)]
87 pub workspace_creation_properties: Option<serde_json::Value>,
88 #[serde(default)]
90 pub streaming_properties: Option<serde_json::Value>,
91 #[serde(default)]
93 pub active_directory_config: Option<serde_json::Value>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct TagView {
98 pub key: String,
99 pub value: String,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct WorkspaceImageView {
104 pub image_id: String,
105 pub name: String,
106 pub description: String,
107 pub state: String,
108 pub operating_system_type: Option<String>,
109 pub owner_account_id: String,
110 pub required_tenancy: String,
111 pub created: String,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ClientPropertiesView {
116 pub reconnect_enabled: Option<String>,
117 pub log_upload_enabled: Option<String>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct SelfservicePermissionsView {
122 pub restart_workspace: Option<String>,
123 pub increase_volume_size: Option<String>,
124 pub change_compute_type: Option<String>,
125 pub switch_running_mode: Option<String>,
126 pub rebuild_workspace: Option<String>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct WorkspaceCreationPropertiesView {
131 pub custom_security_group_id: Option<String>,
132 pub default_ou: Option<String>,
133 pub enable_internet_access: Option<bool>,
134 pub enable_maintenance_mode: Option<bool>,
135 pub user_enabled_as_local_administrator: Option<bool>,
136 pub instance_iam_role_arn: Option<String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct IpGroupView {
141 pub group_id: String,
142 pub group_name: String,
143 pub group_desc: Option<String>,
144 pub user_rules: Vec<IpRuleView>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct IpRuleView {
149 pub ip_rule: String,
150 pub rule_desc: Option<String>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ConnectionAliasView {
155 pub alias_id: String,
156 pub connection_string: String,
157 pub owner_account_id: String,
158 pub state: String,
159 pub associations: Vec<ConnectionAliasAssociationView>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct ConnectionAliasAssociationView {
164 pub connection_identifier: String,
165 pub resource_id: Option<String>,
166 pub associated_account_id: Option<String>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct ConnectionAliasPermissionView {
171 pub shared_account_id: String,
172 pub allow_association: bool,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct WorkspaceBundleView {
177 pub bundle_id: String,
178 pub name: String,
179 pub owner: Option<String>,
180 pub description: Option<String>,
181 pub bundle_type: Option<String>,
182 pub compute_type_name: Option<String>,
183 pub root_storage_capacity: Option<i32>,
184 pub user_storage_capacity: Option<i32>,
185 pub creation_time: String,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct WorkspacesPoolView {
190 pub pool_id: String,
191 pub pool_arn: String,
192 pub pool_name: String,
193 pub description: Option<String>,
194 pub state: String,
195 pub bundle_id: String,
196 pub directory_id: String,
197 pub created_at: String,
198}
199
200impl From<&WorkSpacesState> for WorkSpacesStateView {
203 fn from(state: &WorkSpacesState) -> Self {
204 WorkSpacesStateView {
205 workspaces: state
206 .workspaces
207 .iter()
208 .map(|(k, v)| {
209 (
210 k.clone(),
211 WorkspaceView {
212 workspace_id: v.workspace_id.clone(),
213 directory_id: v.directory_id.clone(),
214 user_name: v.user_name.clone(),
215 bundle_id: v.bundle_id.clone(),
216 state: v.state.clone(),
217 ip_address: v.ip_address.clone(),
218 computer_name: v.computer_name.clone(),
219 subnet_id: v.subnet_id.clone(),
220 root_volume_size_gib: v.root_volume_size_gib,
221 user_volume_size_gib: v.user_volume_size_gib,
222 volume_encryption_key: v.volume_encryption_key.clone(),
223 user_volume_encryption_enabled: v.user_volume_encryption_enabled,
224 root_volume_encryption_enabled: v.root_volume_encryption_enabled,
225 running_mode: v.running_mode.clone(),
226 running_mode_auto_stop_timeout_in_minutes: v
227 .running_mode_auto_stop_timeout_in_minutes,
228 workspace_properties: None,
229 },
230 )
231 })
232 .collect(),
233 directories: state
234 .directories
235 .iter()
236 .map(|(k, v)| {
237 (
238 k.clone(),
239 WorkspaceDirectoryView {
240 directory_id: v.directory_id.clone(),
241 directory_name: v.directory_name.clone(),
242 directory_type: v.directory_type.clone(),
243 alias: v.alias.clone(),
244 state: v.state.clone(),
245 registration_code: v.registration_code.clone(),
246 workspace_security_group_id: v.workspace_security_group_id.clone(),
247 iam_role_id: v.iam_role_id.clone(),
248 self_service_permissions: None,
249 workspace_access_properties: None,
250 workspace_creation_properties: None,
251 streaming_properties: None,
252 active_directory_config: None,
253 },
254 )
255 })
256 .collect(),
257 tags: state
258 .tags
259 .iter()
260 .map(|(k, v)| {
261 (
262 k.clone(),
263 v.iter()
264 .map(|t| TagView {
265 key: t.key.clone(),
266 value: t.value.clone(),
267 })
268 .collect(),
269 )
270 })
271 .collect(),
272 images: state
273 .images
274 .iter()
275 .map(|(k, v)| {
276 (
277 k.clone(),
278 WorkspaceImageView {
279 image_id: v.image_id.clone(),
280 name: v.name.clone(),
281 description: v.description.clone(),
282 state: v.state.clone(),
283 operating_system_type: v.operating_system_type.clone(),
284 owner_account_id: v.owner_account_id.clone(),
285 required_tenancy: v.required_tenancy.clone(),
286 created: v.created.clone(),
287 },
288 )
289 })
290 .collect(),
291 client_properties: state
292 .client_properties
293 .iter()
294 .map(|(k, v)| {
295 (
296 k.clone(),
297 ClientPropertiesView {
298 reconnect_enabled: v.reconnect_enabled.clone(),
299 log_upload_enabled: v.log_upload_enabled.clone(),
300 },
301 )
302 })
303 .collect(),
304 selfservice_permissions: state
305 .selfservice_permissions
306 .iter()
307 .map(|(k, v)| {
308 (
309 k.clone(),
310 SelfservicePermissionsView {
311 restart_workspace: v.restart_workspace.clone(),
312 increase_volume_size: v.increase_volume_size.clone(),
313 change_compute_type: v.change_compute_type.clone(),
314 switch_running_mode: v.switch_running_mode.clone(),
315 rebuild_workspace: v.rebuild_workspace.clone(),
316 },
317 )
318 })
319 .collect(),
320 workspace_creation_properties: state
321 .workspace_creation_properties
322 .iter()
323 .map(|(k, v)| {
324 (
325 k.clone(),
326 WorkspaceCreationPropertiesView {
327 custom_security_group_id: v.custom_security_group_id.clone(),
328 default_ou: v.default_ou.clone(),
329 enable_internet_access: v.enable_internet_access,
330 enable_maintenance_mode: v.enable_maintenance_mode,
331 user_enabled_as_local_administrator: v
332 .user_enabled_as_local_administrator,
333 instance_iam_role_arn: v.instance_iam_role_arn.clone(),
334 },
335 )
336 })
337 .collect(),
338 image_permissions: state.image_permissions.clone(),
339 ip_groups: state
340 .ip_groups
341 .iter()
342 .map(|(k, v)| {
343 (
344 k.clone(),
345 IpGroupView {
346 group_id: v.group_id.clone(),
347 group_name: v.group_name.clone(),
348 group_desc: v.group_desc.clone(),
349 user_rules: v
350 .user_rules
351 .iter()
352 .map(|r| IpRuleView {
353 ip_rule: r.ip_rule.clone(),
354 rule_desc: r.rule_desc.clone(),
355 })
356 .collect(),
357 },
358 )
359 })
360 .collect(),
361 connection_aliases: state
362 .connection_aliases
363 .iter()
364 .map(|(k, v)| {
365 (
366 k.clone(),
367 ConnectionAliasView {
368 alias_id: v.alias_id.clone(),
369 connection_string: v.connection_string.clone(),
370 owner_account_id: v.owner_account_id.clone(),
371 state: v.state.clone(),
372 associations: v
373 .associations
374 .iter()
375 .map(|a| ConnectionAliasAssociationView {
376 connection_identifier: a.connection_identifier.clone(),
377 resource_id: a.resource_id.clone(),
378 associated_account_id: a.associated_account_id.clone(),
379 })
380 .collect(),
381 },
382 )
383 })
384 .collect(),
385 connection_alias_permissions: state
386 .connection_alias_permissions
387 .iter()
388 .map(|(k, v)| {
389 (
390 k.clone(),
391 v.iter()
392 .map(|p| ConnectionAliasPermissionView {
393 shared_account_id: p.shared_account_id.clone(),
394 allow_association: p.allow_association,
395 })
396 .collect(),
397 )
398 })
399 .collect(),
400 bundles: state
401 .bundles
402 .iter()
403 .map(|(k, v)| {
404 (
405 k.clone(),
406 WorkspaceBundleView {
407 bundle_id: v.bundle_id.clone(),
408 name: v.name.clone(),
409 owner: v.owner.clone(),
410 description: v.description.clone(),
411 bundle_type: v.bundle_type.clone(),
412 compute_type_name: v.compute_type_name.clone(),
413 root_storage_capacity: v.root_storage_capacity,
414 user_storage_capacity: v.user_storage_capacity,
415 creation_time: v.creation_time.clone(),
416 },
417 )
418 })
419 .collect(),
420 pools: state
421 .pools
422 .iter()
423 .map(|(k, v)| {
424 (
425 k.clone(),
426 WorkspacesPoolView {
427 pool_id: v.pool_id.clone(),
428 pool_arn: v.pool_arn.clone(),
429 pool_name: v.pool_name.clone(),
430 description: v.description.clone(),
431 state: v.state.clone(),
432 bundle_id: v.bundle_id.clone(),
433 directory_id: v.directory_id.clone(),
434 created_at: v.created_at.clone(),
435 },
436 )
437 })
438 .collect(),
439 }
440 }
441}
442
443impl From<WorkSpacesStateView> for WorkSpacesState {
446 fn from(view: WorkSpacesStateView) -> Self {
447 WorkSpacesState {
448 workspaces: view
449 .workspaces
450 .into_iter()
451 .map(|(k, v)| {
452 (
453 k,
454 Workspace {
455 workspace_id: v.workspace_id,
456 directory_id: v.directory_id,
457 user_name: v.user_name,
458 bundle_id: v.bundle_id,
459 state: v.state,
460 ip_address: v.ip_address,
461 computer_name: v.computer_name,
462 subnet_id: v.subnet_id,
463 root_volume_size_gib: v.root_volume_size_gib,
464 user_volume_size_gib: v.user_volume_size_gib,
465 volume_encryption_key: v.volume_encryption_key,
466 user_volume_encryption_enabled: v.user_volume_encryption_enabled,
467 root_volume_encryption_enabled: v.root_volume_encryption_enabled,
468 running_mode: v.running_mode,
469 running_mode_auto_stop_timeout_in_minutes: v
470 .running_mode_auto_stop_timeout_in_minutes,
471 },
472 )
473 })
474 .collect(),
475 directories: view
476 .directories
477 .into_iter()
478 .map(|(k, v)| {
479 (
480 k,
481 WorkspaceDirectory {
482 directory_id: v.directory_id,
483 directory_name: v.directory_name,
484 directory_type: v.directory_type,
485 alias: v.alias,
486 state: v.state,
487 registration_code: v.registration_code,
488 workspace_security_group_id: v.workspace_security_group_id,
489 iam_role_id: v.iam_role_id,
490 },
491 )
492 })
493 .collect(),
494 tags: view
495 .tags
496 .into_iter()
497 .map(|(k, v)| {
498 (
499 k,
500 v.into_iter()
501 .map(|t| Tag {
502 key: t.key,
503 value: t.value,
504 })
505 .collect(),
506 )
507 })
508 .collect(),
509 images: view
510 .images
511 .into_iter()
512 .map(|(k, v)| {
513 (
514 k,
515 WorkspaceImage {
516 image_id: v.image_id,
517 name: v.name,
518 description: v.description,
519 state: v.state,
520 operating_system_type: v.operating_system_type,
521 owner_account_id: v.owner_account_id,
522 required_tenancy: v.required_tenancy,
523 created: v.created,
524 },
525 )
526 })
527 .collect(),
528 client_properties: view
529 .client_properties
530 .into_iter()
531 .map(|(k, v)| {
532 (
533 k,
534 ClientProperties {
535 reconnect_enabled: v.reconnect_enabled,
536 log_upload_enabled: v.log_upload_enabled,
537 },
538 )
539 })
540 .collect(),
541 selfservice_permissions: view
542 .selfservice_permissions
543 .into_iter()
544 .map(|(k, v)| {
545 (
546 k,
547 SelfservicePermissions {
548 restart_workspace: v.restart_workspace,
549 increase_volume_size: v.increase_volume_size,
550 change_compute_type: v.change_compute_type,
551 switch_running_mode: v.switch_running_mode,
552 rebuild_workspace: v.rebuild_workspace,
553 },
554 )
555 })
556 .collect(),
557 workspace_creation_properties: view
558 .workspace_creation_properties
559 .into_iter()
560 .map(|(k, v)| {
561 (
562 k,
563 WorkspaceCreationProperties {
564 custom_security_group_id: v.custom_security_group_id,
565 default_ou: v.default_ou,
566 enable_internet_access: v.enable_internet_access,
567 enable_maintenance_mode: v.enable_maintenance_mode,
568 user_enabled_as_local_administrator: v
569 .user_enabled_as_local_administrator,
570 instance_iam_role_arn: v.instance_iam_role_arn,
571 },
572 )
573 })
574 .collect(),
575 image_permissions: view.image_permissions,
576 ip_groups: view
577 .ip_groups
578 .into_iter()
579 .map(|(k, v)| {
580 (
581 k,
582 IpGroup {
583 group_id: v.group_id,
584 group_name: v.group_name,
585 group_desc: v.group_desc,
586 user_rules: v
587 .user_rules
588 .into_iter()
589 .map(|r| IpRule {
590 ip_rule: r.ip_rule,
591 rule_desc: r.rule_desc,
592 })
593 .collect(),
594 },
595 )
596 })
597 .collect(),
598 connection_aliases: view
599 .connection_aliases
600 .into_iter()
601 .map(|(k, v)| {
602 (
603 k,
604 ConnectionAlias {
605 alias_id: v.alias_id,
606 connection_string: v.connection_string,
607 owner_account_id: v.owner_account_id,
608 state: v.state,
609 associations: v
610 .associations
611 .into_iter()
612 .map(|a| ConnectionAliasAssociation {
613 connection_identifier: a.connection_identifier,
614 resource_id: a.resource_id,
615 associated_account_id: a.associated_account_id,
616 })
617 .collect(),
618 },
619 )
620 })
621 .collect(),
622 connection_alias_permissions: view
623 .connection_alias_permissions
624 .into_iter()
625 .map(|(k, v)| {
626 (
627 k,
628 v.into_iter()
629 .map(|p| ConnectionAliasPermission {
630 shared_account_id: p.shared_account_id,
631 allow_association: p.allow_association,
632 })
633 .collect(),
634 )
635 })
636 .collect(),
637 bundles: view
638 .bundles
639 .into_iter()
640 .map(|(k, v)| {
641 (
642 k,
643 WorkspaceBundle {
644 bundle_id: v.bundle_id,
645 name: v.name,
646 owner: v.owner,
647 description: v.description,
648 bundle_type: v.bundle_type,
649 compute_type_name: v.compute_type_name,
650 root_storage_capacity: v.root_storage_capacity,
651 user_storage_capacity: v.user_storage_capacity,
652 creation_time: v.creation_time.clone(),
653 },
654 )
655 })
656 .collect(),
657 pools: view
658 .pools
659 .into_iter()
660 .map(|(k, v)| {
661 (
662 k,
663 WorkspacesPool {
664 pool_id: v.pool_id,
665 pool_arn: v.pool_arn,
666 pool_name: v.pool_name,
667 description: v.description,
668 state: v.state,
669 bundle_id: v.bundle_id,
670 directory_id: v.directory_id,
671 created_at: v.created_at,
672 },
673 )
674 })
675 .collect(),
676 }
677 }
678}
679
680impl StatefulService for WorkSpacesService {
683 type StateView = WorkSpacesStateView;
684
685 async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
686 let state = self.state.get(account_id, region);
687 let guard = state.read().await;
688 WorkSpacesStateView::from(&*guard)
689 }
690
691 async fn restore(
692 &self,
693 account_id: &str,
694 region: &str,
695 view: Self::StateView,
696 ) -> Result<(), StateViewError> {
697 let state = self.state.get(account_id, region);
698 {
699 let mut guard = state.write().await;
700 *guard = WorkSpacesState::from(view);
701 }
702 self.notify_state_changed(account_id, region).await;
703 Ok(())
704 }
705
706 async fn merge(
707 &self,
708 account_id: &str,
709 region: &str,
710 view: Self::StateView,
711 ) -> Result<(), StateViewError> {
712 let state = self.state.get(account_id, region);
713 {
714 let mut guard = state.write().await;
715 let new_state = WorkSpacesState::from(view);
716 guard.workspaces.extend(new_state.workspaces);
717 guard.directories.extend(new_state.directories);
718 guard.tags.extend(new_state.tags);
719 guard.images.extend(new_state.images);
720 guard.client_properties.extend(new_state.client_properties);
721 guard
722 .selfservice_permissions
723 .extend(new_state.selfservice_permissions);
724 guard
725 .workspace_creation_properties
726 .extend(new_state.workspace_creation_properties);
727 guard.image_permissions.extend(new_state.image_permissions);
728 guard.ip_groups.extend(new_state.ip_groups);
729 guard
730 .connection_aliases
731 .extend(new_state.connection_aliases);
732 guard
733 .connection_alias_permissions
734 .extend(new_state.connection_alias_permissions);
735 guard.bundles.extend(new_state.bundles);
736 guard.pools.extend(new_state.pools);
737 }
738 self.notify_state_changed(account_id, region).await;
739 Ok(())
740 }
741
742 fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
743 &self.notifier
744 }
745}