From 44035f29efb95061a7295a9f6f4c36051a2ab87a Mon Sep 17 00:00:00 2001 From: Nikhil Prabhu Date: Wed, 1 Jan 2025 22:31:38 +0530 Subject: [PATCH] feat(blocking): add Azure provider --- src/blocking/mod.rs | 1 + src/blocking/providers/azure.rs | 176 ++++++++++++++++++++++++++++++++ src/blocking/providers/mod.rs | 1 + 3 files changed, 178 insertions(+) create mode 100644 src/blocking/providers/azure.rs diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs index 1a31abc..20994f7 100644 --- a/src/blocking/mod.rs +++ b/src/blocking/mod.rs @@ -22,6 +22,7 @@ static PROVIDERS: LazyLock>> = LazyLock::new(|| { Mutex::new(vec![ Arc::new(alibaba::Alibaba) as P, Arc::new(aws::Aws) as P, + Arc::new(azure::Azure) as P, ]) }); diff --git a/src/blocking/providers/azure.rs b/src/blocking/providers/azure.rs new file mode 100644 index 0000000..35de403 --- /dev/null +++ b/src/blocking/providers/azure.rs @@ -0,0 +1,176 @@ +//! Microsoft Azure. + +use std::fs; +use std::path::Path; +use std::sync::mpsc::Sender; +use std::time::Duration; + +use reqwest::blocking::Client; +use serde::{Deserialize, Serialize}; +use tracing::{debug, error, info, instrument}; + +use crate::blocking::Provider; +use crate::ProviderId; + +#[allow(unused)] +const METADATA_URI: &str = "http://169.254.169.254"; +#[allow(unused)] +const METADATA_PATH: &str = "/metadata/instance?api-version=2017-12-01"; +#[allow(unused)] +const VENDOR_FILE: &str = "/sys/class/dmi/id/sys_vendor"; +#[allow(unused)] +const IDENTIFIER: ProviderId = ProviderId::Azure; + +#[derive(Serialize, Deserialize)] +struct Compute { + #[serde(rename = "vmId")] + vm_id: String, +} + +#[derive(Serialize, Deserialize)] +struct MetadataResponse { + compute: Compute, +} + +pub(crate) struct Azure; + +impl Provider for Azure { + fn identifier(&self) -> ProviderId { + IDENTIFIER + } + + #[instrument(skip_all)] + fn identify(&self, tx: Sender, timeout: Duration) { + info!("Checking Microsoft Azure"); + if self.check_vendor_file(VENDOR_FILE) || self.check_metadata_server(METADATA_URI, timeout) + { + info!("Identified Microsoft Azure"); + if let Err(err) = tx.send(IDENTIFIER) { + error!("Error sending message: {:?}", err); + } + } + } +} + +impl Azure { + #[instrument(skip_all)] + fn check_metadata_server(&self, metadata_uri: &str, timeout: Duration) -> bool { + let url = format!("{}{}", metadata_uri, METADATA_PATH); + debug!("Checking {} metadata using url: {}", IDENTIFIER, url); + + let client = if let Ok(client) = Client::builder().timeout(timeout).build() { + client + } else { + error!("Error creating client"); + return false; + }; + + match client.get(url).send() { + Ok(resp) => match resp.json::() { + Ok(resp) => !resp.compute.vm_id.is_empty(), + Err(err) => { + error!("Error reading response: {:?}", err); + false + } + }, + Err(err) => { + error!("Error making request: {:?}", err); + false + } + } + } + + #[instrument(skip_all)] + fn check_vendor_file>(&self, vendor_file: P) -> bool { + debug!( + "Checking {} vendor file: {}", + IDENTIFIER, + vendor_file.as_ref().display() + ); + + if vendor_file.as_ref().is_file() { + return match fs::read_to_string(vendor_file) { + Ok(content) => content.contains("Microsoft Corporation"), + Err(err) => { + error!("Error reading file: {:?}", err); + false + } + }; + } + + false + } +} + +#[cfg(test)] +mod tests { + use std::io::Write; + + use anyhow::Result; + use mockito::Server; + use tempfile::NamedTempFile; + + use super::*; + + #[test] + fn test_check_metadata_server_success() { + let mut server = Server::new(); + let url = server.url(); + + let mock = server + .mock("GET", METADATA_PATH) + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"compute":{"vmId":"vm-1234"}}"#) + .create(); + + let provider = Azure; + let result = provider.check_metadata_server(&url, Duration::from_secs(1)); + + mock.assert(); + assert!(result); + } + + #[test] + fn test_check_metadata_server_failure() { + let mut server = Server::new(); + let url = server.url(); + + let mock = server + .mock("GET", METADATA_PATH) + .with_status(200) + .with_body("ABC") + .create(); + + let provider = Azure; + let result = provider.check_metadata_server(&url, Duration::from_secs(1)); + + mock.assert(); + assert!(!result); + } + + #[test] + fn test_check_vendor_file_success() -> Result<()> { + let mut vendor_file = NamedTempFile::new()?; + vendor_file.write_all("Microsoft Corporation".as_bytes())?; + + let provider = Azure; + let result = provider.check_vendor_file(vendor_file.path()); + + assert!(result); + + Ok(()) + } + + #[test] + fn test_check_vendor_file_failure() -> Result<()> { + let vendor_file = NamedTempFile::new()?; + + let provider = Azure; + let result = provider.check_vendor_file(vendor_file.path()); + + assert!(!result); + + Ok(()) + } +} diff --git a/src/blocking/providers/mod.rs b/src/blocking/providers/mod.rs index be7488e..a4d4ece 100644 --- a/src/blocking/providers/mod.rs +++ b/src/blocking/providers/mod.rs @@ -2,3 +2,4 @@ pub(crate) mod alibaba; pub(crate) mod aws; +pub(crate) mod azure;