Skip to content

Commit

Permalink
feat: Add cargo shuttle logs --latest (#799)
Browse files Browse the repository at this point in the history
* Add `cargo shuttle logs --latest`

* clippy

* Prioritise latest flag

* order by

* fix ordering in test

* Make test more deterministic

* Add test for ordering

* Fix service id in test

* Fix last things
  • Loading branch information
jonaro00 authored Apr 18, 2023
1 parent 44c1299 commit 5bdd892
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 30 deletions.
4 changes: 3 additions & 1 deletion cargo-shuttle/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ pub enum Command {
Logs {
/// Deployment ID to get logs for. Defaults to currently running deployment
id: Option<Uuid>,

#[arg(short, long)]
/// View logs from the most recent deployment (which is not always the latest running one)
latest: bool,
#[arg(short, long)]
/// Follow log output
follow: bool,
Expand Down
31 changes: 25 additions & 6 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ impl Shuttle {
return self.deploy(deploy_args, &self.client()?).await;
}
Command::Status => self.status(&self.client()?).await,
Command::Logs { id, follow } => self.logs(&self.client()?, id, follow).await,
Command::Logs { id, latest, follow } => {
self.logs(&self.client()?, id, latest, follow).await
}
Command::Deployment(DeploymentCommand::List) => {
self.deployments_list(&self.client()?).await
}
Expand Down Expand Up @@ -343,16 +345,33 @@ impl Shuttle {
Ok(())
}

async fn logs(&self, client: &Client, id: Option<Uuid>, follow: bool) -> Result<()> {
async fn logs(
&self,
client: &Client,
id: Option<Uuid>,
latest: bool,
follow: bool,
) -> Result<()> {
let id = if let Some(id) = id {
id
} else {
let summary = client.get_service(self.ctx.project_name()).await?;

if let Some(deployment) = summary.deployment {
let proj_name = self.ctx.project_name();

if latest {
// Find latest deployment (not always an active one)
let deployments = client.get_deployments(proj_name).await?;
let most_recent = deployments.last().context(format!(
"Could not find any deployments for '{proj_name}'. Try passing a deployment ID manually",
))?;

most_recent.id
} else if let Some(deployment) = client.get_service(proj_name).await?.deployment {
// Active deployment
deployment.id
} else {
bail!("Could not automatically find a running deployment for '{}'. Try passing a deployment ID manually", self.ctx.project_name());
bail!(
"Could not find a running deployment for '{proj_name}'. Try with '--latest', or pass a deployment ID manually"
);
}
};

Expand Down
97 changes: 74 additions & 23 deletions deployer/src/persistence/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ impl Persistence {
}

pub async fn get_deployments(&self, service_id: &Uuid) -> Result<Vec<Deployment>> {
sqlx::query_as("SELECT * FROM deployments WHERE service_id = ?")
sqlx::query_as("SELECT * FROM deployments WHERE service_id = ? ORDER BY last_update")
.bind(service_id)
.fetch_all(&self.pool)
.await
Expand Down Expand Up @@ -495,7 +495,7 @@ impl ActiveDeploymentsGetter for Persistence {
mod tests {
use std::net::{Ipv4Addr, SocketAddr};

use chrono::{TimeZone, Utc};
use chrono::{Duration, TimeZone, Utc};
use rand::Rng;
use serde_json::json;

Expand Down Expand Up @@ -604,75 +604,126 @@ mod tests {
);
}

#[tokio::test(flavor = "multi_thread")]
async fn deployment_order() {
let (p, _) = Persistence::new_in_memory().await;

let service_id = add_service(&p.pool).await.unwrap();
let other_id = add_service(&p.pool).await.unwrap();

let deployment_other = Deployment {
id: Uuid::new_v4(),
service_id: other_id,
state: State::Running,
last_update: Utc.with_ymd_and_hms(2023, 4, 17, 1, 1, 2).unwrap(),
address: None,
is_next: false,
};
let deployment_crashed = Deployment {
id: Uuid::new_v4(),
service_id,
state: State::Crashed,
last_update: Utc.with_ymd_and_hms(2023, 4, 17, 1, 1, 2).unwrap(), // second
address: None,
is_next: false,
};
let deployment_stopped = Deployment {
id: Uuid::new_v4(),
service_id,
state: State::Stopped,
last_update: Utc.with_ymd_and_hms(2023, 4, 17, 1, 1, 1).unwrap(), // first
address: None,
is_next: false,
};
let deployment_running = Deployment {
id: Uuid::new_v4(),
service_id,
state: State::Running,
last_update: Utc.with_ymd_and_hms(2023, 4, 17, 1, 1, 3).unwrap(), // third
address: Some(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 9876)),
is_next: true,
};

for deployment in [
&deployment_other,
&deployment_crashed,
&deployment_stopped,
&deployment_running,
] {
p.insert_deployment(deployment.clone()).await.unwrap();
}

let actual = p.get_deployments(&service_id).await.unwrap();
let expected = vec![deployment_stopped, deployment_crashed, deployment_running];

assert_eq!(actual, expected, "deployments should be sorted by time");
}

// Test that we are correctly cleaning up any stale / unexpected states for a deployment
// The reason this does not clean up two (or more) running states for a single deployment is because
// it should theoretically be impossible for a service to have two deployments in the running state.
// And even if a service where to have this, then the start ups of these deployments (more specifically
// And even if a service were to have this, then the start ups of these deployments (more specifically
// the last deployment that is starting up) will stop all the deployments correctly.
#[tokio::test(flavor = "multi_thread")]
async fn cleanup_invalid_states() {
let (p, _) = Persistence::new_in_memory().await;

let service_id = add_service(&p.pool).await.unwrap();

let queued_id = Uuid::new_v4();
let building_id = Uuid::new_v4();
let built_id = Uuid::new_v4();
let loading_id = Uuid::new_v4();
let time = Utc::now();

let deployment_crashed = Deployment {
id: Uuid::new_v4(),
service_id,
state: State::Crashed,
last_update: Utc::now(),
last_update: time,
address: None,
is_next: false,
};
let deployment_stopped = Deployment {
id: Uuid::new_v4(),
service_id,
state: State::Stopped,
last_update: Utc::now(),
last_update: time.checked_add_signed(Duration::seconds(1)).unwrap(),
address: None,
is_next: false,
};
let deployment_running = Deployment {
id: Uuid::new_v4(),
service_id,
state: State::Running,
last_update: Utc::now(),
last_update: time.checked_add_signed(Duration::seconds(2)).unwrap(),
address: Some(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 9876)),
is_next: false,
};
let deployment_queued = Deployment {
id: queued_id,
id: Uuid::new_v4(),
service_id,
state: State::Queued,
last_update: Utc::now(),
last_update: time.checked_add_signed(Duration::seconds(3)).unwrap(),
address: None,
is_next: false,
};
let deployment_building = Deployment {
id: building_id,
id: Uuid::new_v4(),
service_id,
state: State::Building,
last_update: Utc::now(),
last_update: time.checked_add_signed(Duration::seconds(4)).unwrap(),
address: None,
is_next: false,
};
let deployment_built = Deployment {
id: built_id,
id: Uuid::new_v4(),
service_id,
state: State::Built,
last_update: Utc::now(),
last_update: time.checked_add_signed(Duration::seconds(5)).unwrap(),
address: None,
is_next: true,
};
let deployment_loading = Deployment {
id: loading_id,
id: Uuid::new_v4(),
service_id,
state: State::Loading,
last_update: Utc::now(),
last_update: time.checked_add_signed(Duration::seconds(6)).unwrap(),
address: None,
is_next: false,
};
Expand Down Expand Up @@ -702,10 +753,10 @@ mod tests {
(deployment_crashed.id, State::Crashed),
(deployment_stopped.id, State::Stopped),
(deployment_running.id, State::Running),
(queued_id, State::Stopped),
(built_id, State::Stopped),
(building_id, State::Stopped),
(loading_id, State::Stopped),
(deployment_queued.id, State::Stopped),
(deployment_building.id, State::Stopped),
(deployment_built.id, State::Stopped),
(deployment_loading.id, State::Stopped),
];

assert_eq!(
Expand Down

0 comments on commit 5bdd892

Please sign in to comment.