cloud_detect/providers/
gcp.rs

1//! Google Cloud Platform (GCP).
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://metadata.google.internal";
14const METADATA_PATH: &str = "/computeMetadata/v1/instance/tags";
15const VENDOR_FILE: &str = "/sys/class/dmi/id/product_name";
16pub(crate) const IDENTIFIER: ProviderId = ProviderId::GCP;
17
18pub(crate) struct Gcp;
19
20#[async_trait]
21impl Provider for Gcp {
22    fn identifier(&self) -> ProviderId {
23        IDENTIFIER
24    }
25
26    /// Tries to identify GCP using all the implemented options.
27    #[instrument(skip_all)]
28    async fn identify(&self, tx: Sender<ProviderId>, timeout: Duration) {
29        info!("Checking Google Cloud Platform");
30        if self.check_vendor_file(VENDOR_FILE).await
31            || self.check_metadata_server(METADATA_URI, timeout).await
32        {
33            info!("Identified Google Cloud Platform");
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 Gcp {
44    /// Tries to identify GCP 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        let req = client.get(url).header("Metadata-Flavor", "Google");
58        let resp = req.send().await;
59
60        match resp {
61            Ok(resp) => resp.status().is_success(),
62            Err(err) => {
63                error!("Error making request: {:?}", err);
64                false
65            }
66        }
67    }
68
69    /// Tries to identify GCP using vendor file(s).
70    #[instrument(skip_all)]
71    async fn check_vendor_file<P: AsRef<Path>>(&self, vendor_file: P) -> bool {
72        debug!(
73            "Checking {} vendor file: {}",
74            IDENTIFIER,
75            vendor_file.as_ref().display()
76        );
77
78        if vendor_file.as_ref().is_file() {
79            return match fs::read_to_string(vendor_file).await {
80                Ok(content) => content.contains("Google"),
81                Err(err) => {
82                    error!("Error reading file: {:?}", err);
83                    false
84                }
85            };
86        }
87
88        false
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use std::io::Write;
95
96    use anyhow::Result;
97    use tempfile::NamedTempFile;
98    use wiremock::matchers::path;
99    use wiremock::{Mock, MockServer, ResponseTemplate};
100
101    use super::*;
102
103    #[tokio::test]
104    async fn test_check_metadata_server_success() {
105        let mock_server = MockServer::start().await;
106        Mock::given(path(METADATA_PATH))
107            .respond_with(ResponseTemplate::new(200))
108            .expect(1)
109            .mount(&mock_server)
110            .await;
111
112        let provider = Gcp;
113        let metadata_uri = mock_server.uri();
114        let result = provider
115            .check_metadata_server(&metadata_uri, Duration::from_secs(1))
116            .await;
117
118        assert!(result);
119    }
120
121    #[tokio::test]
122    async fn test_check_metadata_server_failure() {
123        let mock_server = MockServer::start().await;
124        Mock::given(path(METADATA_PATH))
125            .respond_with(ResponseTemplate::new(500))
126            .expect(1)
127            .mount(&mock_server)
128            .await;
129
130        let provider = Gcp;
131        let metadata_uri = mock_server.uri();
132        let result = provider
133            .check_metadata_server(&metadata_uri, Duration::from_secs(1))
134            .await;
135
136        assert!(!result);
137    }
138
139    #[tokio::test]
140    async fn test_check_vendor_file_success() -> Result<()> {
141        let mut vendor_file = NamedTempFile::new()?;
142        vendor_file.write_all(b"Google")?;
143
144        let provider = Gcp;
145        let result = provider.check_vendor_file(vendor_file.path()).await;
146
147        assert!(result);
148
149        Ok(())
150    }
151
152    #[tokio::test]
153    async fn test_check_vendor_file_failure() -> Result<()> {
154        let vendor_file = NamedTempFile::new()?;
155
156        let provider = Gcp;
157        let result = provider.check_vendor_file(vendor_file.path()).await;
158
159        assert!(!result);
160
161        Ok(())
162    }
163}