Skip to main content

atlas_local/client/
delete_deployment.rs

1use bollard::query_parameters::{RemoveContainerOptions, StopContainerOptions};
2
3use crate::{
4    client::Client,
5    docker::{DockerInspectContainer, DockerRemoveContainer, DockerStopContainer},
6};
7
8use super::GetDeploymentError;
9
10#[derive(Debug, thiserror::Error)]
11pub enum DeleteDeploymentError {
12    #[error("Failed to delete container: {0}")]
13    ContainerStop(bollard::errors::Error),
14    #[error("Failed to get deployment: {0}")]
15    GetDeployment(#[from] GetDeploymentError),
16    #[error("Failed to delete remove: {0}")]
17    ContainerRemove(bollard::errors::Error),
18}
19
20impl<D: DockerStopContainer + DockerRemoveContainer + DockerInspectContainer> Client<D> {
21    /// Deletes a local Atlas deployment.
22    pub async fn delete_deployment(&self, name: &str) -> Result<(), DeleteDeploymentError> {
23        // Check that a deployment with that name exists and get the container ID.
24        // This ensures we only try to delete valid Atlas local deployments.
25        let deployment = self.get_deployment(name).await?;
26        let container_id = deployment.container_id.as_str();
27
28        // Attempt to stop the container gracefully before removal.
29        self.docker
30            .stop_container(container_id, None::<StopContainerOptions>)
31            .await
32            .map_err(DeleteDeploymentError::ContainerStop)?;
33
34        // Remove the container from Docker.
35        self.docker
36            .remove_container(container_id, None::<RemoveContainerOptions>)
37            .await
38            .map_err(DeleteDeploymentError::ContainerRemove)?;
39
40        Ok(())
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use bollard::{
48        errors::Error as BollardError, query_parameters::InspectContainerOptions,
49        secret::ContainerInspectResponse,
50    };
51    use mockall::mock;
52
53    mock! {
54        Docker {}
55
56        impl DockerStopContainer for Docker {
57            async fn stop_container(
58                &self,
59                container_id: &str,
60                options: Option<StopContainerOptions>,
61            ) -> Result<(), BollardError>;
62        }
63
64        impl DockerRemoveContainer for Docker {
65            async fn remove_container(
66                &self,
67                container_id: &str,
68                options: Option<RemoveContainerOptions>,
69            ) -> Result<(), BollardError>;
70        }
71
72        impl DockerInspectContainer for Docker {
73            async fn inspect_container(
74                &self,
75                container_id: &str,
76                options: Option<InspectContainerOptions>,
77            ) -> Result<ContainerInspectResponse, BollardError>;
78        }
79    }
80
81    fn create_test_container_inspect_response() -> ContainerInspectResponse {
82        use bollard::secret::{ContainerConfig, ContainerState, ContainerStateStatusEnum};
83        use std::collections::HashMap;
84
85        let mut labels = HashMap::new();
86        labels.insert("mongodb-atlas-local".to_string(), "container".to_string());
87        labels.insert("version".to_string(), "8.0.0".to_string());
88        labels.insert("mongodb-type".to_string(), "community".to_string());
89
90        let env_vars = vec!["TOOL=ATLASCLI".to_string()];
91
92        ContainerInspectResponse {
93            id: Some("test_container_id".to_string()),
94            name: Some("/test-deployment".to_string()),
95            config: Some(ContainerConfig {
96                labels: Some(labels),
97                env: Some(env_vars),
98                ..Default::default()
99            }),
100            state: Some(ContainerState {
101                status: Some(ContainerStateStatusEnum::RUNNING),
102                ..Default::default()
103            }),
104            ..Default::default()
105        }
106    }
107
108    #[tokio::test]
109    async fn test_delete_deployment() {
110        // Arrange
111        let mut mock_docker = MockDocker::new();
112
113        // Set up expectations
114        mock_docker
115            .expect_inspect_container()
116            .with(
117                mockall::predicate::eq("test-deployment"),
118                mockall::predicate::eq(None::<InspectContainerOptions>),
119            )
120            .times(1)
121            .returning(move |_, _| Ok(create_test_container_inspect_response()));
122
123        mock_docker
124            .expect_stop_container()
125            .with(
126                mockall::predicate::eq("test_container_id"),
127                mockall::predicate::eq(None::<StopContainerOptions>),
128            )
129            .times(1)
130            .returning(|_, _| Ok(()));
131
132        mock_docker
133            .expect_remove_container()
134            .with(
135                mockall::predicate::eq("test_container_id"),
136                mockall::predicate::eq(None::<RemoveContainerOptions>),
137            )
138            .times(1)
139            .returning(|_, _| Ok(()));
140
141        let client = Client::new(mock_docker);
142
143        // Act
144        let result = client.delete_deployment("test-deployment").await;
145
146        // Assert
147        assert!(result.is_ok());
148    }
149
150    #[tokio::test]
151    async fn test_delete_deployment_get_deployment_error() {
152        // Arrange
153        let mut mock_docker = MockDocker::new();
154
155        // Set up expectations
156        mock_docker
157            .expect_inspect_container()
158            .times(1)
159            .returning(|_, _| {
160                Err(BollardError::DockerResponseServerError {
161                    status_code: 404,
162                    message: "No such container".to_string(),
163                })
164            });
165
166        let client = Client::new(mock_docker);
167
168        // Act
169        let result = client.delete_deployment("nonexistent-deployment").await;
170
171        // Assert
172        assert!(result.is_err());
173        assert!(matches!(
174            result.unwrap_err(),
175            DeleteDeploymentError::GetDeployment(_)
176        ));
177    }
178
179    #[tokio::test]
180    async fn test_delete_deployment_stop_container_error() {
181        // Arrange
182        let mut mock_docker = MockDocker::new();
183
184        // Set up expectations
185        mock_docker
186            .expect_inspect_container()
187            .times(1)
188            .returning(move |_, _| Ok(create_test_container_inspect_response()));
189
190        mock_docker
191            .expect_stop_container()
192            .times(1)
193            .returning(|_, _| {
194                Err(BollardError::DockerResponseServerError {
195                    status_code: 500,
196                    message: "Internal Server Error".to_string(),
197                })
198            });
199
200        let client = Client::new(mock_docker);
201
202        // Act
203        let result = client.delete_deployment("test-deployment").await;
204
205        // Assert
206        assert!(result.is_err());
207        assert!(matches!(
208            result.unwrap_err(),
209            DeleteDeploymentError::ContainerStop(_)
210        ));
211    }
212
213    #[tokio::test]
214    async fn test_delete_deployment_remove_container_error() {
215        // Arrange
216        let mut mock_docker = MockDocker::new();
217
218        // Set up expectations
219        mock_docker
220            .expect_inspect_container()
221            .times(1)
222            .returning(move |_, _| Ok(create_test_container_inspect_response()));
223
224        mock_docker
225            .expect_stop_container()
226            .times(1)
227            .returning(|_, _| Ok(()));
228
229        mock_docker
230            .expect_remove_container()
231            .times(1)
232            .returning(|_, _| {
233                Err(BollardError::DockerResponseServerError {
234                    status_code: 500,
235                    message: "Internal Server Error".to_string(),
236                })
237            });
238
239        let client = Client::new(mock_docker);
240
241        // Act
242        let result = client.delete_deployment("test-deployment").await;
243
244        // Assert
245        assert!(result.is_err());
246        assert!(matches!(
247            result.unwrap_err(),
248            DeleteDeploymentError::ContainerRemove(_)
249        ));
250    }
251}