use crate::cmd::config::exec::ExecConfig;
use crate::{
env::EnvManager,
error::LocketError,
events::{EventHandler, FsEvent, HandlerError},
path::AbsolutePath,
process::ProcessManager,
secrets::SecretFileManager,
watch::FsWatcher,
};
use futures::future::BoxFuture;
use std::collections::HashSet;
use tracing::{debug, info};
pub async fn exec(config: ExecConfig) -> Result<(), LocketError> {
config.logger.init()?;
info!(
"Starting locket v{} `exec` service ",
env!("CARGO_PKG_VERSION")
);
debug!("effective config: {:#?}", config);
let provider = config.provider.build().await?;
let mut env_secrets = config.env_overrides;
env_secrets.extend(config.env_files);
let env_manager = EnvManager::new(env_secrets, provider.clone());
let interactive = config.interactive.unwrap_or(!config.watch);
let command = config.cmd;
let mut process = ProcessManager::new(env_manager, command, interactive, config.timeout);
let files = SecretFileManager::new(config.manager, provider)?;
info!("resolving environment and starting process...");
files.inject_all().await?;
process.start().await?;
let mut handler = ExecOrchestrator::new(process, files);
if config.watch {
let watcher = FsWatcher::new(config.debounce, handler);
handler = watcher.run().await?;
handler.cleanup().await;
info!("watch loop terminated gracefully");
Ok(())
} else {
let result = handler.wait().await;
handler.cleanup().await;
result.map_err(LocketError::from)
}
}
struct ExecOrchestrator {
process: ProcessManager,
files: SecretFileManager,
process_paths: HashSet<AbsolutePath>,
}
impl ExecOrchestrator {
pub fn new(process: ProcessManager, files: SecretFileManager) -> Self {
let process_paths = process.paths().into_iter().collect();
Self {
process,
files,
process_paths,
}
}
}
#[async_trait::async_trait]
impl EventHandler for ExecOrchestrator {
fn paths(&self) -> Vec<AbsolutePath> {
let mut p = self.files.paths();
p.extend(self.process.paths());
p
}
async fn handle(&mut self, events: Vec<FsEvent>) -> Result<(), HandlerError> {
let proc_events: Vec<FsEvent> = events
.iter()
.filter(|e| e.affects(|p| self.process_paths.contains(p)))
.cloned()
.collect();
self.files.handle(events).await?;
if !proc_events.is_empty() {
self.process.handle(proc_events).await?;
}
Ok(())
}
fn wait(&self) -> BoxFuture<'static, Result<(), HandlerError>> {
self.process.wait()
}
async fn cleanup(&mut self) {
self.process.cleanup().await;
}
}