use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecutionEnvironment {
Host,
Docker,
Container,
ContinuousIntegration,
}
impl std::fmt::Display for ExecutionEnvironment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Host => write!(f, "host"),
Self::Docker => write!(f, "docker"),
Self::Container => write!(f, "container"),
Self::ContinuousIntegration => write!(f, "ci"),
}
}
}
pub fn detect_environment() -> ExecutionEnvironment {
if is_ci_environment() {
return ExecutionEnvironment::ContinuousIntegration;
}
if Path::new("/.dockerenv").exists() {
return ExecutionEnvironment::Docker;
}
if Path::new("/run/.containerenv").exists() {
return ExecutionEnvironment::Container;
}
#[cfg(target_os = "linux")]
if is_in_container_cgroup() {
return ExecutionEnvironment::Container;
}
ExecutionEnvironment::Host
}
fn is_ci_environment() -> bool {
std::env::var("CI").is_ok()
|| std::env::var("GITHUB_ACTIONS").is_ok()
|| std::env::var("GITLAB_CI").is_ok()
|| std::env::var("JENKINS_HOME").is_ok()
|| std::env::var("CIRCLECI").is_ok()
|| std::env::var("BUILDKITE").is_ok()
|| std::env::var("TRAVIS").is_ok()
}
#[cfg(target_os = "linux")]
fn is_in_container_cgroup() -> bool {
let Ok(cgroup) = std::fs::read_to_string("/proc/1/cgroup") else {
return false;
};
cgroup.contains("docker")
|| cgroup.contains("lxc")
|| cgroup.contains("containerd")
|| cgroup.contains("kubepods")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_environment_returns_valid_variant() {
let env = detect_environment();
let display = env.to_string();
assert!(
["host", "docker", "container", "ci"].contains(&display.as_str()),
"unexpected environment: {display}"
);
}
#[test]
fn display_formats_correctly() {
assert_eq!(ExecutionEnvironment::Host.to_string(), "host");
assert_eq!(ExecutionEnvironment::Docker.to_string(), "docker");
assert_eq!(ExecutionEnvironment::Container.to_string(), "container");
assert_eq!(
ExecutionEnvironment::ContinuousIntegration.to_string(),
"ci"
);
}
}