Skip to content

Commit 502513e

Browse files
authored
fix(cargo-shuttle): full json output coverage (#2071)
1 parent c9d93ea commit 502513e

File tree

2 files changed

+91
-59
lines changed

2 files changed

+91
-59
lines changed

cargo-shuttle/src/args.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub struct ShuttleArgs {
4242
/// Turn on tracing output for Shuttle libraries. (WARNING: can print sensitive data)
4343
#[arg(global = true, long, env = "SHUTTLE_DEBUG")]
4444
pub debug: bool,
45-
/// What format to output results in (where supported).
45+
/// What format to print output in
4646
#[arg(
4747
global = true,
4848
long = "output",
@@ -57,17 +57,15 @@ pub struct ShuttleArgs {
5757
pub cmd: Command,
5858
}
5959

60-
#[derive(
61-
ValueEnum, Clone, Debug, Default, PartialEq /* , strum::EnumMessage, strum::VariantArray */,
62-
)]
60+
#[derive(ValueEnum, Clone, Debug, Default, PartialEq)]
6361
pub enum OutputMode {
6462
#[default]
6563
Normal,
6664
Json,
6765
// TODO?: add table / non-table / raw table / raw logs variants?
6866
}
6967

70-
/// Global args for subcommands that deal with projects
68+
/// Global project-related options
7169
#[derive(Args, Clone, Debug)]
7270
pub struct ProjectArgs {
7371
/// Specify the working directory

cargo-shuttle/src/lib.rs

Lines changed: 88 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ impl Shuttle {
158158

159159
self.output_mode = args.output_mode;
160160

161-
// All commands that call the API
161+
// Set up the API client for all commands that call the API
162162
if matches!(
163163
args.cmd,
164164
Command::Init(..)
@@ -171,10 +171,6 @@ impl Shuttle {
171171
| Command::Resource(..)
172172
| Command::Certificate(..)
173173
| Command::Project(..)
174-
) || (
175-
// project linking on beta requires api client
176-
// TODO: refactor so that beta local run does not need to know project id / always uses crate name ???
177-
matches!(args.cmd, Command::Run(..))
178174
) {
179175
let api_url = args
180176
.api_url
@@ -210,7 +206,7 @@ impl Shuttle {
210206
self.client = Some(client);
211207
}
212208

213-
// All commands that need to know which project is being handled
209+
// Load project context for all commands that need to know which project is being targetted
214210
if matches!(
215211
args.cmd,
216212
Command::Deploy(..)
@@ -227,12 +223,12 @@ impl Shuttle {
227223
)
228224
| Command::Logs { .. }
229225
) {
230-
// Command::Run only uses load_local_config (below) instead of load_project since it does not target a project in the API
226+
// Command::Run only uses `load_local_config` (below) instead of `load_project` since it does not target a project in the API
231227
self.load_project(
232228
&args.project_args,
233229
matches!(args.cmd, Command::Project(ProjectCommand::Link)),
234-
// only the deploy command should create a project if the provided name is not found in the project list.
235-
// (project start should always make the POST call, it's an upsert operation)
230+
// Only the deploy command should create a project if the provided name is not found in the project list.
231+
// (ProjectCommand::Create should always make the POST call since it's an upsert operation)
236232
matches!(args.cmd, Command::Deploy(..)),
237233
)
238234
.await?;
@@ -255,7 +251,7 @@ impl Shuttle {
255251
}
256252
},
257253
Command::Account => self.account().await,
258-
Command::Login(login_args) => self.login(login_args, args.offline).await,
254+
Command::Login(login_args) => self.login(login_args, args.offline, true).await,
259255
Command::Logout(logout_args) => self.logout(logout_args).await,
260256
Command::Feedback => open_gh_issue(),
261257
Command::Run(run_args) => {
@@ -301,7 +297,7 @@ impl Shuttle {
301297
ProjectCommand::Status => self.project_status().await,
302298
ProjectCommand::List { table, .. } => self.projects_list(table).await,
303299
ProjectCommand::Delete(ConfirmationArgs { yes }) => self.project_delete(yes).await,
304-
ProjectCommand::Link => Ok(()), // logic is done in `load_local`
300+
ProjectCommand::Link => Ok(()), // logic is done in `load_project` in previous step
305301
},
306302
Command::Upgrade { preview } => update_cargo_shuttle(preview).await,
307303
}
@@ -334,10 +330,10 @@ impl Shuttle {
334330
// 1. Log in (if not logged in yet)
335331
if needs_login {
336332
eprintln!("First, let's log in to your Shuttle account.");
337-
self.login(args.login_args.clone(), offline).await?;
333+
self.login(args.login_args.clone(), offline, false).await?;
338334
eprintln!();
339335
} else if args.login_args.api_key.is_some() {
340-
self.login(args.login_args.clone(), offline).await?;
336+
self.login(args.login_args.clone(), offline, false).await?;
341337
}
342338

343339
// 2. Ask for project name or validate the given one
@@ -690,6 +686,7 @@ impl Shuttle {
690686
trace!("did not find project by name");
691687
if create_missing_project {
692688
trace!("creating project since it was not found");
689+
// This is a side effect (non-primary output), so OutputMode::Json is not considered
693690
let proj = client.create_project(name).await?.into_inner();
694691
eprintln!("Created project '{}' with id {}", proj.name, proj.id);
695692
self.ctx.set_project_id(proj.id);
@@ -754,19 +751,11 @@ impl Shuttle {
754751
.with_prompt("Project name")
755752
.interact()?;
756753

757-
let r = client.create_project(&name).await?;
754+
// This is a side effect (non-primary output), so OutputMode::Json is not considered
755+
let proj = client.create_project(&name).await?.into_inner();
756+
eprintln!("Created project '{}' with id {}", proj.name, proj.id);
758757

759-
match self.output_mode {
760-
OutputMode::Normal => {
761-
let proj = r.into_inner();
762-
eprintln!("Created project '{}' with id {}", proj.name, proj.id);
763-
proj
764-
}
765-
OutputMode::Json => {
766-
println!("{}", r.raw_json);
767-
r.into_inner()
768-
}
769-
}
758+
proj
770759
}
771760
};
772761

@@ -793,7 +782,7 @@ impl Shuttle {
793782
}
794783

795784
/// Log in with the given API key or after prompting the user for one.
796-
async fn login(&mut self, login_args: LoginArgs, offline: bool) -> Result<()> {
785+
async fn login(&mut self, login_args: LoginArgs, offline: bool, login_cmd: bool) -> Result<()> {
797786
let api_key = match login_args.api_key {
798787
Some(api_key) => api_key,
799788
None => {
@@ -822,12 +811,23 @@ impl Shuttle {
822811
if offline {
823812
eprintln!("INFO: Skipping API key verification");
824813
} else {
825-
let u = client
814+
let (user, raw_json) = client
826815
.get_current_user()
827816
.await
828817
.context("failed to check API key validity")?
829-
.into_inner();
830-
println!("Logged in as {}", u.id.bold());
818+
.into_parts();
819+
if login_cmd {
820+
match self.output_mode {
821+
OutputMode::Normal => {
822+
println!("Logged in as {}", user.id.bold());
823+
}
824+
OutputMode::Json => {
825+
println!("{}", raw_json);
826+
}
827+
}
828+
} else {
829+
eprintln!("Logged in as {}", user.id.bold());
830+
}
831831
}
832832
}
833833

@@ -914,12 +914,19 @@ impl Shuttle {
914914
}
915915

916916
wait_with_spinner(2000, |_, pb| async move {
917-
let deployment = client.get_current_deployment(pid).await?.into_inner();
917+
let (deployment, raw_json) = client.get_current_deployment(pid).await?.into_parts();
918918

919919
let get_cleanup = |d: Option<DeploymentResponse>| {
920920
move || {
921921
if let Some(d) = d {
922-
eprintln!("{}", d.to_string_colored());
922+
match self.output_mode {
923+
OutputMode::Normal => {
924+
eprintln!("{}", d.to_string_colored());
925+
}
926+
OutputMode::Json => {
927+
// last deployment response already printed
928+
}
929+
}
923930
}
924931
}
925932
};
@@ -928,18 +935,25 @@ impl Shuttle {
928935
};
929936

930937
let state = deployment.state.clone();
931-
pb.set_message(deployment.to_string_summary_colored());
938+
match self.output_mode {
939+
OutputMode::Normal => {
940+
pb.set_message(deployment.to_string_summary_colored());
941+
}
942+
OutputMode::Json => {
943+
println!("{}", raw_json);
944+
}
945+
}
932946
let cleanup = get_cleanup(Some(deployment));
933947
match state {
934-
DeploymentState::Pending
935-
| DeploymentState::Stopping
936-
| DeploymentState::InProgress
937-
| DeploymentState::Running => Ok(None),
938-
DeploymentState::Building // a building deployment should take it back to InProgress then Running, so don't follow that sequence
939-
| DeploymentState::Failed
940-
| DeploymentState::Stopped
941-
| DeploymentState::Unknown(_) => Ok(Some(cleanup)),
942-
}
948+
DeploymentState::Pending
949+
| DeploymentState::Stopping
950+
| DeploymentState::InProgress
951+
| DeploymentState::Running => Ok(None),
952+
DeploymentState::Building // a building deployment should take it back to InProgress then Running, so don't follow that sequence
953+
| DeploymentState::Failed
954+
| DeploymentState::Stopped
955+
| DeploymentState::Unknown(_) => Ok(Some(cleanup)),
956+
}
943957
})
944958
.await?;
945959

@@ -1713,19 +1727,35 @@ impl Shuttle {
17131727
async fn track_deployment_status(&self, pid: &str, id: &str) -> Result<bool> {
17141728
let client = self.client.as_ref().unwrap();
17151729
let failed = wait_with_spinner(2000, |_, pb| async move {
1716-
let deployment = client.get_deployment(pid, id).await?.into_inner();
1730+
let (deployment, raw_json) = client.get_deployment(pid, id).await?.into_parts();
17171731

17181732
let state = deployment.state.clone();
1719-
pb.set_message(deployment.to_string_summary_colored());
1733+
match self.output_mode {
1734+
OutputMode::Normal => {
1735+
pb.set_message(deployment.to_string_summary_colored());
1736+
}
1737+
OutputMode::Json => {
1738+
println!("{}", raw_json);
1739+
}
1740+
}
17201741
let failed = state == DeploymentState::Failed;
17211742
let cleanup = move || {
1722-
eprintln!("{}", deployment.to_string_colored());
1743+
match self.output_mode {
1744+
OutputMode::Normal => {
1745+
eprintln!("{}", deployment.to_string_colored());
1746+
}
1747+
OutputMode::Json => {
1748+
// last deployment response already printed
1749+
}
1750+
}
17231751
failed
17241752
};
17251753
match state {
1754+
// non-end states
17261755
DeploymentState::Pending
17271756
| DeploymentState::Building
17281757
| DeploymentState::InProgress => Ok(None),
1758+
// end states
17291759
DeploymentState::Running
17301760
| DeploymentState::Stopped
17311761
| DeploymentState::Stopping
@@ -1747,16 +1777,20 @@ impl Shuttle {
17471777
let client = self.client.as_ref().unwrap();
17481778
let failed = self.track_deployment_status(proj_id, depl_id).await?;
17491779
if failed {
1750-
for log in client
1751-
.get_deployment_logs(proj_id, depl_id)
1752-
.await?
1753-
.into_inner()
1754-
.logs
1755-
{
1756-
if raw {
1757-
println!("{}", log.line);
1758-
} else {
1759-
println!("{log}");
1780+
let r = client.get_deployment_logs(proj_id, depl_id).await?;
1781+
match self.output_mode {
1782+
OutputMode::Normal => {
1783+
let logs = r.into_inner().logs;
1784+
for log in logs {
1785+
if raw {
1786+
println!("{}", log.line);
1787+
} else {
1788+
println!("{log}");
1789+
}
1790+
}
1791+
}
1792+
OutputMode::Json => {
1793+
println!("{}", r.raw_json);
17601794
}
17611795
}
17621796
return Err(anyhow!("Deployment failed"));

0 commit comments

Comments
 (0)