diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index 7a2c783cb..9976d3f6d 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -79,6 +79,8 @@ pub enum Command { /// Follow log output follow: bool, }, + /// remove artifacts that were generated by cargo + Clean, /// delete this shuttle service Delete, /// manage secrets for this shuttle service diff --git a/cargo-shuttle/src/client.rs b/cargo-shuttle/src/client.rs index 755099b0f..4f3c4c778 100644 --- a/cargo-shuttle/src/client.rs +++ b/cargo-shuttle/src/client.rs @@ -106,6 +106,16 @@ impl Client { .await } + pub async fn clean_project(&self, project: &ProjectName) -> Result> { + let path = format!("/projects/{}/clean", project.as_str(),); + + self.post(path, Option::::None) + .await + .context("failed to get clean output")? + .to_json() + .await + } + pub async fn get_project(&self, project: &ProjectName) -> Result { let path = format!("/projects/{}", project.as_str()); diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 761ebd6a6..3736b3955 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -59,6 +59,7 @@ impl Shuttle { | Command::Deployment(..) | Command::Project(..) | Command::Delete + | Command::Clean | Command::Secrets | Command::Status | Command::Logs { .. } @@ -91,6 +92,7 @@ impl Shuttle { self.deployment_get(&client, id).await } Command::Delete => self.delete(&client).await, + Command::Clean => self.clean(&client).await, 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, @@ -299,6 +301,18 @@ impl Shuttle { Ok(()) } + async fn clean(&self, client: &Client) -> Result<()> { + let lines = client.clean_project(self.ctx.project_name()).await?; + + for line in lines { + println!("{line}"); + } + + println!("Cleaning done!"); + + Ok(()) + } + async fn logs(&self, client: &Client, id: Option, follow: bool) -> Result<()> { let id = if let Some(id) = id { id diff --git a/deployer/src/deployment/mod.rs b/deployer/src/deployment/mod.rs index 865d298c2..d0ba56af8 100644 --- a/deployer/src/deployment/mod.rs +++ b/deployer/src/deployment/mod.rs @@ -26,6 +26,7 @@ const KILL_BUFFER_SIZE: usize = 10; pub struct DeploymentManager { pipeline: Pipeline, kill_send: KillSender, + storage_manager: StorageManager, } impl DeploymentManager { @@ -40,6 +41,7 @@ impl DeploymentManager { artifacts_path: PathBuf, ) -> Self { let (kill_send, _) = broadcast::channel(KILL_BUFFER_SIZE); + let storage_manager = StorageManager::new(artifacts_path); DeploymentManager { pipeline: Pipeline::new( @@ -49,9 +51,10 @@ impl DeploymentManager { build_log_recorder, secret_recorder, active_deployment_getter, - StorageManager::new(artifacts_path), + storage_manager.clone(), ), kill_send, + storage_manager, } } @@ -76,6 +79,10 @@ impl DeploymentManager { self.kill_send.send(id).unwrap(); } } + + pub fn storage_manager(&self) -> StorageManager { + self.storage_manager.clone() + } } /// ```no-test diff --git a/deployer/src/handlers/error.rs b/deployer/src/handlers/error.rs index f7f7e6c7e..f0fb98a0a 100644 --- a/deployer/src/handlers/error.rs +++ b/deployer/src/handlers/error.rs @@ -21,6 +21,8 @@ pub enum Error { }, #[error("record could not be found")] NotFound, + #[error("Custom error: {0}")] + Custom(#[from] anyhow::Error), } impl Serialize for Error { diff --git a/deployer/src/handlers/mod.rs b/deployer/src/handlers/mod.rs index d27ac5b95..69e54625c 100644 --- a/deployer/src/handlers/mod.rs +++ b/deployer/src/handlers/mod.rs @@ -5,7 +5,7 @@ use axum::extract::ws::{self, WebSocket}; use axum::extract::{Extension, MatchedPath, Path, Query}; use axum::http::{Request, Response}; use axum::middleware::from_extractor; -use axum::routing::{get, Router}; +use axum::routing::{get, post, Router}; use axum::{extract::BodyStream, Json}; use bytes::BufMut; use chrono::{TimeZone, Utc}; @@ -17,6 +17,7 @@ use shuttle_common::backends::metrics::Metrics; use shuttle_common::models::secret; use shuttle_common::project::ProjectName; use shuttle_common::LogItem; +use shuttle_service::loader::clean_crate; use tower_http::auth::RequireAuthorizationLayer; use tower_http::trace::TraceLayer; use tracing::{debug, debug_span, error, field, trace, Span}; @@ -66,6 +67,7 @@ pub fn make_router( "/projects/:project_name/secrets/:service_name", get(get_secrets), ) + .route("/projects/:project_name/clean", post(post_clean)) .layer(Extension(persistence)) .layer(Extension(deployment_manager)) .layer(Extension(proxy_fqdn)) @@ -407,6 +409,20 @@ async fn get_secrets( } } +async fn post_clean( + Extension(deployment_manager): Extension, + Path(project_name): Path, +) -> Result>> { + let project_path = deployment_manager + .storage_manager() + .service_build_path(project_name) + .map_err(anyhow::Error::new)?; + + let lines = clean_crate(&project_path, true)?; + + Ok(Json(lines)) +} + async fn get_status() -> String { "Ok".to_string() } diff --git a/service/src/loader.rs b/service/src/loader.rs index d48aceeb3..34d53cdc4 100644 --- a/service/src/loader.rs +++ b/service/src/loader.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use anyhow::{anyhow, Context}; use cargo::core::compiler::{CompileMode, MessageFormat}; use cargo::core::{Manifest, PackageId, Shell, Summary, Verbosity, Workspace}; -use cargo::ops::{compile, CompileOptions}; +use cargo::ops::{clean, compile, CleanOptions, CompileOptions}; use cargo::util::interning::InternedString; use cargo::util::{homedir, ToSemver}; use cargo::Config; @@ -147,6 +147,51 @@ pub async fn build_crate( Ok(compilation?.cdylibs[0].path.clone()) } +pub fn clean_crate(project_path: &Path, release_mode: bool) -> anyhow::Result> { + let (read, write) = pipe::pipe(); + let project_path = project_path.to_owned(); + + tokio::task::spawn_blocking(move || { + let config = get_config(write).unwrap(); + let manifest_path = project_path.join("Cargo.toml"); + let ws = Workspace::new(&manifest_path, &config).unwrap(); + + let requested_profile = if release_mode { + InternedString::new("release") + } else { + InternedString::new("dev") + }; + + let opts = CleanOptions { + config: &config, + spec: Vec::new(), + targets: Vec::new(), + requested_profile, + profile_specified: true, + doc: false, + }; + + clean(&ws, &opts).unwrap(); + }); + + let mut lines = Vec::new(); + + for message in Message::parse_stream(read) { + trace!(?message, "parsed cargo message"); + match message { + Ok(Message::TextLine(line)) => { + lines.push(line); + } + Ok(_) => {} + Err(error) => { + error!("failed to parse cargo message: {error}"); + } + } + } + + Ok(lines) +} + /// Get the default compile config with output redirected to writer pub fn get_config(writer: PipeWriter) -> anyhow::Result { let mut shell = Shell::from_write(Box::new(writer));