Compare commits

..

7 Commits

Author SHA1 Message Date
github-actions[bot] d42c31f3dc chore: release 1.21.1 2026-09-17 11:43:39 +00:00
Charles GTE 84673373a7 Merge pull request #107 from Portabase/fix/mongodb-options
fix: mongodb-options
2026-09-17 13:41:27 +02:00
Charles GTE 6f8a5c4228 fix: mongodb options 2026-09-17 11:20:16 +02:00
charles-gauthereau c5806a6ea4 fix: refactoring 2026-09-17 10:47:32 +02:00
Charles GTE c241020542 fix: mongodb options 2026-09-17 10:13:58 +02:00
github-actions[bot] e0bc6556aa chore: release 1.21.0 2026-09-14 13:11:26 +00:00
Charles GTE 947cb9bed7 Merge pull request #104 from Portabase/dev
Dev
2026-09-14 15:09:04 +02:00
9 changed files with 141 additions and 80 deletions
+1 -1
View File
@@ -6,6 +6,6 @@
.env
.claude
/docs
.superpowers
+1 -1
View File
@@ -27,5 +27,5 @@ keywords:
- self-hosted
- portabase
license: Apache-2.0
version: 1.20.1
version: 1.21.1
date-released: '2026-02-24'
Generated
+1 -1
View File
@@ -3642,7 +3642,7 @@ dependencies = [
[[package]]
name = "portabase-agent"
version = "1.20.1"
version = "1.21.1"
dependencies = [
"aes",
"aes-gcm",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "portabase-agent"
version = "1.20.1"
version = "1.21.1"
edition = "2024"
[dependencies]
+3 -3
View File
@@ -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
View File
@@ -1,6 +1,6 @@
pub mod docker_volume;
pub mod factory;
mod mongodb;
pub mod mongodb;
pub mod mysql;
pub mod postgres;
mod redis;
+33 -71
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
mod backup;
mod connection;
pub mod connection;
pub mod database;
mod ping;
mod restore;
+99
View File
@@ -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"
);
}