Skip to main content

winterbaume_workspaces/
views.rs

1//! Serde-compatible view types for WorkSpaces state snapshots.
2
3use 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/// Serializable view of the entire WorkSpaces state for one account/region.
17#[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    /// `workspace_properties` nested block stored as raw JSON.
65    #[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    /// `self_service_permissions` nested block.
80    #[serde(default)]
81    pub self_service_permissions: Option<serde_json::Value>,
82    /// `workspace_access_properties` nested block.
83    #[serde(default)]
84    pub workspace_access_properties: Option<serde_json::Value>,
85    /// `workspace_creation_properties` nested block.
86    #[serde(default)]
87    pub workspace_creation_properties: Option<serde_json::Value>,
88    /// `streaming_properties` nested block.
89    #[serde(default)]
90    pub streaming_properties: Option<serde_json::Value>,
91    /// `active_directory_config` nested block.
92    #[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
200// --- From internal types to view types ---
201
202impl 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
443// --- From view types to internal types ---
444
445impl 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
680// --- StatefulService implementation ---
681
682impl 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}