use ini::Ini;
use std::{fmt, fs, path::PathBuf};
pub type ToolsDBCluster = Cluster;
pub use Cluster::ANALYTICS as ANALYTICS_CLUSTER;
pub use Cluster::WEB as WEB_CLUSTER;
const DEFAULT_POOL_MAX: usize = 10;
pub struct DBConnectionInfo {
pub database: String,
pub host: String,
pub user: String,
pub password: String,
pool_max: usize,
}
impl DBConnectionInfo {
pub fn pool_max(mut self, pool_max: usize) -> Self {
self.pool_max = pool_max;
self
}
}
impl fmt::Display for DBConnectionInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"mysql://{}:{}@{}:3306/{}?pool_min=0&pool_max={}&inactive_connection_ttl=1&ttl_check_interval=30",
self.user, self.password, self.host, self.database, self.pool_max
)
}
}
pub(crate) struct ReplicaMyCnf {
pub(crate) user: String,
pub(crate) password: String,
pub(crate) local: bool,
}
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum Cluster {
ANALYTICS,
WEB,
}
#[doc(hidden)]
pub fn get_db_connection_info(
dbname: &str,
cluster: Cluster,
my_cnf_path: Option<PathBuf>,
) -> crate::Result<DBConnectionInfo> {
let my_cnf = load_replica_cnf(my_cnf_path)?;
let domain = format!(
"{}.db.svc.{}",
match cluster {
Cluster::ANALYTICS => "analytics",
Cluster::WEB => "web",
},
if my_cnf.local {
"local.wmftest.net"
} else {
"wikimedia.cloud"
}
);
let normalized_dbname = dbname.trim_end_matches("_p");
let host = if normalized_dbname == "meta" {
format!("s7.{domain}")
} else {
format!("{normalized_dbname}.{domain}")
};
Ok(DBConnectionInfo {
database: format!("{normalized_dbname}_p"),
host,
user: my_cnf.user,
password: my_cnf.password,
pool_max: DEFAULT_POOL_MAX,
})
}
pub fn toolsdb(database: String) -> crate::Result<DBConnectionInfo> {
let my_cnf = load_replica_cnf(None)?;
let domain = if my_cnf.local {
"tools.db.svc.local.wmftest.net"
} else {
"tools.db.svc.wikimedia.cloud"
};
Ok(DBConnectionInfo {
database,
host: domain.to_string(),
user: my_cnf.user,
password: my_cnf.password,
pool_max: DEFAULT_POOL_MAX,
})
}
pub(crate) fn load_replica_cnf(
path: Option<PathBuf>,
) -> crate::Result<ReplicaMyCnf> {
let path = path.unwrap_or_else(|| {
dirs::home_dir()
.expect("Couldn't find home directory")
.join("replica.my.cnf")
});
if !path.exists() {
return Err(crate::Error::NotToolforge("replica.my.cnf".to_string()));
}
let contents = fs::read_to_string(path)?;
let conf = Ini::load_from_str(&contents)?;
let section = conf.section(Some("client")).unwrap();
Ok(ReplicaMyCnf {
user: section.get("user").unwrap().to_string(),
password: section.get("password").unwrap().to_string(),
local: section.get("local").is_some(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::get_db_connection_info;
#[test]
fn test_to_string() {
let info = DBConnectionInfo {
database: "meta_p".to_string(),
host: "hostname".to_string(),
user: "u12345".to_string(),
password: "correcthorsebatterystaple".to_string(),
pool_max: DEFAULT_POOL_MAX,
};
assert_eq!(
"mysql://u12345:correcthorsebatterystaple@hostname:3306/meta_p?pool_min=0&pool_max=10&inactive_connection_ttl=1&ttl_check_interval=30"
.to_string(),
info.to_string()
)
}
#[test]
fn test_connection_info() -> crate::Result<()> {
let path = Some(PathBuf::new().join("./tests/replica.my.cnf"));
assert_eq!(
"mysql://u12345:correcthorsebatterystaple@enwiki.web.db.svc.wikimedia.cloud:3306/enwiki_p?pool_min=0&pool_max=10&inactive_connection_ttl=1&ttl_check_interval=30".to_string(),
get_db_connection_info(
"enwiki", Cluster::WEB, path.clone())?.to_string()
);
assert_eq!(
"mysql://u12345:correcthorsebatterystaple@enwiki.analytics.db.svc.wikimedia.cloud:3306/enwiki_p?pool_min=0&pool_max=10&inactive_connection_ttl=1&ttl_check_interval=30"
.to_string(),
get_db_connection_info(
"enwiki_p", Cluster::ANALYTICS, path.clone())?.to_string()
);
assert_eq!(
"mysql://u12345:correcthorsebatterystaple@s7.web.db.svc.wikimedia.cloud:3306/meta_p?pool_min=0&pool_max=10&inactive_connection_ttl=1&ttl_check_interval=30".to_string(),
get_db_connection_info(
"meta_p", Cluster::WEB, path.clone())?.to_string()
);
assert_eq!(
"mysql://u23456:correcthorsebatterystaples@enwiki.web.db.svc.wikimedia.cloud:3306/enwiki_p?pool_min=0&pool_max=10&inactive_connection_ttl=1&ttl_check_interval=30".to_string(),
get_db_connection_info(
"enwiki",
Cluster::WEB,
Some(PathBuf::new().join("./tests/double_quote.my.cnf"))
)?.to_string()
);
assert_eq!(
"mysql://u34567:correcthorsebatterystapler@enwiki.web.db.svc.local.wmftest.net:3306/enwiki_p?pool_min=0&pool_max=10&inactive_connection_ttl=1&ttl_check_interval=30".to_string(),
get_db_connection_info(
"enwiki",
Cluster::WEB,
Some(PathBuf::new().join("./tests/local.my.cnf"))
)?.to_string()
);
assert_eq!(
"mysql://u12345:correcthorsebatterystaple@enwiki.web.db.svc.wikimedia.cloud:3306/enwiki_p?pool_min=0&pool_max=20&inactive_connection_ttl=1&ttl_check_interval=30".to_string(),
get_db_connection_info(
"enwiki",
Cluster::WEB,
path
)?.pool_max(20).to_string()
);
Ok(())
}
}