1use std::collections::HashMap;
2
3use crate::types::*;
4
5#[derive(Debug, Default)]
7pub struct WorkSpacesState {
8 pub workspaces: HashMap<String, Workspace>,
10 pub directories: HashMap<String, WorkspaceDirectory>,
12 pub tags: HashMap<String, Vec<Tag>>,
14 pub images: HashMap<String, WorkspaceImage>,
16 pub client_properties: HashMap<String, ClientProperties>,
18 pub selfservice_permissions: HashMap<String, SelfservicePermissions>,
20 pub workspace_creation_properties: HashMap<String, WorkspaceCreationProperties>,
22 pub image_permissions: HashMap<String, Vec<String>>,
24 pub ip_groups: HashMap<String, IpGroup>,
26 pub connection_aliases: HashMap<String, ConnectionAlias>,
28 pub connection_alias_permissions: HashMap<String, Vec<ConnectionAliasPermission>>,
30 pub bundles: HashMap<String, WorkspaceBundle>,
32 pub pools: HashMap<String, WorkspacesPool>,
34}
35
36#[derive(Debug, thiserror::Error)]
38pub enum WorkSpacesError {
39 #[error("The specified WorkSpace '{0}' was not found.")]
40 WorkspaceNotFound(String),
41
42 #[error("The specified image '{0}' was not found.")]
43 ImageNotFound(String),
44
45 #[error("The specified directory '{0}' was not found.")]
46 DirectoryNotFound(String),
47
48 #[error("The directory '{0}' is already registered.")]
49 DirectoryAlreadyRegistered(String),
50
51 #[error("The specified IP group '{0}' was not found.")]
52 IpGroupNotFound(String),
53
54 #[error("The specified connection alias '{0}' was not found.")]
55 ConnectionAliasNotFound(String),
56
57 #[error("The specified bundle '{0}' was not found.")]
58 BundleNotFound(String),
59
60 #[error("The specified pool '{0}' was not found.")]
61 PoolNotFound(String),
62}
63
64impl WorkSpacesState {
65 pub fn create_workspaces(
68 &mut self,
69 requests: &[WorkspaceRequest],
70 account_id: &str,
71 region: &str,
72 ) -> (Vec<Workspace>, Vec<FailedCreateWorkspaceRequest>) {
73 let mut pending = Vec::new();
74 let failed = Vec::new();
75
76 for req in requests {
77 let workspace_id = format!(
78 "ws-{}",
79 &uuid::Uuid::new_v4().to_string().replace('-', "")[..25]
80 );
81 let props = req.workspace_properties.as_ref();
82
83 let workspace = Workspace {
84 workspace_id: workspace_id.clone(),
85 directory_id: req.directory_id.clone(),
86 user_name: req.user_name.clone(),
87 bundle_id: req.bundle_id.clone(),
88 state: "AVAILABLE".to_string(),
89 ip_address: "10.0.0.1".to_string(),
90 computer_name: format!("WSAMZN-{}", &workspace_id[3..10]).to_uppercase(),
91 subnet_id: format!(
92 "subnet-{}",
93 &uuid::Uuid::new_v4().to_string().replace('-', "")[..8]
94 ),
95 root_volume_size_gib: props.and_then(|p| p.root_volume_size_gib).unwrap_or(80),
96 user_volume_size_gib: props.and_then(|p| p.user_volume_size_gib).unwrap_or(50),
97 volume_encryption_key: req.volume_encryption_key.clone(),
98 user_volume_encryption_enabled: req.user_volume_encryption_enabled.unwrap_or(false),
99 root_volume_encryption_enabled: req.root_volume_encryption_enabled.unwrap_or(false),
100 running_mode: props
101 .and_then(|p| p.running_mode.clone())
102 .unwrap_or_else(|| "AUTO_STOP".to_string()),
103 running_mode_auto_stop_timeout_in_minutes: props
104 .and_then(|p| p.running_mode_auto_stop_timeout_in_minutes)
105 .unwrap_or(60),
106 };
107
108 if !self.directories.contains_key(&req.directory_id) {
110 let dir = WorkspaceDirectory {
111 directory_id: req.directory_id.clone(),
112 directory_name: format!("corp.{}.com", region),
113 directory_type: "SIMPLE_AD".to_string(),
114 alias: req.directory_id.clone(),
115 state: "REGISTERED".to_string(),
116 registration_code: format!("SLiad+{}", &uuid::Uuid::new_v4().to_string()[..8]),
117 workspace_security_group_id: format!(
118 "sg-{}",
119 &uuid::Uuid::new_v4().to_string().replace('-', "")[..8]
120 ),
121 iam_role_id: format!("arn:aws:iam::{}:role/workspaces_DefaultRole", account_id),
122 };
123 self.directories.insert(req.directory_id.clone(), dir);
124 }
125
126 self.workspaces
127 .insert(workspace_id.clone(), workspace.clone());
128 pending.push(workspace);
129 }
130
131 (pending, failed)
132 }
133
134 pub fn describe_workspaces(
136 &self,
137 workspace_ids: Option<&[String]>,
138 directory_id: Option<&str>,
139 user_name: Option<&str>,
140 ) -> Vec<&Workspace> {
141 self.workspaces
142 .values()
143 .filter(|ws| {
144 if let Some(ids) = workspace_ids {
145 if !ids.contains(&ws.workspace_id) {
146 return false;
147 }
148 }
149 if let Some(dir_id) = directory_id {
150 if ws.directory_id != dir_id {
151 return false;
152 }
153 }
154 if let Some(user) = user_name {
155 if ws.user_name != user {
156 return false;
157 }
158 }
159 true
160 })
161 .collect()
162 }
163
164 pub fn terminate_workspaces(
167 &mut self,
168 requests: &[TerminateRequest],
169 ) -> Vec<FailedTerminateWorkspaceRequest> {
170 let mut failed = Vec::new();
171
172 for req in requests {
173 if self.workspaces.contains_key(&req.workspace_id) {
174 if let Some(ws) = self.workspaces.get_mut(&req.workspace_id) {
175 ws.state = "TERMINATING".to_string();
176 }
177 self.workspaces.remove(&req.workspace_id);
179 } else {
180 failed.push(FailedTerminateWorkspaceRequest {
181 workspace_id: req.workspace_id.clone(),
182 error_code: "ResourceNotFoundException".to_string(),
183 error_message: format!(
184 "The specified WorkSpace '{}' was not found.",
185 req.workspace_id
186 ),
187 });
188 }
189 }
190
191 failed
192 }
193
194 pub fn describe_workspace_directories(
196 &self,
197 directory_ids: Option<&[String]>,
198 ) -> Vec<&WorkspaceDirectory> {
199 self.directories
200 .values()
201 .filter(|dir| {
202 if let Some(ids) = directory_ids {
203 if !ids.contains(&dir.directory_id) {
204 return false;
205 }
206 }
207 true
208 })
209 .collect()
210 }
211
212 pub fn create_tags(&mut self, resource_id: &str, tags: &[Tag]) {
214 let entry = self.tags.entry(resource_id.to_string()).or_default();
215 for tag in tags {
216 entry.retain(|t| t.key != tag.key);
218 entry.push(tag.clone());
219 }
220 }
221
222 pub fn describe_tags(&self, resource_id: &str) -> Vec<&Tag> {
224 self.tags
225 .get(resource_id)
226 .map(|tags| tags.iter().collect())
227 .unwrap_or_default()
228 }
229
230 pub fn create_workspace_image(
232 &mut self,
233 name: &str,
234 description: &str,
235 workspace_id: &str,
236 account_id: &str,
237 tags: Option<&[Tag]>,
238 ) -> Result<WorkspaceImage, WorkSpacesError> {
239 if !self.workspaces.contains_key(workspace_id) {
240 return Err(WorkSpacesError::WorkspaceNotFound(workspace_id.to_string()));
241 }
242
243 let image_id = format!(
244 "wsi-{}",
245 &uuid::Uuid::new_v4().to_string().replace('-', "")[..25]
246 );
247
248 let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
249
250 let image = WorkspaceImage {
251 image_id: image_id.clone(),
252 name: name.to_string(),
253 description: description.to_string(),
254 state: "AVAILABLE".to_string(),
255 operating_system_type: Some("WINDOWS".to_string()),
256 owner_account_id: account_id.to_string(),
257 required_tenancy: "DEFAULT".to_string(),
258 created: now,
259 };
260
261 self.images.insert(image_id.clone(), image.clone());
262
263 if let Some(tags) = tags {
264 self.create_tags(&image_id, tags);
265 }
266
267 Ok(image)
268 }
269
270 pub fn describe_workspace_images(&self, image_ids: Option<&[String]>) -> Vec<&WorkspaceImage> {
272 self.images
273 .values()
274 .filter(|img| {
275 if let Some(ids) = image_ids {
276 if !ids.contains(&img.image_id) {
277 return false;
278 }
279 }
280 true
281 })
282 .collect()
283 }
284
285 pub fn describe_workspace_image_permissions(
287 &self,
288 image_id: &str,
289 ) -> Result<Vec<String>, WorkSpacesError> {
290 if !self.images.contains_key(image_id) {
291 return Err(WorkSpacesError::ImageNotFound(image_id.to_string()));
292 }
293 Ok(self
294 .image_permissions
295 .get(image_id)
296 .cloned()
297 .unwrap_or_default())
298 }
299
300 pub fn update_workspace_image_permission(
302 &mut self,
303 image_id: &str,
304 shared_account_id: &str,
305 allow_copy_image: bool,
306 ) -> Result<(), WorkSpacesError> {
307 if !self.images.contains_key(image_id) {
308 return Err(WorkSpacesError::ImageNotFound(image_id.to_string()));
309 }
310 let perms = self
311 .image_permissions
312 .entry(image_id.to_string())
313 .or_default();
314 if allow_copy_image {
315 if !perms.contains(&shared_account_id.to_string()) {
316 perms.push(shared_account_id.to_string());
317 }
318 } else {
319 perms.retain(|id| id != shared_account_id);
320 }
321 Ok(())
322 }
323
324 pub fn describe_client_properties(
326 &self,
327 resource_ids: &[String],
328 ) -> Vec<(String, ClientProperties)> {
329 resource_ids
330 .iter()
331 .filter_map(|id| {
332 self.client_properties
333 .get(id)
334 .map(|cp| (id.clone(), cp.clone()))
335 })
336 .collect()
337 }
338
339 pub fn modify_client_properties(&mut self, resource_id: &str, props: &ClientProperties) {
341 let entry = self
342 .client_properties
343 .entry(resource_id.to_string())
344 .or_insert_with(|| ClientProperties {
345 reconnect_enabled: None,
346 log_upload_enabled: None,
347 });
348 if let Some(ref v) = props.reconnect_enabled {
349 entry.reconnect_enabled = Some(v.clone());
350 }
351 if let Some(ref v) = props.log_upload_enabled {
352 entry.log_upload_enabled = Some(v.clone());
353 }
354 }
355
356 pub fn modify_selfservice_permissions(
358 &mut self,
359 directory_id: &str,
360 perms: &SelfservicePermissions,
361 ) -> Result<(), WorkSpacesError> {
362 if !self.directories.contains_key(directory_id) {
363 return Err(WorkSpacesError::DirectoryNotFound(directory_id.to_string()));
364 }
365 self.selfservice_permissions
366 .insert(directory_id.to_string(), perms.clone());
367 Ok(())
368 }
369
370 pub fn modify_workspace_creation_properties(
372 &mut self,
373 directory_id: &str,
374 props: &WorkspaceCreationProperties,
375 ) -> Result<(), WorkSpacesError> {
376 if !self.directories.contains_key(directory_id) {
377 return Err(WorkSpacesError::DirectoryNotFound(directory_id.to_string()));
378 }
379 self.workspace_creation_properties
380 .insert(directory_id.to_string(), props.clone());
381 Ok(())
382 }
383
384 pub fn register_workspace_directory(
386 &mut self,
387 directory_id: &str,
388 account_id: &str,
389 region: &str,
390 ) -> Result<WorkspaceDirectory, WorkSpacesError> {
391 if self.directories.contains_key(directory_id) {
392 return Err(WorkSpacesError::DirectoryAlreadyRegistered(
393 directory_id.to_string(),
394 ));
395 }
396
397 let dir = WorkspaceDirectory {
398 directory_id: directory_id.to_string(),
399 directory_name: format!("corp.{}.com", region),
400 directory_type: "SIMPLE_AD".to_string(),
401 alias: directory_id.to_string(),
402 state: "REGISTERED".to_string(),
403 registration_code: format!("SLiad+{}", &uuid::Uuid::new_v4().to_string()[..8]),
404 workspace_security_group_id: format!(
405 "sg-{}",
406 &uuid::Uuid::new_v4().to_string().replace('-', "")[..8]
407 ),
408 iam_role_id: format!("arn:aws:iam::{}:role/workspaces_DefaultRole", account_id),
409 };
410
411 self.directories
412 .insert(directory_id.to_string(), dir.clone());
413 Ok(dir)
414 }
415
416 pub fn deregister_workspace_directory(
418 &mut self,
419 directory_id: &str,
420 ) -> Result<(), WorkSpacesError> {
421 if self.directories.remove(directory_id).is_none() {
422 return Err(WorkSpacesError::DirectoryNotFound(directory_id.to_string()));
423 }
424 Ok(())
425 }
426
427 pub fn delete_tags(&mut self, resource_id: &str, tag_keys: &[String]) {
429 if let Some(tags) = self.tags.get_mut(resource_id) {
430 tags.retain(|t| !tag_keys.contains(&t.key));
431 }
432 }
433
434 pub fn start_workspaces(
436 &mut self,
437 requests: &[StartRequest],
438 ) -> Vec<crate::types::FailedTerminateWorkspaceRequest> {
439 let mut failed = Vec::new();
440 for req in requests {
441 if let Some(ws) = self.workspaces.get_mut(&req.workspace_id) {
442 ws.state = "AVAILABLE".to_string();
443 } else {
444 failed.push(crate::types::FailedTerminateWorkspaceRequest {
445 workspace_id: req.workspace_id.clone(),
446 error_code: "ResourceNotFoundException".to_string(),
447 error_message: format!(
448 "The specified WorkSpace '{}' was not found.",
449 req.workspace_id
450 ),
451 });
452 }
453 }
454 failed
455 }
456
457 pub fn stop_workspaces(
459 &mut self,
460 requests: &[StopRequest],
461 ) -> Vec<crate::types::FailedTerminateWorkspaceRequest> {
462 let mut failed = Vec::new();
463 for req in requests {
464 if let Some(ws) = self.workspaces.get_mut(&req.workspace_id) {
465 ws.state = "STOPPED".to_string();
466 } else {
467 failed.push(crate::types::FailedTerminateWorkspaceRequest {
468 workspace_id: req.workspace_id.clone(),
469 error_code: "ResourceNotFoundException".to_string(),
470 error_message: format!(
471 "The specified WorkSpace '{}' was not found.",
472 req.workspace_id
473 ),
474 });
475 }
476 }
477 failed
478 }
479
480 pub fn reboot_workspaces(
482 &mut self,
483 requests: &[RebootRequest],
484 ) -> Vec<crate::types::FailedTerminateWorkspaceRequest> {
485 let mut failed = Vec::new();
486 for req in requests {
487 if !self.workspaces.contains_key(&req.workspace_id) {
488 failed.push(crate::types::FailedTerminateWorkspaceRequest {
489 workspace_id: req.workspace_id.clone(),
490 error_code: "ResourceNotFoundException".to_string(),
491 error_message: format!(
492 "The specified WorkSpace '{}' was not found.",
493 req.workspace_id
494 ),
495 });
496 }
497 }
498 failed
499 }
500
501 pub fn rebuild_workspaces(
503 &mut self,
504 requests: &[RebuildRequest],
505 ) -> Vec<crate::types::FailedTerminateWorkspaceRequest> {
506 let mut failed = Vec::new();
507 for req in requests {
508 if !self.workspaces.contains_key(&req.workspace_id) {
509 failed.push(crate::types::FailedTerminateWorkspaceRequest {
510 workspace_id: req.workspace_id.clone(),
511 error_code: "ResourceNotFoundException".to_string(),
512 error_message: format!(
513 "The specified WorkSpace '{}' was not found.",
514 req.workspace_id
515 ),
516 });
517 }
518 }
519 failed
520 }
521
522 pub fn restore_workspace(&mut self, workspace_id: &str) -> Result<(), WorkSpacesError> {
524 if !self.workspaces.contains_key(workspace_id) {
525 return Err(WorkSpacesError::WorkspaceNotFound(workspace_id.to_string()));
526 }
527 Ok(())
528 }
529
530 pub fn modify_workspace_properties(
532 &mut self,
533 workspace_id: &str,
534 running_mode: Option<&str>,
535 root_volume_size_gib: Option<i32>,
536 user_volume_size_gib: Option<i32>,
537 compute_type_name: Option<&str>,
538 running_mode_auto_stop_timeout_in_minutes: Option<i32>,
539 ) -> Result<(), WorkSpacesError> {
540 let ws = self
541 .workspaces
542 .get_mut(workspace_id)
543 .ok_or_else(|| WorkSpacesError::WorkspaceNotFound(workspace_id.to_string()))?;
544 if let Some(v) = running_mode {
545 ws.running_mode = v.to_string();
546 }
547 if let Some(v) = root_volume_size_gib {
548 ws.root_volume_size_gib = v;
549 }
550 if let Some(v) = user_volume_size_gib {
551 ws.user_volume_size_gib = v;
552 }
553 let _ = compute_type_name; if let Some(v) = running_mode_auto_stop_timeout_in_minutes {
555 ws.running_mode_auto_stop_timeout_in_minutes = v;
556 }
557 Ok(())
558 }
559
560 pub fn modify_workspace_state(
562 &mut self,
563 workspace_id: &str,
564 workspace_state: &str,
565 ) -> Result<(), WorkSpacesError> {
566 let ws = self
567 .workspaces
568 .get_mut(workspace_id)
569 .ok_or_else(|| WorkSpacesError::WorkspaceNotFound(workspace_id.to_string()))?;
570 ws.state = workspace_state.to_string();
571 Ok(())
572 }
573
574 pub fn describe_workspaces_connection_status(
576 &self,
577 workspace_ids: Option<&[String]>,
578 ) -> Vec<(&str, &str)> {
579 self.workspaces
580 .values()
581 .filter(|ws| {
582 if let Some(ids) = workspace_ids {
583 ids.contains(&ws.workspace_id)
584 } else {
585 true
586 }
587 })
588 .map(|ws| (ws.workspace_id.as_str(), ws.state.as_str()))
589 .collect()
590 }
591
592 pub fn create_ip_group(
596 &mut self,
597 group_name: &str,
598 group_desc: Option<&str>,
599 user_rules: Vec<IpRule>,
600 account_id: &str,
601 ) -> IpGroup {
602 let group_id = format!(
603 "wsipg-{}",
604 &uuid::Uuid::new_v4().to_string().replace('-', "")[..25]
605 );
606 let _ = account_id;
607 let group = IpGroup {
608 group_id: group_id.clone(),
609 group_name: group_name.to_string(),
610 group_desc: group_desc.map(|s| s.to_string()),
611 user_rules,
612 };
613 self.ip_groups.insert(group_id, group.clone());
614 group
615 }
616
617 pub fn describe_ip_groups(&self, group_ids: Option<&[String]>) -> Vec<&IpGroup> {
619 self.ip_groups
620 .values()
621 .filter(|g| {
622 if let Some(ids) = group_ids {
623 ids.contains(&g.group_id)
624 } else {
625 true
626 }
627 })
628 .collect()
629 }
630
631 pub fn delete_ip_group(&mut self, group_id: &str) -> Result<(), WorkSpacesError> {
633 if self.ip_groups.remove(group_id).is_none() {
634 return Err(WorkSpacesError::IpGroupNotFound(group_id.to_string()));
635 }
636 Ok(())
637 }
638
639 pub fn authorize_ip_rules(
641 &mut self,
642 group_id: &str,
643 user_rules: Vec<IpRule>,
644 ) -> Result<(), WorkSpacesError> {
645 let group = self
646 .ip_groups
647 .get_mut(group_id)
648 .ok_or_else(|| WorkSpacesError::IpGroupNotFound(group_id.to_string()))?;
649 for rule in user_rules {
650 if !group.user_rules.iter().any(|r| r.ip_rule == rule.ip_rule) {
652 group.user_rules.push(rule);
653 }
654 }
655 Ok(())
656 }
657
658 pub fn revoke_ip_rules(
660 &mut self,
661 group_id: &str,
662 user_rules: &[String],
663 ) -> Result<(), WorkSpacesError> {
664 let group = self
665 .ip_groups
666 .get_mut(group_id)
667 .ok_or_else(|| WorkSpacesError::IpGroupNotFound(group_id.to_string()))?;
668 group
669 .user_rules
670 .retain(|r| !user_rules.contains(&r.ip_rule));
671 Ok(())
672 }
673
674 pub fn update_rules_of_ip_group(
676 &mut self,
677 group_id: &str,
678 user_rules: Vec<IpRule>,
679 ) -> Result<(), WorkSpacesError> {
680 let group = self
681 .ip_groups
682 .get_mut(group_id)
683 .ok_or_else(|| WorkSpacesError::IpGroupNotFound(group_id.to_string()))?;
684 group.user_rules = user_rules;
685 Ok(())
686 }
687
688 pub fn associate_ip_groups(
690 &mut self,
691 directory_id: &str,
692 group_ids: &[String],
693 ) -> Result<(), WorkSpacesError> {
694 if !self.directories.contains_key(directory_id) {
695 return Err(WorkSpacesError::DirectoryNotFound(directory_id.to_string()));
696 }
697 for group_id in group_ids {
698 if !self.ip_groups.contains_key(group_id.as_str()) {
699 return Err(WorkSpacesError::IpGroupNotFound(group_id.to_string()));
700 }
701 }
702 Ok(())
703 }
704
705 pub fn disassociate_ip_groups(
707 &mut self,
708 directory_id: &str,
709 group_ids: &[String],
710 ) -> Result<(), WorkSpacesError> {
711 if !self.directories.contains_key(directory_id) {
712 return Err(WorkSpacesError::DirectoryNotFound(directory_id.to_string()));
713 }
714 let _ = group_ids;
715 Ok(())
716 }
717
718 pub fn create_connection_alias(
722 &mut self,
723 connection_string: &str,
724 account_id: &str,
725 ) -> ConnectionAlias {
726 let alias_id = format!(
727 "wsca-{}",
728 &uuid::Uuid::new_v4().to_string().replace('-', "")[..25]
729 );
730 let alias = ConnectionAlias {
731 alias_id: alias_id.clone(),
732 connection_string: connection_string.to_string(),
733 owner_account_id: account_id.to_string(),
734 state: "CREATED".to_string(),
735 associations: Vec::new(),
736 };
737 self.connection_aliases.insert(alias_id, alias.clone());
738 alias
739 }
740
741 pub fn describe_connection_aliases(
743 &self,
744 alias_ids: Option<&[String]>,
745 resource_id: Option<&str>,
746 ) -> Vec<&ConnectionAlias> {
747 self.connection_aliases
748 .values()
749 .filter(|a| {
750 if let Some(ids) = alias_ids {
751 if !ids.contains(&a.alias_id) {
752 return false;
753 }
754 }
755 if let Some(rid) = resource_id {
756 if !a
757 .associations
758 .iter()
759 .any(|assoc| assoc.resource_id.as_deref() == Some(rid))
760 {
761 return false;
762 }
763 }
764 true
765 })
766 .collect()
767 }
768
769 pub fn delete_connection_alias(&mut self, alias_id: &str) -> Result<(), WorkSpacesError> {
771 if self.connection_aliases.remove(alias_id).is_none() {
772 return Err(WorkSpacesError::ConnectionAliasNotFound(
773 alias_id.to_string(),
774 ));
775 }
776 self.connection_alias_permissions.remove(alias_id);
777 Ok(())
778 }
779
780 pub fn associate_connection_alias(
782 &mut self,
783 alias_id: &str,
784 resource_id: &str,
785 ) -> Result<String, WorkSpacesError> {
786 let alias = self
787 .connection_aliases
788 .get_mut(alias_id)
789 .ok_or_else(|| WorkSpacesError::ConnectionAliasNotFound(alias_id.to_string()))?;
790 let connection_identifier = uuid::Uuid::new_v4().to_string();
791 alias.associations.push(ConnectionAliasAssociation {
792 connection_identifier: connection_identifier.clone(),
793 resource_id: Some(resource_id.to_string()),
794 associated_account_id: None,
795 });
796 alias.state = "ASSOCIATED".to_string();
797 Ok(connection_identifier)
798 }
799
800 pub fn disassociate_connection_alias(&mut self, alias_id: &str) -> Result<(), WorkSpacesError> {
802 let alias = self
803 .connection_aliases
804 .get_mut(alias_id)
805 .ok_or_else(|| WorkSpacesError::ConnectionAliasNotFound(alias_id.to_string()))?;
806 alias.associations.clear();
807 alias.state = "CREATED".to_string();
808 Ok(())
809 }
810
811 pub fn describe_connection_alias_permissions(
813 &self,
814 alias_id: &str,
815 ) -> Result<Vec<&ConnectionAliasPermission>, WorkSpacesError> {
816 if !self.connection_aliases.contains_key(alias_id) {
817 return Err(WorkSpacesError::ConnectionAliasNotFound(
818 alias_id.to_string(),
819 ));
820 }
821 Ok(self
822 .connection_alias_permissions
823 .get(alias_id)
824 .map(|v| v.iter().collect())
825 .unwrap_or_default())
826 }
827
828 pub fn update_connection_alias_permission(
830 &mut self,
831 alias_id: &str,
832 shared_account_id: &str,
833 allow_association: bool,
834 ) -> Result<(), WorkSpacesError> {
835 if !self.connection_aliases.contains_key(alias_id) {
836 return Err(WorkSpacesError::ConnectionAliasNotFound(
837 alias_id.to_string(),
838 ));
839 }
840 let perms = self
841 .connection_alias_permissions
842 .entry(alias_id.to_string())
843 .or_default();
844 if allow_association {
845 if !perms
846 .iter()
847 .any(|p| p.shared_account_id == shared_account_id)
848 {
849 perms.push(ConnectionAliasPermission {
850 shared_account_id: shared_account_id.to_string(),
851 allow_association,
852 });
853 }
854 } else {
855 perms.retain(|p| p.shared_account_id != shared_account_id);
856 }
857 Ok(())
858 }
859
860 #[allow(clippy::too_many_arguments)]
864 pub fn describe_workspace_bundles(
865 &self,
866 bundle_ids: Option<&[String]>,
867 owner: Option<&str>,
868 ) -> Vec<&WorkspaceBundle> {
869 self.bundles
870 .values()
871 .filter(|b| {
872 if let Some(ids) = bundle_ids {
873 if !ids.contains(&b.bundle_id) {
874 return false;
875 }
876 }
877 if let Some(o) = owner {
878 let bundle_owner = b.owner.as_deref().unwrap_or("");
879 if bundle_owner != o {
880 return false;
881 }
882 }
883 true
884 })
885 .collect()
886 }
887
888 pub fn create_workspace_bundle(
890 &mut self,
891 bundle_name: &str,
892 bundle_description: Option<&str>,
893 image_id: &str,
894 compute_type: &str,
895 user_storage: i32,
896 root_storage: Option<i32>,
897 account_id: &str,
898 ) -> Result<WorkspaceBundle, WorkSpacesError> {
899 if !self.images.contains_key(image_id) {
900 return Err(WorkSpacesError::ImageNotFound(image_id.to_string()));
901 }
902 let bundle_id = format!(
903 "wsb-{}",
904 &uuid::Uuid::new_v4().to_string().replace('-', "")[..25]
905 );
906 let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
907 let bundle = WorkspaceBundle {
908 bundle_id: bundle_id.clone(),
909 name: bundle_name.to_string(),
910 owner: Some(account_id.to_string()),
911 description: bundle_description.map(|s| s.to_string()),
912 bundle_type: None,
913 compute_type_name: Some(compute_type.to_string()),
914 root_storage_capacity: root_storage,
915 user_storage_capacity: Some(user_storage),
916 creation_time: now,
917 };
918 self.bundles.insert(bundle_id, bundle.clone());
919 Ok(bundle)
920 }
921
922 pub fn delete_workspace_bundle(&mut self, bundle_id: &str) -> Result<(), WorkSpacesError> {
924 if self.bundles.remove(bundle_id).is_none() {
925 return Err(WorkSpacesError::BundleNotFound(bundle_id.to_string()));
926 }
927 Ok(())
928 }
929
930 pub fn delete_workspace_image(&mut self, image_id: &str) -> Result<(), WorkSpacesError> {
932 if self.images.remove(image_id).is_none() {
933 return Err(WorkSpacesError::ImageNotFound(image_id.to_string()));
934 }
935 self.image_permissions.remove(image_id);
936 Ok(())
937 }
938
939 pub fn create_workspaces_pool(
943 &mut self,
944 pool_name: &str,
945 description: Option<&str>,
946 bundle_id: &str,
947 directory_id: &str,
948 account_id: &str,
949 region: &str,
950 ) -> WorkspacesPool {
951 let pool_id = format!(
952 "wspool-{}",
953 &uuid::Uuid::new_v4().to_string().replace('-', "")[..25]
954 );
955 let pool_arn = format!(
956 "arn:aws:workspaces:{}:{}:workspacespool/{}",
957 region, account_id, pool_id
958 );
959 let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
960 let pool = WorkspacesPool {
961 pool_id: pool_id.clone(),
962 pool_arn,
963 pool_name: pool_name.to_string(),
964 description: description.map(|s| s.to_string()),
965 state: "RUNNING".to_string(),
966 bundle_id: bundle_id.to_string(),
967 directory_id: directory_id.to_string(),
968 created_at: now,
969 };
970 self.pools.insert(pool_id, pool.clone());
971 pool
972 }
973
974 pub fn describe_workspaces_pools(&self, pool_ids: Option<&[String]>) -> Vec<&WorkspacesPool> {
976 self.pools
977 .values()
978 .filter(|p| {
979 if let Some(ids) = pool_ids {
980 ids.contains(&p.pool_id)
981 } else {
982 true
983 }
984 })
985 .collect()
986 }
987
988 pub fn terminate_workspaces_pool(
990 &mut self,
991 pool_id: &str,
992 ) -> Result<WorkspacesPool, WorkSpacesError> {
993 let mut pool = self
994 .pools
995 .remove(pool_id)
996 .ok_or_else(|| WorkSpacesError::PoolNotFound(pool_id.to_string()))?;
997 pool.state = "TERMINATING".to_string();
998 Ok(pool)
999 }
1000
1001 pub fn update_workspaces_pool(
1003 &mut self,
1004 pool_id: &str,
1005 description: Option<&str>,
1006 bundle_id: Option<&str>,
1007 directory_id: Option<&str>,
1008 ) -> Result<WorkspacesPool, WorkSpacesError> {
1009 let pool = self
1010 .pools
1011 .get_mut(pool_id)
1012 .ok_or_else(|| WorkSpacesError::PoolNotFound(pool_id.to_string()))?;
1013 if let Some(v) = description {
1014 pool.description = Some(v.to_string());
1015 }
1016 if let Some(v) = bundle_id {
1017 pool.bundle_id = v.to_string();
1018 }
1019 if let Some(v) = directory_id {
1020 pool.directory_id = v.to_string();
1021 }
1022 Ok(pool.clone())
1023 }
1024
1025 pub fn start_workspaces_pool(&mut self, pool_id: &str) -> Result<(), WorkSpacesError> {
1027 let pool = self
1028 .pools
1029 .get_mut(pool_id)
1030 .ok_or_else(|| WorkSpacesError::PoolNotFound(pool_id.to_string()))?;
1031 pool.state = "RUNNING".to_string();
1032 Ok(())
1033 }
1034
1035 pub fn stop_workspaces_pool(&mut self, pool_id: &str) -> Result<(), WorkSpacesError> {
1037 let pool = self
1038 .pools
1039 .get_mut(pool_id)
1040 .ok_or_else(|| WorkSpacesError::PoolNotFound(pool_id.to_string()))?;
1041 pool.state = "STOPPED".to_string();
1042 Ok(())
1043 }
1044}