mirror of
https://github.com/Portabase/agent.git
synced 2026-10-04 13:33:20 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d42c31f3dc | |||
| 84673373a7 | |||
| 6f8a5c4228 | |||
| c5806a6ea4 | |||
| c241020542 | |||
| e0bc6556aa | |||
| 947cb9bed7 |
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
.claude
|
.claude
|
||||||
|
|
||||||
/docs
|
/docs
|
||||||
|
|
||||||
.superpowers
|
.superpowers
|
||||||
|
|||||||
+1
-1
@@ -27,5 +27,5 @@ keywords:
|
|||||||
- self-hosted
|
- self-hosted
|
||||||
- portabase
|
- portabase
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
version: 1.20.1
|
version: 1.21.1
|
||||||
date-released: '2026-02-24'
|
date-released: '2026-02-24'
|
||||||
|
|||||||
Generated
+1
-1
@@ -3642,7 +3642,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portabase-agent"
|
name = "portabase-agent"
|
||||||
version = "1.20.1"
|
version = "1.21.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "portabase-agent"
|
name = "portabase-agent"
|
||||||
version = "1.20.1"
|
version = "1.21.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ services:
|
|||||||
|
|
||||||
db-mongodb-auth:
|
db-mongodb-auth:
|
||||||
container_name: db-mongodb-auth
|
container_name: db-mongodb-auth
|
||||||
image: mongo:latest
|
image: mongo:8.0.4
|
||||||
ports:
|
ports:
|
||||||
- "27082:27017"
|
- "27082:27017"
|
||||||
environment:
|
environment:
|
||||||
@@ -75,14 +75,14 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- mongodb-data-auth:/data/db
|
- mongodb-data-auth:/data/db
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
|
test: [ "CMD", "mongosh", "--eval", "db.adminCommand('ping')" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
db-mongodb:
|
db-mongodb:
|
||||||
container_name: db-mongodb
|
container_name: db-mongodb
|
||||||
image: mongo:latest
|
image: mongo:8.0.4
|
||||||
ports:
|
ports:
|
||||||
- "27083:27017"
|
- "27083:27017"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
pub mod docker_volume;
|
pub mod docker_volume;
|
||||||
pub mod factory;
|
pub mod factory;
|
||||||
mod mongodb;
|
pub mod mongodb;
|
||||||
pub mod mysql;
|
pub mod mysql;
|
||||||
pub mod postgres;
|
pub mod postgres;
|
||||||
mod redis;
|
mod redis;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::services::config::DatabaseConfig;
|
use crate::services::config::DatabaseConfig;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use mongodb::Client;
|
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
|
const USERINFO_ENCODE: &AsciiSet = &NON_ALPHANUMERIC
|
||||||
.remove(b'-')
|
.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 {
|
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 scheme = if is_srv { "mongodb+srv" } else { "mongodb" };
|
||||||
let has_auth = !cfg.username.is_empty() && !cfg.password.is_empty();
|
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()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let authority = if is_srv {
|
let authority = if is_srv || is_multi_host {
|
||||||
cfg.host.clone()
|
cfg.host.clone()
|
||||||
} else {
|
} else {
|
||||||
format!("{}:{}", cfg.host, cfg.port)
|
format!("{}:{}", cfg.host, cfg.port)
|
||||||
@@ -53,7 +54,35 @@ pub fn build_mongo_uri(cfg: &DatabaseConfig, include_db: bool) -> String {
|
|||||||
"/".to_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)
|
format!("{}://{}{}{}{}", scheme, credentials, authority, path, query)
|
||||||
}
|
}
|
||||||
@@ -71,70 +100,3 @@ pub fn extract_db_name(dry_output: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
dbs.into_iter().next()
|
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 backup;
|
||||||
mod connection;
|
pub mod connection;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
mod ping;
|
mod ping;
|
||||||
mod restore;
|
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