1use bollard::query_parameters::ListContainersOptionsBuilder;
2use maplit::hashmap;
3
4use crate::{
5 client::Client,
6 docker::{DockerInspectContainer, DockerListContainers},
7 models::{Deployment, LOCAL_DEPLOYMENT_LABEL_KEY, LOCAL_DEPLOYMENT_LABEL_VALUE},
8};
9
10use super::GetDeploymentError;
11
12impl<D: DockerListContainers + DockerInspectContainer> Client<D> {
13 pub async fn list_deployments(&self) -> Result<Vec<Deployment>, GetDeploymentError> {
15 let list_container_options = ListContainersOptionsBuilder::default()
17 .all(true)
18 .filters(&hashmap! {
19 "label" => vec![format!("{}={}", LOCAL_DEPLOYMENT_LABEL_KEY, LOCAL_DEPLOYMENT_LABEL_VALUE)],
20 })
21 .build();
22
23 let container_summaries = self
25 .docker
26 .list_containers(Some(list_container_options))
27 .await?;
28
29 let mut deployments = Vec::with_capacity(container_summaries.len());
31
32 for container_summary in container_summaries {
34 if let Some(container_id) = container_summary.id {
37 let deployment = self.get_deployment(container_id.as_ref()).await?;
39 deployments.push(deployment);
40 }
41 }
42
43 Ok(deployments)
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use crate::models::{MongodbType, State};
51 use bollard::{
52 errors::Error as BollardError,
53 query_parameters::{InspectContainerOptions, ListContainersOptions},
54 secret::{
55 ContainerConfig, ContainerInspectResponse, ContainerState, ContainerStateStatusEnum,
56 ContainerSummary,
57 },
58 };
59 use mockall::mock;
60 use std::collections::HashMap;
61
62 mock! {
63 Docker {}
64
65 impl DockerListContainers for Docker {
66 async fn list_containers(
67 &self,
68 options: Option<ListContainersOptions>,
69 ) -> Result<Vec<ContainerSummary>, 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_container_summary(id: &str, name: &str) -> ContainerSummary {
82 ContainerSummary {
83 id: Some(id.to_string()),
84 names: Some(vec![format!("/{}", name)]),
85 ..Default::default()
86 }
87 }
88
89 fn create_container_inspect_response(id: &str, name: &str) -> ContainerInspectResponse {
90 let mut labels = HashMap::new();
91 labels.insert("mongodb-atlas-local".to_string(), "container".to_string());
92 labels.insert("version".to_string(), "8.0.0".to_string());
93 labels.insert("mongodb-type".to_string(), "community".to_string());
94
95 let env_vars = vec!["TOOL=ATLASCLI".to_string()];
96
97 ContainerInspectResponse {
98 id: Some(id.to_string()),
99 name: Some(format!("/{}", name)),
100 config: Some(ContainerConfig {
101 labels: Some(labels),
102 env: Some(env_vars),
103 ..Default::default()
104 }),
105 state: Some(ContainerState {
106 status: Some(ContainerStateStatusEnum::RUNNING),
107 ..Default::default()
108 }),
109 ..Default::default()
110 }
111 }
112
113 #[tokio::test]
114 async fn test_list_deployments() {
115 let mut mock_docker = MockDocker::new();
117
118 let container_summaries = vec![
119 create_container_summary("container1", "deployment1"),
120 create_container_summary("container2", "deployment2"),
121 ];
122
123 let container_inspect_response1 =
124 create_container_inspect_response("container1", "deployment1");
125 let container_inspect_response2 =
126 create_container_inspect_response("container2", "deployment2");
127
128 mock_docker
130 .expect_list_containers()
131 .times(1)
132 .returning(move |_| Ok(container_summaries.clone()));
133
134 mock_docker
135 .expect_inspect_container()
136 .with(
137 mockall::predicate::eq("container1"),
138 mockall::predicate::eq(None::<InspectContainerOptions>),
139 )
140 .times(1)
141 .returning(move |_, _| Ok(container_inspect_response1.clone()));
142
143 mock_docker
144 .expect_inspect_container()
145 .with(
146 mockall::predicate::eq("container2"),
147 mockall::predicate::eq(None::<InspectContainerOptions>),
148 )
149 .times(1)
150 .returning(move |_, _| Ok(container_inspect_response2.clone()));
151
152 let client = Client::new(mock_docker);
153
154 let result = client.list_deployments().await;
156
157 assert!(result.is_ok());
159 let deployments = result.unwrap();
160 assert_eq!(deployments.len(), 2);
161
162 assert_eq!(deployments[0].container_id, "container1");
163 assert_eq!(deployments[0].name, Some("deployment1".to_string()));
164 assert_eq!(deployments[0].state, State::Running);
165 assert_eq!(deployments[0].mongodb_type, MongodbType::Community);
166
167 assert_eq!(deployments[1].container_id, "container2");
168 assert_eq!(deployments[1].name, Some("deployment2".to_string()));
169 assert_eq!(deployments[1].state, State::Running);
170 assert_eq!(deployments[1].mongodb_type, MongodbType::Community);
171 }
172
173 #[tokio::test]
174 async fn test_list_deployments_empty() {
175 let mut mock_docker = MockDocker::new();
177
178 mock_docker
180 .expect_list_containers()
181 .times(1)
182 .returning(|_| Ok(vec![]));
183
184 let client = Client::new(mock_docker);
185
186 let result = client.list_deployments().await;
188
189 assert!(result.is_ok());
191 let deployments = result.unwrap();
192 assert_eq!(deployments.len(), 0);
193 }
194
195 #[tokio::test]
196 async fn test_list_deployments_list_containers_error() {
197 let mut mock_docker = MockDocker::new();
199
200 mock_docker
202 .expect_list_containers()
203 .times(1)
204 .returning(|_| {
205 Err(BollardError::DockerResponseServerError {
206 status_code: 500,
207 message: "Internal Server Error".to_string(),
208 })
209 });
210
211 let client = Client::new(mock_docker);
212
213 let result = client.list_deployments().await;
215
216 assert!(result.is_err());
218 assert!(matches!(
219 result.unwrap_err(),
220 GetDeploymentError::ContainerInspect(_)
221 ));
222 }
223
224 #[tokio::test]
225 async fn test_list_deployments_inspect_container_error() {
226 let mut mock_docker = MockDocker::new();
228
229 let container_summaries = vec![create_container_summary("container1", "deployment1")];
230
231 mock_docker
233 .expect_list_containers()
234 .times(1)
235 .returning(move |_| Ok(container_summaries.clone()));
236
237 mock_docker
238 .expect_inspect_container()
239 .with(
240 mockall::predicate::eq("container1"),
241 mockall::predicate::eq(None::<InspectContainerOptions>),
242 )
243 .times(1)
244 .returning(|_, _| {
245 Err(BollardError::DockerResponseServerError {
246 status_code: 404,
247 message: "No such container".to_string(),
248 })
249 });
250
251 let client = Client::new(mock_docker);
252
253 let result = client.list_deployments().await;
255
256 assert!(result.is_err());
258 assert!(matches!(
259 result.unwrap_err(),
260 GetDeploymentError::ContainerInspect(_)
261 ));
262 }
263
264 #[tokio::test]
265 async fn test_list_deployments_skip_containers_without_id() {
266 let mut mock_docker = MockDocker::new();
268
269 let container_summaries = vec![
270 ContainerSummary {
271 id: None, ..Default::default()
273 },
274 create_container_summary("container2", "deployment2"),
275 ];
276
277 let container_inspect_response2 =
278 create_container_inspect_response("container2", "deployment2");
279
280 mock_docker
282 .expect_list_containers()
283 .times(1)
284 .returning(move |_| Ok(container_summaries.clone()));
285
286 mock_docker
287 .expect_inspect_container()
288 .with(
289 mockall::predicate::eq("container2"),
290 mockall::predicate::eq(None::<InspectContainerOptions>),
291 )
292 .times(1)
293 .returning(move |_, _| Ok(container_inspect_response2.clone()));
294
295 let client = Client::new(mock_docker);
296
297 let result = client.list_deployments().await;
299
300 assert!(result.is_ok());
302 let deployments = result.unwrap();
303 assert_eq!(deployments.len(), 1); assert_eq!(deployments[0].container_id, "container2");
305 assert_eq!(deployments[0].name, Some("deployment2".to_string()));
306 }
307}