use crate::ast::Config;
use crate::error::Result;
use crate::extract;
use crate::prelude::Server;
use crate::types::{AccessLog, LogFormat};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct NginxDiscovery {
config: Config,
config_path: Option<PathBuf>,
}
impl NginxDiscovery {
pub fn from_config_text(text: &str) -> Result<Self> {
let config = crate::parse(text)?;
Ok(Self {
config,
config_path: None,
})
}
pub fn from_config_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let text = std::fs::read_to_string(path)?;
let config = crate::parse(&text)?;
Ok(Self {
config,
config_path: Some(path.to_path_buf()),
})
}
#[cfg(feature = "system")]
pub fn from_running_instance() -> Result<Self> {
crate::system::detect_and_parse()
}
#[must_use]
pub fn access_logs(&self) -> Vec<AccessLog> {
extract::access_logs(&self.config).unwrap_or_default()
}
#[must_use]
pub fn log_formats(&self) -> Vec<LogFormat> {
extract::log_formats(&self.config).unwrap_or_default()
}
#[must_use]
pub fn all_log_files(&self) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = self.access_logs().into_iter().map(|log| log.path).collect();
paths.sort();
paths.dedup();
paths
}
#[must_use]
pub fn server_names(&self) -> Vec<String> {
let mut names = Vec::new();
for server in self.config.find_directives_recursive("server") {
for server_name_directive in server.find_children("server_name") {
names.extend(server_name_directive.args_as_strings());
}
}
names
}
#[cfg(feature = "serde")]
pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(&self.config)
.map_err(|e| crate::Error::Serialization(e.to_string()))
}
#[cfg(feature = "serde")]
pub fn to_yaml(&self) -> Result<String> {
serde_yaml::to_string(&self.config).map_err(|e| crate::Error::Serialization(e.to_string()))
}
#[must_use]
pub fn config(&self) -> &Config {
&self.config
}
#[must_use]
pub fn config_path(&self) -> Option<&Path> {
self.config_path.as_deref()
}
#[must_use]
pub fn summary(&self) -> String {
let directive_count = self.config.count_directives();
let server_count = self.config.find_directives_recursive("server").len();
let access_log_count = self.access_logs().len();
let format_count = self.log_formats().len();
format!(
"NGINX Configuration Summary:\n\
- Total directives: {directive_count}\n\
- Server blocks: {server_count}\n\
- Access logs: {access_log_count}\n\
- Log formats: {format_count}"
)
}
#[must_use]
pub fn servers(&self) -> Vec<crate::types::Server> {
extract::servers(&self.config).unwrap_or_default()
}
#[must_use]
pub fn listening_ports(&self) -> Vec<u16> {
let mut ports: Vec<u16> = self
.servers()
.iter()
.flat_map(|s| s.listen.iter().map(|l| l.port))
.collect();
ports.sort_unstable();
ports.dedup();
ports
}
#[must_use]
pub fn ssl_servers(&self) -> Vec<crate::types::Server> {
self.servers().into_iter().filter(Server::has_ssl).collect()
}
#[must_use]
pub fn proxy_locations(&self) -> Vec<crate::types::Location> {
self.servers()
.iter()
.flat_map(|s| s.locations.iter())
.filter(|l: &&crate::types::Location| l.is_proxy())
.cloned()
.collect()
}
#[must_use]
pub fn location_count(&self) -> usize {
self.servers().iter().map(|s| s.locations.len()).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_config_text() {
let config = "user nginx;";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
assert_eq!(discovery.config.directives.len(), 1);
}
#[test]
fn test_access_logs() {
let config = r"
http {
access_log /var/log/nginx/access.log;
server {
access_log /var/log/nginx/server.log;
}
}
";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let logs = discovery.access_logs();
assert_eq!(logs.len(), 2);
}
#[test]
fn test_log_formats() {
let config = r"
log_format combined '$remote_addr';
log_format main '$request';
";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let formats = discovery.log_formats();
assert_eq!(formats.len(), 2);
}
#[test]
fn test_all_log_files() {
let config = r"
access_log /var/log/nginx/access.log;
access_log /var/log/nginx/access.log;
access_log /var/log/nginx/other.log;
";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let files = discovery.all_log_files();
assert_eq!(files.len(), 2); }
#[test]
fn test_server_names() {
let config = r"
server {
server_name example.com www.example.com;
}
server {
server_name test.com;
}
";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let names = discovery.server_names();
assert_eq!(names.len(), 3);
assert!(names.contains(&"example.com".to_string()));
assert!(names.contains(&"www.example.com".to_string()));
assert!(names.contains(&"test.com".to_string()));
}
#[test]
fn test_summary() {
let config = r"
user nginx;
access_log /var/log/nginx/access.log;
server { listen 80; }
";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let summary = discovery.summary();
assert!(summary.contains("directives"));
assert!(summary.contains("Server blocks: 1"));
}
#[test]
fn test_config_access() {
let config = "user nginx;";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let ast = discovery.config();
assert_eq!(ast.directives.len(), 1);
}
#[test]
#[cfg(feature = "serde")]
fn test_to_json() {
let config = "user nginx;";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let json = discovery.to_json().unwrap();
assert!(json.contains("user"));
}
#[test]
#[cfg(feature = "serde")]
fn test_to_yaml() {
let config = "user nginx;";
let discovery = NginxDiscovery::from_config_text(config).unwrap();
let yaml = discovery.to_yaml().unwrap();
assert!(yaml.contains("user"));
}
}