use glob::glob;
use retry::{OperationResult, delay::Fixed, retry_with_index};
use slack_hooked::{AttachmentBuilder, PayloadBuilder, Slack};
use std::{
fs::{self, OpenOptions},
io::{Error, ErrorKind, prelude::*},
path::Path,
};
use crate::*;
#[instrument]
pub fn read_single_check_result(check_path: &str) -> Result<Check, Error> {
read_text_file(check_path).and_then(|file_contents| {
serde_json::from_str(&file_contents)
.map_err(|err| Error::new(ErrorKind::InvalidInput, err.to_string()))
})
}
#[instrument]
pub fn read_single_check(check_path: &str) -> Option<Check> {
let result = read_text_file(check_path).and_then(|file_contents| {
serde_json::from_str(&file_contents)
.map_err(|err| Error::new(ErrorKind::InvalidInput, err.to_string()))
});
match result {
Ok(check) => Some(check),
Err(err) => {
debug!("Error reading Check from path: {check_path}. Cause: {err}");
None
}
}
}
#[instrument]
pub fn warn_for_undefined_notifiers(stories: &[Story]) {
let notifiers = Config::load().notifiers.unwrap_or_default();
let notifier_names = notifiers
.into_iter()
.map(|notifier| notifier.name)
.collect::<Vec<_>>();
let mut undefined = stories
.iter()
.filter(|&elem| !notifier_names.contains(&elem.notifier.clone().unwrap_or_default()))
.cloned()
.filter_map(|elem| elem.notifier)
.collect::<Vec<String>>();
undefined.dedup();
undefined.into_iter().for_each(|notifier| {
warn!(
"Notifier: '{notifier}' is not defined in configuration file. Notifications won't be sent!"
)
});
}
#[instrument]
pub fn notify(webhook: &str, message: &str, icon: &str, fail: bool) {
if webhook.is_empty() {
warn!("Webhook undefined. Notifications will not be sent.");
return;
}
retry_with_index(Fixed::from_millis(1000), |current_try| {
if current_try > 3 {
return OperationResult::Err("Did not succeed within 3 tries");
}
let notification = Slack::new(webhook).and_then(|slack| {
PayloadBuilder::new()
.username(DEFAULT_SLACK_NAME)
.icon_emoji(icon)
.attachments(vec![
if fail {
AttachmentBuilder::new(message)
.color(DEFAULT_SLACK_FAILURE_COLOR)
.build()
.unwrap_or_default()
} else {
AttachmentBuilder::new(message)
.color(DEFAULT_SLACK_SUCCESS_COLOR)
.build()
.unwrap_or_default()
},
])
.build()
.and_then(|payload| {
debug!("Sending notification with payload: {payload:?}");
slack.send(&payload)
})
});
match notification {
Ok(_) => OperationResult::Ok("Sent!"),
Err(_) => OperationResult::Retry("Failed to send notification!"),
}
})
.map_err(|err| {
error!("Error sending notification: {err}");
err
})
.unwrap_or_default();
}
#[instrument]
pub fn notify_success(webhook: &str, message: &str) {
let success_emoji = Config::load()
.success_emoji
.unwrap_or_else(|| String::from(DEFAULT_SLACK_SUCCESS_ICON));
notify(webhook, message, &success_emoji, false)
}
#[instrument]
pub fn notify_failure(webhook: &str, message: &str) {
let failure_emoji = Config::load()
.failure_emoji
.unwrap_or_else(|| String::from(DEFAULT_SLACK_FAILURE_ICON));
notify(webhook, message, &failure_emoji, true)
}
#[instrument]
pub fn produce_list_absolute(glob_pattern: &str) -> Vec<String> {
let mut list = vec![];
for entry in glob(glob_pattern).unwrap() {
match entry {
Ok(path) => {
if let Some(element) = path.to_str() {
list.push(element.to_string())
}
}
Err(err) => {
error!("Error: produce_list(): {err}");
}
}
}
debug!("{glob_pattern}");
trace!("{list:?}");
list
}
#[instrument]
pub fn list_all_checks_from(checks_dir: &str) -> Vec<String> {
let krecik_root_dir = Config::load().krecik_root.unwrap_or_default();
let glob_pattern = if !Path::new(&krecik_root_dir).exists() {
if !krecik_root_dir.is_empty() {
warn!("Krecik root directory doesn't exists: {krecik_root_dir}!");
} else {
info!("Krecik root directory wasn't specified, using current working directory.");
}
format!("{}/**/*.json", checks_dir)
} else {
format!("{}/{}/**/*.json", krecik_root_dir, checks_dir)
};
produce_list_absolute(&glob_pattern)
}
#[instrument]
pub fn read_text_file(name: &str) -> Result<String, Error> {
fs::read_to_string(name)
}
#[instrument(skip(contents))]
pub fn write_append(file_path: &str, contents: &str) {
if !contents.is_empty() {
match OpenOptions::new().create(true).append(true).open(file_path) {
Ok(mut file) => {
file.write_all(contents.as_bytes())
.expect("Access denied? File can't be written: {file_path}");
debug!("Atomically written data to file: {file_path}");
trace!("Written data: {contents}");
}
Err(err) => {
error!("Atomic write to: {file_path} has failed! Cause: {err}")
}
}
}
}
#[instrument]
pub fn file_name_from_path(path: &str) -> String {
let path = Path::new(path);
path.file_name()
.unwrap_or_default()
.to_os_string()
.into_string()
.unwrap_or_default()
}