saluki_env/features/
detector.rs

1use std::path::PathBuf;
2
3use tracing::info;
4
5use super::{has_host_mapped_cgroupfs, has_host_mapped_procfs, has_legacy_cgroupfs_root, ContainerdDetector, Feature};
6
7/// Detects workload features.
8///
9/// The feature detector is used to determine which features are present in the workload, such as if the application is
10/// running in a Kubernetes environment or directly on Docker. This information is used to drive the collection of
11/// workload metadata that powers entity tagging.
12///
13/// `FeatureDetector` can be used in an automatic detection mode, where it will attempt to detect all features that are
14/// present, or it can be used to assert that a specific feature is present for scenarios where the feature is required,
15/// and the workload is already known upfront.
16#[derive(Clone)]
17pub struct FeatureDetector {
18    detected_features: Feature,
19}
20
21impl FeatureDetector {
22    /// Creates a new `FeatureDetector` that checks for all possible features.
23    ///
24    /// If `containerd_socket_path` is given, that path is used for containerd detection. Otherwise, well-known paths
25    /// are probed.
26    pub fn automatic(containerd_socket_path: Option<PathBuf>) -> Self {
27        Self::for_feature(containerd_socket_path, Feature::all_bits())
28    }
29
30    /// Creates a new `FeatureDetector` that checks for the given features.
31    ///
32    /// Multiple features can be checked for by combining the feature variants in a bitwise OR fashion.
33    ///
34    /// If `containerd_socket_path` is given, that path is used for containerd detection. Otherwise, well-known paths
35    /// are probed.
36    pub fn for_feature(containerd_socket_path: Option<PathBuf>, feature_mask: Feature) -> Self {
37        let detected_features = Self::detect_features(containerd_socket_path, feature_mask);
38
39        Self { detected_features }
40    }
41
42    /// Checks if the given feature was detected and is available.
43    pub fn is_feature_available(&self, feature: Feature) -> bool {
44        self.detected_features.contains(feature)
45    }
46
47    fn detect_features(containerd_socket_path: Option<PathBuf>, feature_mask: Feature) -> Feature {
48        let mut detected_features = Feature::none();
49
50        if feature_mask.contains(Feature::HostMappedProcfs) && has_host_mapped_procfs() {
51            info!("Detected presence of host-mapped procfs.");
52            detected_features |= Feature::HostMappedProcfs;
53        }
54
55        if feature_mask.contains(Feature::HostMappedCgroupfs) && has_host_mapped_cgroupfs() {
56            info!("Detected presence of host-mapped cgroupfs.");
57            detected_features |= Feature::HostMappedCgroupfs;
58        }
59
60        if feature_mask.contains(Feature::LegacyCgroupfsRoot) && has_legacy_cgroupfs_root() {
61            info!("Detected presence of legacy cgroupfs root.");
62            detected_features |= Feature::LegacyCgroupfsRoot;
63        }
64
65        if feature_mask.contains(Feature::Containerd)
66            && ContainerdDetector::detect_grpc_socket_path(containerd_socket_path).is_some()
67        {
68            info!("Detected presence of containerd.");
69            detected_features |= Feature::Containerd;
70        }
71
72        detected_features
73    }
74}
75
76#[cfg(all(test, target_os = "linux"))]
77impl FeatureDetector {
78    /// Creates a `FeatureDetector` that reports exactly the given features.
79    ///
80    /// Detection probes the real filesystem, so tests that need a specific set of features -- the host-mapped ones in
81    /// particular, which depend on running inside a container -- can't reach them through the normal constructors
82    /// without becoming dependent on the environment the tests run in.
83    pub(crate) fn from_detected_features(detected_features: Feature) -> Self {
84        Self { detected_features }
85    }
86}