1mod associations;
2mod automation;
3mod commands;
4mod compliance;
5mod documents;
6mod instances;
7mod inventory;
8mod maintenance;
9mod misc;
10mod ops;
11mod parameters;
12mod patches;
13mod resource_sync;
14mod sessions;
15mod tags;
16
17use std::sync::Arc;
18
19use async_trait::async_trait;
20use http::StatusCode;
21use tokio::sync::Mutex as AsyncMutex;
22
23use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
24use fakecloud_persistence::SnapshotStore;
25
26use crate::state::{SharedSsmState, SsmSnapshot, SSM_SNAPSHOT_SCHEMA_VERSION};
27
28use fakecloud_secretsmanager::SharedSecretsManagerState;
29
30const PARAMETER_VERSION_LIMIT: i64 = 100;
31
32pub struct SsmService {
33 state: SharedSsmState,
34 secretsmanager_state: Option<SharedSecretsManagerState>,
35 snapshot_store: Option<Arc<dyn SnapshotStore>>,
36 snapshot_lock: Arc<AsyncMutex<()>>,
37 pub(crate) kms_hook: Option<Arc<dyn fakecloud_core::delivery::KmsHook>>,
38}
39
40impl SsmService {
41 pub fn new(state: SharedSsmState) -> Self {
42 Self {
43 state,
44 secretsmanager_state: None,
45 snapshot_store: None,
46 snapshot_lock: Arc::new(AsyncMutex::new(())),
47 kms_hook: None,
48 }
49 }
50
51 pub fn with_secretsmanager(mut self, sm_state: SharedSecretsManagerState) -> Self {
52 self.secretsmanager_state = Some(sm_state);
53 self
54 }
55
56 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
57 self.snapshot_store = Some(store);
58 self
59 }
60
61 pub fn with_kms_hook(mut self, hook: Arc<dyn fakecloud_core::delivery::KmsHook>) -> Self {
62 self.kms_hook = Some(hook);
63 self
64 }
65
66 pub fn set_command_status(&self, account_id: &str, command_id: &str, status: &str) -> bool {
73 let mut accounts = self.state.write();
74 let state = accounts.get_or_create(account_id);
75 if let Some(c) = state
76 .commands
77 .iter_mut()
78 .find(|c| c.command_id == command_id)
79 {
80 let now = chrono::Utc::now();
81 let details = commands::friendly_status_details(status);
82 for inv in c.invocations.iter_mut() {
83 inv.status = status.to_string();
84 inv.status_details = details.clone();
85 inv.response_code = match status {
86 "Success" => 0,
87 "Pending" | "InProgress" | "Delayed" => -1,
88 _ => 1,
89 };
90 inv.last_update_at = now;
91 }
92 c.status = status.to_string();
93 return true;
94 }
95 false
96 }
97
98 pub fn fail_command_invocation(
104 &self,
105 account_id: &str,
106 command_id: &str,
107 instance_id: Option<&str>,
108 status_details: Option<&str>,
109 standard_error_content: Option<&str>,
110 ) -> usize {
111 let mut accounts = self.state.write();
112 let state = accounts.get_or_create(account_id);
113 let Some(cmd) = state
114 .commands
115 .iter_mut()
116 .find(|c| c.command_id == command_id)
117 else {
118 return 0;
119 };
120 let now = chrono::Utc::now();
121 let mut updated = 0;
122 for inv in cmd.invocations.iter_mut() {
123 if let Some(iid) = instance_id {
124 if inv.instance_id != iid {
125 continue;
126 }
127 }
128 inv.status = "Failed".to_string();
129 inv.status_details = status_details
130 .map(|s| s.to_string())
131 .unwrap_or_else(|| commands::friendly_status_details("Failed"));
132 if let Some(err) = standard_error_content {
133 inv.standard_error_content = err.to_string();
134 }
135 inv.response_code = 1;
136 inv.last_update_at = now;
137 updated += 1;
138 }
139 if updated > 0 {
140 cmd.status = commands::aggregate_command_status(&cmd.invocations);
141 }
142 updated
143 }
144
145 pub fn parameter_policy_events(
151 &self,
152 account_id: &str,
153 ) -> Vec<crate::state::ParameterPolicyEvent> {
154 let mut accounts = self.state.write();
155 let state = accounts.get_or_create(account_id);
156 parameters::purge_expired_params(state);
157 parameters::tick_policy_notifications(state);
158 state.parameter_policy_events.clone()
159 }
160
161 pub fn clear_parameter_policy_events(&self, account_id: &str) {
164 let mut accounts = self.state.write();
165 let state = accounts.get_or_create(account_id);
166 state.parameter_policy_events.clear();
167 }
168
169 #[allow(clippy::too_many_arguments)]
176 pub fn inject_session(
177 &self,
178 account_id: &str,
179 target: &str,
180 status: Option<&str>,
181 owner: Option<&str>,
182 reason: Option<&str>,
183 session_id: Option<&str>,
184 ) -> String {
185 let now = chrono::Utc::now();
186 let mut accounts = self.state.write();
187 let state = accounts.get_or_create(account_id);
188 let id = match session_id {
189 Some(s) if !s.is_empty() => s.to_string(),
190 _ => {
191 state.session_counter += 1;
192 format!("session-{:012x}", state.session_counter)
193 }
194 };
195 let resolved_status = status.unwrap_or("Connected").to_string();
196 let end_date = if resolved_status == "Terminated" {
197 Some(now)
198 } else {
199 None
200 };
201 let resolved_owner = owner.map(|s| s.to_string()).unwrap_or_else(|| {
202 fakecloud_aws::arn::Arn::global("iam", &state.account_id, "root").to_string()
203 });
204 let session = crate::state::SsmSession {
205 session_id: id.clone(),
206 target: target.to_string(),
207 status: resolved_status,
208 start_date: now,
209 end_date,
210 owner: resolved_owner,
211 reason: reason.map(|s| s.to_string()),
212 };
213 state.sessions.insert(id.clone(), session);
214 id
215 }
216
217 async fn save_snapshot(&self) {
221 save_ssm_snapshot(
222 &self.state,
223 self.snapshot_store.clone(),
224 &self.snapshot_lock,
225 )
226 .await;
227 }
228
229 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
234 let store = self.snapshot_store.clone()?;
235 let state = self.state.clone();
236 let lock = self.snapshot_lock.clone();
237 Some(Arc::new(move || {
238 let state = state.clone();
239 let store = store.clone();
240 let lock = lock.clone();
241 Box::pin(async move {
242 save_ssm_snapshot(&state, Some(store), &lock).await;
243 })
244 }))
245 }
246}
247
248pub async fn save_ssm_snapshot(
254 state: &SharedSsmState,
255 store: Option<Arc<dyn SnapshotStore>>,
256 lock: &AsyncMutex<()>,
257) {
258 let Some(store) = store else {
259 return;
260 };
261 let _guard = lock.lock().await;
262 let snapshot = SsmSnapshot {
263 schema_version: SSM_SNAPSHOT_SCHEMA_VERSION,
264 state: None,
265 accounts: Some(state.read().clone()),
266 };
267 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
268 let bytes = serde_json::to_vec(&snapshot)
269 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
270 store.save(&bytes)
271 })
272 .await;
273 match join {
274 Ok(Ok(())) => {}
275 Ok(Err(err)) => tracing::error!(%err, "failed to write ssm snapshot"),
276 Err(err) => tracing::error!(%err, "ssm snapshot task panicked"),
277 }
278}
279
280#[async_trait]
281impl AwsService for SsmService {
282 fn service_name(&self) -> &str {
283 "ssm"
284 }
285
286 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
287 let mutates = !is_read_only_action(req.action.as_str());
288 let result = match req.action.as_str() {
289 "PutParameter" => self.put_parameter(&req),
290 "GetParameter" => self.get_parameter(&req),
291 "GetParameters" => self.get_parameters(&req),
292 "GetParametersByPath" => self.get_parameters_by_path(&req),
293 "DeleteParameter" => self.delete_parameter(&req),
294 "DeleteParameters" => self.delete_parameters(&req),
295 "DescribeParameters" => self.describe_parameters(&req),
296 "GetParameterHistory" => self.get_parameter_history(&req),
297 "AddTagsToResource" => self.add_tags_to_resource(&req),
298 "RemoveTagsFromResource" => self.remove_tags_from_resource(&req),
299 "ListTagsForResource" => self.list_tags_for_resource(&req),
300 "LabelParameterVersion" => self.label_parameter_version(&req),
301 "UnlabelParameterVersion" => self.unlabel_parameter_version(&req),
302 "CreateDocument" => self.create_document(&req),
303 "GetDocument" => self.get_document(&req),
304 "DeleteDocument" => self.delete_document(&req),
305 "UpdateDocument" => self.update_document(&req),
306 "DescribeDocument" => self.describe_document(&req),
307 "UpdateDocumentDefaultVersion" => self.update_document_default_version(&req),
308 "ListDocuments" => self.list_documents(&req),
309 "DescribeDocumentPermission" => self.describe_document_permission(&req),
310 "ModifyDocumentPermission" => self.modify_document_permission(&req),
311 "SendCommand" => self.send_command(&req),
312 "ListCommands" => self.list_commands(&req),
313 "GetCommandInvocation" => self.get_command_invocation(&req),
314 "ListCommandInvocations" => self.list_command_invocations(&req),
315 "CancelCommand" => self.cancel_command(&req),
316 "CreateMaintenanceWindow" => self.create_maintenance_window(&req),
317 "DescribeMaintenanceWindows" => self.describe_maintenance_windows(&req),
318 "GetMaintenanceWindow" => self.get_maintenance_window(&req),
319 "DeleteMaintenanceWindow" => self.delete_maintenance_window(&req),
320 "UpdateMaintenanceWindow" => self.update_maintenance_window(&req),
321 "RegisterTargetWithMaintenanceWindow" => {
322 self.register_target_with_maintenance_window(&req)
323 }
324 "DeregisterTargetFromMaintenanceWindow" => {
325 self.deregister_target_from_maintenance_window(&req)
326 }
327 "DescribeMaintenanceWindowTargets" => self.describe_maintenance_window_targets(&req),
328 "RegisterTaskWithMaintenanceWindow" => self.register_task_with_maintenance_window(&req),
329 "DeregisterTaskFromMaintenanceWindow" => {
330 self.deregister_task_from_maintenance_window(&req)
331 }
332 "DescribeMaintenanceWindowTasks" => self.describe_maintenance_window_tasks(&req),
333 "CreatePatchBaseline" => self.create_patch_baseline(&req),
334 "DeletePatchBaseline" => self.delete_patch_baseline(&req),
335 "DescribePatchBaselines" => self.describe_patch_baselines(&req),
336 "GetPatchBaseline" => self.get_patch_baseline(&req),
337 "RegisterPatchBaselineForPatchGroup" => {
338 self.register_patch_baseline_for_patch_group(&req)
339 }
340 "DeregisterPatchBaselineForPatchGroup" => {
341 self.deregister_patch_baseline_for_patch_group(&req)
342 }
343 "GetPatchBaselineForPatchGroup" => self.get_patch_baseline_for_patch_group(&req),
344 "DescribePatchGroups" => self.describe_patch_groups(&req),
345 "CreateAssociation" => self.create_association(&req),
347 "DescribeAssociation" => self.describe_association(&req),
348 "DeleteAssociation" => self.delete_association(&req),
349 "ListAssociations" => self.list_associations(&req),
350 "UpdateAssociation" => self.update_association(&req),
351 "ListAssociationVersions" => self.list_association_versions(&req),
352 "UpdateAssociationStatus" => self.update_association_status(&req),
353 "StartAssociationsOnce" => self.start_associations_once(&req),
354 "CreateAssociationBatch" => self.create_association_batch(&req),
355 "DescribeAssociationExecutions" => self.describe_association_executions(&req),
356 "DescribeAssociationExecutionTargets" => {
357 self.describe_association_execution_targets(&req)
358 }
359 "CreateOpsItem" => self.create_ops_item(&req),
361 "GetOpsItem" => self.get_ops_item(&req),
362 "UpdateOpsItem" => self.update_ops_item(&req),
363 "DeleteOpsItem" => self.delete_ops_item(&req),
364 "DescribeOpsItems" => self.describe_ops_items(&req),
365 "ListDocumentVersions" => self.list_document_versions(&req),
367 "ListDocumentMetadataHistory" => self.list_document_metadata_history(&req),
368 "UpdateDocumentMetadata" => self.update_document_metadata(&req),
369 "PutResourcePolicy" => self.put_resource_policy(&req),
371 "GetResourcePolicies" => self.get_resource_policies(&req),
372 "DeleteResourcePolicy" => self.delete_resource_policy(&req),
373 "PutInventory" => self.put_inventory(&req),
375 "GetInventory" => self.get_inventory(&req),
376 "GetInventorySchema" => self.get_inventory_schema(&req),
377 "ListInventoryEntries" => self.list_inventory_entries(&req),
378 "DeleteInventory" => self.delete_inventory(&req),
379 "DescribeInventoryDeletions" => self.describe_inventory_deletions(&req),
380 "PutComplianceItems" => self.put_compliance_items(&req),
382 "ListComplianceItems" => self.list_compliance_items(&req),
383 "ListComplianceSummaries" => self.list_compliance_summaries(&req),
384 "ListResourceComplianceSummaries" => self.list_resource_compliance_summaries(&req),
385 "UpdateMaintenanceWindowTarget" => self.update_maintenance_window_target(&req),
387 "UpdateMaintenanceWindowTask" => self.update_maintenance_window_task(&req),
388 "GetMaintenanceWindowTask" => self.get_maintenance_window_task(&req),
389 "GetMaintenanceWindowExecution" => self.get_maintenance_window_execution(&req),
390 "GetMaintenanceWindowExecutionTask" => self.get_maintenance_window_execution_task(&req),
391 "GetMaintenanceWindowExecutionTaskInvocation" => {
392 self.get_maintenance_window_execution_task_invocation(&req)
393 }
394 "DescribeMaintenanceWindowExecutions" => {
395 self.describe_maintenance_window_executions(&req)
396 }
397 "DescribeMaintenanceWindowExecutionTasks" => {
398 self.describe_maintenance_window_execution_tasks(&req)
399 }
400 "DescribeMaintenanceWindowExecutionTaskInvocations" => {
401 self.describe_maintenance_window_execution_task_invocations(&req)
402 }
403 "DescribeMaintenanceWindowSchedule" => self.describe_maintenance_window_schedule(&req),
404 "DescribeMaintenanceWindowsForTarget" => {
405 self.describe_maintenance_windows_for_target(&req)
406 }
407 "CancelMaintenanceWindowExecution" => self.cancel_maintenance_window_execution(&req),
408 "UpdatePatchBaseline" => self.update_patch_baseline(&req),
410 "DescribeInstancePatchStates" => self.describe_instance_patch_states(&req),
411 "DescribeInstancePatchStatesForPatchGroup" => {
412 self.describe_instance_patch_states_for_patch_group(&req)
413 }
414 "DescribeInstancePatches" => self.describe_instance_patches(&req),
415 "DescribeEffectivePatchesForPatchBaseline" => {
416 self.describe_effective_patches_for_patch_baseline(&req)
417 }
418 "GetDeployablePatchSnapshotForInstance" => {
419 self.get_deployable_patch_snapshot_for_instance(&req)
420 }
421 "CreateResourceDataSync" => self.create_resource_data_sync(&req),
423 "DeleteResourceDataSync" => self.delete_resource_data_sync(&req),
424 "ListResourceDataSync" => self.list_resource_data_sync(&req),
425 "UpdateResourceDataSync" => self.update_resource_data_sync(&req),
426 "AssociateOpsItemRelatedItem" => self.associate_ops_item_related_item(&req),
428 "DisassociateOpsItemRelatedItem" => self.disassociate_ops_item_related_item(&req),
429 "ListOpsItemRelatedItems" => self.list_ops_item_related_items(&req),
430 "ListOpsItemEvents" => self.list_ops_item_events(&req),
431 "CreateOpsMetadata" => self.create_ops_metadata(&req),
433 "GetOpsMetadata" => self.get_ops_metadata(&req),
434 "UpdateOpsMetadata" => self.update_ops_metadata(&req),
435 "DeleteOpsMetadata" => self.delete_ops_metadata(&req),
436 "ListOpsMetadata" => self.list_ops_metadata(&req),
437 "GetOpsSummary" => self.get_ops_summary(&req),
439 "StartAutomationExecution" => self.start_automation_execution(&req),
441 "StopAutomationExecution" => self.stop_automation_execution(&req),
442 "GetAutomationExecution" => self.get_automation_execution(&req),
443 "DescribeAutomationExecutions" => self.describe_automation_executions(&req),
444 "DescribeAutomationStepExecutions" => self.describe_automation_step_executions(&req),
445 "SendAutomationSignal" => self.send_automation_signal(&req),
446 "StartChangeRequestExecution" => self.start_change_request_execution(&req),
447 "StartExecutionPreview" => self.start_execution_preview(&req),
448 "GetExecutionPreview" => self.get_execution_preview(&req),
449 "StartSession" => self.start_session(&req),
451 "ResumeSession" => self.resume_session(&req),
452 "TerminateSession" => self.terminate_session(&req),
453 "DescribeSessions" => self.describe_sessions(&req),
454 "StartAccessRequest" => self.start_access_request(&req),
455 "GetAccessToken" => self.get_access_token(&req),
456 "CreateActivation" => self.create_activation(&req),
458 "DeleteActivation" => self.delete_activation(&req),
459 "DescribeActivations" => self.describe_activations(&req),
460 "DeregisterManagedInstance" => self.deregister_managed_instance(&req),
461 "DescribeInstanceInformation" => self.describe_instance_information(&req),
462 "DescribeInstanceProperties" => self.describe_instance_properties(&req),
463 "UpdateManagedInstanceRole" => self.update_managed_instance_role(&req),
464 "ListNodes" => self.list_nodes(&req),
466 "ListNodesSummary" => self.list_nodes_summary(&req),
467 "DescribeEffectiveInstanceAssociations" => {
468 self.describe_effective_instance_associations(&req)
469 }
470 "DescribeInstanceAssociationsStatus" => {
471 self.describe_instance_associations_status(&req)
472 }
473 "GetConnectionStatus" => self.get_connection_status(&req),
476 "GetCalendarState" => self.get_calendar_state(&req),
477 "DescribePatchGroupState" => self.describe_patch_group_state(&req),
478 "DescribePatchProperties" => self.describe_patch_properties(&req),
479 "GetDefaultPatchBaseline" => self.get_default_patch_baseline(&req),
480 "RegisterDefaultPatchBaseline" => self.register_default_patch_baseline(&req),
481 "DescribeAvailablePatches" => self.describe_available_patches(&req),
482 "GetServiceSetting" => self.get_service_setting(&req),
483 "ResetServiceSetting" => self.reset_service_setting(&req),
484 "UpdateServiceSetting" => self.update_service_setting(&req),
485 _ => Err(AwsServiceError::action_not_implemented("ssm", &req.action)),
486 };
487 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
488 self.save_snapshot().await;
489 }
490 result
491 }
492
493 fn supported_actions(&self) -> &[&str] {
494 &[
495 "PutParameter",
496 "GetParameter",
497 "GetParameters",
498 "GetParametersByPath",
499 "DeleteParameter",
500 "DeleteParameters",
501 "DescribeParameters",
502 "GetParameterHistory",
503 "AddTagsToResource",
504 "RemoveTagsFromResource",
505 "ListTagsForResource",
506 "LabelParameterVersion",
507 "UnlabelParameterVersion",
508 "CreateDocument",
509 "GetDocument",
510 "DeleteDocument",
511 "UpdateDocument",
512 "DescribeDocument",
513 "UpdateDocumentDefaultVersion",
514 "ListDocuments",
515 "DescribeDocumentPermission",
516 "ModifyDocumentPermission",
517 "SendCommand",
518 "ListCommands",
519 "GetCommandInvocation",
520 "ListCommandInvocations",
521 "CancelCommand",
522 "CreateMaintenanceWindow",
523 "DescribeMaintenanceWindows",
524 "GetMaintenanceWindow",
525 "DeleteMaintenanceWindow",
526 "UpdateMaintenanceWindow",
527 "RegisterTargetWithMaintenanceWindow",
528 "DeregisterTargetFromMaintenanceWindow",
529 "DescribeMaintenanceWindowTargets",
530 "RegisterTaskWithMaintenanceWindow",
531 "DeregisterTaskFromMaintenanceWindow",
532 "DescribeMaintenanceWindowTasks",
533 "CreatePatchBaseline",
534 "DeletePatchBaseline",
535 "DescribePatchBaselines",
536 "GetPatchBaseline",
537 "RegisterPatchBaselineForPatchGroup",
538 "DeregisterPatchBaselineForPatchGroup",
539 "GetPatchBaselineForPatchGroup",
540 "DescribePatchGroups",
541 "CreateAssociation",
543 "DescribeAssociation",
544 "DeleteAssociation",
545 "ListAssociations",
546 "UpdateAssociation",
547 "ListAssociationVersions",
548 "UpdateAssociationStatus",
549 "StartAssociationsOnce",
550 "CreateAssociationBatch",
551 "DescribeAssociationExecutions",
552 "DescribeAssociationExecutionTargets",
553 "CreateOpsItem",
555 "GetOpsItem",
556 "UpdateOpsItem",
557 "DeleteOpsItem",
558 "DescribeOpsItems",
559 "ListDocumentVersions",
561 "ListDocumentMetadataHistory",
562 "UpdateDocumentMetadata",
563 "PutResourcePolicy",
565 "GetResourcePolicies",
566 "DeleteResourcePolicy",
567 "PutInventory",
569 "GetInventory",
570 "GetInventorySchema",
571 "ListInventoryEntries",
572 "DeleteInventory",
573 "DescribeInventoryDeletions",
574 "PutComplianceItems",
576 "ListComplianceItems",
577 "ListComplianceSummaries",
578 "ListResourceComplianceSummaries",
579 "UpdateMaintenanceWindowTarget",
581 "UpdateMaintenanceWindowTask",
582 "GetMaintenanceWindowTask",
583 "GetMaintenanceWindowExecution",
584 "GetMaintenanceWindowExecutionTask",
585 "GetMaintenanceWindowExecutionTaskInvocation",
586 "DescribeMaintenanceWindowExecutions",
587 "DescribeMaintenanceWindowExecutionTasks",
588 "DescribeMaintenanceWindowExecutionTaskInvocations",
589 "DescribeMaintenanceWindowSchedule",
590 "DescribeMaintenanceWindowsForTarget",
591 "CancelMaintenanceWindowExecution",
592 "UpdatePatchBaseline",
594 "DescribeInstancePatchStates",
595 "DescribeInstancePatchStatesForPatchGroup",
596 "DescribeInstancePatches",
597 "DescribeEffectivePatchesForPatchBaseline",
598 "GetDeployablePatchSnapshotForInstance",
599 "CreateResourceDataSync",
601 "DeleteResourceDataSync",
602 "ListResourceDataSync",
603 "UpdateResourceDataSync",
604 "AssociateOpsItemRelatedItem",
606 "DisassociateOpsItemRelatedItem",
607 "ListOpsItemRelatedItems",
608 "ListOpsItemEvents",
609 "CreateOpsMetadata",
611 "GetOpsMetadata",
612 "UpdateOpsMetadata",
613 "DeleteOpsMetadata",
614 "ListOpsMetadata",
615 "GetOpsSummary",
617 "StartAutomationExecution",
619 "StopAutomationExecution",
620 "GetAutomationExecution",
621 "DescribeAutomationExecutions",
622 "DescribeAutomationStepExecutions",
623 "SendAutomationSignal",
624 "StartChangeRequestExecution",
625 "StartExecutionPreview",
626 "GetExecutionPreview",
627 "StartSession",
629 "ResumeSession",
630 "TerminateSession",
631 "DescribeSessions",
632 "StartAccessRequest",
633 "GetAccessToken",
634 "CreateActivation",
636 "DeleteActivation",
637 "DescribeActivations",
638 "DeregisterManagedInstance",
639 "DescribeInstanceInformation",
640 "DescribeInstanceProperties",
641 "UpdateManagedInstanceRole",
642 "ListNodes",
644 "ListNodesSummary",
645 "DescribeEffectiveInstanceAssociations",
646 "DescribeInstanceAssociationsStatus",
647 "GetConnectionStatus",
649 "GetCalendarState",
650 "DescribePatchGroupState",
651 "DescribePatchProperties",
652 "GetDefaultPatchBaseline",
653 "RegisterDefaultPatchBaseline",
654 "DescribeAvailablePatches",
655 "GetServiceSetting",
656 "ResetServiceSetting",
657 "UpdateServiceSetting",
658 ]
659 }
660}
661
662mod helpers;
663pub(crate) use helpers::*;
664
665#[cfg(test)]
666mod tests;