1
0
Fork 0
forked from mirror/grapevine

WIP: migrate-db command

This commit is contained in:
Benjamin Lee 2024-09-07 22:12:58 -07:00
parent e7746386e4
commit d8bad2da09
No known key found for this signature in database
GPG key ID: FB9624E2885D55A4
3 changed files with 101 additions and 6 deletions

View file

@ -5,10 +5,11 @@
use std::path::PathBuf; use std::path::PathBuf;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand, ValueEnum};
use crate::error; use crate::error;
mod migrate_db;
mod serve; mod serve;
/// Command line arguments /// Command line arguments
@ -25,6 +26,15 @@ pub(crate) struct Args {
pub(crate) command: Option<Command>, pub(crate) command: Option<Command>,
} }
#[derive(Subcommand)]
pub(crate) enum Command {
/// Run the server. Default if no command is specified.
Serve,
/// Migrate database to be used by a different server implementation.
MigrateDb(MigrateDbArgs),
}
/// CLI args shared between all commands. /// CLI args shared between all commands.
#[derive(clap::Args)] #[derive(clap::Args)]
#[clap(mut_arg("config", |x| { #[clap(mut_arg("config", |x| {
@ -41,14 +51,32 @@ pub(crate) struct Args {
}))] }))]
pub(crate) struct GlobalArgs { pub(crate) struct GlobalArgs {
/// Path to the configuration file /// Path to the configuration file
#[clap(long, short)] #[clap(long, short, global = true)]
pub(crate) config: Option<PathBuf>, pub(crate) config: Option<PathBuf>,
} }
#[derive(Subcommand, Clone)] // TODO: Name in a way that allows versions to be distinguished
pub(crate) enum Command { #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
/// Run the server. Default if no command is specified. pub(crate) enum DbMigrationTarget {
Serve, Grapevine,
Conduit,
}
#[derive(clap::Args)]
pub(crate) struct MigrateDbArgs {
#[clap(long)]
pub(crate) from: DbMigrationTarget,
#[clap(long)]
pub(crate) to: DbMigrationTarget,
/// Path to read database from.
#[clap(long = "in", short)]
pub(crate) in_path: PathBuf,
/// Path to write migrated database to.
#[clap(long = "out", short)]
pub(crate) out_path: PathBuf,
} }
impl Args { impl Args {
@ -60,6 +88,9 @@ impl Args {
match command.unwrap_or(Command::Serve) { match command.unwrap_or(Command::Serve) {
Command::Serve => serve::run(global_args).await, Command::Serve => serve::run(global_args).await,
Command::MigrateDb(args) => {
Ok(migrate_db::run(global_args, args).await?)
}
} }
} }
} }

39
src/cli/migrate_db.rs Normal file
View file

@ -0,0 +1,39 @@
use super::{GlobalArgs, MigrateDbArgs};
use crate::{
services,
config, database::{KeyValueDatabase, DbVersion}, error, observability, utils::copy_dir,
};
pub(crate) async fn run(
global_args: GlobalArgs,
args: MigrateDbArgs,
) -> Result<(), error::MigrateDbCommand> {
use error::MigrateDbCommand as Error;
let mut config = config::load(global_args.config.as_ref()).await?;
// mutating the config like this is ugly, but difficult to avoid. Currently
// the database is very tightly coupled with service code, which reads the
// path only from the config.
config.database.path =
args.out_path.to_str().ok_or(Error::InvalidUnicodeOutPath)?.to_owned();
let (_guard, reload_handles) = observability::init(&config)?;
copy_dir(&args.in_path, &args.out_path).await.map_err(Error::Copy)?;
let db = KeyValueDatabase::load_or_create(config, reload_handles)
.map_err(Error::LoadDatabase)?;
let version = services().globals.database_version().await?;
if version != DbVersion::Grapevine(0) {
return Err(Error::WrongDbVersion(version));
}
// Undo Conduit(13) -> Grapevine(0)
//
// This is a no-op that only changes the db version namespace. Setting the
// version to Conduit(_) will restore the original state.
services().globals.bump_database_version(DbVersion::Conduit(13)).await?;
Ok(())
}

View file

@ -51,6 +51,31 @@ pub(crate) enum Main {
#[error("failed to serve requests")] #[error("failed to serve requests")]
Serve(#[from] Serve), Serve(#[from] Serve),
#[error(transparent)]
MigrateDbCommand(#[from] MigrateDbCommand),
}
/// Top-level errors from the `migrate-db` subcommand.
// Missing docs are allowed here since that kind of information should be
// encoded in the error messages themselves anyway.
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub(crate) enum MigrateDbCommand {
#[error("output path is not valid unicode")]
InvalidUnicodeOutPath,
#[error("failed to copy existing database directory")]
Copy(#[source] CopyDir),
#[error("failed to initialize observability")]
Observability(#[from] Observability),
#[error("failed to load configuration")]
Config(#[from] Config),
#[error("failed to load database")]
LoadDatabase(#[source] crate::utils::error::Error),
} }
/// Errors copying a directory recursively. /// Errors copying a directory recursively.