diff --git a/docker-compose.yml b/docker-compose.yml index 189b569..2ad103b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: APP_ENV: development LOG: debug TZ: "Europe/Paris" - EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiMGZiNDYyMmUtMTMxNS00MzMxLTlkMTMtZWMzMjAyZjZiNTIwIiwibWFzdGVyS2V5QjY0IjoiMUh0djdtWCtYVkJxL0IzUEV2WDlZZjlQeUdVZW5oRHlXemo5THRqNW90WT0ifQ==" + EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiNmNiMmQwODMtZDQ4MS00MWY3LTk5NjItZjNhMzU2ZTJiMzllIiwibWFzdGVyS2V5QjY0IjoiMUh0djdtWCtYVkJxL0IzUEV2WDlZZjlQeUdVZW5oRHlXemo5THRqNW90WT0ifQ==" #CHUNK_SIZE_MB: "1" #POOLING: 1 #DATABASES_CONFIG_FILE: "config.toml" diff --git a/src/domain/postgres/backup.rs b/src/domain/postgres/backup.rs index 61f24f3..906cfd9 100644 --- a/src/domain/postgres/backup.rs +++ b/src/domain/postgres/backup.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use std::collections::HashMap; use std::path::PathBuf; use std::process::Command; use std::sync::Arc; @@ -13,6 +14,7 @@ pub async fn run( cfg: DatabaseConfig, format: PostgresDumpFormat, backup_dir: PathBuf, + env: HashMap, logger: Arc, ) -> Result { tokio::task::spawn_blocking(move || -> Result { @@ -37,18 +39,18 @@ pub async fn run( logger.log("info", format!("Running FC backup for {}", cfg.name)); let file_path = backup_dir.join(format!("{}.dump", cfg.generated_id)); - let url = format!( - "postgresql://{}:{}@{}:{}/{}", - cfg.username, cfg.password, cfg.host, cfg.port, cfg.database - ); let start = Instant::now(); let output = Command::new(&pg_dump) - .arg("--dbname").arg(&url) + .arg("--host").arg(&cfg.host) + .arg("--port").arg(cfg.port.to_string()) + .arg("--username").arg(&cfg.username) + .arg("--dbname").arg(&cfg.database) .arg("-Fc") .arg("-f").arg(&file_path) .arg("-v") .arg("--compress=3") + .envs(env) .output(); let duration_ms = start.elapsed().as_millis() as f64; @@ -87,19 +89,19 @@ pub async fn run( return Err(e.into()); } - let url = format!( - "postgresql://{}:{}@{}:{}/{}", - cfg.username, cfg.password, cfg.host, cfg.port, cfg.database - ); - let cmd_label = format!("pg_dump -Fd {}", url); + let cmd_label = format!("pg_dump -Fd {}@{}:{}/{}", cfg.username, cfg.host, cfg.port, cfg.database); let start = Instant::now(); let output = Command::new(&pg_dump) - .arg("--dbname").arg(&url) + .arg("--host").arg(&cfg.host) + .arg("--port").arg(cfg.port.to_string()) + .arg("--username").arg(&cfg.username) + .arg("--dbname").arg(&cfg.database) .arg("-Fd") .arg("-j").arg("4") .arg("-f").arg(&dump_dir) .arg("-v") + .envs(env) .output(); let duration_ms = start.elapsed().as_millis() as f64; diff --git a/src/domain/postgres/connection.rs b/src/domain/postgres/connection.rs index 92648f3..358b354 100644 --- a/src/domain/postgres/connection.rs +++ b/src/domain/postgres/connection.rs @@ -2,17 +2,21 @@ use crate::domain::postgres::format::PostgresDumpFormat; use crate::services::config::DatabaseConfig; use anyhow::Result; use std::path::Path; -use tokio_postgres::{Client, NoTls}; +use tokio_postgres::{Client, Config, NoTls}; use tracing::{error, info}; pub async fn connect(cfg: &DatabaseConfig) -> Result { info!("Connecting to postgres database {}:{}", cfg.host, cfg.port); - let dsn = format!( - "host={} port={} user={} password={} dbname={}", - cfg.host, cfg.port, cfg.username, cfg.password, cfg.database - ); - let (client, connection) = tokio_postgres::connect(&dsn, NoTls).await?; + let mut config = Config::new(); + config + .host(&cfg.host) + .port(cfg.port) + .user(&cfg.username) + .password(&cfg.password) + .dbname(&cfg.database); + + let (client, connection) = config.connect(NoTls).await?; tokio::spawn(async move { if let Err(e) = connection.await { error!("Postgres connection error: {}", e); diff --git a/src/domain/postgres/database.rs b/src/domain/postgres/database.rs index 205de95..c10b114 100644 --- a/src/domain/postgres/database.rs +++ b/src/domain/postgres/database.rs @@ -1,8 +1,9 @@ use anyhow::Result; use async_trait::async_trait; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; - +use tracing::info; use super::{backup, format::PostgresDumpFormat, ping, restore}; use crate::domain::factory::Database; use crate::services::backup::logger::JobLogger; @@ -18,6 +19,13 @@ impl PostgresDatabase { pub fn new(cfg: DatabaseConfig, format: PostgresDumpFormat) -> Self { Self { cfg, format } } + + fn build_env(&self) -> HashMap { + let mut envs = std::env::vars().collect::>(); + envs.insert("PGPASSWORD".to_string(), self.cfg.password.to_string()); + info!("envs: {:?}", envs); + envs + } } #[async_trait] @@ -35,14 +43,28 @@ impl Database for PostgresDatabase { async fn backup(&self, dir: &Path, logger: Arc) -> Result { FileLock::acquire(&self.cfg.generated_id, DbOpLock::Backup.as_str()).await?; - let res = backup::run(self.cfg.clone(), self.format, dir.to_path_buf(), logger).await; + let res = backup::run( + self.cfg.clone(), + self.format, + dir.to_path_buf(), + self.build_env(), + logger, + ) + .await; FileLock::release(&self.cfg.generated_id).await?; res } async fn restore(&self, file: &Path, logger: Arc) -> Result<()> { FileLock::acquire(&self.cfg.generated_id, DbOpLock::Restore.as_str()).await?; - let res = restore::run(self.cfg.clone(), self.format, file.to_path_buf(), logger).await; + let res = restore::run( + self.cfg.clone(), + self.format, + file.to_path_buf(), + self.build_env(), + logger, + ) + .await; FileLock::release(&self.cfg.generated_id).await?; res } diff --git a/src/domain/postgres/restore.rs b/src/domain/postgres/restore.rs index 2eca26a..9a005ec 100644 --- a/src/domain/postgres/restore.rs +++ b/src/domain/postgres/restore.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use std::collections::HashMap; use std::path::PathBuf; use std::process::Command; use std::sync::Arc; @@ -13,6 +14,7 @@ pub async fn run( cfg: DatabaseConfig, format: PostgresDumpFormat, restore_file: PathBuf, + env: HashMap, logger: Arc, ) -> Result<()> { tokio::task::spawn_blocking(move || -> Result<()> { @@ -39,11 +41,6 @@ pub async fn run( } logger.log("info", format!("Connections terminated for database {}", cfg.name)); - let url = format!( - "postgresql://{}:{}@{}:{}/{}", - cfg.username, cfg.password, cfg.host, cfg.port, cfg.database - ); - match format { PostgresDumpFormat::Fc => { logger.log("info", format!("Running FC restore for {}", cfg.name)); @@ -54,11 +51,13 @@ pub async fn run( .arg("--clean") .arg("--if-exists") // .arg("--create") - .arg("--dbname") - .arg(&url) + .arg("--host").arg(&cfg.host) + .arg("--port").arg(cfg.port.to_string()) + .arg("--username").arg(&cfg.username) + .arg("--dbname").arg(&cfg.database) .arg("-v") .arg(&restore_file) - .env("PGPASSWORD", &cfg.password) + .envs(env) .output(); let duration_ms = start.elapsed().as_millis() as f64; @@ -154,13 +153,15 @@ pub async fn run( .arg("--clean") .arg("--if-exists") // .arg("--create") - .arg("--dbname") - .arg(&url) + .arg("--host").arg(&cfg.host) + .arg("--port").arg(cfg.port.to_string()) + .arg("--username").arg(&cfg.username) + .arg("--dbname").arg(&cfg.database) .arg("-v") .arg("-j") .arg("4") .arg(dump_dir) - .env("PGPASSWORD", &cfg.password) + .envs(env) .output(); let duration_ms = start.elapsed().as_millis() as f64; diff --git a/src/tests/domain/postgres.rs b/src/tests/domain/postgres.rs index 029ef84..bd4a5a5 100644 --- a/src/tests/domain/postgres.rs +++ b/src/tests/domain/postgres.rs @@ -107,3 +107,43 @@ async fn postgres_backup_restore_test() { } } } + +#[tokio::test] +async fn postgres_password_with_slash_test() { + init_tracing_for_test(); + + let special_password = "ch/ange:me@1"; + + let container = Postgres::default() + .with_env_var("POSTGRES_DB", "testdb") + .with_env_var("POSTGRES_USER", "testuser") + .with_env_var("POSTGRES_PASSWORD", special_password) + .with_tag("17") + .start() + .await + .unwrap(); + + let host = container + .get_host() + .await + .unwrap_or(Host::parse("127.0.0.1").unwrap()); + + let port = container.get_host_port_ipv4(5432).await.unwrap_or(5432); + + let config = DatabaseConfig { + name: "My test Postgres Database with slash password".to_string(), + database: "testdb".to_string(), + db_type: DbType::Postgresql, + username: "testuser".to_string(), + password: special_password.to_string(), + port, + host: host.to_string(), + generated_id: "5a1f0e3c-9b8a-4a8e-9b1b-0a1c2d3e4f5a".to_string(), + path: "".to_string(), + }; + + let db = DatabaseFactory::create_for_backup(config.clone()).await; + let reachable = db.ping().await.unwrap_or(false); + + assert_eq!(reachable, true); +}