use bollard::query_parameters::ListContainersOptionsBuilder;
use maplit::hashmap;
use crate::{
client::Client,
docker::{DockerInspectContainer, DockerListContainers},
models::{Deployment, LOCAL_DEPLOYMENT_LABEL_KEY, LOCAL_DEPLOYMENT_LABEL_VALUE},
};
use super::GetDeploymentError;
impl<D: DockerListContainers + DockerInspectContainer> Client<D> {
pub async fn list_deployments(&self) -> Result<Vec<Deployment>, GetDeploymentError> {
let list_container_options = ListContainersOptionsBuilder::default()
.all(true)
.filters(&hashmap! {
"label" => vec![format!("{}={}", LOCAL_DEPLOYMENT_LABEL_KEY, LOCAL_DEPLOYMENT_LABEL_VALUE)],
})
.build();
let container_summaries = self
.docker
.list_containers(Some(list_container_options))
.await?;
let mut deployments = Vec::with_capacity(container_summaries.len());
for container_summary in container_summaries {
if let Some(container_id) = container_summary.id {
let deployment = self.get_deployment(container_id.as_ref()).await?;
deployments.push(deployment);
}
}
Ok(deployments)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
docker::DockerError,
models::{MongodbType, State},
};
use bollard::{
models::{
ContainerConfig, ContainerInspectResponse, ContainerState, ContainerStateStatusEnum,
ContainerSummary,
},
query_parameters::{InspectContainerOptions, ListContainersOptions},
};
use mockall::mock;
use std::collections::HashMap;
mock! {
Docker {}
impl DockerListContainers for Docker {
async fn list_containers(
&self,
options: Option<ListContainersOptions>,
) -> Result<Vec<ContainerSummary>, DockerError>;
}
impl DockerInspectContainer for Docker {
async fn inspect_container(
&self,
container_id: &str,
options: Option<InspectContainerOptions>,
) -> Result<ContainerInspectResponse, DockerError>;
}
}
fn create_container_summary(id: &str, name: &str) -> ContainerSummary {
ContainerSummary {
id: Some(id.to_string()),
names: Some(vec![format!("/{}", name)]),
..Default::default()
}
}
fn create_container_inspect_response(id: &str, name: &str) -> ContainerInspectResponse {
let mut labels = HashMap::new();
labels.insert("mongodb-atlas-local".to_string(), "container".to_string());
labels.insert("version".to_string(), "8.0.0".to_string());
labels.insert("mongodb-type".to_string(), "community".to_string());
let env_vars = vec!["TOOL=ATLASCLI".to_string()];
ContainerInspectResponse {
id: Some(id.to_string()),
name: Some(format!("/{}", name)),
config: Some(ContainerConfig {
labels: Some(labels),
env: Some(env_vars),
..Default::default()
}),
state: Some(ContainerState {
status: Some(ContainerStateStatusEnum::RUNNING),
..Default::default()
}),
..Default::default()
}
}
#[tokio::test]
async fn test_list_deployments() {
let mut mock_docker = MockDocker::new();
let container_summaries = vec![
create_container_summary("container1", "deployment1"),
create_container_summary("container2", "deployment2"),
];
let container_inspect_response1 =
create_container_inspect_response("container1", "deployment1");
let container_inspect_response2 =
create_container_inspect_response("container2", "deployment2");
mock_docker
.expect_list_containers()
.times(1)
.returning(move |_| Ok(container_summaries.clone()));
mock_docker
.expect_inspect_container()
.with(
mockall::predicate::eq("container1"),
mockall::predicate::eq(None::<InspectContainerOptions>),
)
.times(1)
.returning(move |_, _| Ok(container_inspect_response1.clone()));
mock_docker
.expect_inspect_container()
.with(
mockall::predicate::eq("container2"),
mockall::predicate::eq(None::<InspectContainerOptions>),
)
.times(1)
.returning(move |_, _| Ok(container_inspect_response2.clone()));
let client = Client::new(mock_docker);
let result = client.list_deployments().await;
assert!(result.is_ok());
let deployments = result.unwrap();
assert_eq!(deployments.len(), 2);
assert_eq!(deployments[0].container_id, "container1");
assert_eq!(deployments[0].name, Some("deployment1".to_string()));
assert_eq!(deployments[0].state, State::Running);
assert_eq!(deployments[0].mongodb_type, MongodbType::Community);
assert_eq!(deployments[1].container_id, "container2");
assert_eq!(deployments[1].name, Some("deployment2".to_string()));
assert_eq!(deployments[1].state, State::Running);
assert_eq!(deployments[1].mongodb_type, MongodbType::Community);
}
#[tokio::test]
async fn test_list_deployments_empty() {
let mut mock_docker = MockDocker::new();
mock_docker
.expect_list_containers()
.times(1)
.returning(|_| Ok(vec![]));
let client = Client::new(mock_docker);
let result = client.list_deployments().await;
assert!(result.is_ok());
let deployments = result.unwrap();
assert_eq!(deployments.len(), 0);
}
#[tokio::test]
async fn test_list_deployments_list_containers_error() {
let mut mock_docker = MockDocker::new();
mock_docker
.expect_list_containers()
.times(1)
.returning(|_| Err(DockerError::ServerError));
let client = Client::new(mock_docker);
let result = client.list_deployments().await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
GetDeploymentError::ContainerInspect(_)
));
}
#[tokio::test]
async fn test_list_deployments_inspect_container_error() {
let mut mock_docker = MockDocker::new();
let container_summaries = vec![create_container_summary("container1", "deployment1")];
mock_docker
.expect_list_containers()
.times(1)
.returning(move |_| Ok(container_summaries.clone()));
mock_docker
.expect_inspect_container()
.with(
mockall::predicate::eq("container1"),
mockall::predicate::eq(None::<InspectContainerOptions>),
)
.times(1)
.returning(|_, _| Err(DockerError::NotFound));
let client = Client::new(mock_docker);
let result = client.list_deployments().await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
GetDeploymentError::ContainerInspect(_)
));
}
#[tokio::test]
async fn test_list_deployments_skip_containers_without_id() {
let mut mock_docker = MockDocker::new();
let container_summaries = vec![
ContainerSummary {
id: None, ..Default::default()
},
create_container_summary("container2", "deployment2"),
];
let container_inspect_response2 =
create_container_inspect_response("container2", "deployment2");
mock_docker
.expect_list_containers()
.times(1)
.returning(move |_| Ok(container_summaries.clone()));
mock_docker
.expect_inspect_container()
.with(
mockall::predicate::eq("container2"),
mockall::predicate::eq(None::<InspectContainerOptions>),
)
.times(1)
.returning(move |_, _| Ok(container_inspect_response2.clone()));
let client = Client::new(mock_docker);
let result = client.list_deployments().await;
assert!(result.is_ok());
let deployments = result.unwrap();
assert_eq!(deployments.len(), 1); assert_eq!(deployments[0].container_id, "container2");
assert_eq!(deployments[0].name, Some("deployment2".to_string()));
}
}