feature: allow specifying short names for repos

This commit is contained in:
Armaël Guéneau 2024-04-10 19:39:50 +02:00
parent e6917b13fb
commit 7a4818c60a

View file

@ -39,6 +39,7 @@ fn default_alert_duration() -> u64 {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct RepoConfig { struct RepoConfig {
name: Option<String>,
email: String, email: String,
// all durations below are measured in days // all durations below are measured in days
inactivity: u64, inactivity: u64,
@ -72,6 +73,13 @@ impl fmt::Display for AlertStatus {
} }
} }
fn repo_name(repo: &str, name: &Option<String>) -> String {
match name {
None => repo.to_owned(),
Some(short_name) => format!("{} ({})", short_name, repo),
}
}
fn is_alert_needed( fn is_alert_needed(
cfg: &RepoConfig, cfg: &RepoConfig,
inactive_for: Duration, inactive_for: Duration,
@ -86,15 +94,26 @@ fn is_alert_needed(
} }
} }
async fn send_email(email: &str, repo: &str, inactive: Duration) -> eyre::Result<()> { async fn send_email(
name: &Option<String>,
email: &str,
repo: &str,
inactive: Duration,
) -> eyre::Result<()> {
use lettre::{AsyncSendmailTransport, AsyncTransport, Message}; use lettre::{AsyncSendmailTransport, AsyncTransport, Message};
let email = Message::builder() let email = Message::builder()
.from("infracoll <root@infracoll>".parse().unwrap()) .from("infracoll <root@infracoll>".parse().unwrap())
.to(email.parse()?) .to(email.parse()?)
.subject(format!("restic-alarm: inactive repository {}", repo)) .subject(format!(
"restic-alarm: inactive repository {}",
match name {
None => repo,
Some(name) => name,
}
))
.body(format!( .body(format!(
"Alert: Repository {} has been inactive for {} days.\n", "Alert: Repository {} has been inactive for {} days.\n",
repo, repo_name(repo, name),
inactive.as_secs() / (3600 * 24) inactive.as_secs() / (3600 * 24)
)) ))
.unwrap(); .unwrap();
@ -217,33 +236,42 @@ async fn repo_last_snapshot(client: &s3::Client, repo: &str) -> eyre::Result<Opt
} }
} }
struct RepoInfo {
name: Option<String>,
alert_status: Option<AlertStatus>,
}
// this function can fail for reasons that depend on the user-provided 'repo' config // this function can fail for reasons that depend on the user-provided 'repo' config
// (e.g. if it fails to parse). // (e.g. if it fails to parse).
// So the error must not be propagated to the toplevel, which would abort the // So the error must not be propagated to the toplevel, which would abort the
// alert for remaining repositories; it should instead just be reported/logged. // alert for remaining repositories; it should instead just be reported/logged.
async fn check_repo( async fn check_repo(client: &s3::Client, state: &mut State, repo: &str) -> eyre::Result<RepoInfo> {
client: &s3::Client,
state: &mut State,
repo: &str,
) -> eyre::Result<Option<AlertStatus>> {
let config = read_repo_config(client, repo).await?; let config = read_repo_config(client, repo).await?;
if let Some(inactivity) = repo_last_snapshot(client, repo).await? { let alert_status = if let Some(inactivity) = repo_last_snapshot(client, repo).await? {
let last_alert = state.last_alert(repo); let last_alert = state.last_alert(repo);
let alert = is_alert_needed(&config, inactivity, last_alert); let alert = is_alert_needed(&config, inactivity, last_alert);
if alert { if alert {
println!("Sending alert to {} about bucket {}", config.email, repo); println!(
send_email(&config.email, repo, inactivity).await?; "Sending alert to {} about repo {}",
config.email,
repo_name(repo, &config.name)
);
send_email(&config.name, &config.email, repo, inactivity).await?;
state.update_last_alert(repo); state.update_last_alert(repo);
write_state(client, state).await?; write_state(client, state).await?;
} }
Ok(Some(AlertStatus { Some(AlertStatus {
alert, alert,
inactivity, inactivity,
last_alert, last_alert,
})) })
} else { } else {
Ok(None) None
} };
Ok(RepoInfo {
name: config.name.clone(),
alert_status,
})
} }
#[::tokio::main] #[::tokio::main]
@ -260,15 +288,18 @@ async fn main() -> eyre::Result<()> {
for repo in repos { for repo in repos {
match check_repo(&client, &mut state, &repo).await { match check_repo(&client, &mut state, &repo).await {
Ok(None) => { Ok(RepoInfo {
println!("{}: no snapshot, skipping", &repo) name,
} alert_status: None,
Ok(Some(status)) => { }) => println!("{}: no snapshot, skipping", repo_name(&repo, &name)),
println!("{}: {}", &repo, status); Ok(RepoInfo {
} name,
Err(err) => { alert_status: Some(status),
// is this the best way to log the error? }) => println!("{}: {}", repo_name(&repo, &name), status),
println!("Error ({}): {:?}", &repo, err) Err(err) =>
// is this the best way to log the error?
{
println!("{}: ERROR: {:?}", &repo, err)
} }
} }
} }