mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-18 16:21:24 +01:00
add 'db migrate' subcommand
This commit is contained in:
parent
a2ed21f1c2
commit
6446822bf2
3 changed files with 133 additions and 1 deletions
33
src/cli.rs
33
src/cli.rs
|
|
@ -9,6 +9,7 @@ use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
|
|
||||||
|
mod migrate_db;
|
||||||
mod serve;
|
mod serve;
|
||||||
|
|
||||||
/// Command line arguments
|
/// Command line arguments
|
||||||
|
|
@ -26,6 +27,10 @@ pub(crate) struct Args {
|
||||||
pub(crate) enum Command {
|
pub(crate) enum Command {
|
||||||
/// Run the server.
|
/// Run the server.
|
||||||
Serve(ServeArgs),
|
Serve(ServeArgs),
|
||||||
|
|
||||||
|
/// Commands for interacting with the database.
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Db(DbCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for the `--config` arg.
|
/// Wrapper for the `--config` arg.
|
||||||
|
|
@ -57,10 +62,38 @@ pub(crate) struct ServeArgs {
|
||||||
pub(crate) config: ConfigArg,
|
pub(crate) config: ConfigArg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub(crate) enum DbCommand {
|
||||||
|
/// Migrate database from one server implementation to another.
|
||||||
|
///
|
||||||
|
/// This command is not protected against symlink-swapping attacks. Do not
|
||||||
|
/// use it when any subdirectories or parents of the `--in`, `--out`, or
|
||||||
|
/// `--inplace` directories may be written by an untrusted user during
|
||||||
|
/// execution.
|
||||||
|
Migrate(MigrateDbArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
pub(crate) struct MigrateDbArgs {
|
||||||
|
#[clap(flatten)]
|
||||||
|
config: ConfigArg,
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
pub(crate) async fn run(self) -> Result<(), error::Main> {
|
pub(crate) async fn run(self) -> Result<(), error::Main> {
|
||||||
match self.command {
|
match self.command {
|
||||||
Command::Serve(args) => serve::run(args).await?,
|
Command::Serve(args) => serve::run(args).await?,
|
||||||
|
Command::Db(DbCommand::Migrate(args)) => {
|
||||||
|
migrate_db::run(args).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
src/cli/migrate_db.rs
Normal file
55
src/cli/migrate_db.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
use super::MigrateDbArgs;
|
||||||
|
use crate::{
|
||||||
|
config, database::KeyValueDatabase, error, observability,
|
||||||
|
service::globals::DbVersion, services, utils::copy_dir, Services,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) async fn run(
|
||||||
|
args: MigrateDbArgs,
|
||||||
|
) -> Result<(), error::MigrateDbCommand> {
|
||||||
|
use error::MigrateDbCommand as Error;
|
||||||
|
|
||||||
|
let mut config = config::load(args.config.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.
|
||||||
|
args.out_path
|
||||||
|
.to_str()
|
||||||
|
.ok_or(Error::InvalidUnicodeOutPath)?
|
||||||
|
.clone_into(&mut config.database.path);
|
||||||
|
|
||||||
|
let (_guard, reload_handles) = observability::init(&config)?;
|
||||||
|
|
||||||
|
copy_dir(&args.in_path, &args.out_path).await.map_err(Error::Copy)?;
|
||||||
|
|
||||||
|
let db = Box::leak(Box::new(
|
||||||
|
KeyValueDatabase::load_or_create(&config).map_err(Error::LoadDb)?,
|
||||||
|
));
|
||||||
|
|
||||||
|
Services::build(db, config, reload_handles)
|
||||||
|
.map_err(Error::InitializeServices)?
|
||||||
|
.install();
|
||||||
|
|
||||||
|
services().globals.err_if_server_name_changed()?;
|
||||||
|
|
||||||
|
// Migrate from a grapevine-compatible db to a conduit-0.8.0-compatible db
|
||||||
|
|
||||||
|
let version =
|
||||||
|
services().globals.database_version().map_err(Error::MigrateDb)?;
|
||||||
|
if version < DbVersion::Grapevine(0) {
|
||||||
|
return Err(Error::DbVersionTooOld(version));
|
||||||
|
} else if version != DbVersion::Grapevine(0) {
|
||||||
|
return Err(Error::DbVersionUnsupported(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))
|
||||||
|
.map_err(Error::MigrateDb)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
46
src/error.rs
46
src/error.rs
|
|
@ -4,7 +4,7 @@ use std::{fmt, iter, path::PathBuf};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::config::ListenConfig;
|
use crate::{config::ListenConfig, service::globals::DbVersion};
|
||||||
|
|
||||||
/// Formats an [`Error`][0] and its [`source`][1]s with a separator
|
/// Formats an [`Error`][0] and its [`source`][1]s with a separator
|
||||||
///
|
///
|
||||||
|
|
@ -42,6 +42,9 @@ impl fmt::Display for DisplayWithSources<'_> {
|
||||||
pub(crate) enum Main {
|
pub(crate) enum Main {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ServeCommand(#[from] ServeCommand),
|
ServeCommand(#[from] ServeCommand),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
MigrateDbCommand(#[from] MigrateDbCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors returned from the `serve` CLI subcommand.
|
/// Errors returned from the `serve` CLI subcommand.
|
||||||
|
|
@ -85,6 +88,47 @@ pub(crate) enum ServerNameChanged {
|
||||||
Renamed,
|
Renamed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Top-level errors from the `db migrate` 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")]
|
||||||
|
LoadDb(#[source] crate::utils::error::Error),
|
||||||
|
|
||||||
|
#[error("failed to initialize services")]
|
||||||
|
InitializeServices(#[source] crate::utils::error::Error),
|
||||||
|
|
||||||
|
#[error("`server_name` change check failed")]
|
||||||
|
ServerNameChanged(#[from] ServerNameChanged),
|
||||||
|
|
||||||
|
#[error("failed to migrate database")]
|
||||||
|
MigrateDb(#[source] crate::utils::error::Error),
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"initial database version is too old for migration: {_0:?}. Try \
|
||||||
|
loading the database with the latest conduit release and then \
|
||||||
|
attempting migration again."
|
||||||
|
)]
|
||||||
|
DbVersionTooOld(DbVersion),
|
||||||
|
|
||||||
|
#[error("initial database version is not supported for migration: {_0:?}")]
|
||||||
|
DbVersionUnsupported(DbVersion),
|
||||||
|
}
|
||||||
|
|
||||||
/// Errors copying a directory recursively.
|
/// Errors copying a directory recursively.
|
||||||
///
|
///
|
||||||
/// Returned by the [`crate::utils::copy_dir`] function.
|
/// Returned by the [`crate::utils::copy_dir`] function.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue