mirror of
https://github.com/Portabase/agent.git
synced 2026-10-03 21:03:16 +00:00
Merge pull request #107 from Portabase/fix/mongodb-options
fix: mongodb-options
This commit is contained in:
+1
-1
@@ -6,6 +6,6 @@
|
||||
.env
|
||||
|
||||
.claude
|
||||
|
||||
/docs
|
||||
|
||||
.superpowers
|
||||
|
||||
@@ -62,7 +62,7 @@ services:
|
||||
|
||||
db-mongodb-auth:
|
||||
container_name: db-mongodb-auth
|
||||
image: mongo:latest
|
||||
image: mongo:8.0.4
|
||||
ports:
|
||||
- "27082:27017"
|
||||
environment:
|
||||
@@ -75,14 +75,14 @@ services:
|
||||
volumes:
|
||||
- mongodb-data-auth:/data/db
|
||||
healthcheck:
|
||||
test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
|
||||
test: [ "CMD", "mongosh", "--eval", "db.adminCommand('ping')" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
db-mongodb:
|
||||
container_name: db-mongodb
|
||||
image: mongo:latest
|
||||
image: mongo:8.0.4
|
||||
ports:
|
||||
- "27083:27017"
|
||||
volumes:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
pub mod docker_volume;
|
||||
pub mod factory;
|
||||
mod mongodb;
|
||||
pub mod mongodb;
|
||||
pub mod mysql;
|
||||
pub mod postgres;
|
||||
mod redis;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::Result;
|
||||
use mongodb::Client;
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
|
||||
|
||||
const USERINFO_ENCODE: &AsciiSet = &NON_ALPHANUMERIC
|
||||
.remove(b'-')
|
||||
@@ -27,7 +27,8 @@ pub fn get_mongo_uri(cfg: DatabaseConfig) -> Result<String> {
|
||||
}
|
||||
|
||||
pub fn build_mongo_uri(cfg: &DatabaseConfig, include_db: bool) -> String {
|
||||
let is_srv = cfg.port == 0;
|
||||
let is_multi_host = cfg.host.contains(',');
|
||||
let is_srv = cfg.port == 0 && !is_multi_host;
|
||||
let scheme = if is_srv { "mongodb+srv" } else { "mongodb" };
|
||||
let has_auth = !cfg.username.is_empty() && !cfg.password.is_empty();
|
||||
|
||||
@@ -41,7 +42,7 @@ pub fn build_mongo_uri(cfg: &DatabaseConfig, include_db: bool) -> String {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let authority = if is_srv {
|
||||
let authority = if is_srv || is_multi_host {
|
||||
cfg.host.clone()
|
||||
} else {
|
||||
format!("{}:{}", cfg.host, cfg.port)
|
||||
@@ -53,7 +54,35 @@ pub fn build_mongo_uri(cfg: &DatabaseConfig, include_db: bool) -> String {
|
||||
"/".to_string()
|
||||
};
|
||||
|
||||
let query = if has_auth { "?authSource=admin" } else { "" };
|
||||
let mut params: Vec<String> = Vec::new();
|
||||
|
||||
match cfg.options.get("auth_source").and_then(|v| v.as_str()) {
|
||||
Some(s) if !s.is_empty() => params.push(format!(
|
||||
"authSource={}",
|
||||
utf8_percent_encode(s, USERINFO_ENCODE)
|
||||
)),
|
||||
_ if has_auth => params.push("authSource=admin".to_string()),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(rs) = cfg.options.get("replica_set").and_then(|v| v.as_str()) {
|
||||
if !rs.is_empty() {
|
||||
params.push(format!(
|
||||
"replicaSet={}",
|
||||
utf8_percent_encode(rs, USERINFO_ENCODE)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.options.get("tls").and_then(|v| v.as_bool()) == Some(true) {
|
||||
params.push("tls=true".to_string());
|
||||
}
|
||||
|
||||
let query = if params.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("?{}", params.join("&"))
|
||||
};
|
||||
|
||||
format!("{}://{}{}{}{}", scheme, credentials, authority, path, query)
|
||||
}
|
||||
@@ -71,70 +100,3 @@ pub fn extract_db_name(dry_output: &str) -> Option<String> {
|
||||
}
|
||||
dbs.into_iter().next()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::services::config::{DatabaseConfig, DbType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn cfg(host: &str, port: u16, user: &str, pass: &str) -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
name: "t".into(),
|
||||
database: "mydb".into(),
|
||||
db_type: DbType::MongoDB,
|
||||
username: user.into(),
|
||||
password: pass.into(),
|
||||
port,
|
||||
host: host.into(),
|
||||
generated_id: "id".into(),
|
||||
path: String::new(),
|
||||
max_packet_size: String::new(),
|
||||
volume_name: String::new(),
|
||||
container_name: None,
|
||||
options: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standard_with_auth() {
|
||||
let c = cfg("localhost", 27017, "user", "pass");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb://user:pass@localhost:27017/mydb?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standard_no_auth() {
|
||||
let c = cfg("localhost", 27017, "", "");
|
||||
assert_eq!(build_mongo_uri(&c, true), "mongodb://localhost:27017/mydb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn srv_with_auth() {
|
||||
let c = cfg("cluster.example.mongodb.net", 0, "user", "pass");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb+srv://user:pass@cluster.example.mongodb.net/mydb?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn srv_no_db_for_dryrun() {
|
||||
let c = cfg("cluster.example.mongodb.net", 0, "user", "pass");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, false),
|
||||
"mongodb+srv://user:pass@cluster.example.mongodb.net/?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encodes_special_chars_in_credentials() {
|
||||
let c = cfg("cluster.example.mongodb.net", 0, "user", "p@ss:w/rd?");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb+srv://user:p%40ss%3Aw%2Frd%3F@cluster.example.mongodb.net/mydb?authSource=admin"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod backup;
|
||||
mod connection;
|
||||
pub mod connection;
|
||||
pub mod database;
|
||||
mod ping;
|
||||
mod restore;
|
||||
|
||||
@@ -100,3 +100,102 @@ async fn mongodb_backup_restore_test() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::domain::mongodb::connection::build_mongo_uri;
|
||||
|
||||
fn uri_cfg(host: &str, port: u16, user: &str, pass: &str) -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
name: "t".into(),
|
||||
database: "mydb".into(),
|
||||
db_type: DbType::MongoDB,
|
||||
username: user.into(),
|
||||
password: pass.into(),
|
||||
port,
|
||||
host: host.into(),
|
||||
generated_id: "id".into(),
|
||||
path: String::new(),
|
||||
max_packet_size: String::new(),
|
||||
volume_name: String::new(),
|
||||
container_name: None,
|
||||
options: std::collections::HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_standard_with_auth() {
|
||||
let c = uri_cfg("localhost", 27017, "user", "pass");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb://user:pass@localhost:27017/mydb?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_standard_no_auth() {
|
||||
let c = uri_cfg("localhost", 27017, "", "");
|
||||
assert_eq!(build_mongo_uri(&c, true), "mongodb://localhost:27017/mydb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_srv_with_auth() {
|
||||
let c = uri_cfg("cluster.example.mongodb.net", 0, "user", "pass");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb+srv://user:pass@cluster.example.mongodb.net/mydb?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_srv_no_db_for_dryrun() {
|
||||
let c = uri_cfg("cluster.example.mongodb.net", 0, "user", "pass");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, false),
|
||||
"mongodb+srv://user:pass@cluster.example.mongodb.net/?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_options_authsource_replicaset_tls() {
|
||||
let mut c = uri_cfg("localhost", 27017, "user", "pass");
|
||||
c.options.insert("auth_source".into(), "myauthdb".into());
|
||||
c.options.insert("replica_set".into(), "rs0".into());
|
||||
c.options.insert("tls".into(), serde_json::Value::Bool(true));
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb://user:pass@localhost:27017/mydb?authSource=myauthdb&replicaSet=rs0&tls=true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_multi_host_replica_set() {
|
||||
let mut c = uri_cfg(
|
||||
"mongodb0.example.internal:27017,mongodb1.example.internal:27017,mongodb2.example.internal:27017",
|
||||
0,
|
||||
"myDatabaseUser",
|
||||
"D1fficultP@ssw0rd",
|
||||
);
|
||||
c.database = "myDB".into();
|
||||
c.options.insert("replica_set".into(), "myRepl".into());
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb://myDatabaseUser:D1fficultP%40ssw0rd@mongodb0.example.internal:27017,mongodb1.example.internal:27017,mongodb2.example.internal:27017/myDB?authSource=admin&replicaSet=myRepl"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_default_authsource_when_auth() {
|
||||
let c = uri_cfg("localhost", 27017, "user", "pass");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb://user:pass@localhost:27017/mydb?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_encodes_special_chars_in_credentials() {
|
||||
let c = uri_cfg("cluster.example.mongodb.net", 0, "user", "p@ss:w/rd?");
|
||||
assert_eq!(
|
||||
build_mongo_uri(&c, true),
|
||||
"mongodb+srv://user:p%40ss%3Aw%2Frd%3F@cluster.example.mongodb.net/mydb?authSource=admin"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user