use std::path::{Path, PathBuf};
use std::time::SystemTime;
use anyhow::{Context, Result, anyhow};
use pgevolve_core::plan::read_plan_dir;
use crate::cluster_config::ClusterConfig;
use crate::executor::{ApplyOverrides, apply_cluster_plan};
pub async fn run(project_root: &Path, cfg: &ClusterConfig, plan_id: Option<&str>) -> Result<i32> {
let plan_dir = match plan_id {
Some(id) => project_root.join("cluster-plans").join(id),
None => find_latest_plan_dir(&project_root.join("cluster-plans"))
.context("looking for the latest cluster plan")?,
};
eprintln!("Applying {}", plan_dir.display());
let plan = read_plan_dir(&plan_dir)
.with_context(|| format!("reading plan from {}", plan_dir.display()))?;
let (mut client, connection) =
tokio_postgres::connect(&cfg.connection.dsn, tokio_postgres::NoTls)
.await
.context("connecting to cluster for apply")?;
tokio::spawn(async move {
if let Err(err) = connection.await {
tracing::debug!(?err, "cluster apply connection ended");
}
});
let overrides = ApplyOverrides::default();
apply_cluster_plan(&plan, &mut client, overrides)
.await
.map_err(|e| anyhow!("{e}"))?;
eprintln!("Done.");
Ok(0)
}
fn find_latest_plan_dir(plans_root: &Path) -> Result<PathBuf> {
if !plans_root.exists() {
return Err(anyhow!(
"no cluster-plans directory found at {}",
plans_root.display()
));
}
let mut latest: Option<(SystemTime, PathBuf)> = None;
for entry in std::fs::read_dir(plans_root)
.with_context(|| format!("reading {}", plans_root.display()))?
{
let entry = entry.with_context(|| format!("iterating {}", plans_root.display()))?;
if entry
.file_type()
.with_context(|| format!("stat {}", entry.path().display()))?
.is_dir()
{
let mtime = entry
.metadata()
.with_context(|| format!("metadata {}", entry.path().display()))?
.modified()
.with_context(|| format!("mtime {}", entry.path().display()))?;
if latest.as_ref().is_none_or(|(t, _)| mtime > *t) {
latest = Some((mtime, entry.path()));
}
}
}
latest
.map(|(_, p)| p)
.ok_or_else(|| anyhow!("no cluster plans found in {}", plans_root.display()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_latest_errors_when_no_dir() {
let dir = tempfile::tempdir().unwrap();
let err = find_latest_plan_dir(&dir.path().join("no-such-dir")).unwrap_err();
assert!(err.to_string().contains("no cluster-plans directory"));
}
#[test]
fn find_latest_errors_when_empty() {
let dir = tempfile::tempdir().unwrap();
let err = find_latest_plan_dir(dir.path()).unwrap_err();
assert!(err.to_string().contains("no cluster plans found"));
}
#[test]
fn find_latest_returns_most_recent() {
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir(dir.path().join("aaaaaaaa")).unwrap();
std::fs::create_dir(dir.path().join("bbbbbbbb")).unwrap();
let p = find_latest_plan_dir(dir.path()).unwrap();
let name = p.file_name().unwrap().to_str().unwrap();
assert!(name == "aaaaaaaa" || name == "bbbbbbbb");
}
}