feat: implement Nginx command and file system handlers for configuration management
This commit is contained in:
199
apps/nxmesh-agent/src/service/nginx_handler/command_handler.rs
Normal file
199
apps/nxmesh-agent/src/service/nginx_handler/command_handler.rs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use tokio::process::Command;
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
|
use crate::config::settings::NginxSettings;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use mockall::predicate::*;
|
||||||
|
// TODO: custom error type
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[cfg_attr(test, mockall::automock)]
|
||||||
|
pub trait CommandHandler: Send + Sync + 'static {
|
||||||
|
// Reload nginx to apply new config. The config_path is an optional parameter that specifies the path to the nginx config file to be used for this reload operation. If not provided, the default config path will be used.
|
||||||
|
async fn reload(&self, config_path: Option<&str>) -> Result<()>;
|
||||||
|
async fn stop(&self) -> Result<()>;
|
||||||
|
async fn validate(&self, config_path: Option<&str>) -> Result<()>;
|
||||||
|
async fn get_version(&self) -> Result<String>;
|
||||||
|
async fn get_status(&self) -> Result<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CommandHandlerImpl {
|
||||||
|
settings: Arc<NginxSettings>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandHandlerImpl {
|
||||||
|
pub fn new(settings: Arc<NginxSettings>) -> Self {
|
||||||
|
Self { settings }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_nginx_command(&self) -> String {
|
||||||
|
// TODO: rename the setting for better clarity, it can be a binary path or a custom command
|
||||||
|
self.settings
|
||||||
|
.nginx_binary_path
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "nginx".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_config_path(config_path: &str) -> Result<()> {
|
||||||
|
if !std::path::Path::new(config_path).exists() {
|
||||||
|
anyhow::bail!("Config file not found at path: {}", config_path);
|
||||||
|
}
|
||||||
|
if !std::path::Path::new(config_path).is_file() {
|
||||||
|
anyhow::bail!("Config path is not a file: {}", config_path);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_config_path_to_command_vecs<'a>(
|
||||||
|
command: &'a mut Vec<String>,
|
||||||
|
config_path: &str,
|
||||||
|
) -> Result<&'a mut Vec<String>> {
|
||||||
|
// if given a config path, add it to the end of the command arguments to override the default config path used
|
||||||
|
Self::validate_config_path(config_path)?;
|
||||||
|
let parent_dir = match std::path::Path::new(config_path).parent() {
|
||||||
|
Some(dir) => dir,
|
||||||
|
// return root
|
||||||
|
None => std::path::Path::new("/"),
|
||||||
|
};
|
||||||
|
// set prefix path to the parent directory of the config file to ensure nginx can find all related files (e.g. certs, conf.d, etc.)
|
||||||
|
command.push("-p".to_string());
|
||||||
|
command.push(parent_dir.to_string_lossy().to_string());
|
||||||
|
// add the config file path to the command arguments to override the default config path used by nginx
|
||||||
|
command.push("-c".to_string());
|
||||||
|
command.push(config_path.to_string());
|
||||||
|
Ok(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl CommandHandler for CommandHandlerImpl {
|
||||||
|
async fn reload(&self, config_path: Option<&str>) -> Result<()> {
|
||||||
|
// TODO: add timeout for the command execution
|
||||||
|
let reload_command_str = self.settings.override_nginx_reload_command.clone();
|
||||||
|
let program = match reload_command_str.first() {
|
||||||
|
Some(cmd) => cmd,
|
||||||
|
None => &self.get_nginx_command(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reload_command_vec = reload_command_str[1..].to_vec();
|
||||||
|
// if given a config path, add it to the end of the command arguments to override the default config path used
|
||||||
|
if let Some(path) = config_path {
|
||||||
|
Self::apply_config_path_to_command_vecs(&mut reload_command_vec, path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new(program)
|
||||||
|
.args(&reload_command_vec)
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
if !output.status.success() {
|
||||||
|
let error_info = String::from_utf8_lossy(&output.stderr);
|
||||||
|
anyhow::bail!("Failed to reload nginx: {}", error_info.trim());
|
||||||
|
}
|
||||||
|
let success_info = String::from_utf8_lossy(&output.stdout);
|
||||||
|
debug!("Nginx reloaded successfully: {}", success_info.trim());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stop(&self) -> Result<()> {
|
||||||
|
let output = Command::new(self.get_nginx_command())
|
||||||
|
.arg("-s")
|
||||||
|
.arg("stop")
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let error_info = String::from_utf8_lossy(&output.stderr);
|
||||||
|
anyhow::bail!("Failed to stop nginx: {}", error_info.trim());
|
||||||
|
}
|
||||||
|
let success_info = String::from_utf8_lossy(&output.stdout);
|
||||||
|
debug!("Nginx stopped successfully: {}", success_info.trim());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn validate(&self, config_path: Option<&str>) -> Result<()> {
|
||||||
|
// TODO: add timeout for the command execution
|
||||||
|
let validate_command_str = self.settings.override_nginx_test_command.clone();
|
||||||
|
let program = match validate_command_str.first() {
|
||||||
|
Some(cmd) => cmd,
|
||||||
|
None => &self.get_nginx_command(),
|
||||||
|
};
|
||||||
|
let mut validate_args = validate_command_str[1..].to_vec();
|
||||||
|
// if given a config path, add it to the end of the command arguments to override the default config path used
|
||||||
|
if let Some(path) = config_path {
|
||||||
|
Self::apply_config_path_to_command_vecs(&mut validate_args, path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new(program).args(&validate_args).output().await?;
|
||||||
|
if !output.status.success() {
|
||||||
|
let error_info = String::from_utf8_lossy(&output.stderr);
|
||||||
|
anyhow::bail!("Nginx config validation failed: {}", error_info.trim());
|
||||||
|
}
|
||||||
|
let success_info = String::from_utf8_lossy(&output.stdout);
|
||||||
|
debug!("Nginx config validation succeeded: {}", success_info.trim());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_version(&self) -> Result<String> {
|
||||||
|
let output = Command::new(self.get_nginx_command())
|
||||||
|
.arg("-v")
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let error_info = String::from_utf8_lossy(&output.stderr);
|
||||||
|
anyhow::bail!("Failed to get nginx version: {}", error_info.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
let version_info = String::from_utf8_lossy(&output.stderr);
|
||||||
|
Ok(version_info.trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_status(&self) -> Result<String> {
|
||||||
|
let output = Command::new(self.get_nginx_command())
|
||||||
|
.arg("-t")
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let error_info = String::from_utf8_lossy(&output.stderr);
|
||||||
|
anyhow::bail!("Failed to get nginx status: {}", error_info.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
let status_info = String::from_utf8_lossy(&output.stderr);
|
||||||
|
Ok(status_info.trim().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
mod tests {
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn apply_config_path_to_command_vecs_appends_prefix_and_config() -> Result<()> {
|
||||||
|
let temp = TempDir::new()?;
|
||||||
|
let cfg_file = temp.path().join("nginx.conf");
|
||||||
|
tokio::fs::write(&cfg_file, b"data").await?;
|
||||||
|
|
||||||
|
let mut args: Vec<String> = vec!["base".to_string()];
|
||||||
|
let result = CommandHandlerImpl::apply_config_path_to_command_vecs(
|
||||||
|
&mut args,
|
||||||
|
&cfg_file.to_string_lossy(),
|
||||||
|
);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let args = result.expect("Failed to apply config path to command vecs");
|
||||||
|
// expect -p <parent_dir> -c <config>
|
||||||
|
assert!(args.contains(&"-p".to_string()));
|
||||||
|
assert!(args.contains(&"-c".to_string()));
|
||||||
|
assert!(args.contains(&cfg_file.to_string_lossy().to_string()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,7 @@ use mockall::predicate::*;
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[cfg_attr(test, mockall::automock)]
|
#[cfg_attr(test, mockall::automock)]
|
||||||
pub trait NginxHandler: Send + Sync {
|
pub trait FsHandler: Send + Sync + 'static {
|
||||||
// Reload nginx to apply new config. The config_path is an optional parameter that specifies the path to the nginx config file to be used for this reload operation. If not provided, the default config path will be used.
|
|
||||||
async fn reload(&self, config_path: Option<&str>) -> Result<()>;
|
|
||||||
async fn stop(&self) -> Result<()>;
|
|
||||||
async fn validate(&self, config_path: Option<&str>) -> Result<()>;
|
|
||||||
async fn get_version(&self) -> Result<String>;
|
|
||||||
async fn get_status(&self) -> Result<String>;
|
|
||||||
// Write a new config file for nginx.
|
// Write a new config file for nginx.
|
||||||
// The output_path is a relative path to the nginx config directory of the deployment folder. The actual path to the config should not be assumed by the caller, as it can be different in different environments, but will be promised to be relative to the deployment folder for each the corresponding deployment_id. Path traversal is not allowed.
|
// The output_path is a relative path to the nginx config directory of the deployment folder. The actual path to the config should not be assumed by the caller, as it can be different in different environments, but will be promised to be relative to the deployment folder for each the corresponding deployment_id. Path traversal is not allowed.
|
||||||
async fn write_config(
|
async fn write_config(
|
||||||
@@ -41,23 +35,15 @@ pub trait NginxHandler: Send + Sync {
|
|||||||
async fn cleanup_config(&self, n: usize) -> Result<()>;
|
async fn cleanup_config(&self, n: usize) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NginxHandlerImpl {
|
pub struct FsHandlerImpl {
|
||||||
settings: Arc<NginxSettings>,
|
settings: Arc<NginxSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NginxHandlerImpl {
|
impl FsHandlerImpl {
|
||||||
pub fn new(settings: Arc<NginxSettings>) -> Self {
|
pub fn new(settings: Arc<NginxSettings>) -> Self {
|
||||||
Self { settings }
|
Self { settings }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nginx_command(&self) -> String {
|
|
||||||
// TODO: rename the setting for better clarity, it can be a binary path or a custom command
|
|
||||||
self.settings
|
|
||||||
.nginx_binary_path
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| "nginx".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_config_path(config_path: &str) -> Result<()> {
|
fn validate_config_path(config_path: &str) -> Result<()> {
|
||||||
if !std::path::Path::new(config_path).exists() {
|
if !std::path::Path::new(config_path).exists() {
|
||||||
anyhow::bail!("Config file not found at path: {}", config_path);
|
anyhow::bail!("Config file not found at path: {}", config_path);
|
||||||
@@ -68,26 +54,6 @@ impl NginxHandlerImpl {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_config_path_to_command_vecs<'a>(
|
|
||||||
command: &'a mut Vec<String>,
|
|
||||||
config_path: &str,
|
|
||||||
) -> Result<&'a mut Vec<String>> {
|
|
||||||
// if given a config path, add it to the end of the command arguments to override the default config path used
|
|
||||||
Self::validate_config_path(config_path)?;
|
|
||||||
let parent_dir = match std::path::Path::new(config_path).parent() {
|
|
||||||
Some(dir) => dir,
|
|
||||||
// return root
|
|
||||||
None => std::path::Path::new("/"),
|
|
||||||
};
|
|
||||||
// set prefix path to the parent directory of the config file to ensure nginx can find all related files (e.g. certs, conf.d, etc.)
|
|
||||||
command.push("-p".to_string());
|
|
||||||
command.push(parent_dir.to_string_lossy().to_string());
|
|
||||||
// add the config file path to the command arguments to override the default config path used by nginx
|
|
||||||
command.push("-c".to_string());
|
|
||||||
command.push(config_path.to_string());
|
|
||||||
Ok(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_deployment_dir(&self) -> std::path::PathBuf {
|
fn get_deployment_dir(&self) -> std::path::PathBuf {
|
||||||
std::path::Path::new(&self.settings.nginx_config_path).join("deployments")
|
std::path::Path::new(&self.settings.nginx_config_path).join("deployments")
|
||||||
}
|
}
|
||||||
@@ -127,105 +93,7 @@ impl NginxHandlerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl NginxHandler for NginxHandlerImpl {
|
impl FsHandler for FsHandlerImpl {
|
||||||
async fn reload(&self, config_path: Option<&str>) -> Result<()> {
|
|
||||||
// TODO: add timeout for the command execution
|
|
||||||
let reload_command_str = self.settings.override_nginx_reload_command.clone();
|
|
||||||
let program = match reload_command_str.first() {
|
|
||||||
Some(cmd) => cmd,
|
|
||||||
None => &self.get_nginx_command(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut reload_command_vec = reload_command_str[1..].to_vec();
|
|
||||||
// if given a config path, add it to the end of the command arguments to override the default config path used
|
|
||||||
if let Some(path) = config_path {
|
|
||||||
Self::apply_config_path_to_command_vecs(&mut reload_command_vec, path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = Command::new(program)
|
|
||||||
.args(&reload_command_vec)
|
|
||||||
.output()
|
|
||||||
.await?;
|
|
||||||
if !output.status.success() {
|
|
||||||
let error_info = String::from_utf8_lossy(&output.stderr);
|
|
||||||
anyhow::bail!("Failed to reload nginx: {}", error_info.trim());
|
|
||||||
}
|
|
||||||
let success_info = String::from_utf8_lossy(&output.stdout);
|
|
||||||
debug!("Nginx reloaded successfully: {}", success_info.trim());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn stop(&self) -> Result<()> {
|
|
||||||
let output = Command::new(self.get_nginx_command())
|
|
||||||
.arg("-s")
|
|
||||||
.arg("stop")
|
|
||||||
.output()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
let error_info = String::from_utf8_lossy(&output.stderr);
|
|
||||||
anyhow::bail!("Failed to stop nginx: {}", error_info.trim());
|
|
||||||
}
|
|
||||||
let success_info = String::from_utf8_lossy(&output.stdout);
|
|
||||||
debug!("Nginx stopped successfully: {}", success_info.trim());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn validate(&self, config_path: Option<&str>) -> Result<()> {
|
|
||||||
// TODO: add timeout for the command execution
|
|
||||||
let validate_command_str = self.settings.override_nginx_test_command.clone();
|
|
||||||
let program = match validate_command_str.first() {
|
|
||||||
Some(cmd) => cmd,
|
|
||||||
None => &self.get_nginx_command(),
|
|
||||||
};
|
|
||||||
let mut validate_args = validate_command_str[1..].to_vec();
|
|
||||||
// if given a config path, add it to the end of the command arguments to override the default config path used
|
|
||||||
if let Some(path) = config_path {
|
|
||||||
Self::apply_config_path_to_command_vecs(&mut validate_args, path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = Command::new(program).args(&validate_args).output().await?;
|
|
||||||
if !output.status.success() {
|
|
||||||
let error_info = String::from_utf8_lossy(&output.stderr);
|
|
||||||
anyhow::bail!("Nginx config validation failed: {}", error_info.trim());
|
|
||||||
}
|
|
||||||
let success_info = String::from_utf8_lossy(&output.stdout);
|
|
||||||
debug!("Nginx config validation succeeded: {}", success_info.trim());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_version(&self) -> Result<String> {
|
|
||||||
let output = Command::new(self.get_nginx_command())
|
|
||||||
.arg("-v")
|
|
||||||
.output()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
let error_info = String::from_utf8_lossy(&output.stderr);
|
|
||||||
anyhow::bail!("Failed to get nginx version: {}", error_info.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
let version_info = String::from_utf8_lossy(&output.stderr);
|
|
||||||
Ok(version_info.trim().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_status(&self) -> Result<String> {
|
|
||||||
let output = Command::new(self.get_nginx_command())
|
|
||||||
.arg("-t")
|
|
||||||
.output()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
let error_info = String::from_utf8_lossy(&output.stderr);
|
|
||||||
anyhow::bail!("Failed to get nginx status: {}", error_info.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
let status_info = String::from_utf8_lossy(&output.stderr);
|
|
||||||
Ok(status_info.trim().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn write_config(
|
async fn write_config(
|
||||||
&self,
|
&self,
|
||||||
deployment_id: &str,
|
deployment_id: &str,
|
||||||
@@ -337,7 +205,7 @@ mod tests {
|
|||||||
nginx_test_timeout_seconds: 1,
|
nginx_test_timeout_seconds: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handler = NginxHandlerImpl::new(Arc::new(settings));
|
let handler = FsHandlerImpl::new(Arc::new(settings));
|
||||||
|
|
||||||
handler
|
handler
|
||||||
.write_config("deployment1", "hello", "conf/nginx.conf")
|
.write_config("deployment1", "hello", "conf/nginx.conf")
|
||||||
@@ -374,7 +242,7 @@ mod tests {
|
|||||||
nginx_test_timeout_seconds: 1,
|
nginx_test_timeout_seconds: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handler = NginxHandlerImpl::new(Arc::new(settings));
|
let handler = FsHandlerImpl::new(Arc::new(settings));
|
||||||
|
|
||||||
let err = handler
|
let err = handler
|
||||||
.write_config("d", "x", "/absolute/path.conf")
|
.write_config("d", "x", "/absolute/path.conf")
|
||||||
@@ -391,36 +259,16 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn validate_config_path_checks_file_exists_and_is_file() {
|
async fn validate_config_path_checks_file_exists_and_is_file() {
|
||||||
// missing file
|
// missing file
|
||||||
let res = NginxHandlerImpl::validate_config_path("/this/path/does/not/exist.conf");
|
let res = FsHandlerImpl::validate_config_path("/this/path/does/not/exist.conf");
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
// create a temp dir and ensure a directory is rejected
|
// create a temp dir and ensure a directory is rejected
|
||||||
let temp = TempDir::new().expect("Failed to create temp dir");
|
let temp = TempDir::new().expect("Failed to create temp dir");
|
||||||
let dir_path = temp.path();
|
let dir_path = temp.path();
|
||||||
let res = NginxHandlerImpl::validate_config_path(dir_path.to_string_lossy().as_ref());
|
let res = FsHandlerImpl::validate_config_path(dir_path.to_string_lossy().as_ref());
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn apply_config_path_to_command_vecs_appends_prefix_and_config() -> Result<()> {
|
|
||||||
let temp = TempDir::new()?;
|
|
||||||
let cfg_file = temp.path().join("nginx.conf");
|
|
||||||
tokio::fs::write(&cfg_file, b"data").await?;
|
|
||||||
|
|
||||||
let mut args: Vec<String> = vec!["base".to_string()];
|
|
||||||
let result = NginxHandlerImpl::apply_config_path_to_command_vecs(
|
|
||||||
&mut args,
|
|
||||||
&cfg_file.to_string_lossy(),
|
|
||||||
);
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let args = result.expect("Failed to apply config path to command vecs");
|
|
||||||
// expect -p <parent_dir> -c <config>
|
|
||||||
assert!(args.contains(&"-p".to_string()));
|
|
||||||
assert!(args.contains(&"-c".to_string()));
|
|
||||||
assert!(args.contains(&cfg_file.to_string_lossy().to_string()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_deployment_config_path_create_flag_behaviour() -> Result<()> {
|
async fn get_deployment_config_path_create_flag_behaviour() -> Result<()> {
|
||||||
let temp = TempDir::new()?;
|
let temp = TempDir::new()?;
|
||||||
@@ -433,7 +281,7 @@ mod tests {
|
|||||||
nginx_test_timeout_seconds: 1,
|
nginx_test_timeout_seconds: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handler = NginxHandlerImpl::new(Arc::new(settings));
|
let handler = FsHandlerImpl::new(Arc::new(settings));
|
||||||
|
|
||||||
// when create_dir_if_not_exists = false, directory shouldn't be created
|
// when create_dir_if_not_exists = false, directory shouldn't be created
|
||||||
let path = handler
|
let path = handler
|
||||||
@@ -471,7 +319,7 @@ mod tests {
|
|||||||
nginx_test_timeout_seconds: 1,
|
nginx_test_timeout_seconds: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handler = NginxHandlerImpl::new(Arc::new(settings));
|
let handler = FsHandlerImpl::new(Arc::new(settings));
|
||||||
let base = temp.path().join("deployments");
|
let base = temp.path().join("deployments");
|
||||||
|
|
||||||
// create three deployments sequentially so mtimes differ
|
// create three deployments sequentially so mtimes differ
|
||||||
121
apps/nxmesh-agent/src/service/nginx_handler/mod.rs
Normal file
121
apps/nxmesh-agent/src/service/nginx_handler/mod.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::settings::NginxSettings,
|
||||||
|
service::nginx_handler::{
|
||||||
|
command_handler::{CommandHandler, CommandHandlerImpl},
|
||||||
|
fs_handler::{FsHandler, FsHandlerImpl},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod command_handler;
|
||||||
|
mod fs_handler;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use mockall::predicate::*;
|
||||||
|
// TODO: custom error type
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[cfg_attr(test, mockall::automock)]
|
||||||
|
pub trait NginxHandler: Send + Sync + 'static {
|
||||||
|
// Reload nginx to apply new config. The config_path is an optional parameter that specifies the path to the nginx config file to be used for this reload operation. If not provided, the default config path will be used.
|
||||||
|
async fn reload(&self, config_path: Option<&str>) -> Result<()>;
|
||||||
|
async fn stop(&self) -> Result<()>;
|
||||||
|
async fn validate(&self, config_path: Option<&str>) -> Result<()>;
|
||||||
|
async fn get_version(&self) -> Result<String>;
|
||||||
|
async fn get_status(&self) -> Result<String>;
|
||||||
|
// Write a new config file for nginx.
|
||||||
|
// The output_path is a relative path to the nginx config directory of the deployment folder. The actual path to the config should not be assumed by the caller, as it can be different in different environments, but will be promised to be relative to the deployment folder for each the corresponding deployment_id. Path traversal is not allowed.
|
||||||
|
async fn write_config(
|
||||||
|
&self,
|
||||||
|
deployment_id: &str,
|
||||||
|
config_content: &str,
|
||||||
|
output_path: &str,
|
||||||
|
) -> Result<String>;
|
||||||
|
// Append a new config content to an existing config file for nginx. This is useful for some use cases where we want to keep the existing config and just add some new config content to it. The output_path is a relative path to the nginx config directory of the deployment folder, which should be the same as the one used in write_config function. Path traversal is not allowed.
|
||||||
|
async fn append_config(
|
||||||
|
&self,
|
||||||
|
deployment_id: &str,
|
||||||
|
config_content: &str,
|
||||||
|
output_path: &str,
|
||||||
|
) -> Result<String>;
|
||||||
|
|
||||||
|
// clean up old config files that are applied to nginx
|
||||||
|
// keep only latest n deployments.
|
||||||
|
async fn cleanup_config(&self, n: usize) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NginxHandlerImpl<CH, FSH>
|
||||||
|
where
|
||||||
|
CH: CommandHandler + ?Sized,
|
||||||
|
FSH: FsHandler + ?Sized,
|
||||||
|
{
|
||||||
|
settings: Arc<NginxSettings>,
|
||||||
|
command_handler: Arc<CH>,
|
||||||
|
fs_handler: Arc<FSH>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NginxHandlerImpl<CommandHandlerImpl, FsHandlerImpl> {
|
||||||
|
pub fn new(settings: Arc<NginxSettings>) -> Self {
|
||||||
|
Self {
|
||||||
|
settings: settings.clone(),
|
||||||
|
command_handler: Arc::new(CommandHandlerImpl::new(settings.clone())),
|
||||||
|
fs_handler: Arc::new(FsHandlerImpl::new(settings)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<CH, FSH> NginxHandler for NginxHandlerImpl<CH, FSH>
|
||||||
|
where
|
||||||
|
CH: CommandHandler + ?Sized,
|
||||||
|
FSH: FsHandler + ?Sized,
|
||||||
|
{
|
||||||
|
async fn reload(&self, config_path: Option<&str>) -> Result<()> {
|
||||||
|
self.command_handler.reload(config_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stop(&self) -> Result<()> {
|
||||||
|
self.command_handler.stop().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn validate(&self, config_path: Option<&str>) -> Result<()> {
|
||||||
|
self.command_handler.validate(config_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_version(&self) -> Result<String> {
|
||||||
|
self.command_handler.get_version().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_status(&self) -> Result<String> {
|
||||||
|
self.command_handler.get_status().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_config(
|
||||||
|
&self,
|
||||||
|
deployment_id: &str,
|
||||||
|
config_content: &str,
|
||||||
|
output_path: &str,
|
||||||
|
) -> Result<String> {
|
||||||
|
self.fs_handler
|
||||||
|
.write_config(deployment_id, config_content, output_path)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn append_config(
|
||||||
|
&self,
|
||||||
|
deployment_id: &str,
|
||||||
|
config_content: &str,
|
||||||
|
output_path: &str,
|
||||||
|
) -> Result<String> {
|
||||||
|
self.fs_handler
|
||||||
|
.append_config(deployment_id, config_content, output_path)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cleanup_config(&self, n: usize) -> Result<()> {
|
||||||
|
self.fs_handler.cleanup_config(n).await
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user