use colored::Colorize;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use crate::actions::DefinedAction;
use crate::cmd::{CatProjectArgs, CleanArgs};
use crate::entities::info::ShortName;
use crate::entities::runs::Runs;
use crate::entities::variables::Variable;
use crate::globals::DeployerGlobalConfig;
use crate::pipelines::DescribedPipeline;
use crate::{ARTIFACTS_DIR, CACHE_DIR, bmap};
const CURRENT_PROJECT_CONF_VERSION: u8 = 8;
pub fn get_default_project_conf_version() -> u8 {
CURRENT_PROJECT_CONF_VERSION
}
#[derive(Deserialize, Serialize, PartialEq, Clone)]
pub struct DeployerProjectOptions {
pub project_name: String,
#[serde(default = "get_default_project_conf_version")]
pub version: u8,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub ignore_files: BTreeSet<PathBuf>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub cache_files: BTreeSet<PathBuf>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub variables: BTreeMap<ShortName, Variable>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub actions: BTreeSet<DefinedAction>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub pipelines: Vec<DescribedPipeline>,
}
impl Default for DeployerProjectOptions {
fn default() -> Self {
Self {
project_name: String::new(),
ignore_files: Default::default(),
cache_files: Default::default(),
actions: Default::default(),
pipelines: Default::default(),
variables: Default::default(),
version: CURRENT_PROJECT_CONF_VERSION,
}
}
}
impl DeployerProjectOptions {
pub fn variables_for(&self, with: &BTreeMap<String, ShortName>) -> anyhow::Result<BTreeMap<String, Variable>> {
let vars = {
let mut replaced: BTreeMap<String, Variable> = bmap!();
for (placeholder, short_name) in with.iter() {
let variable = self
.variables
.iter()
.find(|(k, _)| k.as_str().eq(short_name.as_str()))
.ok_or(anyhow::anyhow!(
"Can't find requested variable `{}`!",
short_name.as_str()
))?
.1;
replaced.insert(placeholder.to_string(), variable.to_owned());
}
replaced
};
Ok(vars)
}
}
pub fn init_project(globals: &mut DeployerGlobalConfig, config: &mut DeployerProjectOptions) -> anyhow::Result<()> {
let curr_dir = std::env::current_dir()
.expect("Can't get current dir!")
.to_str()
.expect("Can't convert current dir's path to string!")
.to_owned();
if !globals.projects.contains(&curr_dir) {
globals.projects.push(curr_dir.to_owned());
}
config.init_from_prompt(curr_dir)?;
if !std::fs::exists(".depl")? && *config != Default::default() {
std::fs::create_dir(".depl")?;
}
println!("Setup is completed. Don't forget to assign at least one pipeline to the project to run!");
Ok(())
}
pub fn cat_project(config: &DeployerProjectOptions, args: CatProjectArgs) -> anyhow::Result<()> {
if args.full {
let project_ser = serde_pretty_yaml::to_string_pretty(&config)?;
println!("{project_ser}");
} else if args.cat_all_shell_commands {
for pipeline in config.pipelines.as_slice() {
let cmds = pipeline.return_all_cmds(&config.actions)?;
println!("Pipeline `{}`:", pipeline.title.blue().italic());
for cmd in cmds {
println!(">>> {}", cmd.green());
}
}
} else {
for pipeline in config.pipelines.as_slice() {
println!("{}", pipeline.title);
}
}
Ok(())
}
pub async fn edit_project(
globals: &mut DeployerGlobalConfig,
config: &mut DeployerProjectOptions,
) -> anyhow::Result<()> {
if *config == Default::default() {
panic!("Config is invalid! Reinit the project.");
}
config.edit_from_prompt(globals).await?;
if !std::fs::exists(".depl")? && *config != Default::default() {
std::fs::create_dir(".depl")?;
}
Ok(())
}
pub fn clean_runs(
config: &DeployerProjectOptions,
runs: &mut Runs,
cache_dir: &Path,
args: &CleanArgs,
) -> anyhow::Result<()> {
use fs_extra::dir::get_size;
let mut path = PathBuf::new();
path.push(cache_dir);
path.push(CACHE_DIR);
let mut total: u64 = 0;
if let Some(project_builds) = runs
.projects
.iter_mut()
.find(|p| p.name.as_str().eq(config.project_name.as_str()))
{
if !args.preserve_least {
for folder in project_builds.runs.iter().map(|b| b.folder.clone()) {
total += get_size(&folder)?;
let _ = std::fs::remove_dir_all(folder);
}
project_builds.runs.clear();
} else {
let mut hm = bmap!();
for folder in project_builds.runs.iter().cloned() {
hm.insert(folder.exclusive_tag.clone().unwrap_or(String::new()), folder);
}
for folder in project_builds
.runs
.iter()
.filter(|b| !hm.contains_key(&b.exclusive_tag.clone().unwrap_or_default()))
.map(|b| b.folder.clone())
{
total += get_size(&folder)?;
let _ = std::fs::remove_dir_all(folder);
}
project_builds.runs.clear();
for folder in hm.values() {
project_builds.runs.push(folder.clone());
}
}
}
if args.include_artifacts {
let curr_dir = std::env::current_dir()?;
let artifacts_dir = curr_dir.join(ARTIFACTS_DIR);
if artifacts_dir.as_path().exists() {
total += get_size(&artifacts_dir)?;
let _ = std::fs::remove_dir_all(artifacts_dir);
}
}
println!("Cleaned: {}", format_size(total));
Ok(())
}
fn format_size(size: u64) -> String {
const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
let mut size = size as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size as u64, UNITS[unit_index])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}