From 6720d3f97ace01436c0247760cb276667df55945 Mon Sep 17 00:00:00 2001 From: Kekschen <52585984+Kek5chen@users.noreply.github.com> Date: Mon, 10 Jun 2024 23:00:19 +0200 Subject: [PATCH] Revert "vickyctl: handle poisoned locks" --- Cargo.lock | 284 +----------------------- fairy/src/main.rs | 14 +- vicky/src/bin/vicky/locks.rs | 38 +--- vicky/src/bin/vicky/main.rs | 34 +-- vicky/src/bin/vicky/tasks.rs | 22 +- vicky/src/lib/database/entities/lock.rs | 100 ++------- vicky/src/lib/database/entities/task.rs | 126 ++++++----- vicky/src/lib/database/schema.rs | 6 +- vicky/src/lib/vicky/scheduler.rs | 50 ++--- vickyctl/Cargo.toml | 5 +- vickyctl/src/error.rs | 2 +- vickyctl/src/locks.rs | 157 ++----------- vickyctl/src/main.rs | 17 +- vickyctl/src/tui/lock_resolver.rs | 232 ------------------- vickyctl/src/tui/mod.rs | 5 - vickyctl/src/tui/popup.rs | 34 --- vickyctl/src/tui/utils.rs | 28 --- 17 files changed, 164 insertions(+), 990 deletions(-) delete mode 100644 vickyctl/src/tui/lock_resolver.rs delete mode 100644 vickyctl/src/tui/mod.rs delete mode 100644 vickyctl/src/tui/popup.rs delete mode 100644 vickyctl/src/tui/utils.rs diff --git a/Cargo.lock b/Cargo.lock index f6c82c0..e094e33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,18 +52,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -73,12 +61,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -624,9 +606,6 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -dependencies = [ - "serde", -] [[package]] name = "block-buffer" @@ -681,21 +660,6 @@ dependencies = [ "either", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.0.97" @@ -799,20 +763,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" -[[package]] -name = "compact_str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "serde", - "static_assertions", -] - [[package]] name = "cookie" version = "0.18.1" @@ -907,31 +857,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.5.0", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -997,37 +922,6 @@ dependencies = [ "serde", ] -[[package]] -name = "derive_builder" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" -dependencies = [ - "derive_builder_core", - "syn", -] - [[package]] name = "deunicode" version = "1.4.4" @@ -1473,10 +1367,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "heck" @@ -1867,15 +1757,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" @@ -1984,15 +1865,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "lru" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "matchers" version = "0.1.0" @@ -2285,12 +2157,6 @@ dependencies = [ "regex", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pear" version = "0.2.9" @@ -2546,42 +2412,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "ratatui" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" -dependencies = [ - "bitflags 2.5.0", - "cassowary", - "compact_str", - "crossterm", - "itertools 0.12.1", - "lru", - "paste", - "serde", - "stability", - "strum", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - -[[package]] -name = "ratatui-widgets" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98128933d93564a011001af58b5b6ae117af11259fb71a689b1a367379c2830" -dependencies = [ - "bitflags 2.5.0", - "crossterm", - "derive_builder", - "itertools 0.13.0", - "ratatui", - "strum", - "thiserror", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -3190,27 +3020,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3273,16 +3082,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "stability" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "stable-pattern" version = "0.1.0" @@ -3301,12 +3100,6 @@ dependencies = [ "loom", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.10.0" @@ -3319,28 +3112,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7993a8e3a9e88a00351486baae9522c91b123a088f76469e5bd5cc17198ea87" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subtle" version = "2.5.0" @@ -3430,18 +3201,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -3836,28 +3607,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-truncate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" -dependencies = [ - "itertools 0.12.1", - "unicode-width", -] - -[[package]] -name = "unicode-width" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" - [[package]] name = "unicode-xid" version = "0.2.4" @@ -3956,7 +3705,7 @@ dependencies = [ "diesel", "diesel_migrations", "env_logger", - "itertools 0.12.1", + "itertools", "jwtk", "log", "rand", @@ -3977,10 +3726,7 @@ name = "vickyctl" version = "0.1.0" dependencies = [ "clap", - "crossterm", "log", - "ratatui", - "ratatui-widgets", "reqwest 0.12.4", "serde", "serde_json", @@ -4365,26 +4111,6 @@ dependencies = [ "is-terminal", ] -[[package]] -name = "zerocopy" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zeroize" version = "1.7.0" diff --git a/fairy/src/main.rs b/fairy/src/main.rs index 5c912fb..382144b 100644 --- a/fairy/src/main.rs +++ b/fairy/src/main.rs @@ -80,16 +80,16 @@ pub struct FlakeRef { #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "result")] pub enum TaskResult { - Success, - Error, + SUCCESS, + ERROR, } #[derive(Debug, Deserialize)] #[serde(tag = "state")] pub enum TaskStatus { - New, - Running, - Finished(TaskResult), + NEW, + RUNNING, + FINISHED(TaskResult), } #[derive(Debug, Deserialize)] @@ -173,9 +173,9 @@ async fn run_task(cfg: Arc, task: Task) { let result = match try_run_task(cfg.clone(), &task).await { Err(e) => { log::info!("task failed: {} {} {:?}", task.id, task.display_name, e); - TaskResult::Error + TaskResult::ERROR } - Ok(_) => TaskResult::Success, + Ok(_) => TaskResult::SUCCESS, }; tokio::time::sleep(std::time::Duration::from_secs(1)).await; let _ = api::<_, ()>( diff --git a/vicky/src/bin/vicky/locks.rs b/vicky/src/bin/vicky/locks.rs index 4128955..200b9ce 100644 --- a/vicky/src/bin/vicky/locks.rs +++ b/vicky/src/bin/vicky/locks.rs @@ -1,10 +1,8 @@ use diesel::PgConnection; -use rocket::{get, patch}; +use rocket::get; use rocket::serde::json::Json; -use uuid::Uuid; use vickylib::database::entities::{Database, Lock}; use vickylib::database::entities::lock::db_impl::LockDatabase; -use vickylib::database::entities::lock::PoisonedLock; use crate::auth::{Machine, User}; use crate::errors::AppError; @@ -13,11 +11,6 @@ async fn locks_get_poisoned(db: &Database) -> Result>, AppError> Ok(Json(poisoned_locks)) } -async fn locks_get_detailed_poisoned(db: &Database) -> Result>, AppError> { - let poisoned_locks: Vec = db.run(PgConnection::get_poisoned_locks_with_tasks).await?; - Ok(Json(poisoned_locks)) -} - #[get("/poisoned")] pub async fn locks_get_poisoned_user( db: Database, @@ -34,23 +27,6 @@ pub async fn locks_get_poisoned_machine( locks_get_poisoned(&db).await } - -#[get("/poisoned_detailed")] -pub async fn locks_get_detailed_poisoned_user( - db: Database, - _user: User, -) -> Result>, AppError> { - locks_get_detailed_poisoned(&db).await -} - -#[get("/poisoned_detailed", rank = 2)] -pub async fn locks_get_detailed_poisoned_machine( - db: Database, - _machine: Machine, -) -> Result>, AppError> { - locks_get_detailed_poisoned(&db).await -} - async fn locks_get_active(db: &Database) -> Result>, AppError> { let locks: Vec = db.run(PgConnection::get_active_locks).await?; Ok(Json(locks)) @@ -71,15 +47,3 @@ pub async fn locks_get_active_machine( ) -> Result>, AppError> { locks_get_active(&db).await } - -#[patch("/unlock/")] -pub async fn locks_unlock( - db: Database, - _user: Machine, // TODO: Should actually be user-only, but we don't have that yet - lock_id: String, -) -> Result<(), AppError> { - let lock_uuid = Uuid::try_parse(&lock_id)?; - - db.run(move |conn| conn.unlock_lock(&lock_uuid)).await?; - Ok(()) -} \ No newline at end of file diff --git a/vicky/src/bin/vicky/main.rs b/vicky/src/bin/vicky/main.rs index 94741a2..d2dc438 100644 --- a/vicky/src/bin/vicky/main.rs +++ b/vicky/src/bin/vicky/main.rs @@ -16,11 +16,7 @@ use vickylib::logs::LogDrain; use vickylib::s3::client::S3Client; use crate::events::{get_global_events, GlobalEvent}; -use crate::locks::{ - locks_get_active_machine, locks_get_active_user, locks_get_detailed_poisoned_machine, - locks_get_detailed_poisoned_user, locks_get_poisoned_machine, locks_get_poisoned_user, - locks_unlock, -}; +use crate::locks::{locks_get_active_machine, locks_get_active_user, locks_get_poisoned_machine, locks_get_poisoned_user}; use crate::tasks::{ tasks_add, tasks_claim, tasks_finish, tasks_get_logs, tasks_get_machine, tasks_get_user, tasks_put_logs, tasks_specific_get_machine, tasks_specific_get_user, @@ -81,7 +77,7 @@ fn run_migrations(connection: &mut impl MigrationHarness) -> Res Ok(_) => { log::info!("Migrations successfully completed"); Ok(()) - } + }, Err(e) => { log::error!("Error running migrations {e}"); Err(AppError::MigrationError(e.to_string())) @@ -89,11 +85,11 @@ fn run_migrations(connection: &mut impl MigrationHarness) -> Res } } -async fn run_rocket_migrations(rocket: Rocket) -> Result, Rocket> { +async fn run_rocket_migrations(rocket: Rocket) -> Result,Rocket> { let db: Database = Database::get_one(&rocket).await.unwrap(); match db.run(run_migrations).await { Ok(_) => Ok(rocket), - Err(_) => Err(rocket), + Err(_) => Err(rocket) } } @@ -121,16 +117,12 @@ async fn main() -> anyhow::Result<()> { let app_config = build_rocket.figment().extract::()?; - let oidc_config_resolved: OIDCConfigResolved = - reqwest::get(app_config.oidc_config.well_known_uri) - .await? - .json() - .await?; + let oidc_config_resolved: OIDCConfigResolved = reqwest::get(app_config.oidc_config.well_known_uri) + .await? + .json() + .await?; - log::info!( - "Fetched OIDC configuration, found jwks_uri={}", - oidc_config_resolved.jwks_uri - ); + log::info!("Fetched OIDC configuration, found jwks_uri={}", oidc_config_resolved.jwks_uri); let jwks_verifier = RemoteJwksVerifier::new( oidc_config_resolved.jwks_uri.clone(), @@ -170,10 +162,7 @@ async fn main() -> anyhow::Result<()> { .manage(oidc_config_resolved) .attach(Database::fairing()) .attach(AdHoc::config::()) - .attach(AdHoc::try_on_ignite( - "run migrations", - run_rocket_migrations, - )) + .attach(AdHoc::try_on_ignite("run migrations", run_rocket_migrations)) .mount("/api/v1/web-config", routes![get_web_config]) .mount("/api/v1/user", routes![get_user]) .mount("/api/v1/events", routes![get_global_events]) @@ -196,11 +185,8 @@ async fn main() -> anyhow::Result<()> { routes![ locks_get_poisoned_user, locks_get_poisoned_machine, - locks_get_detailed_poisoned_user, - locks_get_detailed_poisoned_machine, locks_get_active_user, locks_get_active_machine, - locks_unlock ], ) .launch() diff --git a/vicky/src/bin/vicky/tasks.rs b/vicky/src/bin/vicky/tasks.rs index 5a841ae..2269088 100644 --- a/vicky/src/bin/vicky/tasks.rs +++ b/vicky/src/bin/vicky/tasks.rs @@ -107,8 +107,8 @@ pub async fn tasks_get_logs<'a>( EventStream! { match task.status { - TaskStatus::New => {}, - TaskStatus::Running => { + TaskStatus::NEW => {}, + TaskStatus::RUNNING => { let mut recv = log_drain.send_handle.subscribe(); let existing_log_messages = log_drain.get_logs(task_uuid.to_string()).await.unwrap(); @@ -137,7 +137,7 @@ pub async fn tasks_get_logs<'a>( } } }, - TaskStatus::Finished(_) => { + TaskStatus::FINISHED(_) => { let logs = s3.get_logs(&id).await.unwrap(); for element in logs { yield Event::data(element) @@ -166,7 +166,7 @@ pub async fn tasks_put_logs( .ok_or(AppError::HttpError(Status::NotFound))?; match task.status { - TaskStatus::Running => { + TaskStatus::RUNNING => { log_drain.push_logs(id, logs.lines.clone())?; Ok(Json(())) } @@ -193,7 +193,7 @@ pub async fn tasks_claim( .run(move |conn| conn.get_task(next_task.id)) .await? .ok_or(AppError::HttpError(Status::NotFound))?; - task.status = TaskStatus::Running; + task.status = TaskStatus::RUNNING; let task2 = task.clone(); db.run(move |conn| conn.update_task(&task2)).await?; global_events.send(GlobalEvent::TaskUpdate { uuid: task.id })?; @@ -220,9 +220,9 @@ pub async fn tasks_finish( log_drain.finish_logs(&id).await?; - task.status = TaskStatus::Finished(finish.result.clone()); + task.status = TaskStatus::FINISHED(finish.result.clone()); - if finish.result == TaskResult::Error { + if finish.result == TaskResult::ERROR { task.locks.iter_mut().for_each(|lock| lock.poison(&task.id)); } @@ -253,7 +253,7 @@ pub async fn tasks_add( ) -> Result, AppError> { let task_uuid = Uuid::new_v4(); - let task = Task::builder() + let task_manifest = Task::builder() .with_id(task_uuid) .with_display_name(&task.display_name) .with_flake(&task.flake_ref.flake) @@ -262,16 +262,16 @@ pub async fn tasks_add( .requires_features(task.features.clone()) .build(); - if check_lock_conflict(&task) { + if check_lock_conflict(&task_manifest) { return Err(AppError::HttpError(Status::Conflict)); } - db.run(move |conn| conn.put_task(task)).await?; + db.run(move |conn| conn.put_task(&task_manifest)).await?; global_events.send(GlobalEvent::TaskAdd)?; let ro_task = RoTask { id: task_uuid, - status: TaskStatus::New, + status: TaskStatus::NEW, }; Ok(Json(ro_task)) diff --git a/vicky/src/lib/database/entities/lock.rs b/vicky/src/lib/database/entities/lock.rs index cba7154..a008e92 100644 --- a/vicky/src/lib/database/entities/lock.rs +++ b/vicky/src/lib/database/entities/lock.rs @@ -1,18 +1,14 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::database::entities::lock::db_impl::DbLock; -use crate::database::entities::Task; -use crate::database::entities::task::db_impl::DbTask; - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum Lock { - Write { + WRITE { name: String, poisoned: Option, }, - Read { + READ { name: String, poisoned: Option, }, @@ -30,20 +26,20 @@ impl Lock { matches!( (self, other), - (Lock::Write { .. }, Lock::Write { .. }) - | (Lock::Read { .. }, Lock::Write { .. }) - | (Lock::Write { .. }, Lock::Read { .. }) + (Lock::WRITE { .. }, Lock::WRITE { .. }) + | (Lock::READ { .. }, Lock::WRITE { .. }) + | (Lock::WRITE { .. }, Lock::READ { .. }) ) } pub fn poison(&mut self, by_task: &Uuid) { match self { - Lock::Write { + Lock::WRITE { ref mut poisoned, .. } => { *poisoned = Some(*by_task); } - Lock::Read { + Lock::READ { ref mut poisoned, .. } => { *poisoned = Some(*by_task); @@ -53,56 +49,27 @@ impl Lock { pub fn name(&self) -> &str { match self { - Lock::Write { name, .. } => name, - Lock::Read { name, .. } => name, + Lock::WRITE { name, .. } => name, + Lock::READ { name, .. } => name, } } pub fn is_poisoned(&self) -> bool { match self { - Lock::Write { poisoned, .. } => poisoned, - Lock::Read { poisoned, .. } => poisoned, + Lock::WRITE { poisoned, .. } => poisoned, + Lock::READ { poisoned, .. } => poisoned, } .is_some() } } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum PoisonedLock { - Write { id: Uuid, name: String, poisoned: Task }, - Read { id: Uuid, name: String, poisoned: Task }, -} - -impl From<(DbLock, DbTask)> for PoisonedLock { - fn from(value: (DbLock, DbTask)) -> Self { - let (lock, task) = value; - match lock.type_.as_str() { - // Other locks data is omitted here because of recursivity and since this is explicitly for a PoisonedLock - "WRITE" => PoisonedLock::Write { - id: lock.id.unwrap_or_default(), - name: lock.name, - poisoned: Task::from((task, vec![])), - }, - "READ" => PoisonedLock::Read { - id: lock.id.unwrap_or_default(), - name: lock.name, - poisoned: Task::from((task, vec![])), - }, - _ => panic!("Unexpected lock type received."), - } - } -} - pub mod db_impl { use diesel::prelude::*; - use diesel::update; use serde::Serialize; use uuid::Uuid; - use crate::database::entities::Lock; - use crate::database::entities::lock::PoisonedLock; - use crate::database::entities::task::db_impl::DbTask; use crate::database::entities::task::TaskStatus; + use crate::database::entities::Lock; use crate::database::schema::{locks, tasks}; use crate::errors::VickyError; @@ -116,15 +83,6 @@ pub mod db_impl { pub poisoned_by_task: Option, } - #[derive(Debug, Serialize)] - pub struct PoisonedDbLock { - pub id: Uuid, - pub task_id: Uuid, - pub name: String, - pub type_: String, - pub task: DbTask, - } - impl DbLock { pub fn from_lock(lock: &Lock, task_id: Uuid) -> Self { // Converting a Lock to a DbLock only happens when inserting or updating the database, @@ -133,14 +91,14 @@ pub mod db_impl { // for inserting a NewDbLock. Thus, id is set to -1 here. Maybe this can be improved wholly? // At least it works. match lock { - Lock::Write { name, poisoned } => DbLock { + Lock::WRITE { name, poisoned } => DbLock { id: None, task_id, name: name.clone(), type_: "WRITE".to_string(), poisoned_by_task: *poisoned, }, - Lock::Read { name, poisoned } => DbLock { + Lock::READ { name, poisoned } => DbLock { id: None, task_id, name: name.clone(), @@ -154,11 +112,11 @@ pub mod db_impl { impl From for Lock { fn from(lock: DbLock) -> Lock { match lock.type_.as_str() { - "WRITE" => Lock::Write { + "WRITE" => Lock::WRITE { name: lock.name, poisoned: lock.poisoned_by_task, }, - "READ" => Lock::Read { + "READ" => Lock::READ { name: lock.name, poisoned: lock.poisoned_by_task, }, @@ -174,9 +132,7 @@ pub mod db_impl { pub trait LockDatabase { fn get_poisoned_locks(&mut self) -> Result, VickyError>; - fn get_poisoned_locks_with_tasks(&mut self) -> Result, VickyError>; fn get_active_locks(&mut self) -> Result, VickyError>; - fn unlock_lock(&mut self, lock_uuid: &Uuid) -> Result<(), VickyError>; } impl LockDatabase for PgConnection { @@ -190,21 +146,6 @@ pub mod db_impl { Ok(poisoned_locks) } - fn get_poisoned_locks_with_tasks(&mut self) -> Result, VickyError> { - let poisoned_locks = { - let poisoned_db_locks = locks::table - .inner_join(tasks::table.on(locks::poisoned_by_task.eq(tasks::id.nullable()))) - .select((locks::all_columns, tasks::all_columns)) - .load::<(DbLock, DbTask)>(self)?; - poisoned_db_locks - .into_iter() - .map(PoisonedLock::from) - .collect() - }; - - Ok(poisoned_locks) - } - fn get_active_locks(&mut self) -> Result, VickyError> { let locks = { let db_locks: Vec = locks::table @@ -213,7 +154,7 @@ pub mod db_impl { .filter( locks::poisoned_by_task .is_not_null() - .or(tasks::status.eq(TaskStatus::Running.to_string())), + .or(tasks::status.eq(TaskStatus::RUNNING.to_string())), ) .load(self)?; db_locks.into_iter().map(Lock::from).collect() @@ -221,12 +162,5 @@ pub mod db_impl { Ok(locks) } - - fn unlock_lock(&mut self, lock_uuid: &Uuid) -> Result<(), VickyError> { - update(locks::table.filter(locks::id.eq(lock_uuid))) - .set(locks::poisoned_by_task.eq(None::)) - .execute(self)?; - Ok(()) - } } } diff --git a/vicky/src/lib/database/entities/task.rs b/vicky/src/lib/database/entities/task.rs index 1a700f0..340a489 100644 --- a/vicky/src/lib/database/entities/task.rs +++ b/vicky/src/lib/database/entities/task.rs @@ -1,22 +1,20 @@ use crate::database::entities::lock::Lock; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::database::entities::lock::db_impl::DbLock; -use crate::database::entities::task::db_impl::DbTask; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "result")] pub enum TaskResult { - Success, - Error, + SUCCESS, + ERROR, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "state")] pub enum TaskStatus { - New, - Running, - Finished(TaskResult), + NEW, + RUNNING, + FINISHED(TaskResult), } type FlakeURI = String; @@ -63,7 +61,7 @@ impl Default for TaskBuilder { TaskBuilder { id: None, display_name: None, - status: TaskStatus::New, + status: TaskStatus::NEW, locks: Vec::new(), flake_ref: FlakeRef { flake: "".to_string(), @@ -91,12 +89,12 @@ impl TaskBuilder { } pub fn with_read_lock>(mut self, name: S) -> Self { - self.locks.push(Lock::Read { name: name.into(), poisoned: None }); + self.locks.push(Lock::READ { name: name.into(), poisoned: None }); self } pub fn with_write_lock>(mut self, name: S) -> Self { - self.locks.push(Lock::Write { name: name.into(), poisoned: None }); + self.locks.push(Lock::WRITE { name: name.into(), poisoned: None }); self } @@ -166,26 +164,11 @@ impl TaskBuilder { } } -impl From<(DbTask, Vec)> for Task { - fn from(value: (DbTask, Vec)) -> Self { - let (task, locks) = value; - Task { - id: task.id, - display_name: task.display_name, - status: task.status.as_str().try_into().expect("Database corrupted"), - locks: locks.into_iter().map(Lock::from).collect(), - flake_ref: FlakeRef { - flake: task.flake_ref_uri, - args: task.flake_ref_args, - }, - features: task.features, - } - } -} - // this was on purpose because these macro-generated entity types // mess up the whole namespace and HAVE to be scoped pub mod db_impl { + use crate::database::entities::lock::Lock; + use crate::database::entities::task::FlakeRef; use crate::database::entities::task::{Task, TaskResult, TaskStatus}; use crate::errors::VickyError; use diesel::{insert_into, update, AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Connection}; @@ -197,28 +180,27 @@ pub mod db_impl { use crate::database::schema::locks; use crate::database::schema::tasks; use itertools::Itertools; - use serde::Serialize; use crate::database::schema::locks::task_id; - #[derive(Insertable, Queryable, AsChangeset, Debug, Serialize)] + #[derive(Insertable, Queryable, AsChangeset, Debug)] #[diesel(table_name = tasks)] - pub struct DbTask { + struct DbTask { pub id: Uuid, pub display_name: String, pub status: String, - pub features: Vec, + pub features: Vec>, pub flake_ref_uri: String, - pub flake_ref_args: Vec, + pub flake_ref_args: Vec>, } impl Display for TaskStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let str = match self { - TaskStatus::New => "NEW", - TaskStatus::Running => "RUNNING", - TaskStatus::Finished(r) => match r { - TaskResult::Success => "FINISHED::SUCCESS", - TaskResult::Error => "FINISHED::ERROR", + TaskStatus::NEW => "NEW", + TaskStatus::RUNNING => "RUNNING", + TaskStatus::FINISHED(r) => match r { + TaskResult::SUCCESS => "FINISHED::SUCCESS", + TaskResult::ERROR => "FINISHED::ERROR", }, }; write!(f, "{}", str) @@ -226,28 +208,28 @@ pub mod db_impl { } impl TryFrom<&str> for TaskStatus { - type Error = &'static str; + type Error = (); fn try_from(str: &str) -> Result { match str { - "NEW" => Ok(TaskStatus::New), - "RUNNING" => Ok(TaskStatus::Running), - "FINISHED::SUCCESS" => Ok(TaskStatus::Finished(TaskResult::Success)), - "FINISHED::ERROR" => Ok(TaskStatus::Finished(TaskResult::Error)), - _ => Err("Could not deserialize to TaskStatus"), + "NEW" => Ok(TaskStatus::NEW), + "RUNNING" => Ok(TaskStatus::RUNNING), + "FINISHED::SUCCESS" => Ok(TaskStatus::FINISHED(TaskResult::SUCCESS)), + "FINISHED::ERROR" => Ok(TaskStatus::FINISHED(TaskResult::ERROR)), + _ => Err(()), } } } - impl From for DbTask { - fn from(task: Task) -> DbTask { + impl From<&Task> for DbTask { + fn from(task: &Task) -> DbTask { DbTask { id: task.id, - display_name: task.display_name, + display_name: task.display_name.clone(), status: task.status.to_string(), - features: task.features, - flake_ref_uri: task.flake_ref.flake, - flake_ref_args: task.flake_ref.args, + features: task.features.clone().iter().cloned().map(Some).collect(), + flake_ref_uri: task.flake_ref.flake.clone(), + flake_ref_args: task.flake_ref.args.iter().cloned().map(Some).collect(), } } } @@ -255,7 +237,7 @@ pub mod db_impl { pub trait TaskDatabase { fn get_all_tasks(&mut self) -> Result, VickyError>; fn get_task(&mut self, task_id: Uuid) -> Result, VickyError>; - fn put_task(&mut self, task: Task) -> Result<(), VickyError>; + fn put_task(&mut self, task: &Task) -> Result<(), VickyError>; fn update_task(&mut self, task: &Task) -> Result<(), VickyError>; } @@ -266,17 +248,31 @@ pub mod db_impl { // prefetching all locks here, so we don't run into the N+1 Query Problem and distribute them let all_locks = locks::table.load::(self).unwrap_or_else(|_| vec![]); - let mut lock_map: HashMap<_, Vec> = all_locks + let lock_map: HashMap<_, Vec> = all_locks .into_iter() - .map(|db_lock| (db_lock.task_id, db_lock)) + .map(|db_lock| (db_lock.task_id, db_lock.into())) .into_group_map(); let real_tasks: Vec = db_tasks .into_iter() .map(|t| { - let real_locks = lock_map.remove(&t.id).unwrap_or_default(); - - (t, real_locks).into() + let real_locks = lock_map.get(&t.id).cloned().unwrap_or_default(); + + Task { + id: t.id, + display_name: t.display_name, + status: t + .status + .as_str() + .try_into() + .expect("Got unexpected status value. Database is corrupted"), + locks: real_locks, + features: t.features.into_iter().flatten().collect(), + flake_ref: FlakeRef { + flake: t.flake_ref_uri, + args: t.flake_ref_args.into_iter().flatten().collect(), + }, + } }) .collect(); @@ -291,12 +287,26 @@ pub mod db_impl { }; let db_locks: Vec = locks::table.filter(task_id.eq(task_id)).load::(self)?; - let task = (db_task, db_locks).into(); + let task = Task { + id: db_task.id, + display_name: db_task.display_name, + locks: db_locks.into_iter().map(|l| l.into()).collect(), + features: db_task.features.into_iter().flatten().collect(), + flake_ref: FlakeRef { + flake: db_task.flake_ref_uri, + args: db_task.flake_ref_args.into_iter().flatten().collect(), + }, + status: db_task + .status + .as_str() + .try_into() + .expect("Got unexpected status value. Database is corrupted"), + }; Ok(Some(task)) } - fn put_task(&mut self, task: Task) -> Result<(), VickyError> { + fn put_task(&mut self, task: &Task) -> Result<(), VickyError> { self.transaction(|conn| { let db_locks: Vec = task .locks @@ -322,7 +332,7 @@ pub mod db_impl { // FIXME: Conversion from DbLock to Lock drops id. No way to update locks here. // this is just a workaround for now. Should behave fine though // and is more performant. - if task.status == TaskStatus::Finished(TaskResult::Error) { + if task.status == TaskStatus::FINISHED(TaskResult::ERROR) { update(locks::table.filter(task_id.eq(task.id))) .set(locks::poisoned_by_task.eq(task.id)) .execute(self)?; diff --git a/vicky/src/lib/database/schema.rs b/vicky/src/lib/database/schema.rs index d7fd968..15a0f1a 100644 --- a/vicky/src/lib/database/schema.rs +++ b/vicky/src/lib/database/schema.rs @@ -16,9 +16,9 @@ diesel::table! { id -> Uuid, display_name -> Varchar, status -> Varchar, - features -> Array, + features -> Array>, flake_ref_uri -> Varchar, - flake_ref_args -> Array, + flake_ref_args -> Array>, } } @@ -30,6 +30,8 @@ diesel::table! { } } +diesel::joinable!(locks -> tasks (task_id)); + diesel::allow_tables_to_appear_in_same_query!( locks, tasks, diff --git a/vicky/src/lib/vicky/scheduler.rs b/vicky/src/lib/vicky/scheduler.rs index 60e8bfe..bba3afb 100644 --- a/vicky/src/lib/vicky/scheduler.rs +++ b/vicky/src/lib/vicky/scheduler.rs @@ -56,7 +56,7 @@ impl<'a> Scheduler<'a> { }; for task in s.tasks { - if task.status != TaskStatus::Running { + if task.status != TaskStatus::RUNNING { continue; } @@ -87,7 +87,7 @@ impl<'a> Scheduler<'a> { } fn should_pick_task(&self, task: &Task) -> bool { - task.status == TaskStatus::New + task.status == TaskStatus::NEW && self.supports_all_features(task) && self.is_unconstrained(task) } @@ -114,11 +114,11 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .build(), ]; @@ -130,12 +130,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_read_lock("foo 1") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_read_lock("foo 1") .build(), ]; @@ -148,12 +148,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_write_lock("foo1") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_write_lock("foo2") .build(), ]; @@ -166,12 +166,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_write_lock("foo1") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_write_lock("foo1") .build(), ]; @@ -185,12 +185,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_write_lock("foo1") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .with_write_lock("foo1") .build(), ]; @@ -205,12 +205,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .requires_feature("huge_cpu") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .requires_feature("huge_cpu") .build(), ]; @@ -225,12 +225,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .requires_feature("huge_cpu") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .requires_feature("huge_cpu") .build(), ]; @@ -246,12 +246,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_write_lock("foo1") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .with_write_lock("foo2") .build(), ]; @@ -266,12 +266,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_read_lock("foo1") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .with_read_lock("foo1") .build(), ]; @@ -286,12 +286,12 @@ mod tests { let tasks = vec![ Task::builder() .with_display_name("Test 1") - .with_status(TaskStatus::Running) + .with_status(TaskStatus::RUNNING) .with_write_lock("foo1") .build(), Task::builder() .with_display_name("Test 2") - .with_status(TaskStatus::New) + .with_status(TaskStatus::NEW) .with_read_lock("foo1") .build(), ]; @@ -307,7 +307,7 @@ mod tests { .with_display_name("I need to do something") .with_write_lock("Entire Prod Cluster") .build()]; - let poisoned_locks = vec![Lock::Write { + let poisoned_locks = vec![Lock::WRITE { name: "Entire Prod Cluster".to_string(), poisoned: Some(Uuid::new_v4()), }]; @@ -329,7 +329,7 @@ mod tests { .with_write_lock("Entire Staging Cluster") .build(), ]; - let poisoned_locks = vec![Lock::Write { + let poisoned_locks = vec![Lock::WRITE { name: "Entire Prod Cluster".to_string(), poisoned: Some(Uuid::new_v4()), }]; @@ -348,7 +348,7 @@ mod tests { .with_display_name("I need to do something") .with_read_lock("Entire Prod Cluster") .build()]; - let poisoned_locks = vec![Lock::Read { + let poisoned_locks = vec![Lock::READ { name: "Entire Prod Cluster".to_string(), poisoned: Some(Uuid::new_v4()), }]; diff --git a/vickyctl/Cargo.toml b/vickyctl/Cargo.toml index 42c546d..fac3176 100644 --- a/vickyctl/Cargo.toml +++ b/vickyctl/Cargo.toml @@ -5,13 +5,10 @@ edition = "2021" [dependencies] clap = { version = "4.5.4", features = ["derive", "env", "suggestions"] } -uuid = { version = "1.8.0", features = ["v4", "serde"] } +uuid = { version = "1.8.0", features = ["v4"] } reqwest = { version = "0.12.4", features = ["blocking"] } log = "0.4.21" serde = { version = "1.0.200", features = [ "derive" ]} serde_json = "1.0.116" yansi = "1.0.1" which = "6.0.1" -ratatui = { version = "0.26.3", features = ["serde"] } -ratatui-widgets = "0.1.6" -crossterm = "0.27.0" diff --git a/vickyctl/src/error.rs b/vickyctl/src/error.rs index 3b871ef..b69981b 100644 --- a/vickyctl/src/error.rs +++ b/vickyctl/src/error.rs @@ -8,7 +8,7 @@ pub enum Error { Io(std::io::Error), Json(serde_json::Error), #[allow(dead_code)] - Custom(&'static str), + Custom(String), } impl From for Error { diff --git a/vickyctl/src/locks.rs b/vickyctl/src/locks.rs index 8de47ad..e2a831e 100644 --- a/vickyctl/src/locks.rs +++ b/vickyctl/src/locks.rs @@ -1,161 +1,30 @@ -use ratatui::widgets::*; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use crate::{AppContext, humanize, LocksArgs}; +use crate::{humanize, LocksArgs}; use crate::error::Error; use crate::http_client::prepare_client; -// TODO: REFACTOR EVERYTHING - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(tag = "result")] -pub enum TaskResult { - Success, - Error, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(tag = "state")] -pub enum TaskStatus { - New, - Running, - Finished(TaskResult), -} - -type FlakeURI = String; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct FlakeRef { - pub flake: FlakeURI, - pub args: Vec, -} - -type Maow = u8; // this does not exist. look away. it's all for a reason. - -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -pub struct Task { - pub id: Uuid, - pub display_name: String, - pub status: TaskStatus, - pub locks: Vec, - pub flake_ref: FlakeRef, - pub features: Vec, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum PoisonedLock { - Write { - id: String, - name: String, - poisoned: Task, - }, - Read { - id: String, - name: String, - poisoned: Task, - }, -} - -impl PoisonedLock { - pub fn id(&self) -> &str { - match self { - PoisonedLock::Write { id, .. } => id, - PoisonedLock::Read { id, .. } => id, - } - } - - pub fn name(&self) -> &str { - match self { - PoisonedLock::Write { name, .. } => name, - PoisonedLock::Read { name, .. } => name, - } - } - - pub fn get_poisoned_by(&self) -> &Task { - match self { - PoisonedLock::Write { poisoned, .. } => poisoned, - PoisonedLock::Read { poisoned, .. } => poisoned, - } - } - - pub fn get_type(&self) -> &'static str { - match self { - PoisonedLock::Write { .. } => "WRITE", - PoisonedLock::Read { .. } => "READ", - } +pub fn get_locks_endpoint(locks_args: &LocksArgs) -> &'static str { + if locks_args.active { + "api/v1/locks/active" + } else { + "api/v1/locks/poisoned" } } -impl<'a> From<&'a PoisonedLock> for Row<'a> { - fn from(value: &'a PoisonedLock) -> Self { - let poisoned_by = value.get_poisoned_by(); - let task_name = poisoned_by.display_name.as_str(); - let name = value.name(); - let ty = value.get_type(); - let uri = poisoned_by.flake_ref.flake.as_str(); - Row::new(vec![name, ty, task_name, uri]) - } -} - -enum LockType { - Poisoned, - Active, -} - -impl From<&LocksArgs> for LockType { - fn from(value: &LocksArgs) -> Self { - match (value.poisoned, value.active) { - (true, false) | (false, false) => LockType::Poisoned, - (false, true) => LockType::Active, - (_, _) => panic!("Cannot use active and poisoned flags at the same time."), - } - } -} - -fn get_locks_endpoint(lock_type: LockType, detailed: bool) -> &'static str { - match lock_type { - LockType::Poisoned => match detailed { - false => "api/v1/locks/poisoned", - true => "api/v1/locks/poisoned_detailed", - }, - LockType::Active => "api/v1/locks/active", +pub fn show_locks(locks_args: &LocksArgs) -> Result<(), Error> { + if locks_args.ctx.humanize { + humanize::ensure_jless("lock")?; } -} -fn fetch_locks_raw(ctx: &AppContext, lock_type: LockType, detailed: bool) -> Result { - let client = prepare_client(ctx)?; + let client = prepare_client(&locks_args.ctx)?; let request = client .get(format!( "{}/{}", - ctx.vicky_url, - get_locks_endpoint(lock_type, detailed) + locks_args.ctx.vicky_url, get_locks_endpoint(locks_args) )) .build()?; let response = client.execute(request)?.error_for_status()?; - let locks = response.text()?; - Ok(locks) -} - -pub(crate) fn fetch_detailed_poisoned_locks(ctx: &AppContext) -> Result, Error> { - let raw_locks = fetch_locks_raw(ctx, LockType::Poisoned, true)?; - let locks: Vec = serde_json::from_str(&raw_locks)?; - Ok(locks) -} - -pub fn show_locks(locks_args: &LocksArgs) -> Result<(), Error> { - if locks_args.ctx.humanize { - humanize::ensure_jless("lock")?; - } - if locks_args.active && locks_args.poisoned { - return Err(Error::Custom( - "Cannot use active and poisoned lock type at the same time.", - )); - } - - let locks_json = fetch_locks_raw(&locks_args.ctx, LockType::from(locks_args), false)?; - - humanize::handle_user_response(&locks_args.ctx, &locks_json)?; + let text = response.text()?; + humanize::handle_user_response(&locks_args.ctx, &text)?; Ok(()) } diff --git a/vickyctl/src/main.rs b/vickyctl/src/main.rs index a9db76a..783c229 100644 --- a/vickyctl/src/main.rs +++ b/vickyctl/src/main.rs @@ -3,14 +3,12 @@ mod humanize; mod tasks; mod error; mod locks; -mod tui; use crate::tasks::{claim_task, create_task, finish_task}; use clap::{Args, Parser, Subcommand}; use uuid::Uuid; use yansi::Paint; -// TODO: Add abouts to arguments #[derive(Parser, Debug, Clone)] struct AppContext { #[clap(env)] @@ -75,24 +73,12 @@ struct LocksArgs { poisoned: bool, } -#[derive(Args, Debug)] -#[command(version, about = "Show all poisoned locks vicky is managing", long_about = None)] -struct ResolveArgs { - #[command(flatten)] - ctx: AppContext, - #[clap(long)] - all: bool, - #[clap(long,short)] - task_id: Option, -} - #[derive(Parser, Debug)] #[command(version, about, long_about = None)] enum Cli { Task(TaskArgs), Tasks(TasksArgs), - Locks(LocksArgs), - Resolve(ResolveArgs), + Locks(LocksArgs) } fn main() { @@ -106,7 +92,6 @@ fn main() { }, Cli::Tasks(tasks_args) => tasks::show_tasks(&tasks_args), Cli::Locks(locks_args) => locks::show_locks(&locks_args), - Cli::Resolve(resolve_args) => tui::resolve_lock(&resolve_args) }; match error { diff --git a/vickyctl/src/tui/lock_resolver.rs b/vickyctl/src/tui/lock_resolver.rs deleted file mode 100644 index 81e9c46..0000000 --- a/vickyctl/src/tui/lock_resolver.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::io; - -use crossterm::{event, execute}; -use crossterm::event::{Event, KeyCode, KeyEvent}; -use crossterm::terminal::{ - disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, -}; -use ratatui::backend::CrosstermBackend; -use ratatui::prelude::*; -use ratatui::widgets::*; - -use crate::{AppContext, ResolveArgs}; -use crate::error::Error; -use crate::http_client::prepare_client; -use crate::locks::PoisonedLock; -use crate::tui::popup::draw_centered_popup; - -pub fn resolve_lock(resolve_args: &ResolveArgs) -> Result<(), Error> { - let mut locks = crate::locks::fetch_detailed_poisoned_locks(&resolve_args.ctx)?; - - enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen)?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - let mut state = TableState::default(); - state.select(Some(0)); - let mut selected_task: Option = None; - let mut selected_button: bool = false; - - let mut should_quit = false; - while !should_quit { - should_quit = handle_events( - &mut state, - locks.len(), - &mut selected_task, - &mut selected_button, - resolve_args, - &mut locks, - )?; - terminal.draw(|f| ui(f, &locks, &mut state, &selected_task, &mut selected_button))?; - } - - disable_raw_mode()?; - execute!(terminal.backend_mut(), LeaveAlternateScreen)?; - - Ok(()) -} - -fn unlock_and_refresh( - resolve_args: &ResolveArgs, - locks: &mut Vec, - selected_task: &mut Option, -) -> Result<(), Error> { - if let Some(task_idx) = selected_task { - unlock_lock(&resolve_args.ctx, &locks[*task_idx])?; - *locks = crate::locks::fetch_detailed_poisoned_locks(&resolve_args.ctx)?; - *selected_task = None; - } - Ok(()) -} - -fn unlock_lock(ctx: &AppContext, lock_to_clear: &PoisonedLock) -> Result<(), Error> { - let client = prepare_client(ctx)?; - let request = client - .patch(format!( - "{}/api/v1/locks/unlock/{}", - ctx.vicky_url, - lock_to_clear.id() - )) - .build()?; - client.execute(request)?.error_for_status()?; - Ok(()) -} - -fn handle_popup( - selected_task: &mut Option, - selected_button: &mut bool, - key: &KeyEvent, - args: &ResolveArgs, - locks: &mut Vec, -) -> Result<(), Error> { - if key.code == KeyCode::Left || key.code == KeyCode::Char('y') { - *selected_button = true; - } else if key.code == KeyCode::Right || key.code == KeyCode::Char('n') { - *selected_button = false; - } - - if key.code == KeyCode::Char('y') || (key.code == KeyCode::Enter && *selected_button) { - unlock_and_refresh(args, locks, selected_task)?; - } else if key.code == KeyCode::Char('n') || (key.code == KeyCode::Enter && !*selected_button) { - *selected_task = None; - } - - Ok(()) -} - -fn handle_events( - state: &mut TableState, - lock_amount: usize, - selected_task: &mut Option, - selected_button: &mut bool, - args: &ResolveArgs, - locks: &mut Vec, -) -> Result { - if event::poll(std::time::Duration::from_millis(50))? { - if let Event::Key(key) = event::read()? { - if key.kind == event::KeyEventKind::Press { - if key.code == KeyCode::Char('q') || key.code == KeyCode::Esc { - if selected_task.is_some() { - *selected_task = None; - return Ok(false); - } - return Ok(true); - } - - match selected_task { - None => handle_task_list(state, lock_amount, selected_task, &key), - Some(_) => handle_popup(selected_task, selected_button, &key, args, locks)?, - } - } - } - } - Ok(false) -} - -fn handle_task_list( - state: &mut TableState, - lock_amount: usize, - selected_task: &mut Option, - key: &KeyEvent, -) { - match state.selected_mut() { - None => (), - Some(cur) => { - if (key.code == KeyCode::Up || key.code == KeyCode::Char('k')) && *cur > 0 { - *cur -= 1; - } else if (key.code == KeyCode::Down || key.code == KeyCode::Char('j')) - && *cur < lock_amount - 1 - { - *cur += 1; - } else if key.code == KeyCode::Enter { - *selected_task = Some(*cur); - } - } - }; -} - -#[allow(dead_code)] -fn get_longest_len<'a, T>(str_iter: T) -> u16 -where - T: Iterator, -{ - str_iter - .map(|l| l.len()) - .max() - .map_or(0, |len| u16::try_from(len).unwrap_or(u16::MAX)) -} - -// This will not make the table equally spaced, but instead use minimal space. -#[allow(dead_code)] -fn minimal_widths(locks: &[PoisonedLock]) -> [Constraint; 4] { - - [ - Constraint::Max(get_longest_len(locks.iter().map(|l| l.name()))), - Constraint::Max(5), - Constraint::Max(get_longest_len( - locks - .iter() - .map(|l| l.get_poisoned_by().display_name.as_str()), - ).max("Failed Task Name".len() as u16)), - Constraint::Min(get_longest_len( - locks - .iter() - .map(|l| l.get_poisoned_by().flake_ref.flake.as_str()), - ).max("Task Flake URI".len() as u16)), - ] -} - -fn draw_task_picker(f: &mut Frame, locks: &[PoisonedLock], state: &mut TableState) { - let rows: Vec = locks.iter().map(|l| l.into()).collect(); - - // let widths = minimal_widths(locks); - let widths = &[]; - let table = Table::new(rows, widths) - .block( - Block::default() - .title("Manually Resolve Locks") - .title_alignment(Alignment::Center) - .borders(Borders::ALL), - ) - .header( - Row::new(vec!["Name", "Type", "Failed Task Name", "Task Flake URI"]) - .set_style(Style::default().bold().italic()), - ) - .highlight_symbol(">>") - .highlight_style(Style::default().fg(Color::Green).italic()) - .highlight_spacing(HighlightSpacing::Always); - f.render_stateful_widget(table, f.size(), state); -} - -fn draw_confirm_clear( - f: &mut Frame, - locks: &[PoisonedLock], - selected: usize, - button_select: &mut bool, -) { - let lock = locks.get(selected); - if lock.is_none() { - return; - } - let lock = lock.unwrap(); - draw_centered_popup( - f, - &format!("Do you really want to clear the lock {}?", lock.name()), - button_select, - ); -} - -fn ui( - f: &mut Frame, - locks: &[PoisonedLock], - state: &mut TableState, - selected_task: &Option, - button_select: &mut bool, -) { - draw_task_picker(f, locks, state); - if let Some(selected) = selected_task { - draw_confirm_clear(f, locks, *selected, button_select); - } -} diff --git a/vickyctl/src/tui/mod.rs b/vickyctl/src/tui/mod.rs deleted file mode 100644 index 275a348..0000000 --- a/vickyctl/src/tui/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod lock_resolver; -pub mod popup; -pub mod utils; - -pub use lock_resolver::resolve_lock; \ No newline at end of file diff --git a/vickyctl/src/tui/popup.rs b/vickyctl/src/tui/popup.rs deleted file mode 100644 index 993637f..0000000 --- a/vickyctl/src/tui/popup.rs +++ /dev/null @@ -1,34 +0,0 @@ -use ratatui::Frame; -use ratatui::layout::{Alignment, Rect}; -use ratatui::prelude::*; -use ratatui::widgets::*; -use crate::tui::utils::centered_rect; - -pub fn draw_centered_popup(f: &mut Frame, title: &str, button_select: &mut bool) { - let mut yes = Text::from("Yes").bold().alignment(Alignment::Center); - let mut no = Text::from("No").bold().alignment(Alignment::Center); - if *button_select { - yes = yes.fg(Color::Green); - } else { - no = no.fg(Color::Green); - } - - let container = Block::default() - .borders(Borders::ALL) - .title(title) - .title_alignment(Alignment::Center); - let centered_rect = centered_rect(60, 20, f.size()); - let half_y = centered_rect.height / 2; - let half_x = centered_rect.width / 2; - let left_side = Rect::new(centered_rect.x, centered_rect.y + half_y, half_x, 1); - let right_side = Rect::new( - centered_rect.x + half_x, - centered_rect.y + half_y, - half_x, - 1, - ); - - f.render_widget(container, centered_rect); - f.render_widget(yes, left_side); - f.render_widget(no, right_side); -} diff --git a/vickyctl/src/tui/utils.rs b/vickyctl/src/tui/utils.rs deleted file mode 100644 index 8967281..0000000 --- a/vickyctl/src/tui/utils.rs +++ /dev/null @@ -1,28 +0,0 @@ -use ratatui::layout::{Constraint, Direction, Layout, Rect}; - -// Source: https://github.com/fdehau/tui-rs/blob/335f5a4563342f9a4ee19e2462059e1159dcbf25/examples/popup.rs#L104C1-L128C2 -pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { - let popup_layout = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage((100 - percent_y) / 2), - Constraint::Percentage(percent_y), - Constraint::Percentage((100 - percent_y) / 2), - ] - .as_ref(), - ) - .split(r); - - Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage((100 - percent_x) / 2), - Constraint::Percentage(percent_x), - Constraint::Percentage((100 - percent_x) / 2), - ] - .as_ref(), - ) - .split(popup_layout[1])[1] -}