use std::path::{Path, PathBuf};
use crate::error::Error;
use crate::executor::Cx;
pub(super) const COPY: &str = "module";
const TERRAFORM_FILES: [&str; 4] = [".tf", ".tf.json", ".tfvars", ".tfvars.json"];
pub(super) fn run(cx: &Cx<'_>, step: &str, from: Option<&str>, when: Option<&str>) -> crate::Result<()> {
let refuse = |what: String| Error::Unsupported {
what: format!("step `{step}`: {what}"),
};
let from = from.ok_or_else(|| refuse("a `terraform.copy_module` without its `from`".into()))?;
let only_missing = match when {
None => false,
Some("missing") => true,
Some(other) => return Err(refuse(format!("`when: {other}` (only `missing`)"))),
};
let source = inside_project(cx.run.dir(), from).ok_or_else(|| refuse(format!("the module `{from}` isn't a folder inside the run's project")))?;
let copy = cx.run.runner.store.run_dir(cx.run.name())?.join(COPY);
if only_missing && copy.is_dir() {
return Ok(());
}
crate::store::private_dir(©).map_err(Error::io(©, "created"))?;
forget_terraform_files(©)?;
copy_tree(&source, ©)
}
pub(super) fn inside_project(dir: &Path, from: &str) -> Option<PathBuf> {
let project = std::fs::canonicalize(crate::container::lab_root(dir)).ok()?;
let source = std::fs::canonicalize(dir.join(from)).ok()?;
(source.starts_with(&project) && source.is_dir()).then_some(source)
}
pub(super) fn has_terraform_files(dir: &Path) -> bool {
std::fs::read_dir(dir).is_ok_and(|d| d.flatten().any(|e| is_terraform_file(&e.path())))
}
fn is_terraform_file(path: &Path) -> bool {
let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
path.is_file() && TERRAFORM_FILES.iter().any(|ext| name.ends_with(ext))
}
fn forget_terraform_files(dir: &Path) -> crate::Result<()> {
let entries = std::fs::read_dir(dir).map_err(Error::io(dir, "read"))?;
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() && entry.file_name() != ".terraform" {
forget_terraform_files(&path)?;
} else if is_terraform_file(&path) {
std::fs::remove_file(&path).map_err(Error::io(&path, "removed"))?;
}
}
Ok(())
}
fn left_out(name: &str) -> bool {
name == ".terraform" || name.contains(".tfstate")
}
fn copy_tree(from: &Path, to: &Path) -> crate::Result<()> {
let entries = std::fs::read_dir(from).map_err(Error::io(from, "read"))?;
for entry in entries.flatten() {
let name = entry.file_name();
if left_out(&name.to_string_lossy()) {
continue;
}
let (source, target) = (entry.path(), to.join(&name));
if source.is_dir() {
crate::store::private_dir(&target).map_err(Error::io(&target, "created"))?;
copy_tree(&source, &target)?;
} else {
std::fs::copy(&source, &target).map_err(Error::io(&target, "written"))?;
}
}
Ok(())
}
#[cfg(test)]
mod tests;