use crate::meta_plugin::{BaseMetaPlugin, MetaPlugin, MetaPluginType}; use smart_default::SmartDefault; #[derive(Debug, Clone, SmartDefault)] pub struct HostnameMetaPlugin { #[default = false] is_finalized: bool, base: BaseMetaPlugin, } impl HostnameMetaPlugin { pub fn new( options: Option>, outputs: Option>, ) -> HostnameMetaPlugin { let mut base = BaseMetaPlugin::new(); // Set default outputs let default_outputs = &["hostname", "hostname_full", "hostname_short"]; base.initialize_plugin(default_outputs, &options, &outputs); // Start with default options - hostname is now boolean only base.options .insert("hostname".to_string(), serde_yaml::Value::Bool(true)); base.options .insert("hostname_full".to_string(), serde_yaml::Value::Bool(true)); base.options .insert("hostname_short".to_string(), serde_yaml::Value::Bool(true)); // Override with provided options if let Some(opts) = &options { for (key, value) in opts { // Convert string "true"/"false" to boolean for hostname option if key == "hostname" && let serde_yaml::Value::String(s) = value { if s == "false" { base.options .insert(key.clone(), serde_yaml::Value::Bool(false)); continue; } else if s == "true" { base.options .insert(key.clone(), serde_yaml::Value::Bool(true)); continue; } } base.options.insert(key.clone(), value.clone()); } } // Determine which outputs are enabled based on options let hostname_enabled = base .options .get("hostname") .and_then(|v| v.as_bool()) .unwrap_or(true); let hostname_full_enabled = base .options .get("hostname_full") .and_then(|v| v.as_bool()) .unwrap_or(true); let hostname_short_enabled = base .options .get("hostname_short") .and_then(|v| v.as_bool()) .unwrap_or(true); // Start with default outputs, setting disabled ones to None let mut final_outputs = std::collections::HashMap::new(); // Handle hostname output if hostname_enabled { final_outputs.insert( "hostname".to_string(), serde_yaml::Value::String("hostname".to_string()), ); } else { final_outputs.insert("hostname".to_string(), serde_yaml::Value::Null); } // Handle hostname_full output if hostname_full_enabled { final_outputs.insert( "hostname_full".to_string(), serde_yaml::Value::String("hostname_full".to_string()), ); } else { final_outputs.insert("hostname_full".to_string(), serde_yaml::Value::Null); } // Handle hostname_short output if hostname_short_enabled { final_outputs.insert( "hostname_short".to_string(), serde_yaml::Value::String("hostname_short".to_string()), ); } else { final_outputs.insert("hostname_short".to_string(), serde_yaml::Value::Null); } // Override with provided outputs, but only if they're enabled if let Some(outs) = &outputs { for (key, value) in outs { // Only add if the output is enabled match key.as_str() { "hostname" => { if hostname_enabled { final_outputs.insert(key.clone(), value.clone()); } } "hostname_full" => { if hostname_full_enabled { final_outputs.insert(key.clone(), value.clone()); } } "hostname_short" => { if hostname_short_enabled { final_outputs.insert(key.clone(), value.clone()); } } _ => { final_outputs.insert(key.clone(), value.clone()); } } } } base.outputs = final_outputs; HostnameMetaPlugin { is_finalized: false, base, } } 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(), }; // First try DNS resolution for both IPv4 and IPv6 addresses // lookup_host should handle both A and AAAA records if let Ok(addrs_iter) = dns_lookup::lookup_host(&short_hostname) { // Collect addresses into a Vec to be able to use first() let addrs: Vec = addrs_iter.collect(); // 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() && 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); } } } } // Fallback: 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() && 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; } } // 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_type(&self) -> MetaPluginType { MetaPluginType::Hostname } 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, }; } // Get the full hostname let full_hostname = self.get_hostname(); let short_hostname = full_hostname .split('.') .next() .unwrap_or(&full_hostname) .to_string(); // Determine which hostnames to include based on options let hostname_enabled = self .base .options .get("hostname") .and_then(|v| v.as_bool()) .unwrap_or(true); let hostname_full_enabled = self .base .options .get("hostname_full") .and_then(|v| v.as_bool()) .unwrap_or(true); let hostname_short_enabled = self .base .options .get("hostname_short") .and_then(|v| v.as_bool()) .unwrap_or(true); // Always use gethostname() for the 'hostname' output when enabled let hostname_value = if hostname_enabled { gethostname::gethostname() .into_string() .unwrap_or_else(|_| "unknown".to_string()) } else { String::new() }; // Prepare metadata to return let mut metadata = Vec::new(); // Add enabled metadata to the response using process_metadata_outputs if hostname_enabled && let Some(meta_data) = crate::meta_plugin::process_metadata_outputs( "hostname", serde_yaml::Value::String(hostname_value.clone()), self.base.outputs(), ) { metadata.push(meta_data); } if hostname_full_enabled && let Some(meta_data) = crate::meta_plugin::process_metadata_outputs( "hostname_full", serde_yaml::Value::String(full_hostname.clone()), self.base.outputs(), ) { metadata.push(meta_data); } if hostname_short_enabled && let Some(meta_data) = crate::meta_plugin::process_metadata_outputs( "hostname_short", serde_yaml::Value::String(short_hostname.clone()), self.base.outputs(), ) { metadata.push(meta_data); } // Update outputs based on enabled status // Handle hostname output if hostname_enabled { if let Some(output_value) = self.base.outputs_mut().get_mut("hostname") { *output_value = serde_yaml::Value::String(hostname_value); } } else { self.base .outputs_mut() .insert("hostname".to_string(), serde_yaml::Value::Null); } // Handle hostname_full output if hostname_full_enabled { if let Some(output_value) = self.base.outputs_mut().get_mut("hostname_full") { *output_value = serde_yaml::Value::String(full_hostname); } } else { self.base .outputs_mut() .insert("hostname_full".to_string(), serde_yaml::Value::Null); } // Handle hostname_short output if hostname_short_enabled { if let Some(output_value) = self.base.outputs_mut().get_mut("hostname_short") { *output_value = serde_yaml::Value::String(short_hostname); } } else { self.base .outputs_mut() .insert("hostname_short".to_string(), serde_yaml::Value::Null); } // 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.base.outputs() } fn outputs_mut(&mut self) -> &mut std::collections::HashMap { self.base.outputs_mut() } fn default_outputs(&self) -> Vec { vec![ "hostname".to_string(), "hostname_full".to_string(), "hostname_short".to_string(), ] } fn options(&self) -> &std::collections::HashMap { self.base.options() } fn options_mut(&mut self) -> &mut std::collections::HashMap { self.base.options_mut() } } use crate::meta_plugin::register_meta_plugin; // Register the plugin at module initialization time #[ctor::ctor] fn register_hostname_plugin() { register_meta_plugin(MetaPluginType::Hostname, |options, outputs| { Box::new(HostnameMetaPlugin::new(options, outputs)) }); }