blob: f9138ec89d9ac91304ae9ae77ed356ffd8d25cb6 [file]
use crate::error::{DorisError, DorisResult};
use crate::product::ProductProfile;
use serde::{Deserialize, Serialize};
/// A resolved environment with all connection details ready to use.
#[derive(Debug, Clone)]
pub struct Environment {
pub product: &'static ProductProfile,
/// The resolved environment's name (kept as metadata; not all paths read it).
#[allow(dead_code)]
pub name: String,
pub host: String,
pub mysql_port: u16,
pub http_port: u16,
pub user: String,
pub password: String,
pub socks5: Option<Socks5Config>,
/// Runtime-only (never persisted): a SQL statement to issue immediately
/// after `MysqlConnection::connect` succeeds — e.g. `USE @<compute-group>`
/// sourced from `--init-sql` / `DORIS_INIT_SQL` (the `cloudcli cloud
/// endpoint` handoff).
pub cluster_routing_directive: Option<String>,
}
/// Stored environment config (without secrets).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentConfig {
pub host: String,
#[serde(default = "default_mysql_port")]
pub mysql_port: u16,
#[serde(default = "default_http_port")]
pub http_port: u16,
pub user: String,
}
/// Stored credentials (secrets).
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EnvironmentCredentials {
#[serde(default)]
pub password: String,
}
/// Runtime-only SOCKS5 proxy configuration.
///
/// Never persisted to disk. Sourced from the `--socks5` flag or `<PREFIX>_SOCKS5_*`
/// env vars because BYOC bastions are multi-tenant — one tenant's credentials
/// must not leak to another through a shared config file.
#[derive(Debug, Clone)]
pub struct Socks5Config {
pub host: String,
pub port: u16,
pub user: String,
pub pass: String,
}
impl Socks5Config {
/// Build from env vars. Returns None unless both host and port are set.
/// user/pass default to "admin"/"admin" by convention.
pub fn from_env(product: &ProductProfile) -> Option<Self> {
let host = std::env::var(product.env_key("SOCKS5_HOST")).ok()?;
let port: u16 = std::env::var(product.env_key("SOCKS5_PORT"))
.ok()?
.parse()
.ok()?;
let user =
std::env::var(product.env_key("SOCKS5_USER")).unwrap_or_else(|_| "admin".to_string());
let pass =
std::env::var(product.env_key("SOCKS5_PASS")).unwrap_or_else(|_| "admin".to_string());
Some(Socks5Config {
host,
port,
user,
pass,
})
}
/// Parse `user:pass@host:port` (matches the Go mysql-client reference).
pub fn parse_flag(s: &str) -> DorisResult<Self> {
let (auth, hostport) = s.split_once('@').ok_or_else(|| {
DorisError::config(format!(
"--socks5 must be in 'user:pass@host:port' form, got '{s}'"
))
})?;
let (user, pass) = auth.split_once(':').ok_or_else(|| {
DorisError::config(format!("--socks5 auth must be 'user:pass', got '{auth}'"))
})?;
let (host, port_s) = hostport.rsplit_once(':').ok_or_else(|| {
DorisError::config(format!(
"--socks5 host must be 'host:port', got '{hostport}'"
))
})?;
let port: u16 = port_s.parse().map_err(|_| {
DorisError::config(format!("--socks5 port '{port_s}' is not a valid u16"))
})?;
Ok(Socks5Config {
host: host.to_string(),
port,
user: user.to_string(),
pass: pass.to_string(),
})
}
}
fn default_mysql_port() -> u16 {
9030
}
fn default_http_port() -> u16 {
8030
}
impl Environment {
/// Apply environment variable overrides (highest precedence before the --socks5 flag).
pub fn with_env_overrides(mut self) -> Self {
if let Ok(host) = std::env::var(self.product.env_key("HOST")) {
self.host = host;
}
if let Ok(user) = std::env::var(self.product.env_key("USER")) {
self.user = user;
}
if let Ok(password) = std::env::var(self.product.env_key("PASSWORD")) {
self.password = password;
}
if let Ok(port) = std::env::var(self.product.env_key("PORT")) {
if let Ok(p) = port.parse() {
self.mysql_port = p;
}
}
if let Ok(port) = std::env::var(self.product.env_key("HTTP_PORT")) {
if let Ok(p) = port.parse() {
self.http_port = p;
}
}
if self.socks5.is_none() {
self.socks5 = Socks5Config::from_env(self.product);
}
self
}
/// Build from config + credentials.
pub fn from_config(
product: &'static ProductProfile,
name: String,
config: &EnvironmentConfig,
creds: &EnvironmentCredentials,
) -> Self {
Environment {
product,
name,
host: config.host.clone(),
mysql_port: config.mysql_port,
http_port: config.http_port,
user: config.user.clone(),
password: creds.password.clone(),
socks5: None,
cluster_routing_directive: None,
}
}
}