diff --git a/Cargo.lock b/Cargo.lock index e4dc6ac5..c1979d4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,6 +561,15 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -629,6 +638,33 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.9.1", + "crossterm_winapi", + "derive_more 2.1.1", + "document-features", + "mio", + "parking_lot", + "rustix 1.0.8", + "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" version = "0.3.65" @@ -705,7 +741,7 @@ dependencies = [ "console", "cucumber-codegen", "cucumber-expressions", - "derive_more", + "derive_more 0.99.20", "drain_filter_polyfill", "either", "futures", @@ -745,7 +781,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d794fed319eea24246fb5f57632f7ae38d61195817b7eb659455aa5bdd7c1810" dependencies = [ - "derive_more", + "derive_more 0.99.20", "either", "nom", "nom_locate", @@ -855,6 +891,28 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.104", +] + [[package]] name = "digest" version = "0.10.7" @@ -925,6 +983,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "drain_filter_polyfill" version = "0.1.3" @@ -1176,6 +1243,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1618,6 +1694,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inquire" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6654738b8024300cf062d04a1c13c10c8e2cea598ec1c47dc9b6641159429756" +dependencies = [ + "bitflags 2.9.1", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "unicode-segmentation", + "unicode-width 0.2.1", +] + [[package]] name = "inventory" version = "0.3.21" @@ -1796,6 +1886,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.13" @@ -1886,6 +1982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -2687,6 +2784,15 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -2855,6 +2961,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -3029,6 +3141,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -3642,6 +3775,7 @@ dependencies = [ "futures-util", "http", "indicatif", + "inquire", "promptly", "reqwest", "reqwest-eventsource", diff --git a/Cargo.toml b/Cargo.toml index 4b483bff..90976384 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ tracing-appender = "0.2" tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] } url = { version = "2", features = ["serde"] } webbrowser = "1" +inquire = "0.9.4" # The profile that 'dist' will build with [profile.dist] diff --git a/crates/tower-cmd/Cargo.toml b/crates/tower-cmd/Cargo.toml index cb6a5f4f..dac7d31b 100644 --- a/crates/tower-cmd/Cargo.toml +++ b/crates/tower-cmd/Cargo.toml @@ -39,6 +39,7 @@ schemars = "1.0" toml = { workspace = true } toml_edit = { workspace = true } tracing-subscriber = { workspace = true } +inquire = { workspace = true } [dev-dependencies] tempfile = "3.12" diff --git a/crates/tower-cmd/src/api.rs b/crates/tower-cmd/src/api.rs index a61eb0be..c882026c 100644 --- a/crates/tower-cmd/src/api.rs +++ b/crates/tower-cmd/src/api.rs @@ -1021,6 +1021,17 @@ impl ResponseEntity for tower_api::apis::default_api::ListEnvironmentsSuccess { } } +impl ResponseEntity for tower_api::apis::default_api::DeleteEnvironmentSuccess { + type Data = tower_api::models::DeleteEnvironmentResponse; + + fn extract_data(self) -> Option { + match self { + Self::Status200(resp) => Some(resp), + Self::UnknownValue(_) => None, + } + } +} + pub async fn list_environments( config: &Config, ) -> Result< @@ -1067,6 +1078,25 @@ pub async fn create_environment( .await } +pub async fn delete_environment( + config: &Config, + name: &str, +) -> Result< + tower_api::models::DeleteEnvironmentResponse, + Error, +> { + let api_config = &config.into(); + + let params = tower_api::apis::default_api::DeleteEnvironmentParams { + name: name.to_string(), + }; + + unwrap_api_response(tower_api::apis::default_api::delete_environment( + api_config, params, + )) + .await +} + pub async fn list_schedules( config: &Config, app_name: Option<&str>, diff --git a/crates/tower-cmd/src/environments.rs b/crates/tower-cmd/src/environments.rs index a9dfad37..939f4146 100644 --- a/crates/tower-cmd/src/environments.rs +++ b/crates/tower-cmd/src/environments.rs @@ -1,5 +1,6 @@ use clap::{value_parser, Arg, ArgMatches, Command}; use config::Config; +use inquire::Confirm; use crate::{api, output}; @@ -8,6 +9,18 @@ pub fn environments_cmd() -> Command { .about("Manage the environments in your current Tower account") .arg_required_else_help(true) .subcommand(Command::new("list").about("List all of your environments")) + .subcommand( + Command::new("delete") + .arg( + Arg::new("name") + .short('n') + .long("name") + .value_parser(value_parser!(String)) + .required(true) + .action(clap::ArgAction::Set), + ) + .about("Delete an environment"), + ) .subcommand( Command::new("create") .arg( @@ -50,3 +63,33 @@ pub async fn do_create(config: Config, args: &ArgMatches) { output::success(&format!("Environment '{}' created", name)); } + +pub async fn do_delete(config: Config, args: &ArgMatches) { + let name = args.get_one::("name").unwrap_or_else(|| { + output::die("Environment name (--name) is required"); + }); + + let ans = Confirm::new(&format!( + "Are you sure you want to delete your {name} environment?" + )) + .with_default(false) + .prompt(); + + match ans { + Ok(true) => { + output::with_spinner( + &format!("Deleting environment {name}"), + api::delete_environment(&config, name), + ) + .await; + + output::success(&format!("Environment '{name}' deleted")); + } + Ok(false) => output::write("Ok.\n"), + Err(_) => { + output::error( + "Something went wrong. Please try again, and contact us if the issue persists.", + ); + } + } +} diff --git a/crates/tower-cmd/src/lib.rs b/crates/tower-cmd/src/lib.rs index 3560d3c4..0b630091 100644 --- a/crates/tower-cmd/src/lib.rs +++ b/crates/tower-cmd/src/lib.rs @@ -170,6 +170,7 @@ impl App { Some(("create", args)) => { environments::do_create(sessionized_config, args).await } + Some(("delete", args)) => environments::do_delete(sessionized_config, args).await, _ => { environments::environments_cmd().print_help().unwrap(); }