Skip to content

Commit

Permalink
feat(cargo-shuttle): add suggestions in case of cmd failures (#1245)
Browse files Browse the repository at this point in the history
* feat(cargo-shuttle): add suggestions in case of cmd failures

* refactor(cargo-shuttle): break suggestions module
  • Loading branch information
iulianbarbu committed Sep 18, 2023
1 parent c7c0ceb commit 27092b8
Show file tree
Hide file tree
Showing 8 changed files with 340 additions and 20 deletions.
139 changes: 119 additions & 20 deletions cargo-shuttle/src/lib.rs
Expand Up @@ -3,6 +3,7 @@ mod client;
mod config;
mod init;
mod provisioner_server;
mod suggestions;

use std::collections::{BTreeMap, HashMap};
use std::ffi::OsString;
Expand Down Expand Up @@ -395,7 +396,9 @@ impl Shuttle {

async fn logout(&mut self, logout_args: LogoutArgs) -> Result<()> {
if logout_args.reset_api_key {
self.reset_api_key(&self.client()?).await?;
self.reset_api_key(&self.client()?)
.await
.map_err(suggestions::api_key::reset_api_key_failed)?;
println!("Successfully reset the API key.");
println!(" -> Go to {SHUTTLE_LOGIN_URL} to get a new one.\n");
}
Expand All @@ -417,7 +420,10 @@ impl Shuttle {

async fn stop(&self, client: &Client) -> Result<()> {
let proj_name = self.ctx.project_name();
let mut service = client.stop_service(proj_name).await?;
let mut service = client
.stop_service(proj_name)
.await
.map_err(suggestions::deployment::stop_deployment_failure)?;

let progress_bar = create_spinner();
loop {
Expand Down Expand Up @@ -460,7 +466,10 @@ impl Shuttle {
}

async fn secrets(&self, client: &Client) -> Result<()> {
let secrets = client.get_secrets(self.ctx.project_name()).await?;
let secrets = client
.get_secrets(self.ctx.project_name())
.await
.map_err(suggestions::resources::get_secrets_failure)?;
let table = secret::get_table(&secrets);

println!("{table}");
Expand All @@ -469,7 +478,17 @@ impl Shuttle {
}

async fn clean(&self, client: &Client) -> Result<()> {
let lines = client.clean_project(self.ctx.project_name()).await?;
let lines = client
.clean_project(self.ctx.project_name())
.await
.map_err(|err| {
suggestions::project::project_request_failure(
err,
"Project clean failed",
true,
"cleaning your project or checking its status fail repeteadly",
)
})?;

for line in lines {
println!("{line}");
Expand All @@ -494,7 +513,15 @@ impl Shuttle {

if latest {
// Find latest deployment (not always an active one)
let deployments = client.get_deployments(proj_name, 0, 1).await?;
let deployments = client
.get_deployments(proj_name, 0, 1)
.await
.map_err(|err| {
suggestions::logs::get_logs_failure(
err,
"Fetching the latest deployment failed",
)
})?;
let most_recent = deployments.first().context(format!(
"Could not find any deployments for '{proj_name}'. Try passing a deployment ID manually",
))?;
Expand All @@ -511,7 +538,12 @@ impl Shuttle {
};

if follow {
let mut stream = client.get_logs_ws(self.ctx.project_name(), &id).await?;
let mut stream = client
.get_logs_ws(self.ctx.project_name(), &id)
.await
.map_err(|err| {
suggestions::logs::get_logs_failure(err, "Connecting to the logs stream failed")
})?;

while let Some(Ok(msg)) = stream.next().await {
if let tokio_tungstenite::tungstenite::Message::Text(line) = msg {
Expand All @@ -521,7 +553,12 @@ impl Shuttle {
}
}
} else {
let logs = client.get_logs(self.ctx.project_name(), &id).await?;
let logs = client
.get_logs(self.ctx.project_name(), &id)
.await
.map_err(|err| {
suggestions::logs::get_logs_failure(err, "Fetching the deployment failed")
})?;

for log in logs.into_iter() {
println!("{log}");
Expand All @@ -538,7 +575,10 @@ impl Shuttle {
}

let proj_name = self.ctx.project_name();
let deployments = client.get_deployments(proj_name, page, limit).await?;
let deployments = client
.get_deployments(proj_name, page, limit)
.await
.map_err(suggestions::deployment::get_deployments_list_failure)?;
let table = get_deployments_table(&deployments, proj_name.as_str(), page);

println!("{table}");
Expand All @@ -550,7 +590,8 @@ impl Shuttle {
async fn deployment_get(&self, client: &Client, deployment_id: Uuid) -> Result<()> {
let deployment = client
.get_deployment_details(self.ctx.project_name(), &deployment_id)
.await?;
.await
.map_err(suggestions::deployment::get_deployment_status_failure)?;

println!("{deployment}");

Expand All @@ -560,7 +601,8 @@ impl Shuttle {
async fn resources_list(&self, client: &Client) -> Result<()> {
let resources = client
.get_service_resources(self.ctx.project_name())
.await?;
.await
.map_err(suggestions::resources::get_service_resources_failure)?;
let table = get_resources_table(&resources, self.ctx.project_name().as_str());

println!("{table}");
Expand Down Expand Up @@ -1176,11 +1218,18 @@ impl Shuttle {

let deployment = client
.deploy(self.ctx.project_name(), deployment_req)
.await?;
.await
.map_err(suggestions::deploy::deploy_request_failure)?;

let mut stream = client
.get_logs_ws(self.ctx.project_name(), &deployment.id)
.await?;
.await
.map_err(|err| {
suggestions::deploy::deployment_setup_failure(
err,
"Connecting to the deployment logs failed",
)
})?;

loop {
let message = stream.next().await;
Expand Down Expand Up @@ -1219,7 +1268,13 @@ impl Shuttle {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
stream = client
.get_logs_ws(self.ctx.project_name(), &deployment.id)
.await?;
.await
.map_err(|err| {
suggestions::deploy::deployment_setup_failure(
err,
"Connecting to the deployment logs failed",
)
})?;
}
}

Expand All @@ -1230,7 +1285,13 @@ impl Shuttle {

let deployment = client
.get_deployment_details(self.ctx.project_name(), &deployment.id)
.await?;
.await
.map_err(|err| {
suggestions::deploy::deployment_setup_failure(
err,
"Assessing deployment state failed",
)
})?;

// A deployment will only exist if there is currently one in the running state
if deployment.state != shuttle_common::deployment::State::Running {
Expand Down Expand Up @@ -1306,15 +1367,28 @@ impl Shuttle {
self.ctx.project_name(),
client,
)
.await?;
.await
.map_err(|err| {
suggestions::project::project_request_failure(
err,
"Project creation failed",
true,
"the project creation or retrieving the status fails repeteadly",
)
})?;

println!("Run `cargo shuttle deploy --allow-dirty` to deploy your Shuttle service.");

Ok(())
}

async fn project_recreate(&self, client: &Client, idle_minutes: u64) -> Result<()> {
self.project_delete(client).await?;
self.project_create(client, idle_minutes).await?;
self.project_delete(client)
.await
.map_err(suggestions::project::project_restart_failure)?;
self.project_create(client, idle_minutes)
.await
.map_err(suggestions::project::project_restart_failure)?;

Ok(())
}
Expand All @@ -1325,7 +1399,14 @@ impl Shuttle {
return Ok(());
}

let projects = client.get_projects_list(page, limit).await?;
let projects = client.get_projects_list(page, limit).await.map_err(|err| {
suggestions::project::project_request_failure(
err,
"Getting projects list failed",
false,
"getting the projects list fails repeteadly",
)
})?;
let projects_table = project::get_table(&projects, page);

println!("{projects_table}");
Expand All @@ -1349,7 +1430,17 @@ impl Shuttle {
)
.await?;
} else {
let project = client.get_project(self.ctx.project_name()).await?;
let project = client
.get_project(self.ctx.project_name())
.await
.map_err(|err| {
suggestions::project::project_request_failure(
err,
"Getting project status failed",
false,
"getting project status failed repeteadly",
)
})?;
println!("{project}");
}

Expand All @@ -1368,7 +1459,15 @@ impl Shuttle {
self.ctx.project_name(),
client,
)
.await?;
.await
.map_err(|err| {
suggestions::project::project_request_failure(
err,
"Project destroy failed",
true,
"deleting the project or getting project status fails repeteadly",
)
})?;
println!("Run `cargo shuttle project start` to recreate project environment on Shuttle.");

Ok(())
Expand Down
19 changes: 19 additions & 0 deletions cargo-shuttle/src/suggestions/api_key.rs
@@ -0,0 +1,19 @@
//! Suggestions to be shown to users encountering errors while using cargo-shuttle.
// TODO: Ideally, the suggestions would be inferred from the status codes returned by
// the gateway in case of requests to it, or errors thrown by the client doing work
// on the users machines. This is a naive way of handling the errors that should suggest
// retrying common commands or reach out on our Discord server in case failures persist.

use crossterm::style::Stylize;

// --------------------------
// API key related

/// Used when logging out and resetting API key fails
pub fn reset_api_key_failed(err: anyhow::Error) -> anyhow::Error {
println!();
println!("{}", "Logging out failed".red());
println!();
println!("If trying to log out and reset the API key at the same time fails repeteadly, please check Shuttle status at https://status.shuttle.rs or open a help thread on the Discord server.");
err
}
52 changes: 52 additions & 0 deletions cargo-shuttle/src/suggestions/deploy.rs
@@ -0,0 +1,52 @@
use crossterm::style::Stylize;

/// Used when the deploy request doesn't succeed.
pub fn deploy_request_failure(err: anyhow::Error) -> anyhow::Error {
println!();
println!("{}", "Deploy request failed".red());
println!();
println!("Please check your project status and deployments:");
println!();
println!("1. cargo shuttle project status");
println!();
println!("2. cargo shuttle deployment list");
println!();
println!(
"If deploying fails repeteadly, please try restarting your project before deploying again or contacting the team on the Discord server:"
);
println!();
println!("cargo shuttle project restart");
err
}

/// Especially used for cases where the deployment fails after the
/// deploy request went through (e.g. following the deployment logs, checking
/// the deployment state).
pub fn deployment_setup_failure(err: anyhow::Error, title: &str) -> anyhow::Error {
println!();
println!("{}", title.dark_red());
println!();
println!(
"Please check your project status and if the last deployment is recent and is running:"
);
println!();
println!("1. cargo shuttle project status");
println!();
println!("2. cargo shuttle deployment list");
println!();
println!("You should be able to get the logs of the deployment by running:");
println!();
println!("cargo shuttle logs");
println!();
println!("Or follow the logs of the deployment by running:");
println!();
println!("cargo shuttle logs --follow");
println!("If the last deployment is not recent or is not running, please try deploying again or contacting the team on the Discord server:");
println!();
println!("cargo shuttle deploy");
println!();
println!("Or restart the project before deploying again:");
println!();
println!("cargo shuttle project restart");
err
}
52 changes: 52 additions & 0 deletions cargo-shuttle/src/suggestions/deployment.rs
@@ -0,0 +1,52 @@
use crossterm::style::Stylize;

/// Used in case of deployment list request failure.
pub fn get_deployments_list_failure(err: anyhow::Error) -> anyhow::Error {
println!();
println!("{}", "Fetching the deployments list failed".red());
println!();
println!("Please check your project status:");
println!();
println!("cargo shuttle project status");
println!(
"If getting the deployment list fails repeteadly, please try restarting your project before getting the deployment list again or contacting the team on the Discord server:"
);
println!();
println!("cargo shuttle project restart");
err
}

/// Used in case of deployment list request failures.
pub fn get_deployment_status_failure(err: anyhow::Error) -> anyhow::Error {
println!();
println!("{}", "Fetching the deployments status failed".red());
println!();
println!("Please check your project status:");
println!();
println!("cargo shuttle project status");
println!();
println!(
"If getting the deployment state fails repeteadly, please try restarting your project before getting the deployment status again or contacting the team on the Discord server:"
);
println!();
println!("cargo shuttle project restart");
err
}

pub fn stop_deployment_failure(err: anyhow::Error) -> anyhow::Error {
println!();
println!("{}", "Stopping the running deployment failed".red());
println!();
println!("Please check your project status and whether you have a running deployment:");
println!();
println!("1. cargo shuttle project status");
println!();
println!("2. cargo shuttle status");
println!();
println!(
"If stopping the running deployment repeteadly, please try restarting your project before stopping the deployment again or contacting the team on the Discord server:"
);
println!();
println!("cargo shuttle project restart");
err
}

0 comments on commit 27092b8

Please sign in to comment.