diff --git a/Cargo.lock b/Cargo.lock index cf5a5f084..8fd854818 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,6 +1433,7 @@ dependencies = [ "git2", "headers", "ignore", + "indicatif", "indoc", "log", "openssl", @@ -3290,6 +3291,18 @@ dependencies = [ "serde", ] +[[package]] +name = "indicatif" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4295cbb7573c16d310e99e713cf9e75101eb190ab31fccd35f2d2691b4352b19" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "indoc" version = "1.0.7" @@ -3949,6 +3962,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "objc" version = "0.2.7" @@ -4421,6 +4440,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15eb2c6e362923af47e13c23ca5afb859e83d54452c55b0b9ac763b8f7c1ac16" + [[package]] name = "portpicker" version = "0.1.1" diff --git a/cargo-shuttle/Cargo.toml b/cargo-shuttle/Cargo.toml index 30e286c84..ce4b92f43 100644 --- a/cargo-shuttle/Cargo.toml +++ b/cargo-shuttle/Cargo.toml @@ -25,6 +25,7 @@ flate2 = "1.0.25" futures = "0.3.25" git2 = "0.14.2" headers = "0.3.8" +indicatif = "0.17.2" ignore = "0.4.18" indoc = "1.0.7" log = "0.4.17" diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index d096914e7..7a2c783cb 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -112,7 +112,11 @@ pub enum ProjectCommand { /// remove this project environment from shuttle Rm, /// show the status of this project's environment on shuttle - Status, + Status { + #[clap(short, long)] + /// Follow status of project command + follow: bool, + }, } #[derive(Parser, Clone, Debug)] diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 4dfd06701..0405a8a18 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -4,6 +4,7 @@ pub mod config; mod factory; mod init; +use shuttle_common::project::ProjectName; use std::collections::BTreeMap; use std::ffi::OsString; use std::fs::{read_to_string, File}; @@ -27,7 +28,7 @@ use futures::StreamExt; use git2::{Repository, StatusOptions}; use ignore::overrides::OverrideBuilder; use ignore::WalkBuilder; -use shuttle_common::models::secret; +use shuttle_common::models::{project, secret}; use shuttle_service::loader::{build_crate, Loader}; use shuttle_service::Logger; use std::fmt::Write; @@ -93,7 +94,9 @@ impl Shuttle { Command::Secrets => self.secrets(&client).await, Command::Auth(auth_args) => self.auth(auth_args, &client).await, Command::Project(ProjectCommand::New) => self.project_create(&client).await, - Command::Project(ProjectCommand::Status) => self.project_status(&client).await, + Command::Project(ProjectCommand::Status { follow }) => { + self.project_status(&client, follow).await + } Command::Project(ProjectCommand::Rm) => self.project_delete(&client).await, _ => { unreachable!("commands that don't need a client have already been matched") @@ -485,18 +488,82 @@ impl Shuttle { } async fn project_create(&self, client: &Client) -> Result<()> { - let project = client.create_project(self.ctx.project_name()).await?; + self.wait_with_spinner( + &[project::State::Ready, project::state::Errored], + Client::create_project, + self.ctx.project_name(), + client, + ) + .await?; - println!("{project}"); + Ok(()) + } + + async fn project_status(&self, client: &Client, follow: bool) -> Result<()> { + match follow { + true => { + self.wait_with_spinner( + &[ + project::State::Ready, + project::State::Destroyed, + project::state::Errored, + ], + Client::get_project, + self.ctx.project_name(), + client, + ) + .await?; + } + false => { + let project = client.get_project(self.ctx.project_name()).await?; + println!("{project}"); + } + } Ok(()) } - async fn project_status(&self, client: &Client) -> Result<()> { - let project = client.get_project(self.ctx.project_name()).await?; + async fn wait_with_spinner<'a, F, Fut>( + &self, + states_to_check: &[project::State], + f: F, + project_name: &'a ProjectName, + client: &'a Client, + ) -> Result<(), anyhow::Error> + where + F: Fn(&'a Client, &'a ProjectName) -> Fut, + Fut: std::future::Future> + 'a, + { + let mut project = f(client, project_name).await?; + let pb = indicatif::ProgressBar::new_spinner(); + pb.enable_steady_tick(std::time::Duration::from_millis(350)); + pb.set_style( + indicatif::ProgressStyle::with_template("{spinner:.orange} {msg}") + .unwrap() + .tick_strings(&[ + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "( ●)", + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "(● )", + "(●●●●●●)", + ]), + ); + loop { + if states_to_check.contains(&project.state) { + break; + } + pb.set_message(format!("{project}")); + project = client.get_project(project_name).await?; + } + pb.finish_with_message("Done"); println!("{project}"); - Ok(()) }