Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement rds resource on beta platform #1812

Merged
merged 8 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");

Expand Down Expand Up @@ -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
)
);

//
Expand Down Expand Up @@ -2102,7 +2117,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}");

Expand Down
67 changes: 66 additions & 1 deletion common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl AsRef<str> 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<String>,
/// Override the default db name. Only applies to RDS.
Expand Down Expand Up @@ -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<String>,
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<String>,
}

impl DatabaseInfoBeta {
pub fn new(
engine: String,
role_name: String,
role_password: String,
database_name: String,
port: String,
hostname: String,
instance_name: Option<String>,
) -> 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<String> {
self.instance_name.clone()
}
}

/// Used to request a container from the local run provisioner
#[derive(Serialize, Deserialize)]
pub struct ContainerRequest {
Expand Down
66 changes: 58 additions & 8 deletions common/src/models/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ 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;

use crate::{
resource::{Response, Type},
secrets::SecretStore,
DatabaseResource,
DatabaseInfoBeta, DatabaseResource,
};

pub fn get_resource_tables(
resources: &[Response],
service_name: &str,
raw: bool,
show_secrets: bool,
beta: bool,
) -> String {
if resources.is_empty() {
if raw {
Expand All @@ -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") {
Expand Down Expand Up @@ -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::<DatabaseInfoBeta>(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();

Expand Down
1 change: 1 addition & 0 deletions common/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub enum ResourceState {
Failed,
Ready,
Deleting,
Deleted,
}

/// Returned when provisioning a Shuttle resource
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/alpha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down