use crate::{
Client,
client::{get_deployment::GetDeploymentError, get_mongodb_secret::get_mongodb_secret},
docker::{DockerInspectContainer, RunCommandInContainer, RunCommandInContainerError},
};
#[derive(Debug, thiserror::Error)]
pub enum GetDeploymentIdError {
#[error("Failed to get deployment: {0}")]
GetDeployment(#[from] GetDeploymentError),
#[error("Failed to get MongoDB username: {0}")]
GetMongodbUsername(RunCommandInContainerError),
#[error("Failed to get MongoDB password: {0}")]
GetMongodbPassword(RunCommandInContainerError),
#[error("Failed to run mongosh command: {0}")]
RunMongoshCommand(RunCommandInContainerError),
#[error("Deployment ID is empty")]
DeploymentIdEmpty,
}
impl<D: DockerInspectContainer + RunCommandInContainer> Client<D> {
pub async fn get_deployment_id(
&self,
cluster_id_or_name: &str,
) -> Result<String, GetDeploymentIdError> {
let deployment = self.get_deployment(cluster_id_or_name).await?;
let mongodb_root_username = get_mongodb_secret(
self.docker.as_ref(),
&deployment,
|d| d.mongodb_initdb_root_username.as_deref(),
|d| d.mongodb_initdb_root_username_file.as_deref(),
)
.await
.map_err(GetDeploymentIdError::GetMongodbUsername)?;
let mongodb_root_password = get_mongodb_secret(
self.docker.as_ref(),
&deployment,
|d| d.mongodb_initdb_root_password.as_deref(),
|d| d.mongodb_initdb_root_password_file.as_deref(),
)
.await
.map_err(GetDeploymentIdError::GetMongodbPassword)?;
let mut mongosh_command = vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
];
if let Some(username) = mongodb_root_username {
mongosh_command.push(format!("--username={}", username));
}
if let Some(password) = mongodb_root_password {
mongosh_command.push(format!("--password={}", password));
}
mongosh_command.push("--eval".to_string());
mongosh_command.push("db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string());
mongosh_command.push("--quiet".to_string());
let command_output = self
.docker
.run_command_in_container(&deployment.container_id, mongosh_command)
.await
.map_err(GetDeploymentIdError::RunMongoshCommand)?;
match command_output.stdout.into_iter().next() {
Some(line) if line.is_empty() => Err(GetDeploymentIdError::DeploymentIdEmpty),
Some(line) => Ok(line),
None => Err(GetDeploymentIdError::DeploymentIdEmpty),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::docker::DockerError;
use crate::{client::get_deployment::GetDeploymentError, docker::CommandOutput};
use bollard::{
models::{
ContainerConfig, ContainerInspectResponse, ContainerState, ContainerStateStatusEnum,
},
query_parameters::InspectContainerOptions,
};
use maplit::hashmap;
use mockall::{mock, predicate::eq};
mock! {
Docker {}
impl DockerInspectContainer for Docker {
async fn inspect_container(
&self,
container_id: &str,
options: Option<InspectContainerOptions>,
) -> Result<ContainerInspectResponse, DockerError>;
}
impl RunCommandInContainer for Docker {
async fn run_command_in_container(
&self,
container_id: &str,
command: Vec<String>,
) -> Result<CommandOutput, RunCommandInContainerError>;
}
}
fn create_test_container_inspect_response() -> ContainerInspectResponse {
ContainerInspectResponse {
id: Some("test_container_id".to_string()),
name: Some("/test-deployment".to_string()),
config: Some(ContainerConfig {
labels: Some(hashmap! {
"mongodb-atlas-local".to_string() => "container".to_string(),
"version".to_string() => "8.0.0".to_string(),
"mongodb-type".to_string() => "community".to_string(),
}),
env: Some(vec!["TOOL=ATLASCLI".to_string()]),
..Default::default()
}),
state: Some(ContainerState {
status: Some(ContainerStateStatusEnum::RUNNING),
..Default::default()
}),
..Default::default()
}
}
#[tokio::test]
async fn test_get_deployment_id_happy_path_no_auth() {
let mut mock_docker = MockDocker::new();
let container_inspect_response = create_test_container_inspect_response();
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["deployment-uuid-123".to_string()],
stderr: vec![],
})
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "deployment-uuid-123");
}
#[tokio::test]
async fn test_get_deployment_id_happy_path_env_auth() {
let mut mock_docker = MockDocker::new();
let mut container_inspect_response = create_test_container_inspect_response();
if let Some(config) = container_inspect_response.config.as_mut()
&& let Some(env) = config.env.as_mut()
{
env.push("MONGODB_INITDB_ROOT_USERNAME=testuser".to_string());
env.push("MONGODB_INITDB_ROOT_PASSWORD=testpass".to_string());
}
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--username=testuser".to_string(),
"--password=testpass".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["deployment-uuid-456".to_string()],
stderr: vec![],
})
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "deployment-uuid-456");
}
#[tokio::test]
async fn test_get_deployment_id_happy_path_file_auth() {
let mut mock_docker = MockDocker::new();
let mut container_inspect_response = create_test_container_inspect_response();
if let Some(config) = container_inspect_response.config.as_mut()
&& let Some(env) = config.env.as_mut()
{
env.push("MONGODB_INITDB_ROOT_USERNAME_FILE=/run/secrets/username".to_string());
env.push("MONGODB_INITDB_ROOT_PASSWORD_FILE=/run/secrets/password".to_string());
}
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec!["cat".to_string(), "/run/secrets/username".to_string()]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["fileuser".to_string()],
stderr: vec![],
})
});
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec!["cat".to_string(), "/run/secrets/password".to_string()]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["filepass".to_string()],
stderr: vec![],
})
});
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--username=fileuser".to_string(),
"--password=filepass".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["deployment-uuid-789".to_string()],
stderr: vec![],
})
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "deployment-uuid-789");
}
#[tokio::test]
async fn test_get_deployment_id_get_deployment_error() {
let mut mock_docker = MockDocker::new();
mock_docker
.expect_inspect_container()
.with(
eq("nonexistent-deployment"),
eq(None::<InspectContainerOptions>),
)
.times(1)
.returning(|_, _| Err(DockerError::NotFound));
let client = Client::new(mock_docker);
let result = client.get_deployment_id("nonexistent-deployment").await;
assert!(result.is_err());
match result.unwrap_err() {
GetDeploymentIdError::GetDeployment(GetDeploymentError::ContainerInspect(_)) => {
}
other => panic!("Expected GetDeployment error, got: {:?}", other),
}
}
#[tokio::test]
async fn test_get_deployment_id_get_mongodb_username_error() {
let mut mock_docker = MockDocker::new();
let mut container_inspect_response = create_test_container_inspect_response();
if let Some(config) = container_inspect_response.config.as_mut()
&& let Some(env) = config.env.as_mut()
{
env.push("MONGODB_INITDB_ROOT_USERNAME_FILE=/run/secrets/username".to_string());
}
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec!["cat".to_string(), "/run/secrets/username".to_string()]),
)
.times(1)
.returning(|_, _| {
Err(RunCommandInContainerError::CreateExec(
DockerError::ServerError,
))
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_err());
match result.unwrap_err() {
GetDeploymentIdError::GetMongodbUsername(_) => {
}
other => panic!("Expected GetMongodbUsername error, got: {:?}", other),
}
}
#[tokio::test]
async fn test_get_deployment_id_get_mongodb_password_error() {
let mut mock_docker = MockDocker::new();
let mut container_inspect_response = create_test_container_inspect_response();
if let Some(config) = container_inspect_response.config.as_mut()
&& let Some(env) = config.env.as_mut()
{
env.push("MONGODB_INITDB_ROOT_PASSWORD_FILE=/run/secrets/password".to_string());
}
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec!["cat".to_string(), "/run/secrets/password".to_string()]),
)
.times(1)
.returning(|_, _| {
Err(RunCommandInContainerError::StartExec(
DockerError::ServerError,
))
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_err());
match result.unwrap_err() {
GetDeploymentIdError::GetMongodbPassword(_) => {
}
other => panic!("Expected GetMongodbPassword error, got: {:?}", other),
}
}
#[tokio::test]
async fn test_get_deployment_id_run_mongosh_command_error() {
let mut mock_docker = MockDocker::new();
let container_inspect_response = create_test_container_inspect_response();
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| Err(RunCommandInContainerError::GetOutput));
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_err());
match result.unwrap_err() {
GetDeploymentIdError::RunMongoshCommand(_) => {
}
other => panic!("Expected RunMongoshCommand error, got: {:?}", other),
}
}
#[tokio::test]
async fn test_get_deployment_id_mixed_auth_env_username_file_password() {
let mut mock_docker = MockDocker::new();
let mut container_inspect_response = create_test_container_inspect_response();
if let Some(config) = container_inspect_response.config.as_mut()
&& let Some(env) = config.env.as_mut()
{
env.push("MONGODB_INITDB_ROOT_USERNAME=envuser".to_string());
env.push("MONGODB_INITDB_ROOT_PASSWORD_FILE=/run/secrets/password".to_string());
}
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec!["cat".to_string(), "/run/secrets/password".to_string()]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["filepass".to_string()],
stderr: vec![],
})
});
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--username=envuser".to_string(),
"--password=filepass".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["deployment-uuid-mixed".to_string()],
stderr: vec![],
})
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "deployment-uuid-mixed");
}
#[tokio::test]
async fn test_get_deployment_id_all_run_command_in_container_error_variants() {
let mut mock_docker = MockDocker::new();
let container_inspect_response = create_test_container_inspect_response();
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Err(RunCommandInContainerError::GetOutputError(
DockerError::ServerError,
))
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_err());
match result.unwrap_err() {
GetDeploymentIdError::RunMongoshCommand(
RunCommandInContainerError::GetOutputError(_),
) => {
}
other => panic!(
"Expected RunMongoshCommand GetOutputError, got: {:?}",
other
),
}
}
#[tokio::test]
async fn test_get_deployment_id_empty_stdout() {
let mut mock_docker = MockDocker::new();
let container_inspect_response = create_test_container_inspect_response();
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec![],
stderr: vec![],
})
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_get_deployment_id_username_only() {
let mut mock_docker = MockDocker::new();
let mut container_inspect_response = create_test_container_inspect_response();
if let Some(config) = container_inspect_response.config.as_mut()
&& let Some(env) = config.env.as_mut()
{
env.push("MONGODB_INITDB_ROOT_USERNAME=onlyuser".to_string());
}
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--username=onlyuser".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["deployment-uuid-username-only".to_string()],
stderr: vec![],
})
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "deployment-uuid-username-only");
}
#[tokio::test]
async fn test_get_deployment_id_password_only() {
let mut mock_docker = MockDocker::new();
let mut container_inspect_response = create_test_container_inspect_response();
if let Some(config) = container_inspect_response.config.as_mut()
&& let Some(env) = config.env.as_mut()
{
env.push("MONGODB_INITDB_ROOT_PASSWORD=onlypass".to_string());
}
mock_docker
.expect_inspect_container()
.with(eq("test-deployment"), eq(None::<InspectContainerOptions>))
.times(1)
.returning(move |_, _| Ok(container_inspect_response.clone()));
mock_docker
.expect_run_command_in_container()
.with(
eq("test_container_id"),
eq(vec![
"mongosh".to_string(),
"mongodb://127.0.0.1:27017/?directConnection=true".to_string(),
"--password=onlypass".to_string(),
"--eval".to_string(),
"db.getSiblingDB('admin').atlascli.findOne()?.uuid".to_string(),
"--quiet".to_string(),
]),
)
.times(1)
.returning(|_, _| {
Ok(CommandOutput {
stdout: vec!["deployment-uuid-password-only".to_string()],
stderr: vec![],
})
});
let client = Client::new(mock_docker);
let result = client.get_deployment_id("test-deployment").await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "deployment-uuid-password-only");
}
}