use std::{ env, fs, path::{Path, PathBuf}, }; use anyhow::{Context as _, Result}; use tempfile::TempDir; /// Copy the repository stack assets into a scenario-specific temp dir. #[derive(Debug)] pub struct ComposeWorkspace { root: TempDir, } impl ComposeWorkspace { /// Clone the stack assets into a temporary directory. pub fn create() -> Result { let repo_root = env::var("CARGO_WORKSPACE_DIR") .map(PathBuf::from) .or_else(|_| { Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .and_then(Path::parent) .and_then(Path::parent) .map(Path::to_path_buf) .context("resolving workspace root from manifest dir") }) .context("locating repository root")?; let temp = tempfile::Builder::new() .prefix("nomos-testnet-") .tempdir() .context("creating testnet temp dir")?; let stack_source = stack_assets_root(&repo_root); if !stack_source.exists() { anyhow::bail!( "stack assets directory not found at {}", stack_source.display() ); } copy_dir_recursive(&stack_source, &temp.path().join("stack"))?; let scripts_source = stack_scripts_root(&repo_root); if scripts_source.exists() { copy_dir_recursive(&scripts_source, &temp.path().join("stack/scripts"))?; } // Ensure Prometheus config is a file (Docker bind mount fails if a directory // exists). let prometheus_src = stack_source.join("monitoring/prometheus.yml"); let prometheus_dst = temp.path().join("stack/monitoring/prometheus.yml"); if prometheus_dst.exists() && prometheus_dst.is_dir() { fs::remove_dir_all(&prometheus_dst) .with_context(|| format!("removing bogus dir {}", prometheus_dst.display()))?; } if !prometheus_dst.exists() { fs::copy(&prometheus_src, &prometheus_dst).with_context(|| { format!( "copying prometheus.yml {} -> {}", prometheus_src.display(), prometheus_dst.display() ) })?; } let kzg_source = repo_root.join("testing-framework/assets/stack/kzgrs_test_params"); let target = temp.path().join("kzgrs_test_params"); if kzg_source.exists() { if kzg_source.is_dir() { copy_dir_recursive(&kzg_source, &target)?; } else { fs::copy(&kzg_source, &target).with_context(|| { format!("copying {} -> {}", kzg_source.display(), target.display()) })?; } } // Fail fast if the KZG bundle is missing or empty; DA verifier will panic // otherwise. if !target.exists() || fs::read_dir(&target) .ok() .map(|mut it| it.next().is_none()) .unwrap_or(true) { anyhow::bail!( "KZG params missing in stack assets (expected files in {})", kzg_source.display() ); } Ok(Self { root: temp }) } #[must_use] /// Root of the temporary workspace on disk. pub fn root_path(&self) -> &Path { self.root.path() } #[must_use] /// Path to the copied assets directory. pub fn stack_dir(&self) -> PathBuf { self.root.path().join("stack") } #[must_use] /// Consume the workspace and return the underlying temp directory. pub fn into_inner(self) -> TempDir { self.root } } fn stack_assets_root(repo_root: &Path) -> PathBuf { let new_layout = repo_root.join("testing-framework/assets/stack"); if new_layout.exists() { new_layout } else { repo_root.join("testnet") } } fn stack_scripts_root(repo_root: &Path) -> PathBuf { let new_layout = repo_root.join("testing-framework/assets/stack/scripts"); if new_layout.exists() { new_layout } else { repo_root.join("testnet/scripts") } } fn copy_dir_recursive(source: &Path, target: &Path) -> Result<()> { fs::create_dir_all(target) .with_context(|| format!("creating target dir {}", target.display()))?; for entry in fs::read_dir(source).with_context(|| format!("reading {}", source.display()))? { let entry = entry?; let file_type = entry.file_type()?; let dest = target.join(entry.file_name()); if file_type.is_dir() { copy_dir_recursive(&entry.path(), &dest)?; } else if !file_type.is_dir() { fs::copy(entry.path(), &dest).with_context(|| { format!("copying {} -> {}", entry.path().display(), dest.display()) })?; } } Ok(()) }