From a669d57000800c3f20bc24bf065e3a3a59ba677d Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Thu, 4 Sep 2025 16:03:58 +0000 Subject: [PATCH] feat: Reimplement keystone-db to support config Replace the migration cli coming from the sea_orm_migration with the custom implementation to support config reading. --- .github/workflows/functional.yml | 2 +- Cargo.lock | 18 +++-- Cargo.toml | 4 +- src/bin/keystone_db.rs | 116 ++++++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 13 deletions(-) diff --git a/.github/workflows/functional.yml b/.github/workflows/functional.yml index b72436b7..b3d29a7a 100644 --- a/.github/workflows/functional.yml +++ b/.github/workflows/functional.yml @@ -299,7 +299,7 @@ jobs: keystone-manage --config-file etc/keystone.conf bootstrap --bootstrap-password password --bootstrap-public-url http://localhost:8080 - name: Apply Rust keystone DB changes - run: ./keystone-db up + run: ./keystone-db -c ${{ github.workspace }}/etc/keystone.conf up - name: Start python keystone run: uwsgi --module "keystone.server.wsgi:initialize_public_application()" --http-socket :5001 -b 65535 --http-keepalive --so-keepalive --logformat "Request %(uri):%(method) returned %(status) in %(msecs)ms" > python.log 2>&1 & diff --git a/Cargo.lock b/Cargo.lock index eb7b3cef..c161eeee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1981,7 +1981,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -2634,7 +2634,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.22.1", + "base64 0.21.7", "chrono", "getrandom 0.2.16", "http", @@ -2715,7 +2715,7 @@ dependencies = [ "sha1", "sha2", "sprintf", - "thiserror 2.0.12", + "thiserror 1.0.69", "tokio", "tracing", "urlencoding", @@ -3934,9 +3934,9 @@ dependencies = [ [[package]] name = "sea-orm" -version = "1.1.14" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34963b2d68331ef5fbc8aa28a53781471c15f90ba1ad4f2689d21ce8b9a9d1f1" +checksum = "458d38dfa73e8ab64260f9fd96d61e1ca96a312d06e94b71615a417ef29efcac" dependencies = [ "async-stream", "async-trait", @@ -3972,6 +3972,8 @@ dependencies = [ "dotenvy", "glob", "regex", + "sea-schema", + "sqlx", "tracing", "tracing-subscriber", "url", @@ -3979,9 +3981,9 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "1.1.14" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a489127c872766445b4e28f846825f89a076ac3af2591d1365503a68f93e974c" +checksum = "af976292446b09dd51d7b1784d6195dec76844e9e9e980b5fb12634ef417d6ea" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -4062,7 +4064,9 @@ checksum = "2239ff574c04858ca77485f112afea1a15e53135d3097d0c86509cef1def1338" dependencies = [ "futures", "sea-query", + "sea-query-binder", "sea-schema-derive", + "sqlx", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0de2423a..6fb8725b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ reqwest = { version = "0.12", features = ["json"] } rmp = { version = "0.8" } schemars = { version = "1.0" } sea-orm = { version = "1.1", features = ["sqlx-mysql", "sqlx-postgres", "runtime-tokio"] } -sea-orm-migration = { version = "1.1" } +sea-orm-migration = { version = "1.1", features = ["sqlx-mysql", "sqlx-postgres", "runtime-tokio"] } serde = { version = "1.0" } serde_bytes = { version = "0.11" } serde_json = { version = "1.0" } @@ -72,7 +72,7 @@ reqwest = { version = "0.12", features = ["json", "multipart"] } sea-orm = { version = "1.1", features = ["mock"]} serde_urlencoded = { version = "0.7" } tempfile = { version = "3.21" } -thirtyfour = "0.36.0" +thirtyfour = "0.36" tracing-test = { version = "0.2" } url = { version = "2.5" } diff --git a/src/bin/keystone_db.rs b/src/bin/keystone_db.rs index 882dbfe1..016d4a06 100644 --- a/src/bin/keystone_db.rs +++ b/src/bin/keystone_db.rs @@ -11,12 +11,122 @@ // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 +use clap::{Parser, Subcommand}; +use color_eyre::Report; +use std::io; +use std::path::PathBuf; +use tracing::info; +use tracing_subscriber::{filter::*, prelude::*}; + +use sea_orm::ConnectOptions; +use sea_orm::Database; use sea_orm_migration::prelude::*; -use openstack_keystone::db_migration; +use openstack_keystone::config::Config; +use openstack_keystone::db_migration::Migrator; + +#[derive(Parser)] +#[command(author, version, about)] +struct Cli { + /// Path to the keystone config file. + #[arg(short, long, default_value = "/etc/keystone/keystone.conf")] + config: PathBuf, + + /// Verbosity level. Repeat to increase level. + #[arg(short, long, global=true, action = clap::ArgAction::Count)] + pub verbose: u8, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Apply pending migrations. + Up { + /// Number of pending migrations to apply. + #[arg(short('n'))] + steps: Option, + }, + /// Rollback applied migrations. + Down { + /// Number of migrations to rollback. + #[arg(short('n'))] + steps: Option, + }, + /// Check the status of all migrations. + Status, + /// Drop all tables from the database, then reapply all migrations. + Fresh, + /// Rollback all applied migrations, then reapply all migrations. + Refresh, + /// Rollback all applied migrations. + Reset, +} #[tokio::main] -async fn main() { - cli::run_cli(db_migration::Migrator).await; +async fn main() -> Result<(), Report> { + let cli = Cli::parse(); + + let filter = Targets::new().with_default(match cli.verbose { + 0 => LevelFilter::WARN, + 1 => LevelFilter::INFO, + 2 => LevelFilter::DEBUG, + _ => LevelFilter::TRACE, + }); + + let log_layer = tracing_subscriber::fmt::layer() + .with_writer(io::stderr) + .with_filter(filter); + + // build the tracing registry + tracing_subscriber::registry().with(log_layer).init(); + let cfg = Config::new(cli.config)?; + let db_url = cfg.database.get_connection(); + let mut opt = ConnectOptions::new(db_url.to_owned()); + + if cli.verbose < 2 { + opt.sqlx_logging(false); + } + + info!("Establishing the database connection..."); + let conn = Database::connect(opt) + .await + .expect("Database connection failed"); + + match cli.command { + Commands::Up { steps } => { + Migrator::up(&conn, steps).await?; + } + Commands::Down { steps } => { + Migrator::down(&conn, steps).await?; + } + Commands::Status => { + let migrations = Migrator::get_pending_migrations(&conn).await?; + if !migrations.is_empty() { + println!("Pending migrations:"); + for mig in migrations { + println!("{}", mig.name()); + } + } else { + println!("No pending migrations!") + }; + let migrations = Migrator::get_applied_migrations(&conn).await?; + println!("Applied migrations:"); + for mig in migrations { + println!("{}", mig.name()); + } + } + Commands::Fresh => { + Migrator::fresh(&conn).await?; + } + Commands::Refresh => { + Migrator::refresh(&conn).await?; + } + Commands::Reset => { + Migrator::reset(&conn).await?; + } + } + Ok(()) }