1use std::path::Path;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum Environment {
20 Kubernetes,
22 Docker,
24 Container,
26 BareMetal,
28}
29
30impl Environment {
31 #[must_use]
40 pub fn detect() -> Self {
41 if Self::is_kubernetes_by_token() || Self::is_kubernetes_by_env() {
43 return Self::Kubernetes;
44 }
45
46 if Self::is_docker_by_file() {
48 return Self::Docker;
49 }
50
51 if Self::is_container_by_cgroups() {
53 return Self::Container;
54 }
55
56 Self::BareMetal
57 }
58
59 #[must_use]
61 pub const fn is_container(&self) -> bool {
62 matches!(self, Self::Kubernetes | Self::Docker | Self::Container)
63 }
64
65 #[must_use]
67 pub const fn is_kubernetes(&self) -> bool {
68 matches!(self, Self::Kubernetes)
69 }
70
71 #[must_use]
73 pub const fn is_docker(&self) -> bool {
74 matches!(self, Self::Docker)
75 }
76
77 #[must_use]
79 pub const fn is_bare_metal(&self) -> bool {
80 matches!(self, Self::BareMetal)
81 }
82
83 fn is_kubernetes_by_token() -> bool {
86 Path::new("/var/run/secrets/kubernetes.io/serviceaccount/token").exists()
87 }
88
89 fn is_kubernetes_by_env() -> bool {
90 std::env::var("KUBERNETES_SERVICE_HOST").is_ok()
91 }
92
93 fn is_docker_by_file() -> bool {
94 Path::new("/.dockerenv").exists()
95 }
96
97 fn is_container_by_cgroups() -> bool {
98 if let Ok(content) = std::fs::read_to_string("/proc/1/cgroup")
100 && (content.contains("/docker/")
101 || content.contains("/kubepods/")
102 || content.contains("/lxc/")
103 || content.contains("/containerd/"))
104 {
105 return true;
106 }
107
108 if let Ok(content) = std::fs::read_to_string("/proc/1/mountinfo")
110 && (content.contains("/docker/")
111 || content.contains("/kubepods/")
112 || content.contains("/containerd/"))
113 {
114 return true;
115 }
116
117 false
118 }
119}
120
121impl std::fmt::Display for Environment {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 match self {
124 Self::Kubernetes => write!(f, "kubernetes"),
125 Self::Docker => write!(f, "docker"),
126 Self::Container => write!(f, "container"),
127 Self::BareMetal => write!(f, "bare_metal"),
128 }
129 }
130}
131
132impl Default for Environment {
133 fn default() -> Self {
134 Self::detect()
135 }
136}
137
138#[must_use]
142pub fn is_helm() -> bool {
143 if std::env::var("HELM_RELEASE_NAME").is_ok() {
145 return true;
146 }
147
148 let labels_path = Path::new("/etc/podinfo/labels");
150 if labels_path.exists()
151 && let Ok(content) = std::fs::read_to_string(labels_path)
152 {
153 return content.contains("helm.sh/chart")
154 || content.contains("app.kubernetes.io/managed-by=\"Helm\"");
155 }
156
157 false
158}
159
160#[must_use]
164pub fn get_app_env() -> String {
165 std::env::var("APP_ENV")
166 .or_else(|_| std::env::var("ENVIRONMENT"))
167 .or_else(|_| std::env::var("ENV"))
168 .unwrap_or_else(|_| "development".to_string())
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_environment_display() {
177 assert_eq!(Environment::Kubernetes.to_string(), "kubernetes");
178 assert_eq!(Environment::Docker.to_string(), "docker");
179 assert_eq!(Environment::Container.to_string(), "container");
180 assert_eq!(Environment::BareMetal.to_string(), "bare_metal");
181 }
182
183 #[test]
184 fn test_environment_is_container() {
185 assert!(Environment::Kubernetes.is_container());
186 assert!(Environment::Docker.is_container());
187 assert!(Environment::Container.is_container());
188 assert!(!Environment::BareMetal.is_container());
189 }
190
191 #[test]
192 fn test_environment_is_kubernetes() {
193 assert!(Environment::Kubernetes.is_kubernetes());
194 assert!(!Environment::Docker.is_kubernetes());
195 assert!(!Environment::Container.is_kubernetes());
196 assert!(!Environment::BareMetal.is_kubernetes());
197 }
198
199 #[test]
200 fn test_environment_is_bare_metal() {
201 assert!(!Environment::Kubernetes.is_bare_metal());
202 assert!(!Environment::Docker.is_bare_metal());
203 assert!(!Environment::Container.is_bare_metal());
204 assert!(Environment::BareMetal.is_bare_metal());
205 }
206
207 #[test]
208 fn test_get_app_env_default() {
209 temp_env::with_vars(
210 [
211 ("APP_ENV", None::<&str>),
212 ("ENVIRONMENT", None),
213 ("ENV", None),
214 ],
215 || assert_eq!(get_app_env(), "development"),
216 );
217 }
218
219 #[test]
220 fn test_get_app_env_from_app_env() {
221 temp_env::with_var("APP_ENV", Some("production"), || {
222 assert_eq!(get_app_env(), "production");
223 });
224 }
225
226 #[test]
227 fn test_environment_detect_returns_valid() {
228 let env = Environment::detect();
230 assert!(matches!(
231 env,
232 Environment::Kubernetes
233 | Environment::Docker
234 | Environment::Container
235 | Environment::BareMetal
236 ));
237 }
238}