Some checks failed
Test / test-frontend (pull_request) Successful in 23s
Test / lint-frontend (pull_request) Successful in 27s
Verify / verify-openapi-spec (pull_request) Successful in 4s
Test / frontend-build (pull_request) Successful in 30s
Verify / verify-frontend-api-client (pull_request) Has been cancelled
Verify / verify-generated-code (pull_request) Has been cancelled
Test / test (pull_request) Has been cancelled
Test / lint (pull_request) Has been cancelled
167 lines
6.7 KiB
Rust
167 lines
6.7 KiB
Rust
use tracing::{info, warn};
|
|
|
|
use crate::commands::{run::run_cmd, write_config::INTERNAL_CONFIG_FOLDER_NAME};
|
|
use std::path::PathBuf;
|
|
|
|
pub struct ValidateCommand {
|
|
nginx_config_dir: PathBuf,
|
|
}
|
|
|
|
impl ValidateCommand {
|
|
pub fn new(nginx_config_dir: PathBuf) -> Self {
|
|
Self { nginx_config_dir }
|
|
}
|
|
|
|
pub fn nginx_config_dir(&self) -> PathBuf {
|
|
self.nginx_config_dir.clone()
|
|
}
|
|
|
|
pub async fn validate_all(
|
|
&self,
|
|
) -> Result<(i32, String), Box<dyn std::error::Error + Send + Sync>> {
|
|
// Try a normal config test first. If it fails due to pid permission
|
|
// errors (common when running unprivileged against /run/nginx.pid),
|
|
// retry with a writable pid override so validation can succeed.
|
|
match run_cmd("nginx", &["-t"], 10).await {
|
|
Ok(res) => Ok(res),
|
|
Err(e) => {
|
|
info!(
|
|
"nginx -t failed: {}. Trying with privileged wrapper or writable pid override.",
|
|
e
|
|
);
|
|
let es = e.to_string();
|
|
if es.contains("/run/nginx.pid") && es.contains("Permission denied") {
|
|
// Try privileged validate wrapper if available (allows the agent to run
|
|
// nginx -t via sudo without modifying the main config).
|
|
match run_cmd(
|
|
"sudo",
|
|
// TODO: allow configuring the path to the wrapper
|
|
&["-n", "/usr/local/sbin/yanpm-nginx-validate"],
|
|
10,
|
|
)
|
|
.await
|
|
{
|
|
Ok(res) => return Ok(res),
|
|
Err(e) => {
|
|
warn!(
|
|
"Privileged validate wrapper failed: {}. Falling back to writable pid override.",
|
|
e
|
|
);
|
|
// Fallback to the existing writable-pid override if sudo wrapper
|
|
// isn't available or fails.
|
|
let pid_path = format!(
|
|
"{}/yanpm-validate-{}.pid",
|
|
std::env::temp_dir().display(),
|
|
std::process::id()
|
|
);
|
|
let g_arg = format!("pid {};", pid_path);
|
|
let args_vec = ["-t".to_string(), "-g".to_string(), g_arg];
|
|
let args_ref: Vec<&str> = args_vec.iter().map(|s| s.as_str()).collect();
|
|
return run_cmd("nginx", args_ref.as_slice(), 10).await;
|
|
}
|
|
}
|
|
}
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn validate(
|
|
&self,
|
|
config_name: &str,
|
|
timestamp: u64,
|
|
) -> Result<(i32, String), Box<dyn std::error::Error + Send + Sync>> {
|
|
let filename = crate::commands::run::to_file_name(config_name, timestamp)?;
|
|
// fragments are written into the YANPM subdirectory
|
|
let full_path = self
|
|
.nginx_config_dir
|
|
.join(INTERNAL_CONFIG_FOLDER_NAME)
|
|
.join(&filename);
|
|
|
|
// ensure the fragment file exists
|
|
if tokio::fs::metadata(&full_path).await.is_err() {
|
|
return Err(format!("Config file not found: {}", full_path.display()).into());
|
|
}
|
|
|
|
// Create a temporary wrapper nginx config that provides the required
|
|
// top-level sections (`events` and `http`) and includes the fragment.
|
|
let fragment_path = full_path.to_str().ok_or("invalid config path")?.to_string();
|
|
|
|
let mut tmp_path = std::env::temp_dir();
|
|
let tmp_name = format!("yanpm-validate-{}-{}.conf", timestamp, std::process::id());
|
|
tmp_path.push(tmp_name);
|
|
|
|
let wrapper = format!(
|
|
"worker_processes 1;\nevents {{ worker_connections 1024; }}\nhttp {{\n include {};\n}}\n",
|
|
fragment_path
|
|
);
|
|
|
|
// Write the temporary wrapper file
|
|
tokio::fs::write(&tmp_path, wrapper).await?;
|
|
let tmp_path_str = tmp_path
|
|
.to_str()
|
|
.ok_or("invalid temp config path")?
|
|
.to_string();
|
|
|
|
// Run the test against the wrapper, telling nginx to place its pid
|
|
// somewhere writable so the config test doesn't fail with permission
|
|
// errors when running as an unprivileged user.
|
|
let result = match run_cmd("nginx", &["-t", "-c", &tmp_path_str], 10).await {
|
|
Ok(res) => Ok(res),
|
|
Err(e) => {
|
|
info!(
|
|
"nginx -t failed: {}. Trying with privileged wrapper or writable pid override.",
|
|
e
|
|
);
|
|
let es = e.to_string();
|
|
if es.contains("/run/nginx.pid") && es.contains("Permission denied") {
|
|
// Try privileged validate wrapper if available (allows the agent to run
|
|
// nginx -t via sudo without modifying the main config).
|
|
match run_cmd(
|
|
"sudo",
|
|
// TODO: allow configuring the path to the wrapper
|
|
&[
|
|
"-n",
|
|
"/usr/local/sbin/yanpm-nginx-validate-file",
|
|
&tmp_path_str,
|
|
],
|
|
10,
|
|
)
|
|
.await
|
|
{
|
|
Ok(res) => return Ok(res),
|
|
Err(e) => {
|
|
warn!(
|
|
"Privileged validate wrapper failed: {}. Falling back to writable pid override.",
|
|
e
|
|
);
|
|
let pid_path = format!(
|
|
"{}/yanpm-validate-{}.pid",
|
|
std::env::temp_dir().display(),
|
|
std::process::id()
|
|
);
|
|
let g_arg = format!("pid {};", pid_path);
|
|
|
|
let args_vec = [
|
|
"-t".to_string(),
|
|
"-c".to_string(),
|
|
tmp_path_str.clone(),
|
|
"-g".to_string(),
|
|
g_arg,
|
|
];
|
|
let args_ref: Vec<&str> = args_vec.iter().map(|s| s.as_str()).collect();
|
|
|
|
return run_cmd("nginx", args_ref.as_slice(), 10).await;
|
|
}
|
|
}
|
|
}
|
|
Err(e)
|
|
}
|
|
};
|
|
|
|
let _ = tokio::fs::remove_file(&tmp_path).await;
|
|
|
|
result
|
|
}
|
|
}
|