use std::path::{Path, PathBuf};
use std::{fs, path};
use crate::cli::logs;
use crate::hooks::executor::HookExecutor;
use crate::models::cli::StoreCommands;
use crate::models::configuration::Configuration;
use crate::{one_time_passwords, recipients, secrets};
use anyhow::Context;
pub fn dispatch(
command: &StoreCommands,
configuration: Configuration,
offline: bool,
) -> anyhow::Result<()> {
match command {
StoreCommands::Add(args) => {
add(configuration, args.path.as_path(), &args.name, args.default)
}
StoreCommands::Decrypt(args) => decrypt(
&configuration,
args.store_selection.store.as_ref(),
args.store_path.as_ref(),
offline,
),
StoreCommands::List(_) => {
list(&configuration);
Ok(())
}
StoreCommands::Merge(args) => merge(
&configuration,
args.store_selection.store.as_ref(),
&args.common_ancestor,
&args.current_version,
&args.other_version,
),
StoreCommands::Remove(args) => remove(configuration, args.store.as_ref(), args.remove_data),
StoreCommands::SetDefault(args) => set_default(configuration, &args.name),
StoreCommands::Exec(args) => exec(
&configuration,
args.store_selection.store.as_ref(),
&args.command,
),
}
}
fn add(
mut configuration: Configuration,
store_path: &Path,
store_name: &str,
default: bool,
) -> anyhow::Result<()> {
if configuration.find_store(store_name).is_some() {
anyhow::bail!("Store name already exists. Please use a different name.");
}
let absolute_path = path::absolute(store_path)?;
if absolute_path.is_dir() {
anyhow::bail!("Cannot use directory as store path. Please use a file path.");
}
if let Some(parent) = absolute_path.parent() {
fs::create_dir_all(parent)?;
configuration.add_store(&absolute_path.display().to_string(), store_name)?;
logs::store_add_success(store_name, &absolute_path.display().to_string());
if default {
set_default(configuration, store_name)?;
}
Ok(())
} else {
anyhow::bail!("Cannot create store path. Please check the path and try again.");
}
}
fn remove(
mut configuration: Configuration,
store_name: Option<&String>,
remove_data: bool,
) -> anyhow::Result<()> {
let (name, path) = if let Some(registration) = configuration.select_store(store_name) {
(®istration.name.clone(), ®istration.path.clone())
} else {
anyhow::bail!(
"No store found in configuration. Run 'pasejo store add ...' first to add one"
)
};
configuration.remove_store(name)?;
if remove_data {
let path_to_store = Path::new(path);
if path_to_store.exists() {
fs::remove_file(path_to_store)?;
}
}
logs::store_remove_success(name);
Ok(())
}
fn set_default(mut configuration: Configuration, store_name: &str) -> anyhow::Result<()> {
configuration.set_default_store(store_name)?;
logs::store_set_default(store_name);
Ok(())
}
fn decrypt(
configuration: &Configuration,
store_name: Option<&String>,
store_path: Option<&PathBuf>,
offline: bool,
) -> anyhow::Result<()> {
if let Some(registration) = configuration.select_store(store_name) {
if store_path.is_none() {
let hooks = HookExecutor {
configuration,
registration,
offline,
force: false,
};
hooks.execute_pull_commands()?;
}
let store = if let Some(path) = store_path {
configuration
.decrypt_store_from_path(registration, path)
.context("Cannot decrypt store")?
} else {
configuration
.decrypt_store(registration)
.context("Cannot decrypt store")?
};
let content = toml::to_string(&store)?;
println!("{content}");
Ok(())
} else {
anyhow::bail!(
"No store found in configuration. Run 'pasejo store add ...' first to add one"
)
}
}
fn list(configuration: &Configuration) {
for store in &configuration.stores {
let text = configuration
.default_store
.clone()
.filter(|default| default == &store.name)
.map_or_else(
|| format!("{}: {}", store.name, store.path.display()),
|default| format!("{}: {} (default)", default, store.path.display()),
);
println!("{text}");
}
}
fn merge(
configuration: &Configuration,
store_name: Option<&String>,
common_ancestor: &Path,
current_version: &Path,
other_version: &Path,
) -> anyhow::Result<()> {
if let Some(registration) = configuration.select_store(store_name) {
let common_ancestor_store = configuration
.decrypt_store_from_path(registration, common_ancestor)
.context("Cannot decrypt common ancestor store")?;
let mut current_version_store = configuration
.decrypt_store_from_path(registration, current_version)
.context("Cannot decrypt current version store")?;
let other_version_store = configuration
.decrypt_store_from_path(registration, other_version)
.context("Cannot decrypt other version store")?;
let mut errors = vec![];
match recipients::merge_recipients(
&common_ancestor_store.recipients,
¤t_version_store.recipients,
&other_version_store.recipients,
) {
Ok(merged_recipients) => {
current_version_store.recipients = merged_recipients;
}
Err(error) => {
errors.push(error);
}
}
match secrets::merge_secrets(
&common_ancestor_store.secrets,
¤t_version_store.secrets,
&other_version_store.secrets,
) {
Ok(merged_secrets) => {
current_version_store.secrets = merged_secrets;
}
Err(error) => {
errors.push(error);
}
}
match one_time_passwords::merge_one_time_passwords(
&common_ancestor_store.otp,
¤t_version_store.otp,
&other_version_store.otp,
) {
Ok(merged_one_time_passwords) => {
current_version_store.otp = merged_one_time_passwords;
}
Err(error) => {
errors.push(error);
}
}
if errors.is_empty() {
Configuration::encrypt_store_to_path(¤t_version_store, current_version)
} else {
let error_messages: Vec<String> =
errors.into_iter().map(|error| error.to_string()).collect();
anyhow::bail!(error_messages.join("\n "))
}
} else {
anyhow::bail!(
"No store found in configuration. Run 'pasejo store add ...' first to add one"
)
}
}
fn exec(
configuration: &Configuration,
store_name: Option<&String>,
command: &[String],
) -> anyhow::Result<()> {
if let Some(registration) = configuration.select_store(store_name) {
let store_path = registration.path();
if let Some(parent) = store_path.parent() {
if let Some(split) = command.split_first() {
duct::cmd(split.0, split.1)
.env("PASEJO_EXEC_STORE_PATH", store_path)
.env("PASEJO_EXEC_STORE_PARENT", parent)
.env("PASEJO_EXEC_COMMAND", split.0)
.dir(parent)
.run()
.with_context(|| format!("Failed to run command {}", split.0))?;
}
} else {
anyhow::bail!(
"Cannot get parent directory of store path. Please check the path and try again."
)
}
Ok(())
} else {
anyhow::bail!(
"No store found in configuration. Run 'pasejo store add ...' first to add one"
)
}
}