use crate::meta_plugin::MetaPlugin; #[derive(Debug, Clone, Default)] pub struct HostnameMetaPlugin { meta_name: String, is_finalized: bool, outputs: std::collections::HashMap, options: std::collections::HashMap, } impl HostnameMetaPlugin { pub fn new( options: Option>, outputs: Option>, ) -> HostnameMetaPlugin { // Start with default options let mut final_options = std::collections::HashMap::new(); // Add default value for "full" option final_options.insert("full".to_string(), serde_yaml::Value::Bool(true)); if let Some(opts) = options { for (key, value) in opts { final_options.insert(key, value); } } // Start with default outputs let mut final_outputs = std::collections::HashMap::new(); let default_outputs = Self::default().default_outputs(); for output_name in default_outputs { final_outputs.insert(output_name.clone(), serde_yaml::Value::String(output_name)); } if let Some(outs) = outputs { for (key, value) in outs { final_outputs.insert(key, value); } } HostnameMetaPlugin { meta_name: "hostname".to_string(), is_finalized: false, outputs: final_outputs, options: final_options, } } pub fn new_simple() -> HostnameMetaPlugin { Self::new(None, None) } fn get_hostname(&self) -> String { // First get the short hostname let short_hostname = match gethostname::gethostname().into_string() { Ok(hostname) => hostname, Err(_) => return "unknown".to_string(), }; // Try to get the FQDN using the system's hostname resolution // This should give us the full hostname if configured if let Ok(full_hostname) = std::process::Command::new("hostname") .arg("-f") .output() { if full_hostname.status.success() { let full_hostname_str = String::from_utf8_lossy(&full_hostname.stdout).trim().to_string(); if !full_hostname_str.is_empty() && full_hostname_str != short_hostname { return full_hostname_str; } } } // Fallback: try DNS resolution for both IPv4 and IPv6 addresses // lookup_host should handle both A and AAAA records if let Ok(addrs) = dns_lookup::lookup_host(&short_hostname) { // Try each address (both IPv4 and IPv6) for addr in addrs { // Convert to IpAddr for lookup_addr let ip_addr = match addr { std::net::IpAddr::V4(ipv4) => std::net::IpAddr::V4(ipv4), std::net::IpAddr::V6(ipv6) => std::net::IpAddr::V6(ipv6), }; // Perform reverse lookup for each address match dns_lookup::lookup_addr(&ip_addr) { Ok(full_hostname) => { // Only use if it's different from the short hostname and looks like a FQDN if full_hostname != short_hostname && full_hostname.contains('.') { return full_hostname; } } Err(_) => continue, } } // If no reverse lookup worked, but we have addresses, try to construct FQDN // from the first address's domain if the short hostname is part of a domain if let Some(first_addr) = addrs.first() { // For local addresses, we might not get a reverse lookup, so try to infer // from the system's domain name if let Ok(domain) = std::process::Command::new("domainname").output() { if domain.status.success() { let domain_str = String::from_utf8_lossy(&domain.stdout).trim().to_string(); if !domain_str.is_empty() && domain_str != "(none)" { return format!("{}.{}", short_hostname, domain_str); } } } } } // Final fallback: return the short hostname short_hostname } } impl MetaPlugin for HostnameMetaPlugin { fn is_finalized(&self) -> bool { self.is_finalized } fn set_finalized(&mut self, finalized: bool) { self.is_finalized = finalized; } fn finalize(&mut self) -> crate::meta_plugin::MetaPluginResponse { // If already finalized, don't process again if self.is_finalized { return crate::meta_plugin::MetaPluginResponse { metadata: Vec::new(), is_finalized: true, }; } // Mark as finalized self.is_finalized = true; crate::meta_plugin::MetaPluginResponse { metadata: Vec::new(), is_finalized: true, } } fn update(&mut self, _data: &[u8]) -> crate::meta_plugin::MetaPluginResponse { // If already finalized, don't process more data if self.is_finalized { return crate::meta_plugin::MetaPluginResponse { metadata: Vec::new(), is_finalized: true, }; } crate::meta_plugin::MetaPluginResponse { metadata: Vec::new(), is_finalized: false, } } fn meta_name(&self) -> String { self.meta_name.clone() } fn initialize(&mut self) -> crate::meta_plugin::MetaPluginResponse { // If already finalized, don't process again if self.is_finalized { return crate::meta_plugin::MetaPluginResponse { metadata: Vec::new(), is_finalized: true, }; } let mut metadata = Vec::new(); // Check if we should use full hostname or short hostname let use_full = self.options.get("full") .and_then(|v| v.as_bool()) .unwrap_or(true); // Default to true log::debug!("HOSTNAME: use_full option: {:?}", use_full); let full_hostname = self.get_hostname(); log::debug!("HOSTNAME: full hostname from system: {:?}", full_hostname); let hostname = if use_full { full_hostname.clone() } else { // Get short hostname (first part before the first dot) full_hostname.split('.').next().unwrap_or(&full_hostname).to_string() }; log::debug!("HOSTNAME: final hostname to use: {:?}", hostname); // Use process_metadata_outputs to handle output mapping if let Some(meta_data) = crate::meta_plugin::process_metadata_outputs( "hostname", hostname, &self.outputs ) { metadata.push(meta_data); } // Mark as finalized since this plugin only needs to run once self.is_finalized = true; crate::meta_plugin::MetaPluginResponse { metadata, is_finalized: true, } } fn outputs(&self) -> &std::collections::HashMap { &self.outputs } fn outputs_mut(&mut self) -> &mut std::collections::HashMap { &mut self.outputs } fn default_outputs(&self) -> Vec { vec!["hostname".to_string()] } fn default_options(&self) -> std::collections::HashMap { let mut options = std::collections::HashMap::new(); options.insert("full".to_string(), serde_yaml::Value::Bool(true)); options } fn options(&self) -> &std::collections::HashMap { &self.options } fn options_mut(&mut self) -> &mut std::collections::HashMap { &mut self.options } fn configure_options(&mut self, options: &std::collections::HashMap) -> anyhow::Result<()> { for (key, value) in options { self.options.insert(key.clone(), value.clone()); } Ok(()) } } use gethostname::gethostname;