From 9903a16602012b600bdb3a946a66a881bcc9a410 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Tue, 30 Sep 2025 14:25:38 +0200 Subject: [PATCH 1/3] fix: Removes input dbusename and dbpassword from get_connection_string --- examples/get_connection_string.rs | 13 +--- src/client/get_connection_string.rs | 103 +++++++++++++++++----------- src/client/get_deployment_id.rs | 83 +++++++++++----------- src/test_utils.rs | 4 ++ tests/e2e_test.rs | 10 +-- 5 files changed, 114 insertions(+), 99 deletions(-) diff --git a/examples/get_connection_string.rs b/examples/get_connection_string.rs index e158a7d..5017aeb 100644 --- a/examples/get_connection_string.rs +++ b/examples/get_connection_string.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use atlas_local::{Client, models::GetConnectionStringOptions}; +use atlas_local::Client; use bollard::Docker; #[tokio::main] @@ -14,16 +14,9 @@ async fn main() -> Result<()> { println!("DEPLOYMENT \t CONNECTION STRING"); for deployment in deployments { - let username = deployment.mongodb_initdb_root_username.unwrap_or_default(); - let password = deployment.mongodb_initdb_root_password.unwrap_or_default(); - - let req = GetConnectionStringOptions { - container_id_or_name: deployment.container_id, - db_username: Some(username), - db_password: Some(password), - }; + let container_id_or_name = deployment.container_id; let conn_str = client - .get_connection_string(req) + .get_connection_string(container_id_or_name.to_string()) .await .unwrap_or_else(|e| format!("Error: {}", e)); diff --git a/src/client/get_connection_string.rs b/src/client/get_connection_string.rs index 4ce1a2c..b8f1ea7 100644 --- a/src/client/get_connection_string.rs +++ b/src/client/get_connection_string.rs @@ -1,7 +1,7 @@ use crate::{ - client::Client, - docker::DockerInspectContainer, - models::{GetConnectionStringOptions, MongoDBPortBinding}, + client::get_deployment_id::get_mongodb_secret, + docker::{DockerInspectContainer, RunCommandInContainer, RunCommandInContainerError}, + models::MongoDBPortBinding, }; use bollard::secret::PortBinding; @@ -11,18 +11,22 @@ use super::GetDeploymentError; pub enum GetConnectionStringError { #[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("Missing port binding information")] MissingPortBinding, } -impl Client { +impl crate::client::Client { // Gets a local Atlas deployment's connection string. pub async fn get_connection_string( &self, - req: GetConnectionStringOptions, + container_id_or_name: String, ) -> Result { // Get deployment - let deployment = self.get_deployment(&req.container_id_or_name).await?; + let deployment = self.get_deployment(&container_id_or_name).await?; // Extract port binding let port = match &deployment.port_bindings { @@ -42,9 +46,29 @@ impl Client { .host_ip .ok_or(GetConnectionStringError::MissingPortBinding)?; + // Try to get the MongoDB root username + let mongodb_root_username = get_mongodb_secret( + &self.docker, + &deployment, + |d| d.mongodb_initdb_root_username.as_deref(), + |d| d.mongodb_initdb_root_username_file.as_deref(), + ) + .await + .map_err(GetConnectionStringError::GetMongodbUsername)?; + + // Try to get the MongoDB root password + let mongodb_root_password = get_mongodb_secret( + &self.docker, + &deployment, + |d| d.mongodb_initdb_root_password.as_deref(), + |d| d.mongodb_initdb_root_password_file.as_deref(), + ) + .await + .map_err(GetConnectionStringError::GetMongodbPassword)?; + // Construct the connection string let connection_string = - format_connection_string(hostname, req.db_username, req.db_password, port); + format_connection_string(hostname, mongodb_root_username, mongodb_root_password, port); Ok(connection_string) } @@ -70,8 +94,15 @@ fn format_connection_string( #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{ - create_container_inspect_response_no_auth, create_container_inspect_response_with_auth, + use crate::{ + client::Client, + docker::{ + CommandOutput, DockerInspectContainer, RunCommandInContainer, + RunCommandInContainerError, + }, + test_utils::{ + create_container_inspect_response_no_auth, create_container_inspect_response_with_auth, + }, }; use bollard::{ errors::Error as BollardError, @@ -93,6 +124,14 @@ mod tests { options: Option, ) -> Result; } + + impl RunCommandInContainer for Docker { + async fn run_command_in_container( + &self, + container_id: &str, + command: Vec, + ) -> Result; + } } #[tokio::test] @@ -100,6 +139,7 @@ mod tests { // Arrange let mut mock_docker = MockDocker::new(); + // Mock call to get_deployment mock_docker .expect_inspect_container() .with( @@ -110,14 +150,10 @@ mod tests { .returning(move |_, _| Ok(create_container_inspect_response_with_auth(27017))); let client = Client::new(mock_docker); - let req = GetConnectionStringOptions { - container_id_or_name: "test-deployment".to_string(), - db_username: Some("testuser".to_string()), - db_password: Some("testpass".to_string()), - }; + let container_id_or_name = "test-deployment".to_string(); // Act - let result = client.get_connection_string(req).await; + let result = client.get_connection_string(container_id_or_name).await; // Assert assert!(result.is_ok()); @@ -132,6 +168,7 @@ mod tests { // Arrange let mut mock_docker = MockDocker::new(); + // Mock call to get_deployment mock_docker .expect_inspect_container() .with( @@ -142,14 +179,10 @@ mod tests { .returning(move |_, _| Ok(create_container_inspect_response_no_auth(27017))); let client = Client::new(mock_docker); - let req = GetConnectionStringOptions { - container_id_or_name: "test-deployment".to_string(), - db_username: None, - db_password: None, - }; + let container_id_or_name = "test-deployment".to_string(); // Act - let result = client.get_connection_string(req).await; + let result = client.get_connection_string(container_id_or_name).await; // Assert assert!(result.is_ok()); @@ -164,6 +197,7 @@ mod tests { // Arrange let mut mock_docker = MockDocker::new(); + // Mock call to get_deployment mock_docker .expect_inspect_container() .with( @@ -179,14 +213,10 @@ mod tests { }); let client = Client::new(mock_docker); - let req = GetConnectionStringOptions { - container_id_or_name: "nonexistent-deployment".to_string(), - db_username: None, - db_password: None, - }; + let container_id_or_name = "nonexistent-deployment".to_string(); // Act - let result = client.get_connection_string(req).await; + let result = client.get_connection_string(container_id_or_name).await; // Assert assert!(result.is_err()); @@ -223,6 +253,7 @@ mod tests { ..Default::default() }; + // Mock call to get_deployment mock_docker .expect_inspect_container() .with( @@ -233,14 +264,10 @@ mod tests { .returning(move |_, _| Ok(container_inspect_response.clone())); let client = Client::new(mock_docker); - let req = GetConnectionStringOptions { - container_id_or_name: "test-deployment".to_string(), - db_username: None, - db_password: None, - }; + let container_id_or_name = "test-deployment".to_string(); // Act - let result = client.get_connection_string(req).await; + let result = client.get_connection_string(container_id_or_name).await; // Assert assert!(result.is_err()); @@ -254,6 +281,8 @@ mod tests { async fn test_get_connection_string_verify_success() { // Arrange let mut mock_docker = MockDocker::new(); + + // Mock call to get_deployment mock_docker .expect_inspect_container() .with( @@ -265,14 +294,10 @@ mod tests { let client = Client::new(mock_docker); - let req = GetConnectionStringOptions { - container_id_or_name: "test-deployment".to_string(), - db_username: Some("testuser".to_string()), - db_password: Some("testpass".to_string()), - }; + let container_id_or_name = "test-deployment".to_string(); // Act - let result = client.get_connection_string(req).await; + let result = client.get_connection_string(container_id_or_name).await; // Assert assert!(result.is_ok()); diff --git a/src/client/get_deployment_id.rs b/src/client/get_deployment_id.rs index 45d1e45..5bdb241 100644 --- a/src/client/get_deployment_id.rs +++ b/src/client/get_deployment_id.rs @@ -28,24 +28,24 @@ impl Client { let deployment = self.get_deployment(cluster_id_or_name).await?; // Try to get the MongoDB root username - let mongodb_root_username = self - .get_mongodb_secret( - &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_username = get_mongodb_secret( + &self.docker, + &deployment, + |d| d.mongodb_initdb_root_username.as_deref(), + |d| d.mongodb_initdb_root_username_file.as_deref(), + ) + .await + .map_err(GetDeploymentIdError::GetMongodbUsername)?; // Try to get the MongoDB root password - let mongodb_root_password = self - .get_mongodb_secret( - &deployment, - |d| d.mongodb_initdb_root_password.as_deref(), - |d| d.mongodb_initdb_root_password_file.as_deref(), - ) - .await - .map_err(GetDeploymentIdError::GetMongodbPassword)?; + let mongodb_root_password = get_mongodb_secret( + &self.docker, + &deployment, + |d| d.mongodb_initdb_root_password.as_deref(), + |d| d.mongodb_initdb_root_password_file.as_deref(), + ) + .await + .map_err(GetDeploymentIdError::GetMongodbPassword)?; // Build the mongosh command let mut mongosh_command = vec![ @@ -76,36 +76,35 @@ impl Client { None => Err(GetDeploymentIdError::DeploymentIdEmpty), } } +} - async fn get_mongodb_secret( - &self, - deployment: &Deployment, - value: impl FnOnce(&Deployment) -> Option<&str>, - file: impl FnOnce(&Deployment) -> Option<&str>, - ) -> Result, RunCommandInContainerError> { - // Try to get the value from the environment variables first - if let Some(env_value) = value(deployment) { - return Ok(Some(env_value.to_string())); - } +pub async fn get_mongodb_secret( + docker: &D, + deployment: &Deployment, + value: impl FnOnce(&Deployment) -> Option<&str>, + file: impl FnOnce(&Deployment) -> Option<&str>, +) -> Result, RunCommandInContainerError> { + // Try to get the value from the environment variables first + if let Some(env_value) = value(deployment) { + return Ok(Some(env_value.to_string())); + } - // If the value is not found in the environment variables, try to get it from the file - if let Some(file_value) = file(deployment) { - let command_output = self - .docker - .run_command_in_container( - &deployment.container_id, - vec!["cat".to_string(), file_value.to_string()], - ) - .await?; - - if let Some(line) = command_output.stdout.into_iter().next() { - return Ok(Some(line)); - } - } + // If the value is not found in the environment variables, try to get it from the file + if let Some(file_value) = file(deployment) { + let command_output = docker + .run_command_in_container( + &deployment.container_id, + vec!["cat".to_string(), file_value.to_string()], + ) + .await?; - // If the value is not found in the environment variables or the file, return None - Ok(None) + if let Some(line) = command_output.stdout.into_iter().next() { + return Ok(Some(line)); + } } + + // If the value is not found in the environment variables or the file, return None + Ok(None) } #[cfg(test)] diff --git a/src/test_utils.rs b/src/test_utils.rs index 175fc27..5f0d10b 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -13,6 +13,10 @@ pub fn create_container_inspect_response_with_auth(port: u16) -> ContainerInspec "version".to_string() => "7.0.0".to_string(), "mongodb-type".to_string() => "community".to_string(), }), + env: Some(vec![ + "MONGODB_INITDB_ROOT_USERNAME=testuser".to_string(), + "MONGODB_INITDB_ROOT_PASSWORD=testpass".to_string(), + ]), ..Default::default() }), state: Some(ContainerState { diff --git a/tests/e2e_test.rs b/tests/e2e_test.rs index dc263a4..2114b74 100644 --- a/tests/e2e_test.rs +++ b/tests/e2e_test.rs @@ -1,7 +1,7 @@ #![cfg(feature = "e2e-tests")] use atlas_local::{ Client, - models::{CreateDeploymentOptions, GetConnectionStringOptions, MongoDBPortBinding}, + models::{CreateDeploymentOptions, MongoDBPortBinding}, }; use bollard::{Docker, query_parameters::RemoveContainerOptionsBuilder}; use tokio::runtime::Handle; @@ -90,14 +90,8 @@ async fn test_e2e_smoke_test() { }; // Get Connection String - let get_conn_string_req = GetConnectionStringOptions { - container_id_or_name: name.to_string(), - db_username: Some(username.to_string()), - db_password: Some(password.to_string()), - }; - let conn_string = client - .get_connection_string(get_conn_string_req) + .get_connection_string(name.to_string()) .await .expect("Getting connection string"); From f09a994c3a1d9b4555dbfb743aca594b0a928020 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Tue, 30 Sep 2025 14:34:09 +0200 Subject: [PATCH 2/3] Removes get_connection_string_options --- src/models/get_connection_string_options.rs | 6 ------ src/models/mod.rs | 2 -- 2 files changed, 8 deletions(-) delete mode 100644 src/models/get_connection_string_options.rs diff --git a/src/models/get_connection_string_options.rs b/src/models/get_connection_string_options.rs deleted file mode 100644 index db859d9..0000000 --- a/src/models/get_connection_string_options.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(Debug, Clone, Default)] -pub struct GetConnectionStringOptions { - pub container_id_or_name: String, - pub db_username: Option, - pub db_password: Option, -} diff --git a/src/models/mod.rs b/src/models/mod.rs index 8ad2651..6f8c326 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,7 +2,6 @@ mod create_deployment_options; mod creation_source; mod deployment; mod environment_variables; -mod get_connection_string_options; mod labels; mod mongodb_type; mod port_binding; @@ -12,7 +11,6 @@ pub use create_deployment_options::*; pub use creation_source::*; pub use deployment::*; pub use environment_variables::*; -pub use get_connection_string_options::*; pub use labels::*; pub use mongodb_type::*; pub use port_binding::*; From e528f9826f90b31c1e0a77ee829e1da64787d7bd Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Wed, 1 Oct 2025 16:25:38 +0200 Subject: [PATCH 3/3] moves get_mongodb_secret to seperate file --- src/client/get_connection_string.rs | 2 +- src/client/get_deployment_id.rs | 32 +--------------------------- src/client/get_mongodb_secret.rs | 33 +++++++++++++++++++++++++++++ src/client/mod.rs | 1 + 4 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 src/client/get_mongodb_secret.rs diff --git a/src/client/get_connection_string.rs b/src/client/get_connection_string.rs index b8f1ea7..430c17c 100644 --- a/src/client/get_connection_string.rs +++ b/src/client/get_connection_string.rs @@ -1,5 +1,5 @@ use crate::{ - client::get_deployment_id::get_mongodb_secret, + client::get_mongodb_secret::get_mongodb_secret, docker::{DockerInspectContainer, RunCommandInContainer, RunCommandInContainerError}, models::MongoDBPortBinding, }; diff --git a/src/client/get_deployment_id.rs b/src/client/get_deployment_id.rs index 5bdb241..46bd8fd 100644 --- a/src/client/get_deployment_id.rs +++ b/src/client/get_deployment_id.rs @@ -1,8 +1,7 @@ use crate::{ Client, - client::get_deployment::GetDeploymentError, + client::{get_deployment::GetDeploymentError, get_mongodb_secret::get_mongodb_secret}, docker::{DockerInspectContainer, RunCommandInContainer, RunCommandInContainerError}, - models::Deployment, }; #[derive(Debug, thiserror::Error)] @@ -78,35 +77,6 @@ impl Client { } } -pub async fn get_mongodb_secret( - docker: &D, - deployment: &Deployment, - value: impl FnOnce(&Deployment) -> Option<&str>, - file: impl FnOnce(&Deployment) -> Option<&str>, -) -> Result, RunCommandInContainerError> { - // Try to get the value from the environment variables first - if let Some(env_value) = value(deployment) { - return Ok(Some(env_value.to_string())); - } - - // If the value is not found in the environment variables, try to get it from the file - if let Some(file_value) = file(deployment) { - let command_output = docker - .run_command_in_container( - &deployment.container_id, - vec!["cat".to_string(), file_value.to_string()], - ) - .await?; - - if let Some(line) = command_output.stdout.into_iter().next() { - return Ok(Some(line)); - } - } - - // If the value is not found in the environment variables or the file, return None - Ok(None) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/client/get_mongodb_secret.rs b/src/client/get_mongodb_secret.rs new file mode 100644 index 0000000..181c152 --- /dev/null +++ b/src/client/get_mongodb_secret.rs @@ -0,0 +1,33 @@ +use crate::{ + docker::{RunCommandInContainer, RunCommandInContainerError}, + models::Deployment, +}; + +pub async fn get_mongodb_secret( + docker: &D, + deployment: &Deployment, + value: impl FnOnce(&Deployment) -> Option<&str>, + file: impl FnOnce(&Deployment) -> Option<&str>, +) -> Result, RunCommandInContainerError> { + // Try to get the value from the environment variables first + if let Some(env_value) = value(deployment) { + return Ok(Some(env_value.to_string())); + } + + // If the value is not found in the environment variables, try to get it from the file + if let Some(file_value) = file(deployment) { + let command_output = docker + .run_command_in_container( + &deployment.container_id, + vec!["cat".to_string(), file_value.to_string()], + ) + .await?; + + if let Some(line) = command_output.stdout.into_iter().next() { + return Ok(Some(line)); + } + } + + // If the value is not found in the environment variables or the file, return None + Ok(None) +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 72f834a..e086650 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -5,6 +5,7 @@ mod delete_deployment; mod get_connection_string; mod get_deployment; mod get_deployment_id; +mod get_mongodb_secret; mod list_deployments; mod pull_image;