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