cloud_detect/providers/
gcp.rs1use 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 #[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 #[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 #[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}