use std::path::Path;
use anyhow::{anyhow, Context as _};
use clap::Parser;
use reedline_repl_rs::Repl;
use s_macro::s;
use crate::context::Context;
use crate::match_context;
use crate::plugins::cargo::containers::aux_load_template_file;
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct CliArguments {
#[arg(short = 'l', long)]
with_lss: Option<String>,
#[arg(short = 'r', long)]
with_sfo: Option<String>,
#[arg(short = 'c', long)]
with_cfg: Option<String>,
#[arg(short = 'u', long)]
with_user: Option<String>,
#[arg(short = 't', long)]
with_template_path: Option<String>,
#[arg(short = 'n', long)]
with_container_name: Option<String>,
#[arg(short = 'm', long)]
with_container_path: Option<String>,
#[arg(short = 's', long = "script", help = "script to run")]
script: Option<String>,
#[arg(
short = 'p',
long = "piped",
help = "accept piped input",
default_value = "false"
)]
piped: bool,
#[arg(
short = 'i',
long = "interact",
help = "follow with interactive mode after script",
default_value = "false"
)]
interact: bool,
}
impl CliArguments {
pub fn check_containers_eligible(&self) -> anyhow::Result<(String, String, String)> {
let template_path = self
.with_template_path
.as_ref()
.context("template path is not provided")?;
let container_name = self
.with_container_name
.as_ref()
.context("container name is not provided")?;
let container_path = self
.with_container_path
.as_ref()
.context("container path is not provided")?;
Ok((
template_path.to_string(),
container_name.to_string(),
container_path.to_string(),
))
}
pub fn check_io_eligible(&self) -> anyhow::Result<()> {
if self.script.is_some() && self.piped {
return Err(anyhow::anyhow!("script and piped cannot be used together"));
}
if let Some(script) = self.script.as_ref() {
let path = std::path::PathBuf::from(script);
if !path.exists() {
return Err(anyhow::anyhow!("script file does not exist"));
};
if !path.is_file() {
return Err(anyhow::anyhow!("path does not point to a file"));
};
}
if self.interact && self.piped {
eprintln!("`interact` option does not make sense if `piped` is used");
eprintln!("`interact` drops devshell to interactive mode after script is executed");
eprintln!("in case of pipes we do not have possibility to drop to interactive mode");
}
Ok(())
}
pub fn process_io(&self, repl: &mut Repl<Context, anyhow::Error>) {
let result = if self.piped {
Self::process_pipe(repl)
} else if let Some(script) = self.script.as_ref() {
Self::process_script(repl, script)
} else {
return;
};
match result {
Ok(_) => (),
Err(e) => {
if !self.interact {
eprintln!("{}", e);
tracing::error!("{}", e);
std::process::exit(1);
}
}
}
}
fn process_pipe(repl: &mut Repl<Context, anyhow::Error>) -> anyhow::Result<String> {
let stdin = std::io::stdin();
let bufreader = std::io::BufReader::new(stdin);
match repl.run_with_reader(bufreader) {
Ok(_) => Ok(s!("Pipe processing finished")),
Err(_) => Err(anyhow!("Error occurred while processing piped input")),
}
}
fn process_script(
repl: &mut Repl<Context, anyhow::Error>,
script: &String,
) -> anyhow::Result<String> {
let file = std::fs::File::open(script)?;
let script_reader = std::io::BufReader::new(file);
match repl.run_with_reader(script_reader) {
Ok(_) => Ok(s!("Script processing finished")),
Err(_) => Err(anyhow!("Error occurred during script processing")),
}
}
pub fn uses_script(&self) -> bool {
self.script.is_some()
}
pub fn uses_pipe(&self) -> bool {
self.piped
}
pub fn is_interactive(&self) -> bool {
self.interact
}
}
#[tracing::instrument(level = "trace", err(Debug), skip(ctx))]
pub fn try_with_sfo(sfo_path: &String, ctx: &mut Context) -> anyhow::Result<String> {
ctx.init_sfo(sfo_path)?;
Ok(s!("new sfo is set correctly"))
}
#[tracing::instrument(level = "trace", err(Debug), skip(ctx))]
pub fn try_with_lss(lss_path: &String, ctx: &mut Context) -> anyhow::Result<String> {
ctx.set_lss_from_path(lss_path)?;
Ok(s!("new lss is set correctly"))
}
#[tracing::instrument(level = "trace", err(Debug), skip(ctx))]
pub fn try_with_cfg(cfg_path: &str, ctx: &mut Context) -> anyhow::Result<String> {
ctx.set_cargo_config_from_path(cfg_path)?;
Ok(s!("new lss is set correctly"))
}
#[tracing::instrument(level = "trace", err(Debug), skip(ctx))]
pub fn try_with_cargo(ctx: &mut Context) -> anyhow::Result<String> {
let (cargo_cfg, lss, _sfo) =
match_context!(ctx, cargo_cfg => Some(_), lss => Some(_), sfo => Some(_))?;
let local_fsacfg = cargo_cfg.fsa_config.clone();
let catlib_cfg = cargo_cfg.catlib_config.clone();
let multidevice_state = cargo_cfg.multidevice_state.clone();
let cargo = wildland_cargo_lib::api::CargoLib::new(
lss.boxed(),
local_fsacfg,
catlib_cfg,
multidevice_state,
)
.unwrap();
ctx.set_cargo(cargo)?;
Ok(s!("cargo created successfully"))
}
pub fn try_with_user(devicename: &String, ctx: &mut Context) -> anyhow::Result<String> {
match_context!(ctx, cargo => Some(_))?;
if ctx.set_cargo_user().is_ok() {
return Ok(s!("cargo user recovered correctly"));
} else {
let userapi = ctx.cargo.as_ref().unwrap().user_api();
let mnemonic_payload = userapi
.generate_mnemonic()
.context("Mnemonic generation error")?;
userapi
.create_user_from_mnemonic(&mnemonic_payload, s!(devicename))
.context("User creation error")?;
ctx.set_cargo_user()?;
}
Ok(s!("user created successfully"))
}
pub fn try_user_recovery(ctx: &mut Context) -> anyhow::Result<String> {
match_context!(ctx, cargo => Some(_))?;
if ctx.set_cargo_user().is_ok() {
return Ok(s!("cargo user recovered correctly"));
}
Err(anyhow::anyhow!("user recovery failed"))
}
#[tracing::instrument(level = "trace", err(Debug), skip(ctx))]
pub fn try_with_template(
name: String,
template_path: String,
claim_path: String,
ctx: &mut Context,
) -> anyhow::Result<String> {
let (cargo, cargo_user) = match_context!(ctx, cargo => Some(_), cargo_user => Some(_))?;
let cargo_template = aux_load_template_file(cargo, Path::new(&template_path))?;
cargo_user.create_container(name, &cargo_template, claim_path, false)?;
Ok(s!("new container added"))
}
#[tracing::instrument(level = "trace")]
fn display_configuration_status(cfg_vec: Vec<String>) -> anyhow::Result<String> {
println!("Configuration status:");
if cfg_vec.is_empty() {
anyhow::bail!("No cli args config was performed.");
}
for cfg in cfg_vec {
println!("{cfg}");
}
Ok(s!("all provided args are set correctly"))
}
#[tracing::instrument(level = "trace", err(Debug), skip(ctx, args))]
pub fn act_on_args(args: &CliArguments, ctx: &mut Context) -> anyhow::Result<String> {
let mut configured: Vec<String> = vec![];
if let Some(lss_path) = &args.with_lss {
_ = try_with_lss(lss_path, ctx);
configured.push(s!(" - [ok] lss"));
} else {
configured.push(s!(" - [xx] lss"));
};
if let Some(sfo_path) = &args.with_sfo {
_ = try_with_sfo(sfo_path, ctx);
configured.push(s!(" - [ok] sfo"));
} else {
configured.push(s!(" - [xx] sfo"));
};
if let Some(cfg_path) = &args.with_cfg {
_ = try_with_cfg(cfg_path, ctx);
configured.push(s!(" - [ok] cfg"));
} else {
configured.push(s!(" - [xx] cfg"));
};
if let Err(e) = try_with_cargo(ctx) {
tracing::error!("setting cargo failed because: {:?}", e);
configured.push(s!(" - [xx] cargo"));
} else {
configured.push(s!(" - [ok] cargo"));
};
if let Some(devicename) = &args.with_user {
if try_with_user(devicename, ctx).is_ok() {
configured.push(s!(" - [ok] user"));
} else {
configured.push(s!(" - [xx] user"));
}
} else if try_user_recovery(ctx).is_ok() {
configured.push(s!(" - [ok] user"));
} else {
configured.push(s!(" - [xx] user"));
};
if let Ok((path, name, claim)) = args.check_containers_eligible() {
if let Err(e) = try_with_template(name, path, claim, ctx) {
tracing::error!("setting template failed because: {:?}", e);
configured.push(s!(" - [xx] container"));
} else {
configured.push(s!(" - [ok] container"));
}
};
display_configuration_status(configured)
}