From 1b2e0d2a37fb5f1112cbcd24ea14b4e2d834f8a8 Mon Sep 17 00:00:00 2001 From: Miguel Piedrafita Date: Tue, 23 Jul 2024 13:16:38 -0400 Subject: [PATCH] migrate, optimize & other things --- Cargo.lock | 7 +++ crates/cli/src/main.rs | 4 +- crates/server/Cargo.toml | 1 + crates/server/src/deploy.rs | 121 +++++++++++++++++++++++++++++++++--- crates/types/src/lib.rs | 15 +++++ 5 files changed, 138 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cfe4c6..8f1f664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1422,6 +1422,7 @@ dependencies = [ "serde_json", "shlex", "slug", + "symlink", "tar", "thiserror", "tokio", @@ -1992,6 +1993,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "2.0.71" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 6c6c0d8..387f3fd 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -87,10 +87,12 @@ async fn run_deploy(slug: String, r#ref: Option, client: &Client) -> Res Log::Error(message) => eprintln!("{message}"), }, Ok(Progress::Stage(stage)) => match stage { + Stage::Deployed => log::info!("Deployed site"), + Stage::Migrated => log::info!("Migrated database"), Stage::Starting => log::info!("Starting deployment"), + Stage::Optimized => log::info!("Optimized deployment"), Stage::Downloaded => log::info!("Downloaded repository"), Stage::DepsInstalled => log::info!("Installed dependencies"), - Stage::Deployed => log::info!("Deployed site"), }, Err(error) => return Err(error.into()), } diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index e150656..dde0fbd 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -13,6 +13,7 @@ slug = "0.1.5" toml = "0.8.15" shlex = "1.3.0" anyhow = "1.0.71" +symlink = "0.1.0" serde = "1.0.165" flate2 = "1.0.30" indexmap = "2.2.6" diff --git a/crates/server/src/deploy.rs b/crates/server/src/deploy.rs index 61acbf5..3dbdf53 100644 --- a/crates/server/src/deploy.rs +++ b/crates/server/src/deploy.rs @@ -15,11 +15,17 @@ use crate::{ #[derive(Debug, thiserror::Error)] pub enum Error { + #[error("Failed to bootstrap the project.")] + Bootstrap(std::io::Error), + #[error("Failed to clone the repository.")] Download(#[from] reqwest::Error), #[error("Failed to extract the repository contents.")] - Extraction(#[from] std::io::Error), + Extraction(std::io::Error), + + #[error("Failed to configure the deployment.")] + Configure(std::io::Error), #[error("Failed to install dependencies.")] InstallDeps(std::io::Error), @@ -27,6 +33,9 @@ pub enum Error { #[error("Failed to run defined commands.")] RunCommands(std::io::Error), + #[error("Failed to optimize the deployment.")] + Optimize(std::io::Error), + #[error("Failed to cleanup old deployments.")] Cleanup(std::io::Error), @@ -40,6 +49,9 @@ impl From for orbit_types::Error { Error::Cleanup(_) => Self::Cleanup, Error::Publish(_) => Self::Publish, Error::Download(_) => Self::Download, + Error::Optimize(_) => Self::Optimize, + Error::Bootstrap(_) => Self::Bootstrap, + Error::Configure(_) => Self::Configure, Error::Extraction(_) => Self::Extraction, Error::InstallDeps(_) => Self::InstallDeps, Error::RunCommands(_) => Self::RunCommands, @@ -82,37 +94,79 @@ impl Deployer { try_fn_stream(|stream| async move { stream.emit(Stage::Starting.into()).await; + self.bootstrap_site()?; self.download_repo().await?; stream.emit(Stage::Downloaded.into()).await; - self.install_deps() + self.configure_deployment()?; + + if self.should_install_deps() { + self.install_deps() + .try_for_each(|log| async { + stream.emit(Progress::Log(log)).await; + Ok(()) + }) + .await?; + + stream.emit(Stage::DepsInstalled.into()).await; + } + + self.run_commands() .try_for_each(|log| async { stream.emit(Progress::Log(log)).await; Ok(()) }) .await?; - stream.emit(Stage::DepsInstalled.into()).await; + self.optimize_deployment() + .try_for_each(|log| async { + stream.emit(Progress::Log(log)).await; + Ok(()) + }) + .await?; - self.run_commands() + stream.emit(Stage::Optimized.into()).await; + + self.migrate() .try_for_each(|log| async { stream.emit(Progress::Log(log)).await; Ok(()) }) .await?; - // migrate, optimize, etc. here + stream.emit(Stage::Migrated.into()).await; self.set_live()?; - self.clear_old_deployments()?; - stream.emit(Stage::Deployed.into()).await; + self.clear_old_deployments()?; + Ok(()) }) } + fn bootstrap_site(&self) -> Result<(), Error> { + fs::create_dir_all(self.get_path()).map_err(Error::Bootstrap)?; + + let current_path = self.site.path.join("current"); + if current_path.exists() && !current_path.is_symlink() { + fs::remove_dir_all(current_path).map_err(Error::Bootstrap)?; + } + + let storage_path = self.site.path.join("storage"); + if !storage_path.exists() { + fs::create_dir_all(storage_path.join("logs")).map_err(Error::Bootstrap)?; + fs::create_dir_all(storage_path.join("app/public")).map_err(Error::Bootstrap)?; + fs::create_dir_all(storage_path.join("framework/cache")).map_err(Error::Bootstrap)?; + fs::create_dir_all(storage_path.join("framework/views")).map_err(Error::Bootstrap)?; + fs::create_dir_all(storage_path.join("framework/sessions")) + .map_err(Error::Bootstrap)?; + } + + Ok(()) + } + async fn download_repo(&self) -> Result<(), Error> { // we unwrap here since Config::validate errors ealier if `github_repo` does not cointain a `/` let (owner, repo) = self.site.github_repo.split_once('/').unwrap(); @@ -138,7 +192,25 @@ impl Deployer { untar_to( tar::Archive::new(GzDecoder::new(tarball.as_ref())), &self.get_path(), - )?; + ) + .map_err(Error::Extraction)?; + + Ok(()) + } + + fn configure_deployment(&self) -> Result<(), Error> { + let env_path = self.site.path.join(".env"); + if env_path.exists() { + symlink::symlink_dir(env_path, self.get_path().join(".env")) + .map_err(Error::Configure)?; + } + + let storage_path = self.get_path().join("storage"); + if storage_path.exists() { + fs::remove_dir_all(&storage_path).map_err(Error::Configure)?; + } + symlink::symlink_dir(self.site.path.join("storage"), storage_path) + .map_err(Error::Configure)?; Ok(()) } @@ -147,6 +219,10 @@ impl Deployer { spawn_with_logs( Command::new("composer") .arg("install") + .arg("--no-dev") + .arg("--prefer-dist") + .arg("--no-interaction") + .arg("--optimize-autoloader") .current_dir(self.get_path()), ) .map_err(Error::InstallDeps) @@ -170,13 +246,34 @@ impl Deployer { .map_err(Error::RunCommands) } + fn migrate(&self) -> impl Stream> { + spawn_with_logs( + Command::new("php") + .arg("artisan") + .arg("migrate") + .arg("--force") + .current_dir(self.get_path()), + ) + .map_err(Error::InstallDeps) + } + + fn optimize_deployment(&self) -> impl Stream> { + spawn_with_logs( + Command::new("php") + .arg("artisan") + .arg("optimize") + .current_dir(self.get_path()), + ) + .map_err(Error::Optimize) + } + fn set_live(&self) -> Result<(), Error> { let current_deployment = self.site.path.join("current"); if current_deployment.exists() { fs::remove_file(¤t_deployment).map_err(Error::Publish)?; } - std::os::unix::fs::symlink( + symlink::symlink_dir( format!("deployments/{}", self.deployment_id), current_deployment, ) @@ -217,4 +314,10 @@ impl Deployer { .path .join(format!("deployments/{}", self.deployment_id)) } + + fn should_install_deps(&self) -> bool { + let path = self.get_path(); + + path.join("composer.json").exists() && !path.join("vendor").exists() + } } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index fed9b42..b23841f 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -26,6 +26,10 @@ pub enum Stage { Downloaded, /// Dependencies for the current deployment have been installed. DepsInstalled, + /// The current deployment has been migrated. + Migrated, + /// The current deployment has been optimized. + Optimized, /// The deployment is now live. Deployed, } @@ -33,6 +37,10 @@ pub enum Stage { #[derive(Debug, thiserror::Error, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Error { + /// Failed to boorstrap the project. + #[error("Failed to bootstrap the project.")] + Bootstrap, + /// Failed to clone the repository. #[error("Failed to clone the repository.")] Download, @@ -41,6 +49,10 @@ pub enum Error { #[error("Failed to extract the repository contents.")] Extraction, + /// Failed to configure the deployment. + #[error("Failed to configure the deployment.")] + Configure, + /// Failed to install dependencies. #[error("Failed to install dependencies.")] InstallDeps, @@ -49,6 +61,9 @@ pub enum Error { #[error("Failed to run defined commands.")] RunCommands, + #[error("Failed to optimize the deployment.")] + Optimize, + /// Failed to build the deployment. #[error("Failed to cleanup old deployments.")] Cleanup,