Skip to main content

fakecloud_ecs/
service.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use chrono::Utc;
5use http::StatusCode;
6use serde_json::{json, Map, Value};
7use tokio::sync::Mutex as AsyncMutex;
8
9use fakecloud_aws::arn::Arn;
10use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
11use fakecloud_persistence::SnapshotStore;
12
13use crate::state::{
14    Attribute, AttributeRef, AwsLogsConfig, CapacityProvider, CircuitBreakerConfig, Cluster,
15    Container, ContainerInstance, Deployment, EcsSnapshot, EcsState, Service, SharedEcsState,
16    TagEntry, Task, TaskDefinition, TaskSet, ECS_SNAPSHOT_SCHEMA_VERSION,
17};
18
19const SUPPORTED_ACTIONS: &[&str] = &[
20    "CreateCluster",
21    "DescribeClusters",
22    "DeleteCluster",
23    "ListClusters",
24    "UpdateCluster",
25    "UpdateClusterSettings",
26    "PutClusterCapacityProviders",
27    "RegisterTaskDefinition",
28    "DescribeTaskDefinition",
29    "DeregisterTaskDefinition",
30    "DeleteTaskDefinitions",
31    "ListTaskDefinitions",
32    "ListTaskDefinitionFamilies",
33    "TagResource",
34    "UntagResource",
35    "ListTagsForResource",
36    "PutAccountSetting",
37    "PutAccountSettingDefault",
38    "DeleteAccountSetting",
39    "ListAccountSettings",
40    "RunTask",
41    "StartTask",
42    "StopTask",
43    "DescribeTasks",
44    "ListTasks",
45    "CreateService",
46    "UpdateService",
47    "DeleteService",
48    "DescribeServices",
49    "ListServices",
50    "ListServicesByNamespace",
51    "RegisterContainerInstance",
52    "DeregisterContainerInstance",
53    "DescribeContainerInstances",
54    "ListContainerInstances",
55    "UpdateContainerAgent",
56    "UpdateContainerInstancesState",
57    "PutAttributes",
58    "DeleteAttributes",
59    "ListAttributes",
60    "CreateCapacityProvider",
61    "DeleteCapacityProvider",
62    "DescribeCapacityProviders",
63    "UpdateCapacityProvider",
64    "GetTaskProtection",
65    "UpdateTaskProtection",
66    "CreateTaskSet",
67    "UpdateTaskSet",
68    "DeleteTaskSet",
69    "DescribeTaskSets",
70    "UpdateServicePrimaryTaskSet",
71    "ExecuteCommand",
72    "SubmitContainerStateChange",
73    "SubmitTaskStateChange",
74    "SubmitAttachmentStateChanges",
75    "DiscoverPollEndpoint",
76    "StopServiceDeployment",
77    "ListServiceDeployments",
78    "DescribeServiceDeployments",
79    "DescribeServiceRevisions",
80    "RegisterDaemonTaskDefinition",
81    "DescribeDaemonTaskDefinition",
82    "DeleteDaemonTaskDefinition",
83    "ListDaemonTaskDefinitions",
84    "CreateDaemon",
85    "DescribeDaemon",
86    "UpdateDaemon",
87    "DeleteDaemon",
88    "ListDaemons",
89    "DescribeDaemonDeployments",
90    "ListDaemonDeployments",
91    "DescribeDaemonRevisions",
92    "CreateExpressGatewayService",
93    "DescribeExpressGatewayService",
94    "UpdateExpressGatewayService",
95    "DeleteExpressGatewayService",
96];
97
98pub struct EcsService {
99    state: SharedEcsState,
100    snapshot_store: Option<Arc<dyn SnapshotStore>>,
101    snapshot_lock: Arc<AsyncMutex<()>>,
102    runtime: Option<Arc<crate::runtime::EcsRuntime>>,
103    role_trust_validator: Option<Arc<dyn fakecloud_core::auth::RoleTrustValidator>>,
104}
105
106impl EcsService {
107    pub fn new(state: SharedEcsState) -> Self {
108        Self {
109            state,
110            snapshot_store: None,
111            snapshot_lock: Arc::new(AsyncMutex::new(())),
112            runtime: None,
113            role_trust_validator: None,
114        }
115    }
116
117    pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
118        self.snapshot_store = Some(store);
119        self
120    }
121
122    pub fn with_runtime(mut self, runtime: Arc<crate::runtime::EcsRuntime>) -> Self {
123        self.runtime = Some(runtime);
124        self
125    }
126
127    pub fn with_role_trust_validator(
128        mut self,
129        validator: Arc<dyn fakecloud_core::auth::RoleTrustValidator>,
130    ) -> Self {
131        self.role_trust_validator = Some(validator);
132        self
133    }
134
135    fn check_pass_role(&self, account_id: &str, role_arn: &str) -> Result<(), AwsServiceError> {
136        let Some(ref validator) = self.role_trust_validator else {
137            return Ok(());
138        };
139        if let Err(err) = validator.validate(account_id, role_arn, "ecs-tasks.amazonaws.com") {
140            return Err(AwsServiceError::aws_error(
141                StatusCode::BAD_REQUEST,
142                "InvalidParameterException",
143                err.to_string(),
144            ));
145        }
146        Ok(())
147    }
148
149    pub fn state_handle(&self) -> &SharedEcsState {
150        &self.state
151    }
152
153    async fn save_snapshot(&self) {
154        let Some(store) = self.snapshot_store.clone() else {
155            return;
156        };
157        let _guard = self.snapshot_lock.lock().await;
158        let snapshot = EcsSnapshot {
159            schema_version: ECS_SNAPSHOT_SCHEMA_VERSION,
160            accounts: Some(self.state.read().clone()),
161        };
162        let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
163            let bytes = serde_json::to_vec(&snapshot)
164                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
165            store.save(&bytes)
166        })
167        .await;
168        match join {
169            Ok(Ok(())) => {}
170            Ok(Err(err)) => tracing::error!(%err, "failed to write ecs snapshot"),
171            Err(err) => tracing::error!(%err, "ecs snapshot task panicked"),
172        }
173    }
174}
175
176#[async_trait]
177impl AwsService for EcsService {
178    fn service_name(&self) -> &str {
179        "ecs"
180    }
181
182    async fn handle(&self, request: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
183        let mutates = is_mutating(request.action.as_str());
184        let result = match request.action.as_str() {
185            "CreateCluster" => self.create_cluster(&request),
186            "DescribeClusters" => self.describe_clusters(&request),
187            "DeleteCluster" => self.delete_cluster(&request),
188            "ListClusters" => self.list_clusters(&request),
189            "UpdateCluster" => self.update_cluster(&request),
190            "UpdateClusterSettings" => self.update_cluster_settings(&request),
191            "PutClusterCapacityProviders" => self.put_cluster_capacity_providers(&request),
192            "RegisterTaskDefinition" => self.register_task_definition(&request),
193            "DescribeTaskDefinition" => self.describe_task_definition(&request),
194            "DeregisterTaskDefinition" => self.deregister_task_definition(&request),
195            "DeleteTaskDefinitions" => self.delete_task_definitions(&request),
196            "ListTaskDefinitions" => self.list_task_definitions(&request),
197            "ListTaskDefinitionFamilies" => self.list_task_definition_families(&request),
198            "TagResource" => self.tag_resource(&request),
199            "UntagResource" => self.untag_resource(&request),
200            "ListTagsForResource" => self.list_tags_for_resource(&request),
201            "PutAccountSetting" => self.put_account_setting(&request),
202            "PutAccountSettingDefault" => self.put_account_setting_default(&request),
203            "DeleteAccountSetting" => self.delete_account_setting(&request),
204            "ListAccountSettings" => self.list_account_settings(&request),
205            "RunTask" => self.run_task(&request),
206            "StartTask" => self.start_task(&request),
207            "StopTask" => self.stop_task(&request).await,
208            "DescribeTasks" => self.describe_tasks(&request),
209            "ListTasks" => self.list_tasks(&request),
210            "CreateService" => self.create_service(&request),
211            "UpdateService" => self.update_service(&request),
212            "DeleteService" => self.delete_service(&request).await,
213            "DescribeServices" => self.describe_services(&request),
214            "ListServices" => self.list_services(&request),
215            "ListServicesByNamespace" => self.list_services_by_namespace(&request),
216            "RegisterContainerInstance" => self.register_container_instance(&request),
217            "DeregisterContainerInstance" => self.deregister_container_instance(&request),
218            "DescribeContainerInstances" => self.describe_container_instances(&request),
219            "ListContainerInstances" => self.list_container_instances(&request),
220            "UpdateContainerAgent" => self.update_container_agent(&request),
221            "UpdateContainerInstancesState" => self.update_container_instances_state(&request),
222            "PutAttributes" => self.put_attributes(&request),
223            "DeleteAttributes" => self.delete_attributes(&request),
224            "ListAttributes" => self.list_attributes(&request),
225            "CreateCapacityProvider" => self.create_capacity_provider(&request),
226            "DeleteCapacityProvider" => self.delete_capacity_provider(&request),
227            "DescribeCapacityProviders" => self.describe_capacity_providers(&request),
228            "UpdateCapacityProvider" => self.update_capacity_provider(&request),
229            "GetTaskProtection" => self.get_task_protection(&request),
230            "UpdateTaskProtection" => self.update_task_protection(&request),
231            "CreateTaskSet" => self.create_task_set(&request),
232            "UpdateTaskSet" => self.update_task_set(&request),
233            "DeleteTaskSet" => self.delete_task_set(&request),
234            "DescribeTaskSets" => self.describe_task_sets(&request),
235            "UpdateServicePrimaryTaskSet" => self.update_service_primary_task_set(&request),
236            "ExecuteCommand" => self.execute_command(&request).await,
237            "SubmitContainerStateChange" => self.submit_container_state_change(&request),
238            "SubmitTaskStateChange" => self.submit_task_state_change(&request),
239            "SubmitAttachmentStateChanges" => self.submit_attachment_state_changes(&request),
240            "DiscoverPollEndpoint" => self.discover_poll_endpoint(&request),
241            "StopServiceDeployment" => self.stop_service_deployment(&request),
242            "ListServiceDeployments" => self.list_service_deployments(&request),
243            "DescribeServiceDeployments" => self.describe_service_deployments(&request),
244            "DescribeServiceRevisions" => self.describe_service_revisions(&request),
245            "RegisterDaemonTaskDefinition" => self.register_daemon_task_definition(&request),
246            "DescribeDaemonTaskDefinition" => self.describe_daemon_task_definition(&request),
247            "DeleteDaemonTaskDefinition" => self.delete_daemon_task_definition(&request),
248            "ListDaemonTaskDefinitions" => self.list_daemon_task_definitions(&request),
249            "CreateDaemon" => self.create_daemon(&request),
250            "DescribeDaemon" => self.describe_daemon(&request),
251            "UpdateDaemon" => self.update_daemon(&request),
252            "DeleteDaemon" => self.delete_daemon(&request),
253            "ListDaemons" => self.list_daemons(&request),
254            "DescribeDaemonDeployments" => self.describe_daemon_deployments(&request),
255            "ListDaemonDeployments" => self.list_daemon_deployments(&request),
256            "DescribeDaemonRevisions" => self.describe_daemon_revisions(&request),
257            "CreateExpressGatewayService" => self.create_express_gateway_service(&request),
258            "DescribeExpressGatewayService" => self.describe_express_gateway_service(&request),
259            "UpdateExpressGatewayService" => self.update_express_gateway_service(&request),
260            "DeleteExpressGatewayService" => self.delete_express_gateway_service(&request),
261            _ => Err(AwsServiceError::action_not_implemented(
262                "ecs",
263                &request.action,
264            )),
265        };
266        if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
267            self.save_snapshot().await;
268        }
269        result
270    }
271
272    fn supported_actions(&self) -> &[&str] {
273        SUPPORTED_ACTIONS
274    }
275}
276
277// -------- helpers --------
278
279// -------- operations: clusters --------
280
281// -------- operations: task definitions --------
282
283// -------- operations: tagging --------
284
285// -------- operations: account settings --------
286
287// -------- operations: tasks --------
288
289// -------- operations: services --------
290
291// -------- operations: container instances, attributes, capacity providers, task sets, task protection, ExecuteCommand, agent surface --------
292
293#[path = "service_clusters.rs"]
294mod service_clusters;
295
296#[path = "service_task_definitions.rs"]
297mod service_task_definitions;
298
299#[path = "service_tagging.rs"]
300mod service_tagging;
301
302#[path = "service_account_settings.rs"]
303mod service_account_settings;
304
305#[path = "service_tasks.rs"]
306mod service_tasks;
307
308#[path = "service_services_resource.rs"]
309mod service_services_resource;
310
311#[path = "service_container_instances_etc.rs"]
312mod service_container_instances_etc;
313
314#[path = "service_daemons.rs"]
315mod service_daemons;
316
317#[path = "service_express_gateway.rs"]
318mod service_express_gateway;
319
320#[path = "helpers.rs"]
321mod helpers;
322pub(crate) use helpers::*;
323
324#[cfg(test)]
325#[path = "service_tests.rs"]
326mod tests;