Skip to content

Commit

Permalink
fix: failing cargo shuttle deploy returns successful exit code (#166)
Browse files Browse the repository at this point in the history
* fix: return shell error code when `cargo shuttle deploy` results in deployment failure

* feat: return error code for failed deployment with `cargo status` and `delete` also

* refactor: use new `CommandOutcome` type instead of `DeploymentStateMeta`

* tests: update

Co-authored-by: chesedo <pieter@chesedo.me>
  • Loading branch information
bmoxb and chesedo authored Jul 5, 2022
1 parent f01aa01 commit eea2d8f
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 55 deletions.
4 changes: 2 additions & 2 deletions cargo-shuttle/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub(crate) async fn deploy(
api_url: ApiUrl,
api_key: &ApiKey,
project: &ProjectName,
) -> Result<()> {
) -> Result<DeploymentStateMeta> {
let mut url = api_url.clone();
let _ = write!(url, "/projects/{}", project.as_str());

Expand Down Expand Up @@ -177,7 +177,7 @@ pub(crate) async fn deploy(

println!("{}", &deployment_meta);

Ok(())
Ok(deployment_meta.state)
}

pub(crate) async fn secrets(
Expand Down
41 changes: 27 additions & 14 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use cargo_edit::{find, get_latest_dependency, registry_url};
use colored::Colorize;
use config::RequestContext;
use factory::LocalFactory;
use futures::future::TryFutureExt;
use semver::{Version, VersionReq};
use shuttle_service::loader::{build_crate, Loader};
use tokio::sync::mpsc;
Expand All @@ -32,6 +31,8 @@ use uuid::Uuid;
#[macro_use]
extern crate log;

use shuttle_common::DeploymentStateMeta;

pub struct Shuttle {
ctx: RequestContext,
}
Expand All @@ -48,7 +49,7 @@ impl Shuttle {
Self { ctx }
}

pub async fn run(mut self, mut args: Args) -> Result<()> {
pub async fn run(mut self, mut args: Args) -> Result<CommandOutcome> {
trace!("running local client");
if matches!(
args.cmd,
Expand All @@ -66,7 +67,7 @@ impl Shuttle {
match args.cmd {
Command::Deploy(deploy_args) => {
self.check_lib_version(args.project_args).await?;
self.deploy(deploy_args).await
return self.deploy(deploy_args).await;
}
Command::Init(init_args) => self.init(init_args).await,
Command::Status => self.status().await,
Expand All @@ -76,6 +77,7 @@ impl Shuttle {
Command::Login(login_args) => self.login(login_args).await,
Command::Run(run_args) => self.local_run(run_args).await,
}
.map(|_| CommandOutcome::Ok)
}

async fn init(&self, args: InitArgs) -> Result<()> {
Expand Down Expand Up @@ -250,7 +252,7 @@ impl Shuttle {
Ok(())
}

async fn deploy(&self, args: DeployArgs) -> Result<()> {
async fn deploy(&self, args: DeployArgs) -> Result<CommandOutcome> {
self.run_tests(args.no_test)?;

let package_file = self
Expand All @@ -259,22 +261,28 @@ impl Shuttle {

let key = self.ctx.api_key()?;

client::deploy(
let state_meta = client::deploy(
package_file,
self.ctx.api_url(),
&key,
self.ctx.project_name(),
)
.and_then(|_| {
client::secrets(
self.ctx.api_url(),
&key,
self.ctx.project_name(),
self.ctx.secrets(),
)
})
.await
.context("failed to deploy cargo project")
.context("failed to deploy cargo project")?;

client::secrets(
self.ctx.api_url(),
&key,
self.ctx.project_name(),
self.ctx.secrets(),
)
.await
.context("failed to set up secrets for deployment")?;

Ok(match state_meta {
DeploymentStateMeta::Error(_) => CommandOutcome::DeploymentFailure,
_ => CommandOutcome::Ok,
})
}

async fn check_lib_version(&self, project_args: ProjectArgs) -> Result<()> {
Expand Down Expand Up @@ -359,6 +367,11 @@ impl Shuttle {
}
}

pub enum CommandOutcome {
Ok,
DeploymentFailure,
}

#[cfg(test)]
mod tests {
use crate::args::ProjectArgs;
Expand Down
13 changes: 11 additions & 2 deletions cargo-shuttle/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
use anyhow::Result;
use cargo_shuttle::{Args, Shuttle};
use cargo_shuttle::{Args, CommandOutcome, Shuttle};
use structopt::StructOpt;

#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
Shuttle::new().run(Args::from_args()).await

let result = Shuttle::new().run(Args::from_args()).await;

if matches!(result, Ok(CommandOutcome::DeploymentFailure)) {
// Deployment failure results in a shell error exit code being returned (this allows
// chaining of commands with `&&` for example to fail at the first deployment failure).
std::process::exit(1); // TODO: use `std::process::ExitCode::FAILURE` once stable.
}

result.map(|_| ())
}
29 changes: 15 additions & 14 deletions cargo-shuttle/tests/integration/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
use std::path::Path;

use cargo_shuttle::{Args, Command, DeployArgs, ProjectArgs, Shuttle};
use futures::Future;
use cargo_shuttle::{Args, Command, CommandOutcome, DeployArgs, ProjectArgs, Shuttle};
use reqwest::StatusCode;
use test_context::test_context;
use tokiotest_httpserver::{handler::HandlerBuilder, HttpTestContext};

/// creates a `cargo-shuttle` deploy instance with some reasonable defaults set.
fn cargo_shuttle_deploy(path: &str, api_url: String) -> impl Future<Output = anyhow::Result<()>> {
async fn cargo_shuttle_deploy(path: &str, api_url: String) -> anyhow::Result<CommandOutcome> {
let working_directory = Path::new(path).to_path_buf();

Shuttle::new().run(Args {
api_url: Some(api_url),
project_args: ProjectArgs {
working_directory,
name: None,
},
cmd: Command::Deploy(DeployArgs {
allow_dirty: false,
no_test: false,
}),
})
Shuttle::new()
.run(Args {
api_url: Some(api_url),
project_args: ProjectArgs {
working_directory,
name: None,
},
cmd: Command::Deploy(DeployArgs {
allow_dirty: false,
no_test: false,
}),
})
.await
}

#[should_panic(
Expand Down
23 changes: 12 additions & 11 deletions cargo-shuttle/tests/integration/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ use std::{
path::Path,
};

use cargo_shuttle::{Args, Command, InitArgs, ProjectArgs, Shuttle};
use futures::Future;
use cargo_shuttle::{Args, Command, CommandOutcome, InitArgs, ProjectArgs, Shuttle};

/// creates a `cargo-shuttle` init instance with some reasonable defaults set.
fn cargo_shuttle_init(path: &str) -> impl Future<Output = anyhow::Result<()>> {
async fn cargo_shuttle_init(path: &str) -> anyhow::Result<CommandOutcome> {
let _result = remove_dir_all(path);

let working_directory = Path::new(".").to_path_buf();
let path = Path::new(path).to_path_buf();

Shuttle::new().run(Args {
api_url: Some("http://shuttle.invalid:80".to_string()),
project_args: ProjectArgs {
working_directory,
name: None,
},
cmd: Command::Init(InitArgs { path }),
})
Shuttle::new()
.run(Args {
api_url: Some("http://shuttle.invalid:80".to_string()),
project_args: ProjectArgs {
working_directory,
name: None,
},
cmd: Command::Init(InitArgs { path }),
})
.await
}

#[tokio::test]
Expand Down
26 changes: 14 additions & 12 deletions cargo-shuttle/tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ mod deploy;
mod init;
mod run;

use cargo_shuttle::{Args, Command, ProjectArgs, Shuttle};
use std::{future::Future, path::Path};
use cargo_shuttle::{Args, Command, CommandOutcome, ProjectArgs, Shuttle};
use std::path::Path;

/// creates a `cargo-shuttle` run instance with some reasonable defaults set.
fn cargo_shuttle_command(
async fn cargo_shuttle_command(
cmd: Command,
working_directory: &str,
) -> impl Future<Output = anyhow::Result<()>> {
) -> anyhow::Result<CommandOutcome> {
let working_directory = Path::new(working_directory).to_path_buf();

Shuttle::new().run(Args {
api_url: Some("http://shuttle.invalid:80".to_string()),
project_args: ProjectArgs {
working_directory,
name: None,
},
cmd,
})
Shuttle::new()
.run(Args {
api_url: Some("http://shuttle.invalid:80".to_string()),
project_args: ProjectArgs {
working_directory,
name: None,
},
cmd,
})
.await
}

#[tokio::test]
Expand Down

0 comments on commit eea2d8f

Please sign in to comment.