refactor repository opening

This commit is contained in:
Alexander Weiss 2023-07-03 10:14:20 +02:00
parent e6f72e4f5d
commit 21a0376b4e
24 changed files with 122 additions and 148 deletions

1
Cargo.lock generated
View File

@ -2402,7 +2402,6 @@ dependencies = [
"crossbeam-channel",
"derivative",
"derive_more",
"dialoguer",
"directories",
"dirs",
"displaydoc",

View File

@ -94,7 +94,6 @@ clap = { workspace = true, optional = true }
clap_complete = { workspace = true, optional = true }
merge = { workspace = true, optional = true }
dialoguer = "0.10.4"
directories = { workspace = true }
nom = { workspace = true }
path-dedot = { workspace = true }

View File

@ -205,6 +205,8 @@ pub enum IdErrorKind {
pub enum RepositoryErrorKind {
/// No repository given. Please use the --repository option.
NoRepositoryGiven,
/// No password given. Please use one of the --password-* options.
NoPasswordGiven,
/// warm-up command must contain %id!
NoIDSpecified,
/// error opening password file `{0:?}`

View File

@ -9,7 +9,6 @@ use std::{
use bytes::Bytes;
use log::{debug, error, info};
use dialoguer::Password;
use nom::{
branch::alt,
bytes::complete::{is_not, tag},
@ -35,17 +34,13 @@ use crate::{
repoinfo::{IndexInfos, RepoFileInfos},
},
crypto::aespoly1305::Key,
error::RepositoryErrorKind,
error::{KeyFileErrorKind, RepositoryErrorKind, RusticErrorKind},
repofile::{configfile::ConfigFile, keyfile::find_key_in_backend},
BlobType, DecryptFullBackend, Id, IndexBackend, IndexedBackend, NoProgressBars, Node,
ProgressBars, PruneOpts, PrunePlan, RusticResult, SnapshotFile, SnapshotGroup,
SnapshotGroupCriterion, Tree,
};
pub(super) mod constants {
pub(super) const MAX_PASSWORD_RETRIES: usize = 5;
}
mod warm_up;
use warm_up::{warm_up, warm_up_wait};
@ -176,7 +171,7 @@ pub fn read_password_from_reader(file: &mut impl BufRead) -> RusticResult<String
Ok(password)
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Repository<P, S> {
name: String,
pub be: HotColdBackend<ChooseBackend>,
@ -279,17 +274,31 @@ impl<P> Repository<P, ()> {
}
}
pub fn open(self) -> RusticResult<Repository<P, OpenStatus>> {
pub fn config_id(&self) -> RusticResult<Option<Id>> {
let config_ids = self
.be
.list(FileType::Config)
.map_err(|_| RepositoryErrorKind::ListingRepositoryConfigFileFailed)?;
match config_ids.len() {
1 => {} // ok, continue
0 => return Err(RepositoryErrorKind::NoRepositoryConfigFound(self.name).into()),
_ => return Err(RepositoryErrorKind::MoreThanOneRepositoryConfig(self.name).into()),
1 => Ok(Some(config_ids[0])),
0 => Ok(None),
_ => Err(RepositoryErrorKind::MoreThanOneRepositoryConfig(self.name.clone()).into()),
}
}
pub fn open(self) -> RusticResult<Repository<P, OpenStatus>> {
let password = self
.password()?
.ok_or(RepositoryErrorKind::NoPasswordGiven)?;
self.open_with_password(&password)
}
pub fn open_with_password(self, password: &str) -> RusticResult<Repository<P, OpenStatus>> {
let config_id = self
.config_id()?
.ok_or(RepositoryErrorKind::NoRepositoryConfigFound(
self.name.clone(),
))?;
if let Some(be_hot) = &self.be_hot {
let mut keys = self.be.list_with_size(FileType::Key)?;
@ -301,11 +310,18 @@ impl<P> Repository<P, ()> {
}
}
let key = get_key(&self.be, self.password()?)?;
let key = find_key_in_backend(&self.be, &password, None).map_err(|err| {
match err.into_inner() {
RusticErrorKind::KeyFile(KeyFileErrorKind::NoSuitableKeyFound) => {
RepositoryErrorKind::IncorrectPassword.into()
}
err => err,
}
})?;
info!("repository {}: password is correct.", self.name);
let dbe = DecryptBackend::new(&self.be, key);
let config: ConfigFile = dbe.get_file(&config_ids[0])?;
let config: ConfigFile = dbe.get_file(&config_id)?;
match (config.is_hot == Some(true), self.be_hot.is_some()) {
(true, false) => return Err(RepositoryErrorKind::HotRepositoryFlagMissing.into()),
(false, true) => return Err(RepositoryErrorKind::IsNotHotRepository.into()),
@ -354,32 +370,6 @@ impl<P: ProgressBars, S> Repository<P, S> {
}
}
pub(crate) fn get_key(be: &impl ReadBackend, password: Option<String>) -> RusticResult<Key> {
for _ in 0..constants::MAX_PASSWORD_RETRIES {
match password {
// if password is given, directly return the result of find_key_in_backend and don't retry
Some(pass) => {
return find_key_in_backend(be, &pass, None).map_err(std::convert::Into::into)
}
None => {
// TODO: Differentiate between wrong password and other error!
if let Ok(key) = find_key_in_backend(
be,
&Password::new()
.with_prompt("enter repository password")
.allow_empty_password(true)
.interact()
.map_err(RepositoryErrorKind::ReadingPasswordFromPromptFailed)?,
None,
) {
return Ok(key);
}
}
}
}
Err(RepositoryErrorKind::IncorrectPassword.into())
}
pub trait Open {
type DBE: DecryptFullBackend;
fn key(&self) -> &Key;

View File

@ -38,11 +38,15 @@ use crate::{
{Application, RUSTIC_APP},
};
use abscissa_core::{
config::Override, status_err, Command, Configurable, FrameworkError, Runnable, Shutdown,
};
use abscissa_core::{config::Override, Command, Configurable, FrameworkError, Runnable, Shutdown};
use anyhow::{anyhow, Result};
use dialoguer::Password;
use rustic_core::{OpenStatus, Repository};
pub(super) mod constants {
pub(super) const MAX_PASSWORD_RETRIES: usize = 5;
}
/// Rustic Subcommands
/// Subcommands need to be listed in an enum.
#[derive(clap::Parser, Command, Debug, Runnable)]
@ -167,25 +171,29 @@ impl Configurable<RusticConfig> for EntryPoint {
}
}
fn open_repository<P>(repo: Repository<P, ()>) -> Repository<P, OpenStatus> {
match repo.open() {
Ok(it) => it,
Err(err) => {
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
}
}
}
fn get_repository(config: &Arc<RusticConfig>) -> Repository<ProgressOptions, ()> {
fn open_repository(config: &Arc<RusticConfig>) -> Result<Repository<ProgressOptions, OpenStatus>> {
let po = config.global.progress_options;
match Repository::new_with_progress(&config.repository, po) {
Ok(it) => it,
Err(err) => {
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
let repo = Repository::new_with_progress(&config.repository, po)?;
match repo.password()? {
// if password is given, directly return the result of find_key_in_backend and don't retry
Some(pass) => {
return Ok(repo.open_with_password(&pass)?);
}
None => {
for _ in 0..constants::MAX_PASSWORD_RETRIES {
let pass = Password::new()
.with_prompt("enter repository password")
.allow_empty_password(true)
.interact()?;
match repo.clone().open_with_password(&pass) {
Ok(repo) => return Ok(repo),
// TODO: fail if error != Password incorrect
Err(_) => continue,
}
}
}
}
Err(anyhow!("incorrect password"))
}
#[test]

View File

@ -3,7 +3,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
commands::open_repository,
helpers::bytes_size_to_string,
{status_err, Application, RUSTIC_APP},
};
@ -153,7 +153,7 @@ impl BackupCmd {
let config = RUSTIC_APP.config();
let progress_options = &config.global.progress_options;
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
// manually check for a "source" field, check is not done by serde, see above.
if !config.backup.source.is_empty() {

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
@ -61,7 +58,7 @@ impl Runnable for CatCmd {
impl CatCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let data = match &self.cmd {
CatSubCmd::Config => repo.cat_file(FileType::Config, "")?,

View File

@ -2,13 +2,10 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::Result;
use rustic_core::CheckOpts;
/// `check` subcommand
@ -20,12 +17,18 @@ pub(crate) struct CheckCmd {
impl Runnable for CheckCmd {
fn run(&self) {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
if let Err(err) = repo.check(self.opts) {
if let Err(err) = self.inner_run() {
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
};
}
}
impl CheckCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(&config)?;
repo.check(self.opts)?;
Ok(())
}
}

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
@ -33,7 +30,7 @@ impl Runnable for ConfigCmd {
impl ConfigCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let mut new_config = repo.config().clone();
self.config_opts.apply(&mut new_config)?;

View File

@ -3,7 +3,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, init::save_config, open_repository},
commands::{init::save_config, open_repository},
helpers::copy,
status_err, Application, RUSTIC_APP,
};
@ -54,7 +54,7 @@ impl CopyCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
if config.copy.targets.is_empty() {
status_err!("no [[copy.targets]] section in config file found!");

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
@ -56,7 +53,7 @@ impl DiffCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();
let (id1, path1) = arg_to_snap_path(&self.snap1, "");

View File

@ -2,7 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{commands::get_repository, status_err, Application, RUSTIC_APP};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::Result;
@ -28,7 +28,7 @@ impl DumpCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = get_repository(&config).open()?.to_indexed()?;
let repo = open_repository(&config)?.to_indexed()?;
let node =
repo.node_from_snapshot_path(&self.snap, |sn| config.snapshot_filter.matches(sn))?;

View File

@ -3,7 +3,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::get_repository, helpers::table_with_titles, status_err, Application, RusticConfig,
commands::open_repository, helpers::table_with_titles, status_err, Application, RusticConfig,
RUSTIC_APP,
};
@ -88,7 +88,7 @@ impl Runnable for ForgetCmd {
impl ForgetCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = get_repository(&config).open()?;
let repo = open_repository(&config)?;
let group_by = config.forget.group_by.unwrap_or_default();

View File

@ -5,14 +5,14 @@
use abscissa_core::{status_err, Command, Runnable, Shutdown};
use anyhow::{bail, Result};
use crate::{commands::get_repository, Application, RUSTIC_APP};
use crate::{Application, RUSTIC_APP};
use bytes::Bytes;
use dialoguer::Password;
use rustic_core::{
hash, random_poly, ConfigFile, DecryptBackend, DecryptWriteBackend, FileType, Id, Key, KeyFile,
ReadBackend, WriteBackend,
ReadBackend, Repository, WriteBackend,
};
use crate::commands::{config::ConfigOpts, key::KeyOpts};
@ -40,7 +40,8 @@ impl InitCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = get_repository(&config);
let po = config.global.progress_options;
let repo = Repository::new_with_progress(&config.repository, po)?;
let config_ids = repo.be.list(FileType::Config)?;
@ -79,8 +80,8 @@ pub(crate) fn save_config(
// generate key
let key = Key::new();
let pass = password.map_or_else(
|| match Password::new()
let pass = password.unwrap_or_else(|| {
match Password::new()
.with_prompt("enter password for new key")
.allow_empty_password(true)
.with_confirmation("confirm password", "passwords do not match")
@ -91,9 +92,8 @@ pub(crate) fn save_config(
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
}
},
|pass| pass,
);
}
});
let keyfile = KeyFile::generate(
key,

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::Result;
@ -73,7 +70,7 @@ impl AddCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();
let key = repo.key();

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
@ -34,7 +31,7 @@ impl ListCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let tpe = match self.tpe.as_str() {
// special treatment for listing blobs: read the index and display it

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::Result;
@ -45,7 +42,7 @@ impl LsCmd {
let config = RUSTIC_APP.config();
let progress_options = &config.global.progress_options;
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();
let mut recursive = self.recursive;

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::Result;
use log::info;
@ -57,7 +54,7 @@ impl MergeCmd {
let config = RUSTIC_APP.config();
let progress_options = &config.global.progress_options;
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();

View File

@ -3,9 +3,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
helpers::bytes_size_to_string,
status_err, Application, RUSTIC_APP,
commands::open_repository, helpers::bytes_size_to_string, status_err, Application, RUSTIC_APP,
};
use abscissa_core::{Command, Runnable, Shutdown};
use log::debug;
@ -35,7 +33,7 @@ impl Runnable for PruneCmd {
impl PruneCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let pruner = repo.prune_plan(&self.opts)?;

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use log::{debug, info, warn};
@ -87,7 +84,7 @@ impl IndexSubCmd {
let config = RUSTIC_APP.config();
let progress_options = &config.global.progress_options;
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();
let p = progress_options.progress_spinner("listing packs...");
@ -235,7 +232,7 @@ impl SnapSubCmd {
let config = RUSTIC_APP.config();
let progress_options = &config.global.progress_options;
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();
let config_file = repo.config();

View File

@ -3,7 +3,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::get_repository, helpers::bytes_size_to_string, status_err, Application, RUSTIC_APP,
commands::open_repository, helpers::bytes_size_to_string, status_err, Application, RUSTIC_APP,
};
use abscissa_core::{Command, Runnable, Shutdown};
@ -11,7 +11,7 @@ use serde::Serialize;
use crate::helpers::table_right_from;
use anyhow::Result;
use rustic_core::{IndexInfos, RepoFileInfo, RepoFileInfos};
use rustic_core::{IndexInfos, RepoFileInfo, RepoFileInfos, Repository};
/// `repoinfo` subcommand
#[derive(clap::Parser, Command, Debug)]
@ -48,15 +48,19 @@ struct Infos {
impl RepoInfoCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = get_repository(&config);
let infos = Infos {
files: (!self.only_index).then(|| repo.infos_files()).transpose()?,
files: (!self.only_index)
.then(|| {
let po = config.global.progress_options;
let repo = Repository::new_with_progress(&config.repository, po)?;
repo.infos_files()
})
.transpose()?,
index: (!self.only_files)
.then(|| -> Result<_> {
let repo = repo.open()?;
let info_index = repo.infos_index()?;
Ok(info_index)
let repo = open_repository(&config)?;
Ok(repo.infos_index()?)
})
.transpose()?,
};

View File

@ -3,9 +3,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
helpers::bytes_size_to_string,
status_err, Application, RUSTIC_APP,
commands::open_repository, helpers::bytes_size_to_string, status_err, Application, RUSTIC_APP,
};
use log::{debug, error, info, trace, warn};
@ -90,7 +88,7 @@ impl RestoreCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let progress_options = &config.global.progress_options;
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();
let (id, path) = self.snap.split_once(':').unwrap_or((&self.snap, ""));

View File

@ -3,7 +3,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::get_repository,
commands::open_repository,
helpers::{bold_cell, bytes_size_to_string, table, table_right_from},
status_err, Application, RUSTIC_APP,
};
@ -56,7 +56,7 @@ impl Runnable for SnapshotCmd {
impl SnapshotCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
let repo = get_repository(&config).open()?;
let repo = open_repository(&config)?;
let groups = repo.get_snapshot_group(&self.ids, self.group_by, |sn| {
config.snapshot_filter.matches(sn)

View File

@ -2,10 +2,7 @@
/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{
commands::{get_repository, open_repository},
status_err, Application, RUSTIC_APP,
};
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
@ -79,7 +76,7 @@ impl Runnable for TagCmd {
impl TagCmd {
fn inner_run(&self) -> anyhow::Result<()> {
let config = RUSTIC_APP.config();
let repo = open_repository(get_repository(&config));
let repo = open_repository(&config)?;
let be = repo.dbe();