use std::collections::BTreeMap;
use super::dump::DumpRow;
const SESSION_SHELL: &[&str] = &[
"COLORTERM",
"DBUS_SESSION_BUS_ADDRESS",
"DISPLAY",
"EDITOR",
"HISTFILE",
"HISTSIZE",
"HOME",
"HOSTNAME",
"LANG",
"LOGNAME",
"LS_COLORS",
"MAIL",
"MOTD_SHOWN",
"OLDPWD",
"PAGER",
"PATH",
"PWD",
"SHELL",
"SHLVL",
"TERM",
"TMPDIR",
"USER",
"VISUAL",
"_",
];
const SESSION_SHELL_PREFIXES: &[&str] = &["LC_", "SSH_", "SUDO_", "XDG_"];
const PM2_INJECTED: &[&str] = &["NODE_APP_INSTANCE", "pm_id", "unique_id"];
const PM2_INJECTED_PREFIXES: &[&str] = &["PM2_"];
const PM2_INSTANCE_VAR: &str = "NODE_APP_INSTANCE";
#[derive(Debug, Default, PartialEq, Eq)]
pub(crate) struct AppEnv {
pub env: BTreeMap<String, String>,
pub inherited: Vec<String>,
pub instance_var: Option<String>,
}
pub(crate) fn split(row: &DumpRow) -> AppEnv {
let mut declared_union: BTreeMap<&str, &str> = BTreeMap::new();
for declared in row.declared.values() {
for (key, value) in declared {
declared_union.insert(key.as_str(), value.as_str());
}
}
let env = declared_union
.iter()
.map(|(&key, &declared_value)| {
let value = row.env.get(key).map_or(declared_value, String::as_str);
(key.to_string(), value.to_string())
})
.collect();
let mut inherited = Vec::new();
let mut instance_var = None;
for key in row.env.keys() {
if declared_union.contains_key(key.as_str()) {
continue;
}
if key.as_str() == PM2_INSTANCE_VAR {
instance_var = Some(key.clone());
} else if !is_session_shell(key) && !is_pm2_injected(key) {
inherited.push(key.clone());
}
}
AppEnv {
env,
inherited,
instance_var,
}
}
fn is_session_shell(key: &str) -> bool {
SESSION_SHELL.contains(&key)
|| SESSION_SHELL_PREFIXES
.iter()
.any(|prefix| key.starts_with(prefix))
}
fn is_pm2_injected(key: &str) -> bool {
PM2_INJECTED.contains(&key)
|| PM2_INJECTED_PREFIXES
.iter()
.any(|prefix| key.starts_with(prefix))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::import::dump;
fn rows() -> Vec<DumpRow> {
dump::parse(include_str!("testdata/dump.pm2.json")).unwrap()
}
#[test]
fn only_declared_keys_are_written() {
let api = split(&rows()[0]);
assert_eq!(
api.env.keys().collect::<Vec<_>>(),
["NODE_ENV"],
"one declared key, and the ten inherited ones are not it"
);
assert_eq!(api.env["NODE_ENV"], "production");
}
#[test]
fn a_declared_key_missing_from_the_running_env_still_comes_across() {
let worker = split(&rows()[2]);
assert_eq!(worker.env["QUEUE_CONCURRENCY"], "4");
assert_eq!(worker.env["QUEUE_URL"], "redis://127.0.0.1:6379/2");
}
#[test]
fn an_unrecognised_inherited_key_is_named_and_not_written() {
let api = split(&rows()[0]);
assert_eq!(api.inherited, ["BUN_INSTALL"]);
assert!(!api.env.contains_key("BUN_INSTALL"));
let worker = split(&rows()[2]);
assert_eq!(worker.inherited, ["JAVA_HOME"]);
let migrate = split(&rows()[3]);
assert_eq!(migrate.inherited, ["DATABASE_URL"]);
assert!(migrate.env.is_empty());
}
#[test]
fn session_and_pm2_keys_are_dropped_without_comment() {
let api = split(&rows()[0]);
for quiet in [
"SSH_TTY",
"XDG_SESSION_ID",
"MOTD_SHOWN",
"LS_COLORS",
"LANG",
"SHLVL",
"PATH",
"PM2_HOME",
"NODE_APP_INSTANCE",
] {
assert!(
!api.inherited.iter().any(|k| k == quiet),
"{quiet} was named"
);
assert!(!api.env.contains_key(quiet), "{quiet} was written");
}
assert_eq!(api.instance_var.as_deref(), Some("NODE_APP_INSTANCE"));
}
}