From 9fe7e56a26b36e10a5e26ffe9c20dbce6148509e Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:48:00 +0200 Subject: [PATCH 1/2] feat: beta builder args (#1813) * feat: beta builder args * feat: more flags * fix * nit * feat: mold flag * adjacent enum tag * fix: features = None when not used --- cargo-shuttle/src/lib.rs | 22 ++++++++----- common/src/log.rs | 6 +--- common/src/models/deployment.rs | 55 ++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index cdcd0db00..c7e09df09 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -33,7 +33,7 @@ use ignore::WalkBuilder; use indicatif::ProgressBar; use indoc::{formatdoc, printdoc}; use shuttle_common::models::deployment::{ - BuildMetaBeta, DeploymentRequestBuildArchiveBeta, DeploymentRequestImageBeta, + BuildArgsBeta, BuildMetaBeta, DeploymentRequestBuildArchiveBeta, DeploymentRequestImageBeta, }; use shuttle_common::{ constants::{ @@ -1821,6 +1821,8 @@ impl Shuttle { }; if self.beta { + let mut build_args = BuildArgsBeta::default(); + let metadata = async_cargo_metadata(manifest_path.as_path()).await?; let packages = find_shuttle_packages(&metadata)?; // TODO: support overriding this @@ -1828,16 +1830,22 @@ impl Shuttle { .first() .expect("at least one shuttle crate in the workspace"); let package_name = package.name.to_owned(); - deployment_req_buildarch_beta.package_name = package_name; + build_args.package_name = Some(package_name); - // TODO: add these to the request and builder - let (_no_default_features, _features) = if package.features.contains_key("shuttle") { - (true, vec!["shuttle".to_owned()]) + // activate shuttle feature is present + let (no_default_features, features) = if package.features.contains_key("shuttle") { + (true, Some(vec!["shuttle".to_owned()])) } else { - (false, vec![]) + (false, None) }; + build_args.no_default_features = no_default_features; + build_args.features = features.map(|v| v.join(",")); + // TODO: determine which (one) binary to build - // TODO: have the above be configurable in CLI and Shuttle.toml + + deployment_req_buildarch_beta.build_args = Some(build_args); + + // TODO: have all of the above be configurable in CLI and Shuttle.toml } if let Ok(repo) = Repository::discover(working_directory) { diff --git a/common/src/log.rs b/common/src/log.rs index 1bdfde2d6..0cb4beacb 100644 --- a/common/src/log.rs +++ b/common/src/log.rs @@ -57,13 +57,9 @@ pub struct LogItem { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct LogItemBeta { - /// Time log was captured pub timestamp: DateTime, - - /// Stdout/stderr + /// Which container / log stream this line came from pub source: String, - - /// The log line pub line: String, } diff --git a/common/src/models/deployment.rs b/common/src/models/deployment.rs index 0eef6036c..cb8d51864 100644 --- a/common/src/models/deployment.rs +++ b/common/src/models/deployment.rs @@ -285,7 +285,7 @@ pub struct DeploymentRequest { } #[derive(Deserialize, Serialize)] -#[serde(untagged)] +#[serde(tag = "type", content = "content")] pub enum DeploymentRequestBeta { /// Build an image from the source code in an attached zip archive BuildArchive(DeploymentRequestBuildArchiveBeta), @@ -298,15 +298,62 @@ pub enum DeploymentRequestBeta { pub struct DeploymentRequestBuildArchiveBeta { /// Zip archive pub data: Vec, - /// The cargo package name to compile and run. - pub package_name: String, - // TODO: Binary name, feature flags?, other cargo args? + pub build_args: Option, /// Secrets to add before this deployment. /// TODO: Remove this in favour of a separate secrets uploading action. pub secrets: Option>, pub build_meta: Option, } +#[derive(Deserialize, Serialize)] +pub struct BuildArgsBeta { + /// Use the built in cargo chef setup for caching + pub cargo_chef: bool, + /// Build with the built in `cargo build` setup + pub cargo_build: bool, + /// The cargo package name to compile + pub package_name: Option, + /// The cargo binary name to compile + pub binary_name: Option, + /// comma-separated list of features to activate + pub features: Option, + /// Passed on to `cargo build` + pub no_default_features: bool, + /// Use the mold linker + pub mold: bool, +} + +impl Default for BuildArgsBeta { + fn default() -> Self { + Self { + cargo_chef: true, + cargo_build: true, + package_name: Default::default(), + binary_name: Default::default(), + features: Default::default(), + no_default_features: Default::default(), + mold: Default::default(), + } + } +} + +impl BuildArgsBeta { + pub fn into_vars(&self) -> [(&str, &str); 7] { + [ + ("CARGO_CHEF", if self.cargo_chef { "true" } else { "" }), + ("CARGO_BUILD", if self.cargo_build { "true" } else { "" }), + ("PACKAGE", self.package_name.as_deref().unwrap_or_default()), + ("BIN", self.binary_name.as_deref().unwrap_or_default()), + ("FEATURES", self.features.as_deref().unwrap_or_default()), + ( + "NO_DEFAULT_FEATURES", + if self.no_default_features { "true" } else { "" }, + ), + ("MOLD", if self.mold { "true" } else { "" }), + ] + } +} + #[derive(Default, Deserialize, Serialize)] pub struct BuildMetaBeta { pub git_commit_id: Option, From 22dc7c71a3ab7bf86481b8fce0bb0286dd614bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20Gr=C3=B8dem?= <29732646+oddgrd@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:54:08 +0200 Subject: [PATCH 2/2] feat: implement rds resource on beta platform (#1812) --- cargo-shuttle/src/lib.rs | 22 ++++++++++-- common/src/lib.rs | 67 ++++++++++++++++++++++++++++++++++- common/src/models/resource.rs | 66 +++++++++++++++++++++++++++++----- common/src/resource.rs | 1 + runtime/src/alpha.rs | 2 +- 5 files changed, 145 insertions(+), 13 deletions(-) diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index c7e09df09..af485bde9 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -1055,7 +1055,14 @@ impl Shuttle { client.get_service_resources(self.ctx.project_name()).await } .map_err(suggestions::resources::get_service_resources_failure)?; - let table = get_resource_tables(&resources, self.ctx.project_name(), raw, show_secrets); + + let table = get_resource_tables( + &resources, + self.ctx.project_name(), + raw, + show_secrets, + self.beta, + ); println!("{table}"); @@ -1261,7 +1268,15 @@ impl Shuttle { println!( "{}", - get_resource_tables(&mocked_responses, service_name.as_str(), false, false) + get_resource_tables( + &mocked_responses, + service_name.as_str(), + false, + false, + // Set beta to false to avoid breaking local run with beta changes. + // TODO: make local run compatible with --beta. + false + ) ); // @@ -2110,7 +2125,8 @@ impl Shuttle { let resources = client .get_service_resources(self.ctx.project_name()) .await?; - let resources = get_resource_tables(&resources, self.ctx.project_name(), false, false); + let resources = + get_resource_tables(&resources, self.ctx.project_name(), false, false, self.beta); println!("{resources}{service}"); diff --git a/common/src/lib.rs b/common/src/lib.rs index 6d2ffb36f..9355a43ed 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -81,7 +81,7 @@ impl AsRef for ApiKey { ////// Resource Input/Output types /// The input given to Shuttle DB resources -#[derive(Deserialize, Serialize, Default)] +#[derive(Clone, Deserialize, Serialize, Default)] pub struct DbInput { pub local_uri: Option, /// Override the default db name. Only applies to RDS. @@ -172,6 +172,71 @@ impl DatabaseInfo { } } +/// Holds the data for building a database connection string on the Beta platform. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DatabaseInfoBeta { + engine: String, + role_name: String, + role_password: Secret, + database_name: String, + port: String, + hostname: String, + /// The RDS instance name, which is required for deleting provisioned RDS instances, it's + /// optional because it isn't needed for shared PG deletion. + instance_name: Option, +} + +impl DatabaseInfoBeta { + pub fn new( + engine: String, + role_name: String, + role_password: String, + database_name: String, + port: String, + hostname: String, + instance_name: Option, + ) -> Self { + Self { + engine, + role_name, + role_password: Secret::new(role_password), + database_name, + port, + hostname, + instance_name, + } + } + + /// For connecting to the database. + pub fn connection_string(&self, show_password: bool) -> String { + format!( + "{}://{}:{}@{}:{}/{}", + self.engine, + self.role_name, + if show_password { + self.role_password.expose() + } else { + self.role_password.redacted() + }, + self.hostname, + self.port, + self.database_name, + ) + } + + pub fn role_name(&self) -> String { + self.role_name.to_string() + } + + pub fn database_name(&self) -> String { + self.database_name.to_string() + } + + pub fn instance_name(&self) -> Option { + self.instance_name.clone() + } +} + /// Used to request a container from the local run provisioner #[derive(Serialize, Deserialize)] pub struct ContainerRequest { diff --git a/common/src/models/resource.rs b/common/src/models/resource.rs index 236106498..9cd79b27f 100644 --- a/common/src/models/resource.rs +++ b/common/src/models/resource.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use comfy_table::{ modifiers::UTF8_ROUND_CORNERS, - presets::{NOTHING, UTF8_FULL}, + presets::{NOTHING, UTF8_BORDERS_ONLY, UTF8_FULL}, Attribute, Cell, CellAlignment, ContentArrangement, Table, }; use crossterm::style::Stylize; @@ -10,7 +10,7 @@ use crossterm::style::Stylize; use crate::{ resource::{Response, Type}, secrets::SecretStore, - DatabaseResource, + DatabaseInfoBeta, DatabaseResource, }; pub fn get_resource_tables( @@ -18,6 +18,7 @@ pub fn get_resource_tables( service_name: &str, raw: bool, show_secrets: bool, + beta: bool, ) -> String { if resources.is_empty() { if raw { @@ -44,12 +45,21 @@ pub fn get_resource_tables( let mut output = Vec::new(); if let Some(databases) = resource_groups.get("Databases") { - output.push(get_databases_table( - databases, - service_name, - raw, - show_secrets, - )); + if beta { + output.push(get_databases_table_beta( + databases, + service_name, + raw, + show_secrets, + )); + } else { + output.push(get_databases_table( + databases, + service_name, + raw, + show_secrets, + )); + } }; if let Some(secrets) = resource_groups.get("Secrets") { @@ -137,6 +147,46 @@ fn get_databases_table( format!("These databases are linked to {service_name}\n{table}\n{show_secret_hint}") } +fn get_databases_table_beta( + databases: &Vec<&Response>, + service_name: &str, + raw: bool, + show_secrets: bool, +) -> String { + let mut table = Table::new(); + + if raw { + table + .load_preset(NOTHING) + .set_content_arrangement(ContentArrangement::Disabled) + .set_header(vec![ + Cell::new("Type").set_alignment(CellAlignment::Left), + Cell::new("Connection string").set_alignment(CellAlignment::Left), + ]); + } else { + table + .load_preset(UTF8_BORDERS_ONLY) + .set_content_arrangement(ContentArrangement::Disabled) + .set_header(vec![Cell::new("Type"), Cell::new("Connection string")]); + } + + for database in databases { + let connection_string = serde_json::from_value::(database.data.clone()) + .expect("resource data to be a valid database") + .connection_string(show_secrets); + + table.add_row(vec![database.r#type.to_string(), connection_string]); + } + + let show_secret_hint = if databases.is_empty() || show_secrets { + "" + } else { + "Hint: you can show the secrets of these resources using `cargo shuttle resource list --show-secrets`\n" + }; + + format!("These databases are linked to {service_name}\n{table}\n{show_secret_hint}") +} + fn get_secrets_table(secrets: &[&Response], service_name: &str, raw: bool) -> String { let mut table = Table::new(); diff --git a/common/src/resource.rs b/common/src/resource.rs index a0776232e..e658496e2 100644 --- a/common/src/resource.rs +++ b/common/src/resource.rs @@ -51,6 +51,7 @@ pub enum ResourceState { Failed, Ready, Deleting, + Deleted, } /// Returned when provisioning a Shuttle resource diff --git a/runtime/src/alpha.rs b/runtime/src/alpha.rs index 8bf0ff543..de1fecda6 100644 --- a/runtime/src/alpha.rs +++ b/runtime/src/alpha.rs @@ -311,7 +311,7 @@ where // it has sent a load response, so that the ECS task will fail. tokio::spawn(async move { // Note: The timeout is quite long since RDS can take a long time to provision. - tokio::time::sleep(Duration::from_secs(180)).await; + tokio::time::sleep(Duration::from_secs(270)).await; if !matches!(state.lock().unwrap().deref(), State::Running) { println!("the runtime failed to enter the running state before timing out");