cloud_detect/providers/
alibaba.rs

1//! Alibaba Cloud.
2
3use std::path::Path;
4use std::time::Duration;
5
6use async_trait::async_trait;
7use tokio::fs;
8use tokio::sync::mpsc::Sender;
9use tracing::{debug, error, info, instrument};
10
11use crate::{Provider, ProviderId};
12
13const METADATA_URI: &str = "http://100.100.100.200";
14const METADATA_PATH: &str = "/latest/meta-data/latest/meta-data/instance/virtualization-solution";
15const VENDOR_FILE: &str = "/sys/class/dmi/id/product_name";
16pub(crate) const IDENTIFIER: ProviderId = ProviderId::Alibaba;
17
18pub(crate) struct Alibaba;
19
20#[async_trait]
21impl Provider for Alibaba {
22    fn identifier(&self) -> ProviderId {
23        IDENTIFIER
24    }
25
26    /// Tries to identify Alibaba Cloud using all the implemented options.
27    #[instrument(skip_all)]
28    async fn identify(&self, tx: Sender<ProviderId>, timeout: Duration) {
29        info!("Checking Alibaba Cloud");
30        if self.check_vendor_file(VENDOR_FILE).await
31            || self.check_metadata_server(METADATA_URI, timeout).await
32        {
33            info!("Identified Alibaba Cloud");
34            let res = tx.send(IDENTIFIER).await;
35
36            if let Err(err) = res {
37                error!("Error sending message: {:?}", err);
38            }
39        }
40    }
41}
42
43impl Alibaba {
44    /// Tries to identify Alibaba via metadata server.
45    #[instrument(skip_all)]
46    async fn check_metadata_server(&self, metadata_uri: &str, timeout: Duration) -> bool {
47        let url = format!("{metadata_uri}{METADATA_PATH}");
48        debug!("Checking {} metadata using url: {}", IDENTIFIER, url);
49
50        let client = if let Ok(client) = reqwest::Client::builder().timeout(timeout).build() {
51            client
52        } else {
53            error!("Error creating client");
54            return false;
55        };
56
57        match client.get(url).send().await {
58            Ok(resp) => match resp.text().await {
59                Ok(text) => text.contains("ECS Virt"),
60                Err(err) => {
61                    error!("Error reading response: {:?}", err);
62                    false
63                }
64            },
65            Err(err) => {
66                error!("Error making request: {:?}", err);
67                false
68            }
69        }
70    }
71
72    /// Tries to identify Alibaba using vendor file(s).
73    #[instrument(skip_all)]
74    async fn check_vendor_file<P: AsRef<Path>>(&self, vendor_file: P) -> bool {
75        debug!(
76            "Checking {} vendor file: {}",
77            IDENTIFIER,
78            vendor_file.as_ref().display()
79        );
80
81        if vendor_file.as_ref().is_file() {
82            return match fs::read_to_string(vendor_file).await {
83                Ok(content) => content.contains("Alibaba Cloud ECS"),
84                Err(err) => {
85                    error!("Error reading file: {:?}", err);
86                    false
87                }
88            };
89        }
90
91        false
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use std::io::Write;
98
99    use anyhow::Result;
100    use tempfile::NamedTempFile;
101    use wiremock::matchers::path;
102    use wiremock::{Mock, MockServer, ResponseTemplate};
103
104    use super::*;
105
106    #[tokio::test]
107    async fn test_check_metadata_server_success() {
108        let mock_server = MockServer::start().await;
109        Mock::given(path(METADATA_PATH))
110            .respond_with(ResponseTemplate::new(200).set_body_string("ECS Virt"))
111            .expect(1)
112            .mount(&mock_server)
113            .await;
114
115        let provider = Alibaba;
116        let metadata_uri = mock_server.uri();
117        let result = provider
118            .check_metadata_server(&metadata_uri, Duration::from_secs(1))
119            .await;
120
121        assert!(result);
122    }
123
124    #[tokio::test]
125    async fn test_check_metadata_server_failure() {
126        let mock_server = MockServer::start().await;
127        Mock::given(path(METADATA_PATH))
128            .respond_with(ResponseTemplate::new(200).set_body_string("ABC"))
129            .expect(1)
130            .mount(&mock_server)
131            .await;
132
133        let provider = Alibaba;
134        let metadata_uri = mock_server.uri();
135        let result = provider
136            .check_metadata_server(&metadata_uri, Duration::from_secs(1))
137            .await;
138
139        assert!(!result);
140    }
141
142    #[tokio::test]
143    async fn test_check_vendor_file_success() -> Result<()> {
144        let mut vendor_file = NamedTempFile::new()?;
145        vendor_file.write_all(b"Alibaba Cloud ECS")?;
146
147        let provider = Alibaba;
148        let result = provider.check_vendor_file(vendor_file.path()).await;
149
150        assert!(result);
151
152        Ok(())
153    }
154
155    #[tokio::test]
156    async fn test_check_vendor_file_failure() -> Result<()> {
157        let vendor_file = NamedTempFile::new()?;
158
159        let provider = Alibaba;
160        let result = provider.check_vendor_file(vendor_file.path()).await;
161
162        assert!(!result);
163
164        Ok(())
165    }
166}