mirror of
https://github.com/qwjyh/xdbm
synced 2025-06-26 17:59:20 +09:00
change init command & add integration test
- now need to specify device name via cmd arg - can use private repository with ssh key or ssh-agent - adding integration test utility crates
This commit is contained in:
parent
9935f79920
commit
4283e1e98a
8 changed files with 387 additions and 59 deletions
|
@ -6,7 +6,7 @@ use crate::PathBuf;
|
|||
use clap::{Parser, Subcommand};
|
||||
use clap_verbosity_flag::Verbosity;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub(crate) struct Cli {
|
||||
#[command(subcommand)]
|
||||
|
@ -20,13 +20,21 @@ pub(crate) struct Cli {
|
|||
pub(crate) verbose: Verbosity,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub(crate) enum Commands {
|
||||
/// Initialize for this device.
|
||||
/// Provide `repo_url` to use existing repository, otherwise this device will be configured as the
|
||||
/// first device.
|
||||
Init {
|
||||
/// Name for this device
|
||||
device_name: String,
|
||||
/// Url for existing repository. Empty if init for the first time.
|
||||
#[arg(short, long)]
|
||||
repo_url: Option<String>, // url?
|
||||
/// Whether to use ssh-agent
|
||||
#[arg(long)]
|
||||
use_sshagent: bool,
|
||||
/// Manually specify ssh key
|
||||
#[arg(long)]
|
||||
ssh_key: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Manage storages.
|
||||
|
@ -42,14 +50,14 @@ pub(crate) enum Commands {
|
|||
Check {},
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
#[derive(clap::Args, Debug)]
|
||||
#[command(args_conflicts_with_subcommands = true)]
|
||||
pub(crate) struct StorageArgs {
|
||||
#[command(subcommand)]
|
||||
pub(crate) command: StorageCommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub(crate) enum StorageCommands {
|
||||
/// Add new storage.
|
||||
Add {
|
||||
|
|
|
@ -1,24 +1,91 @@
|
|||
//! Init subcommand.
|
||||
//! Initialize xdbm for the device.
|
||||
|
||||
use crate::{
|
||||
add_and_commit, full_status, get_devices, set_device_name, write_devices, Device, DEVICESFILE,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use git2::Repository;
|
||||
use crate::{add_and_commit, full_status, get_devices, write_devices, Device, DEVICESFILE};
|
||||
use anyhow::{anyhow, Context, Ok, Result};
|
||||
use core::panic;
|
||||
use git2::{Cred, RemoteCallbacks, Repository};
|
||||
use inquire::Password;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::{self, Path};
|
||||
use std::path::{self, Path, PathBuf};
|
||||
|
||||
pub(crate) fn cmd_init(repo_url: Option<String>, config_dir: &path::PathBuf) -> Result<()> {
|
||||
let is_first_device: bool;
|
||||
fn clone_repo(
|
||||
repo_url: &String,
|
||||
use_sshagent: bool,
|
||||
ssh_key: Option<PathBuf>,
|
||||
config_dir: &path::PathBuf,
|
||||
) -> Result<Repository> {
|
||||
// dont use credentials
|
||||
if ssh_key.is_none() && !use_sshagent {
|
||||
info!("No authentication will be used.");
|
||||
info!("Use either ssh_key or ssh-agent to access private repository");
|
||||
return Ok(Repository::clone(&repo_url, &config_dir)?);
|
||||
}
|
||||
|
||||
// using credentials
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.credentials(|_url, username_from_url, _allowed_types| {
|
||||
if let Some(key) = &ssh_key {
|
||||
info!("Using provided ssh key to access the repository");
|
||||
let passwd = match Password::new("SSH passphrase").prompt() {
|
||||
std::result::Result::Ok(s) => Some(s),
|
||||
Err(err) => {
|
||||
error!("Failed to get ssh passphrase: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
Cred::ssh_key(
|
||||
username_from_url
|
||||
.context("No username found from the url")
|
||||
.unwrap(),
|
||||
None,
|
||||
&key as &Path,
|
||||
passwd.as_deref(),
|
||||
)
|
||||
} else if use_sshagent {
|
||||
// use ssh agent
|
||||
info!("Using ssh agent to access the repository");
|
||||
Cred::ssh_key_from_agent(
|
||||
username_from_url
|
||||
.context("No username found from the url")
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
error!("no ssh_key and use_sshagent");
|
||||
panic!("This option must be unreachable.")
|
||||
}
|
||||
});
|
||||
|
||||
// fetch options
|
||||
let mut fo = git2::FetchOptions::new();
|
||||
fo.remote_callbacks(callbacks);
|
||||
|
||||
let mut builder = git2::build::RepoBuilder::new();
|
||||
builder.fetch_options(fo);
|
||||
|
||||
Ok(builder.clone(&repo_url, config_dir)?)
|
||||
}
|
||||
|
||||
pub(crate) fn cmd_init(
|
||||
device_name: String,
|
||||
repo_url: Option<String>,
|
||||
use_sshagent: bool,
|
||||
ssh_key: Option<PathBuf>,
|
||||
config_dir: &path::PathBuf,
|
||||
) -> Result<()> {
|
||||
// validate device name
|
||||
if device_name.chars().count() == 0 {
|
||||
log::error!("Device name cannnot by empty");
|
||||
return Err(anyhow!("Device name is empty"));
|
||||
}
|
||||
// get repo or initialize it
|
||||
let repo = match repo_url {
|
||||
let (is_first_device, repo) = match repo_url {
|
||||
Some(repo_url) => {
|
||||
trace!("repo: {}", repo_url);
|
||||
let repo = Repository::clone(&repo_url, &config_dir)?;
|
||||
is_first_device = false;
|
||||
repo
|
||||
let repo = clone_repo(&repo_url, use_sshagent, ssh_key, config_dir)?;
|
||||
(false, repo)
|
||||
}
|
||||
None => {
|
||||
trace!("No repo provided");
|
||||
|
@ -37,14 +104,15 @@ pub(crate) fn cmd_init(repo_url: Option<String>, config_dir: &path::PathBuf) ->
|
|||
add_and_commit(&repo, Path::new(".gitignore"), "Add devname to gitignore.")?;
|
||||
full_status(&repo)?;
|
||||
}
|
||||
is_first_device = true;
|
||||
repo
|
||||
(true, repo)
|
||||
}
|
||||
};
|
||||
full_status(&repo)?;
|
||||
|
||||
// set device name
|
||||
let device = set_device_name()?;
|
||||
// let device = set_device_name()?;
|
||||
let device = Device::new(device_name);
|
||||
trace!("Device information: {:?}", device);
|
||||
|
||||
// save devname
|
||||
let devname_path = &config_dir.join("devname");
|
||||
|
|
|
@ -87,7 +87,8 @@ pub fn get_device(config_dir: &Path) -> Result<Device> {
|
|||
/// Get `Vec<Device>` from yaml file in `config_dir`.
|
||||
pub fn get_devices(config_dir: &Path) -> Result<Vec<Device>> {
|
||||
trace!("get_devices");
|
||||
let f = File::open(config_dir.join(DEVICESFILE))?;
|
||||
let f =
|
||||
File::open(config_dir.join(DEVICESFILE)).context(format!("{} not found", DEVICESFILE))?;
|
||||
let reader = BufReader::new(f);
|
||||
let yaml: Vec<Device> =
|
||||
serde_yaml::from_reader(reader).context("Failed to parse devices.yml")?;
|
||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -45,11 +45,13 @@ fn main() -> Result<()> {
|
|||
.filter_level(cli.verbose.log_level_filter())
|
||||
.init();
|
||||
trace!("Start logging...");
|
||||
trace!("args: {:?}", cli);
|
||||
|
||||
let config_dir: std::path::PathBuf = match cli.config_dir {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
let mut config_dir = dirs::config_local_dir().context("Failed to get default config dir.")?;
|
||||
let mut config_dir =
|
||||
dirs::config_local_dir().context("Failed to get default config dir.")?;
|
||||
config_dir.push("xdbm");
|
||||
config_dir
|
||||
}
|
||||
|
@ -57,7 +59,12 @@ fn main() -> Result<()> {
|
|||
trace!("Config dir: {:?}", config_dir);
|
||||
|
||||
match cli.command {
|
||||
Commands::Init { repo_url } => cmd_init::cmd_init(repo_url, &config_dir)?,
|
||||
Commands::Init {
|
||||
device_name,
|
||||
repo_url,
|
||||
use_sshagent,
|
||||
ssh_key,
|
||||
} => cmd_init::cmd_init(device_name, repo_url, use_sshagent, ssh_key, &config_dir)?,
|
||||
Commands::Storage(storage) => {
|
||||
let repo = Repository::open(&config_dir).context(
|
||||
"Repository doesn't exist on the config path. Please run init to initialize the repository.",
|
||||
|
@ -98,38 +105,6 @@ fn main() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Set device name interactively.
|
||||
fn set_device_name() -> Result<Device> {
|
||||
let validator = |input: &str| {
|
||||
if input.chars().count() == 0 {
|
||||
Ok(Validation::Invalid("Need at least 1 character.".into()))
|
||||
} else {
|
||||
Ok(Validation::Valid)
|
||||
}
|
||||
};
|
||||
|
||||
let device_name = Text::new("Provide name for this device:")
|
||||
.with_validator(validator)
|
||||
.prompt();
|
||||
|
||||
let device_name = match device_name {
|
||||
Ok(device_name) => {
|
||||
println!("device name: {}", device_name);
|
||||
device_name
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error {}", err);
|
||||
return Err(anyhow!(err));
|
||||
}
|
||||
};
|
||||
|
||||
let device = Device::new(device_name);
|
||||
trace!("Device information: {:?}", device);
|
||||
trace!("Serialized: \n{}", serde_yaml::to_string(&device).unwrap());
|
||||
|
||||
return Ok(device);
|
||||
}
|
||||
|
||||
fn ask_unique_name(storages: &HashMap<String, Storage>, target: String) -> Result<String> {
|
||||
let mut disk_name = String::new();
|
||||
loop {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue