apollo_environment_detector/
environment.rs

1use std::fmt::Display;
2
3use crate::{detector::Detector, env_vars, smbios};
4
5/// Supported compute environments that can be detected by this crate
6#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
7pub enum ComputeEnvironment {
8    // AWS supported platforms.
9    /// Amazon Elastic Compute Cloud (EC2)
10    AwsEc2,
11    /// Amazon Elastic Container Service (ECS)
12    AwsEcs,
13    /// AWS Lambda
14    AwsLambda,
15    /// Kubernetes on AWS
16    AwsKubernetes,
17    /// Nomad on AWS
18    AwsNomad,
19
20    // Azure supported platforms.
21    /// Azure Containers Apps
22    AzureContainerApps,
23    /// Azure Container Apps Job
24    AzureContainerAppsJob,
25    /// Azure Container Instance
26    AzureContainerInstance,
27    /// Kubernetes on Azure
28    AzureKubernetes,
29    /// Azure VM
30    AzureVM,
31    /// Nomad on Azure
32    AzureNomad,
33
34    // GCP supported platforms.
35    /// Google Cloud Run (Gen1)
36    GcpCloudRunGen1,
37    /// Google Cloud Run (Gen2)
38    GcpCloudRunGen2,
39    /// Google Cloud Run (Job)
40    GcpCloudRunJob,
41    /// Google Compute Engine
42    GcpComputeEngine,
43    /// Kubernetes on Google Cloud
44    GcpKubernetes,
45    /// Nomad on Google Cloud
46    GcpNomad,
47
48    // Generic supported platforms.
49    /// Kubernetes
50    Kubernetes,
51    /// Nomad
52    Nomad,
53    /// QEMU
54    Qemu,
55
56    #[cfg(test)]
57    Testing,
58}
59
60impl ComputeEnvironment {
61    pub(crate) fn iter() -> ComputeEnvironmentIter {
62        ComputeEnvironmentIter { idx: 0 }
63    }
64
65    pub(crate) fn detector(&self) -> Detector {
66        match self {
67            Self::AwsEc2 => Detector::new(*self, smbios::AWS, env_vars::EMPTY),
68            Self::AwsEcs => Detector::new(*self, smbios::EMPTY, env_vars::AWS_ECS),
69            Self::AwsLambda => Detector::new(*self, smbios::EMPTY, env_vars::AWS_LAMBDA),
70            Self::AwsKubernetes => Detector::new(*self, smbios::AWS, env_vars::KUBERNETES),
71            Self::AwsNomad => Detector::new(*self, smbios::AWS, env_vars::NOMAD),
72            Self::AzureContainerApps => {
73                Detector::new(*self, smbios::AZURE, env_vars::AZURE_CONTAINER_APPS)
74            }
75            Self::AzureContainerAppsJob => {
76                Detector::new(*self, smbios::AZURE, env_vars::AZURE_CONTAINER_APPS_JOB)
77            }
78            Self::AzureContainerInstance => {
79                Detector::new(*self, smbios::EMPTY, env_vars::AZURE_CONTAINER_INSTANCE)
80            }
81            Self::AzureKubernetes => Detector::new(*self, smbios::AZURE, env_vars::KUBERNETES),
82            Self::AzureVM => Detector::new(*self, smbios::AZURE, env_vars::EMPTY),
83            Self::AzureNomad => Detector::new(*self, smbios::AZURE, env_vars::NOMAD),
84            Self::GcpCloudRunGen1 => {
85                Detector::new(*self, smbios::EMPTY, env_vars::GCP_CLOUD_RUN_SERVICE)
86            }
87            Self::GcpCloudRunGen2 => {
88                Detector::new(*self, smbios::GCP, env_vars::GCP_CLOUD_RUN_SERVICE)
89            }
90            Self::GcpCloudRunJob => Detector::new(*self, smbios::GCP, env_vars::GCP_CLOUD_RUN_JOB),
91            Self::GcpComputeEngine => Detector::new(*self, smbios::GCP, env_vars::EMPTY),
92            Self::GcpKubernetes => Detector::new(*self, smbios::GCP, env_vars::KUBERNETES),
93            Self::GcpNomad => Detector::new(*self, smbios::GCP, env_vars::NOMAD),
94            Self::Kubernetes => Detector::new(*self, smbios::EMPTY, env_vars::KUBERNETES),
95            Self::Nomad => Detector::new(*self, smbios::EMPTY, env_vars::NOMAD),
96            Self::Qemu => Detector::new(*self, smbios::QEMU, env_vars::EMPTY),
97
98            #[cfg(test)]
99            Self::Testing => Detector::new(*self, smbios::EMPTY, env_vars::EMPTY),
100        }
101    }
102
103    /// Static str representation of the [`ComputeEnvironment`]
104    pub fn as_str(&self) -> &'static str {
105        match self {
106            Self::AwsEc2 => "AWS EC2",
107            Self::AwsEcs => "AWS ECS",
108            Self::AwsLambda => "AWS Lambda",
109            Self::AwsKubernetes => "Kubernetes on AWS",
110            Self::AwsNomad => "Nomad on AWS",
111            Self::AzureContainerApps => "Azure Container Apps",
112            Self::AzureContainerAppsJob => "Azure Container Apps Job",
113            Self::AzureContainerInstance => "Azure Container Instance",
114            Self::AzureKubernetes => "Kubernetes on Azure",
115            Self::AzureVM => "Azure VM",
116            Self::AzureNomad => "Nomad on Azure",
117            Self::GcpCloudRunGen1 => "Google Cloud Run (Gen1)",
118            Self::GcpCloudRunGen2 => "Google Cloud Run (Gen2)",
119            Self::GcpCloudRunJob => "Google Cloud Run (Job)",
120            Self::GcpComputeEngine => "Google Compute Engine",
121            Self::GcpKubernetes => "Kubernetes on Google Cloud",
122            Self::GcpNomad => "Nomad on Google Cloud",
123            Self::Kubernetes => "Kubernetes",
124            Self::Nomad => "Nomad",
125            Self::Qemu => "QEMU",
126
127            #[cfg(test)]
128            Self::Testing => "Testing",
129        }
130    }
131
132    /// Compute Platform code
133    ///
134    /// This corresponds to the `cloud.platform` attribute in OpenTelemetry semantic conventions
135    /// where possible.
136    ///
137    /// For Kubernetes on Cloud Providers, this always assumes that someone is using the
138    /// corresponding managed service (e.g. AWS EKS, AKS, or GKE).
139    ///
140    /// This may also return one of the following values for some environments:
141    ///
142    /// * `kubernetes`
143    /// * `nomad`
144    /// * `qemu`
145    ///
146    /// See <https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/>
147    pub fn platform_code(&self) -> &'static str {
148        match self {
149            Self::AwsEc2 => "aws_ec2",
150            Self::AwsEcs => "aws_ecs",
151            Self::AwsLambda => "aws_lambda",
152            // We're assuming Kubernetes on AWS = EKS
153            Self::AwsKubernetes => "aws_eks",
154            Self::AwsNomad => "nomad",
155            Self::AzureContainerApps => "azure_container_apps",
156            Self::AzureContainerAppsJob => "azure_container_apps",
157            Self::AzureContainerInstance => "azure_container_instances",
158            // We're assuming Kubernetes on Azure = AKS
159            Self::AzureKubernetes => "azure_aks",
160            Self::AzureVM => "azure_vm",
161            Self::AzureNomad => "nomad",
162            Self::GcpCloudRunGen1 => "gcp_cloud_run",
163            Self::GcpCloudRunGen2 => "gcp_cloud_run",
164            Self::GcpCloudRunJob => "gcp_cloud_run",
165            Self::GcpComputeEngine => "gcp_compute_engine",
166            // We're assuming Kubernetes on GCP = GKE
167            Self::GcpKubernetes => "gcp_kubernetes_engine",
168            Self::GcpNomad => "nomad",
169            Self::Kubernetes => "kubernetes",
170            Self::Nomad => "nomad",
171            Self::Qemu => "qemu",
172
173            #[cfg(test)]
174            Self::Testing => "testing",
175        }
176    }
177
178    /// [`CloudProvider`] for this compute environment
179    pub fn cloud_provider(&self) -> Option<CloudProvider> {
180        match self {
181            Self::AwsEc2
182            | Self::AwsEcs
183            | Self::AwsLambda
184            | Self::AwsKubernetes
185            | Self::AwsNomad => Some(CloudProvider::Aws),
186            Self::AzureContainerApps
187            | Self::AzureContainerAppsJob
188            | Self::AzureContainerInstance
189            | Self::AzureKubernetes
190            | Self::AzureVM
191            | Self::AzureNomad => Some(CloudProvider::Azure),
192            Self::GcpCloudRunGen1
193            | Self::GcpCloudRunGen2
194            | Self::GcpCloudRunJob
195            | Self::GcpComputeEngine
196            | Self::GcpKubernetes
197            | Self::GcpNomad => Some(CloudProvider::GoogleCloud),
198            Self::Kubernetes | Self::Nomad | Self::Qemu => None,
199
200            #[cfg(test)]
201            Self::Testing => None,
202        }
203    }
204}
205
206impl Display for ComputeEnvironment {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        f.write_str(self.as_str())
209    }
210}
211
212pub(crate) struct ComputeEnvironmentIter {
213    idx: usize,
214}
215
216impl ComputeEnvironmentIter {
217    fn get(&self, idx: usize) -> Option<ComputeEnvironment> {
218        match idx {
219            0usize => Some(ComputeEnvironment::AwsEc2),
220            1usize => Some(ComputeEnvironment::AwsEcs),
221            2usize => Some(ComputeEnvironment::AwsLambda),
222            3usize => Some(ComputeEnvironment::AwsKubernetes),
223            4usize => Some(ComputeEnvironment::AwsNomad),
224            5usize => Some(ComputeEnvironment::AzureContainerApps),
225            6usize => Some(ComputeEnvironment::AzureContainerAppsJob),
226            7usize => Some(ComputeEnvironment::AzureContainerInstance),
227            8usize => Some(ComputeEnvironment::AzureKubernetes),
228            9usize => Some(ComputeEnvironment::AzureVM),
229            10usize => Some(ComputeEnvironment::AzureNomad),
230            11usize => Some(ComputeEnvironment::GcpCloudRunGen1),
231            12usize => Some(ComputeEnvironment::GcpCloudRunGen2),
232            13usize => Some(ComputeEnvironment::GcpCloudRunJob),
233            14usize => Some(ComputeEnvironment::GcpComputeEngine),
234            15usize => Some(ComputeEnvironment::GcpKubernetes),
235            16usize => Some(ComputeEnvironment::GcpNomad),
236            17usize => Some(ComputeEnvironment::Kubernetes),
237            18usize => Some(ComputeEnvironment::Nomad),
238            19usize => Some(ComputeEnvironment::Qemu),
239            _ => None,
240        }
241    }
242}
243
244impl Iterator for ComputeEnvironmentIter {
245    type Item = ComputeEnvironment;
246
247    fn next(&mut self) -> Option<Self::Item> {
248        let ret = self.get(self.idx);
249        self.idx += 1;
250        ret
251    }
252}
253
254/// Supported cloud providers that can be detected by this crate.
255#[derive(Clone, Copy, PartialEq, Eq)]
256pub enum CloudProvider {
257    /// Amazon Web Services
258    Aws,
259    /// Microsoft Azure
260    Azure,
261    /// Google Cloud Platform
262    GoogleCloud,
263}
264
265impl CloudProvider {
266    /// Static str representation of the [`CloudProvider`].
267    pub fn as_str(&self) -> &'static str {
268        match self {
269            Self::Aws => "AWS",
270            Self::Azure => "Azure",
271            Self::GoogleCloud => "Google Cloud",
272        }
273    }
274    /// Cloud Provider code.
275    ///
276    /// This corresponds to the `cloud.provider` attribute in OpenTelemetry semantic conventions.
277    ///
278    /// See: <https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/>
279    pub fn code(&self) -> &'static str {
280        match self {
281            Self::Aws => "aws",
282            Self::Azure => "azure",
283            Self::GoogleCloud => "gcp",
284        }
285    }
286}
287
288impl Display for CloudProvider {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        f.write_str(self.as_str())
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use std::cmp::Ordering;
297    use std::collections::HashMap;
298
299    use rstest::{fixture, rstest};
300
301    use crate::specificity::Specificity as _;
302
303    use super::*;
304
305    #[fixture]
306    fn expected_matrix() -> HashMap<(ComputeEnvironment, ComputeEnvironment), Option<Ordering>> {
307        let envs: HashMap<_, _> = ComputeEnvironment::iter().enumerate().collect();
308        let matrix = include_str!("tests/specificity_matrix.txt");
309        matrix
310            .split('\n')
311            .enumerate()
312            .flat_map(|(y, line)| {
313                let envs = &envs;
314                line.chars().enumerate().map(move |(x, c)| {
315                    (
316                        (*envs.get(&y).unwrap(), *envs.get(&x).unwrap()),
317                        match c {
318                            '-' => Some(Ordering::Less),
319                            '=' => Some(Ordering::Equal),
320                            '+' => Some(Ordering::Greater),
321                            _ => None,
322                        },
323                    )
324                })
325            })
326            .collect()
327    }
328
329    #[rstest]
330    fn test_specificity(
331        #[values(
332            ComputeEnvironment::AwsEc2,
333            ComputeEnvironment::AwsEcs,
334            ComputeEnvironment::AwsLambda,
335            ComputeEnvironment::AwsKubernetes,
336            ComputeEnvironment::AwsNomad,
337            ComputeEnvironment::AzureContainerApps,
338            ComputeEnvironment::AzureContainerAppsJob,
339            ComputeEnvironment::AzureContainerInstance,
340            ComputeEnvironment::AzureKubernetes,
341            ComputeEnvironment::AzureVM,
342            ComputeEnvironment::AzureNomad,
343            ComputeEnvironment::GcpCloudRunGen1,
344            ComputeEnvironment::GcpCloudRunGen2,
345            ComputeEnvironment::GcpCloudRunJob,
346            ComputeEnvironment::GcpComputeEngine,
347            ComputeEnvironment::GcpKubernetes,
348            ComputeEnvironment::GcpNomad,
349            ComputeEnvironment::Kubernetes,
350            ComputeEnvironment::Nomad,
351            ComputeEnvironment::Qemu
352        )]
353        left: ComputeEnvironment,
354        #[values(
355            ComputeEnvironment::AwsEc2,
356            ComputeEnvironment::AwsEcs,
357            ComputeEnvironment::AwsLambda,
358            ComputeEnvironment::AwsKubernetes,
359            ComputeEnvironment::AwsNomad,
360            ComputeEnvironment::AzureContainerApps,
361            ComputeEnvironment::AzureContainerAppsJob,
362            ComputeEnvironment::AzureContainerInstance,
363            ComputeEnvironment::AzureKubernetes,
364            ComputeEnvironment::AzureVM,
365            ComputeEnvironment::AzureNomad,
366            ComputeEnvironment::GcpCloudRunGen1,
367            ComputeEnvironment::GcpCloudRunGen2,
368            ComputeEnvironment::GcpCloudRunJob,
369            ComputeEnvironment::GcpComputeEngine,
370            ComputeEnvironment::GcpKubernetes,
371            ComputeEnvironment::GcpNomad,
372            ComputeEnvironment::Kubernetes,
373            ComputeEnvironment::Nomad,
374            ComputeEnvironment::Qemu
375        )]
376        right: ComputeEnvironment,
377        expected_matrix: HashMap<(ComputeEnvironment, ComputeEnvironment), Option<Ordering>>,
378    ) {
379        let expected = expected_matrix.get(&(left, right)).cloned().flatten();
380
381        let result = left.detector().specificity_cmp(&right.detector());
382
383        assert_eq!(expected, result);
384    }
385}