use std::collections::{BTreeMap, HashMap};
use crate::config::model::DevrigConfig;
use crate::discovery::url::generate_url;
pub fn build_service_env(
service_name: &str,
config: &DevrigConfig,
resolved_ports: &HashMap<String, u16>,
) -> BTreeMap<String, String> {
let mut env = BTreeMap::new();
for (k, v) in &config.env {
env.insert(k.clone(), v.clone());
}
for (docker_name, docker_config) in &config.docker {
let upper = docker_name.to_uppercase();
let port_key = format!("docker:{}", docker_name);
env.insert(format!("DEVRIG_{}_HOST", upper), "localhost".to_string());
if let Some(&port) = resolved_ports.get(&port_key) {
env.insert(format!("DEVRIG_{}_PORT", upper), port.to_string());
let url = generate_url(docker_name, docker_config, port);
env.insert(format!("DEVRIG_{}_URL", upper), url);
}
for port_name in docker_config.ports.keys() {
let named_key = format!("docker:{}:{}", docker_name, port_name);
if let Some(&port) = resolved_ports.get(&named_key) {
let upper_port_name = port_name.to_uppercase();
env.insert(
format!("DEVRIG_{}_PORT_{}", upper, upper_port_name),
port.to_string(),
);
}
}
}
for svc_name in config.services.keys() {
if svc_name == service_name {
continue;
}
let upper = svc_name.to_uppercase();
let svc_key = format!("service:{}", svc_name);
env.insert(format!("DEVRIG_{}_HOST", upper), "localhost".to_string());
if let Some(&port) = resolved_ports.get(&svc_key) {
env.insert(format!("DEVRIG_{}_PORT", upper), port.to_string());
env.insert(
format!("DEVRIG_{}_URL", upper),
format!("http://localhost:{}", port),
);
}
}
let own_key = format!("service:{}", service_name);
if let Some(&port) = resolved_ports.get(&own_key) {
env.insert("PORT".to_string(), port.to_string());
}
env.insert("HOST".to_string(), "localhost".to_string());
if let Some(ref dash) = config.dashboard {
if dash.enabled.unwrap_or(true) {
if let Some(&http_port) = resolved_ports.get("otel-http") {
env.insert(
"OTEL_EXPORTER_OTLP_ENDPOINT".to_string(),
format!("http://localhost:{}", http_port),
);
}
env.insert("OTEL_SERVICE_NAME".to_string(), service_name.to_string());
if let Some(&dash_port) = resolved_ports.get("dashboard") {
env.insert(
"DEVRIG_DASHBOARD_URL".to_string(),
format!("http://localhost:{}", dash_port),
);
}
}
}
if let Some(svc_config) = config.services.get(service_name) {
for (k, v) in &svc_config.env {
env.insert(k.clone(), v.clone());
}
}
env
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::model::{DevrigConfig, DockerConfig, Port, ProjectConfig, ServiceConfig};
fn minimal_config() -> DevrigConfig {
DevrigConfig {
project: ProjectConfig {
name: "test".to_string(),
env_file: None,
},
services: BTreeMap::new(),
docker: BTreeMap::new(),
compose: None,
cluster: None,
dashboard: None,
oidc: None,
env: BTreeMap::new(),
network: None,
links: BTreeMap::new(),
}
}
fn make_infra(image: &str, env: Vec<(&str, &str)>) -> DockerConfig {
DockerConfig {
image: image.to_string(),
port: None,
container_port: None,
protocol: None,
ports: BTreeMap::new(),
env: env
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
volumes: Vec::new(),
command: None,
entrypoint: None,
ready_check: None,
init: Vec::new(),
depends_on: Vec::new(),
registry_auth: None,
}
}
fn make_service(command: &str, port: Option<u16>) -> ServiceConfig {
ServiceConfig {
path: None,
command: command.to_string(),
port: port.map(Port::Fixed),
protocol: None,
env: BTreeMap::new(),
env_file: None,
depends_on: Vec::new(),
restart: None,
}
}
#[test]
fn infra_vars_present() {
let mut config = minimal_config();
let mut pg = make_infra(
"postgres:16-alpine",
vec![("POSTGRES_USER", "devrig"), ("POSTGRES_PASSWORD", "secret")],
);
pg.port = Some(Port::Fixed(5432));
config.docker.insert("postgres".into(), pg);
config
.services
.insert("api".into(), make_service("cargo run", Some(3000)));
let mut ports = HashMap::new();
ports.insert("docker:postgres".into(), 5432u16);
ports.insert("service:api".into(), 3000u16);
let env = build_service_env("api", &config, &ports);
assert_eq!(env["DEVRIG_POSTGRES_HOST"], "localhost");
assert_eq!(env["DEVRIG_POSTGRES_PORT"], "5432");
assert_eq!(
env["DEVRIG_POSTGRES_URL"],
"postgres://devrig:secret@localhost:5432"
);
}
#[test]
fn named_port_vars() {
let mut config = minimal_config();
let mut mailpit = make_infra("axllent/mailpit:latest", vec![]);
mailpit.ports.insert("smtp".into(), Port::Fixed(1025));
mailpit.ports.insert("ui".into(), Port::Fixed(8025));
config.docker.insert("mailpit".into(), mailpit);
config
.services
.insert("api".into(), make_service("cargo run", Some(3000)));
let mut ports = HashMap::new();
ports.insert("docker:mailpit".into(), 1025u16);
ports.insert("docker:mailpit:smtp".into(), 1025u16);
ports.insert("docker:mailpit:ui".into(), 8025u16);
ports.insert("service:api".into(), 3000u16);
let env = build_service_env("api", &config, &ports);
assert_eq!(env["DEVRIG_MAILPIT_HOST"], "localhost");
assert_eq!(env["DEVRIG_MAILPIT_PORT_SMTP"], "1025");
assert_eq!(env["DEVRIG_MAILPIT_PORT_UI"], "8025");
}
#[test]
fn service_own_port_host() {
let mut config = minimal_config();
config
.services
.insert("api".into(), make_service("cargo run", Some(3000)));
let mut ports = HashMap::new();
ports.insert("service:api".into(), 3000u16);
let env = build_service_env("api", &config, &ports);
assert_eq!(env["PORT"], "3000");
assert_eq!(env["HOST"], "localhost");
}
#[test]
fn service_env_overrides() {
let mut config = minimal_config();
config.env.insert("RUST_LOG".into(), "info".into());
let mut svc = make_service("cargo run", Some(3000));
svc.env.insert("RUST_LOG".into(), "debug".into());
svc.env.insert("HOST".into(), "0.0.0.0".into());
config.services.insert("api".into(), svc);
let mut ports = HashMap::new();
ports.insert("service:api".into(), 3000u16);
let env = build_service_env("api", &config, &ports);
assert_eq!(env["RUST_LOG"], "debug");
assert_eq!(env["HOST"], "0.0.0.0");
}
#[test]
fn service_to_service_discovery() {
let mut config = minimal_config();
config
.services
.insert("api".into(), make_service("cargo run", Some(3000)));
config
.services
.insert("web".into(), make_service("npm run dev", Some(4000)));
let mut ports = HashMap::new();
ports.insert("service:api".into(), 3000u16);
ports.insert("service:web".into(), 4000u16);
let env = build_service_env("web", &config, &ports);
assert_eq!(env["DEVRIG_API_HOST"], "localhost");
assert_eq!(env["DEVRIG_API_PORT"], "3000");
assert_eq!(env["DEVRIG_API_URL"], "http://localhost:3000");
assert!(!env.contains_key("DEVRIG_WEB_HOST"));
let env2 = build_service_env("api", &config, &ports);
assert_eq!(env2["DEVRIG_WEB_HOST"], "localhost");
assert_eq!(env2["DEVRIG_WEB_PORT"], "4000");
assert_eq!(env2["DEVRIG_WEB_URL"], "http://localhost:4000");
assert!(!env2.contains_key("DEVRIG_API_HOST"));
}
}