mod put_back;
use std::path::{Path, PathBuf};
use crate::daemon::Daemon;
use crate::paths::{self, Tree};
use crate::roll;
use crate::state::State;
use put_back::{PutBack, put_back, registered_against};
#[derive(Debug)]
pub enum Restored {
Returned {
sheep: String,
to: PathBuf,
},
NeverMoved {
sheep: String,
at: PathBuf,
},
LeftRunning {
sheep: String,
from: PathBuf,
},
Failed {
sheep: String,
why: String,
},
Unlisted {
dir: PathBuf,
why: String,
},
Reset {
sheep: String,
why: String,
},
PartlyDeleted {
sheep: String,
why: String,
},
Lost {
sheep: String,
why: String,
},
RollUnreadable {
sheep: Vec<String>,
why: String,
},
}
pub async fn all<D: Daemon>(daemon: &D, shep_home: &Path) -> Vec<Restored> {
let found = match paths::targets(shep_home) {
Ok(found) => found,
Err(err) => {
return vec![Restored::Unlisted {
dir: shep_home.join("deploy"),
why: err.to_string(),
}];
}
};
let names = found.named;
let registered = roll::registered(daemon).await;
let mut results = Vec::new();
for dir in found.unnamed {
results.push(Restored::Unlisted {
why: "its directory's name cannot be a sheep's, so it was never polled and is not \
restored. A sheep may still be running from inside it: `shep flock` lists \
every sheep and `shep describe <sheep>` its cwd. Rename the directory once \
none does, then restore by hand"
.to_owned(),
dir,
});
}
let mut blocked_by_roll = Vec::new();
for sheep in names {
let tree = Tree::for_sheep(shep_home, &sheep);
let state = match State::read(&tree.state_file()) {
Ok(state) => state,
Err(err) => {
results.push(Restored::Failed {
sheep,
why: err.to_string(),
});
continue;
}
};
let never_moved = state.deployed.is_none();
let origin = state.origin;
let (Some(cwd), Some(script)) = (state.origin_cwd, state.origin_script) else {
results.push(Restored::LeftRunning {
sheep,
from: tree.current(),
});
continue;
};
let Ok(registered) = ®istered else {
blocked_by_roll.push(sheep);
continue;
};
if never_moved && !registered_against(registered, &sheep, &tree) {
results.push(Restored::NeverMoved { sheep, at: cwd });
continue;
}
let to = origin
.as_ref()
.and_then(|origin| origin.cwd.as_deref())
.map_or_else(|| cwd.clone(), PathBuf::from);
results.push(
match put_back(daemon, &sheep, registered, origin.as_ref(), &cwd, &script).await {
PutBack::Done => Restored::Returned { sheep, to },
PutBack::Untouched(err) => Restored::Failed {
sheep,
why: err.to_string(),
},
PutBack::Reset(err) => Restored::Reset {
sheep,
why: err.to_string(),
},
PutBack::PartlyDeleted(err) => Restored::PartlyDeleted {
sheep,
why: err.to_string(),
},
PutBack::Deleted(err) => Restored::Lost {
sheep,
why: err.to_string(),
},
},
);
}
if let Err(err) = ®istered
&& !blocked_by_roll.is_empty()
{
results.push(Restored::RollUnreadable {
sheep: blocked_by_roll,
why: err.to_string(),
});
}
results
}
#[must_use]
pub fn report(results: &[Restored]) -> String {
if results.is_empty() {
return "no deploy targets, nothing to restore\n".to_owned();
}
results
.iter()
.map(|result| match result {
Restored::Returned { sheep, to } => {
format!("{sheep} restored to {}\n", to.display())
}
Restored::NeverMoved { sheep, at } => {
format!(
"{sheep} was never moved - its cutover did not land - and is still running \
from {}\n",
at.display()
)
}
Restored::LeftRunning { sheep, from } => {
format!("{sheep} still running from {}\n", from.display())
}
Restored::Failed { sheep, why } => {
format!("{sheep} could not be restored ({why}); nothing was changed by this dog\n")
}
Restored::Unlisted { dir, why } => format!(
"{} could not be listed ({why}); nothing under it was changed by this dog\n",
crate::shared::printable(dir.display())
),
Restored::Reset { sheep, why } => format!(
"{sheep} could not be restored ({why}), so its previous configuration was put \
back instead - doing that stopped it and started it again, so it is running \
with the same config as before but nothing mid-flight survived\n"
),
Restored::PartlyDeleted { sheep, why } => format!(
"{sheep}: only SOME of its instances could be stopped before a delete failed \
({why}), so it is neither fully running nor fully removed. Run `shep describe \
{sheep}` to see what is actually still there, and `shep flock` for whether it \
is still registered, before assuming either.\n"
),
Restored::Lost { sheep, why } => format!(
"{sheep} IS NO LONGER REGISTERED: restoring it failed ({why}) and so did \
putting its previous configuration back, so it is stopped and gone from the \
flock. It will not come back on its own after a restart. Re-register it from \
its own Flockfile.\n"
),
Restored::RollUnreadable { sheep, why } => format!(
"{}: none of these could be checked against the muster roll, so none of them \
could be restored ({why})\n",
sheep.join(", ")
),
})
.collect()
}
#[cfg(test)]
mod test_support;
#[cfg(test)]
mod tests;