use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context as _};
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
use reedline_repl_rs::Repl;
use s_macro::s;
use wildland_cargo_lib::api::container::{CargoContainer, MountState};
use wildland_cargo_lib::api::CargoLib;
use wildland_corex::dfs::interface::SpaceUsage;
use wildland_corex::StorageTemplate;
use wildland_storage_backend::lfs::template::LocalFilesystemStorageTemplate;
use crate::{match_context, Context};
#[tracing::instrument(level = "trace", err(Debug), skip(cargo))]
pub fn aux_load_template_file(cargo: &CargoLib, file: &Path) -> anyhow::Result<StorageTemplate> {
if !file.exists() || !file.is_file() {
return Err(anyhow::anyhow!(
"File {} does not exist or is not a file",
file.to_str().unwrap()
));
}
let template_payload = std::fs::read_to_string(file)?.into_bytes();
cargo
.storage_template_from_json(template_payload)
.map_err(Into::into)
}
#[tracing::instrument(skip(container), ret)]
pub fn aux_container_info(container: &CargoContainer) -> String {
let mut out = format!(
r#"
container info:
name: {}
uuid: {}
path: {}
storages:
"#,
container.name().unwrap_or(s!("?no-name?")),
container.uuid(),
container.get_path().unwrap_or_default()
);
if let Ok(storages) = container.clone().get_storages() {
for s in storages {
let data = String::from_utf8(s.data()).unwrap_or("null".into());
let space_usage = s.get_space_usage().unwrap_or(SpaceUsage::new(0u64, 0u64));
let template_uuid = match s.template_uuid() {
Some(template_uuid) => template_uuid.to_string(),
None => s!("None"),
};
let storage_uuid = s.uuid().to_string();
let backend_type = s.backend_type();
let name = s.name().unwrap_or("error".into());
out.push_str(&format!(
"
\t >> used_space: {space_usage}
\t >> template uuid: {template_uuid}
\t >> storage uuid: {storage_uuid}
\t >> backend type: {backend_type}
\t >> name: {name}
\t >> data: {data}
"
));
}
}
out
}
#[tracing::instrument(level = "trace", err(Debug), err(Debug), skip(_args, context))]
fn h_ct_list(_args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
let containers = cargo_user
.find_containers(None, MountState::MountedOrUnmounted)
.context(anyhow!("failed to list containers"))?;
let containers_len = containers.len();
let container_list = containers.iter().fold(String::new(), |mut acc, c| {
acc += &aux_container_info(c);
acc
});
let output = format!(
r#"
Containers ({containers_len} found):
{container_list}
"#
);
Ok(Some(output))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_create_from_file(
args: ArgMatches,
context: &mut Context,
) -> anyhow::Result<Option<String>> {
let (cargo, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
let path = args
.get_one::<String>("claim")
.context(anyhow!("claim path not provided"))?;
let name = args
.get_one::<String>("name")
.context(anyhow!("name not provided"))?;
let template_file = args
.get_one::<String>("template_file")
.context(anyhow!("template filename not provided"))?;
let is_encrypted = args
.get_one::<bool>("encrypted")
.unwrap_or(&false)
.to_owned();
let templatefile = PathBuf::from(template_file);
let cargo_template = aux_load_template_file(cargo, &templatefile)?;
let cthandle =
cargo_user.create_container(s!(name), &cargo_template, s!(path), is_encrypted)?;
Ok(Some(aux_container_info(&cthandle)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_create_lfs(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
let path = args
.get_one::<String>("claim")
.context("path not provided")?;
let name = args
.get_one::<String>("name")
.context("name not provided")?;
let local_path = args
.get_one::<String>("path")
.context("where locally to bound contaiener to the FS")?;
let is_encrypted = args
.get_one::<bool>("encrypted")
.unwrap_or(&false)
.to_owned();
let local_path = PathBuf::from(local_path);
if !local_path.exists() || !local_path.is_dir() {
return Err(anyhow!("local directory does not seems to exist"));
}
let cargo_template = LocalFilesystemStorageTemplate::new(local_path.clone());
let cargo_template = StorageTemplate::try_from(cargo_template)?;
let cthandle =
cargo_user.create_container(s!(name), &cargo_template, s!(path), is_encrypted)?;
let uuid = cthandle.uuid();
let local_path_full = local_path.join(name).join(uuid.to_string());
std::fs::create_dir_all(local_path_full).context("container created but i failed to create local directory. File oprations referencing this container may fail")?;
Ok(Some(aux_container_info(&cthandle)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_mount(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
let name_or_uuid = args
.get_one::<String>("name-or-uuid")
.context("path/uuid not provided")?;
let containers = cargo_user
.find_containers(None, MountState::Unmounted)
.context(anyhow!("failed to find eligible containers"))?;
let container = if let Ok(uuid) = uuid::Uuid::parse_str(name_or_uuid) {
tracing::debug!("trying to find container by uuid");
println!("trying to find container by uuid");
containers
.iter()
.find(|c| c.uuid() == uuid)
.context("container not found or is already mounted")?
} else {
tracing::debug!("failed to parse param as an uuid, trying to find container by name");
println!("failed to parse param as an uuid, trying to find container by name");
containers
.iter()
.find(|c| c.name() == Ok(name_or_uuid.clone()))
.context("container not found or is already mounted")?
};
let container_info = aux_container_info(container);
container
.mount(None)
.context(s!("failed to mount container: {}", container_info))?;
Ok(Some(s!("mounted container: {}", container_info)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_remove(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
let name_or_uuid = args
.get_one::<String>("name-or-uuid")
.context("path/uuid not provided")?;
let containers = cargo_user
.find_containers(None, MountState::Unmounted)
.context(anyhow!("failed to find eligible containers"))?;
let container = if let Ok(uuid) = uuid::Uuid::parse_str(name_or_uuid) {
tracing::debug!("trying to find container by uuid");
containers
.iter()
.find(|c| c.uuid() == uuid)
.context("container not found or is mounted")?
} else {
println!("failed to parse param as an uuid, trying to find container by name");
containers
.iter()
.find(|c| c.name() == Ok(name_or_uuid.clone()))
.context("container not found or is mounted")?
};
let container_info = aux_container_info(container);
container
.remove()
.context(s!("failed to remove container: {}", container_info))?;
Ok(Some(s!("removed container {}", container_info)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_unmount(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
let name_or_uuid = args
.get_one::<String>("name-or-uuid")
.context("path/uuid not provided")?;
let containers = cargo_user
.find_containers(None, MountState::Mounted)
.context(anyhow!("failed to find eligible containers"))?;
let container = if let Ok(uuid) = uuid::Uuid::parse_str(name_or_uuid) {
tracing::debug!("trying to find container by uuid");
println!("trying to find container by uuid");
containers
.iter()
.find(|c| c.uuid() == uuid)
.context("container not found or is already mounted")?
} else {
tracing::debug!("failed to parse param as an uuid, trying to find container by name");
println!("failed to parse param as an uuid, trying to find container by name");
containers
.iter()
.find(|c| c.name() == Ok(name_or_uuid.clone()))
.context("container not found or is already mounted")?
};
let container_info = aux_container_info(container);
container
.unmount(None)
.context(s!("failed to unmount container: {}", container_info))?;
Ok(Some(s!("unmount container: {}", container_info)))
}
pub fn extend(repl: Repl<Context, anyhow::Error>) -> Repl<Context, anyhow::Error> {
repl.with_command(
Command::new("ct-new-file")
.about("container->create new from file template")
.arg(Arg::new("name").required(true).index(1))
.arg(Arg::new("claim").required(true).index(2))
.arg(Arg::new("template_file").required(true).index(3))
.arg(
Arg::new("encrypted")
.short('e')
.help("encrypt storage")
.action(clap::ArgAction::SetTrue),
),
h_ct_create_from_file,
)
.with_command(
Command::new("ct-new-lfs")
.about("container->create new local FS storage")
.arg(Arg::new("name").required(true).index(1))
.arg(Arg::new("claim").required(true).index(2))
.arg(Arg::new("path").required(true).index(3))
.arg(
Arg::new("encrypted")
.short('e')
.help("encrypt storage")
.action(clap::ArgAction::SetTrue),
),
h_ct_create_lfs,
)
.with_command(
Command::new("ct-mount")
.about("container->mount by name or uuid")
.arg(Arg::new("name-or-uuid").required(true).index(1)),
h_ct_mount,
)
.with_command(
Command::new("ct-unmount")
.about("container->unmount")
.arg(Arg::new("name-or-uuid").required(true).index(1)),
h_ct_unmount,
)
.with_command(
Command::new("ct-list").about("container->list containers"),
h_ct_list,
)
.with_command(
Command::new("ct-remove")
.about("container->remove containers")
.arg(Arg::new("name-or-uuid").required(true).index(1)),
h_ct_remove,
)
}