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: AWS RDS #180

Merged
merged 62 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
6baee8b
feat: simple RDS support
chesedo May 13, 2022
008b204
refactor: cleanup and prep
chesedo May 13, 2022
5fbff83
feat: Simple AWS RDS Postgres tuple
chesedo May 13, 2022
e743f3d
feat: MySql and MariaDB support
chesedo May 18, 2022
90d9103
refactor: add engine to DB info
chesedo May 16, 2022
4e0adc5
refactor: 'master' for username
chesedo May 16, 2022
6e20ccf
refactor: clippy suggestions
chesedo May 19, 2022
6e244c5
refactor: better debug logs
chesedo May 19, 2022
366d583
refactor: set region
chesedo May 19, 2022
9d9c2a0
refactor: increase tracing
chesedo May 19, 2022
2b541dc
refactor: try more timeouts
chesedo May 19, 2022
b6bd166
refactor: try manual profile
chesedo May 19, 2022
3ed7c8b
refactor: try manual imds provider
chesedo May 19, 2022
c7444f3
refactor: try manual client
chesedo May 19, 2022
268dcbb
refactor: set read timeout
chesedo May 20, 2022
17c61fa
refactor: try endpoint override
chesedo May 20, 2022
966c233
refactor: trying aws update
chesedo May 20, 2022
c206d5a
refactor: finally found it
chesedo May 20, 2022
310f833
refactor: remove region
chesedo May 20, 2022
ed6ee42
bug: increase hop limit
chesedo May 20, 2022
589487f
feat: add policy to handle RDS
chesedo May 20, 2022
537a18c
refactor: try subnet group
chesedo May 20, 2022
25d8ada
refactor: more permission fixes
chesedo May 20, 2022
0023a18
feat: add rds subnet
chesedo May 20, 2022
f26519a
refactor: cleanup
chesedo May 20, 2022
1aab7a4
feat: switch to attribute annotation
chesedo May 23, 2022
e21e005
refactor: move to aws::rds
chesedo May 23, 2022
ff6cbef
feat: aws::rds example
chesedo May 23, 2022
f088eff
refactor: move to shared::Postgres
chesedo May 23, 2022
0c550e0
refactor: improve rds waiting
chesedo May 24, 2022
cca3a59
feat: open acl ports for acl
chesedo May 24, 2022
9f8f4ac
bug: running tokio inside tide (async-std)
chesedo May 24, 2022
9bf234b
docs: service configuration using attributes
chesedo May 24, 2022
f7950e9
bug: fix uuid/v4 in test
chesedo May 24, 2022
9de5977
refactor: clippy suggestions
chesedo May 24, 2022
428f1d7
tests: update sqlx tests
chesedo May 24, 2022
74fc059
refactor: make attribute config required
chesedo May 24, 2022
689ed27
refactor: make transition error better
chesedo May 24, 2022
c8dc739
tests: trybuild
chesedo May 30, 2022
8506b0b
refactor: better hints
chesedo May 30, 2022
ad00744
refactor: builders vec
chesedo May 30, 2022
b95ac5a
refactor: update examples to main
chesedo May 30, 2022
c592963
refactor: make tf use account id
chesedo May 30, 2022
5a0c8c7
Merge remote-tracking branch 'origin/main' into feat/rds
chesedo Jun 15, 2022
556a455
feat: add AWS RDS to proto
chesedo Jun 15, 2022
5ff3483
feat: local runs for AWS RDS
chesedo Jun 15, 2022
4c54842
refactor: sort Cargo.toml
chesedo Jun 15, 2022
9dea253
tests: provisioner await
chesedo Jun 16, 2022
7b406f2
refactor: undo secrets patch
chesedo Jun 16, 2022
de19f41
refactor: fix pg_isready
chesedo Jun 16, 2022
21f6bf8
refactor: args for public and private PG addresses
chesedo Jun 16, 2022
96ac064
refactor: clippy suggestions
chesedo Jun 16, 2022
dec2e23
bug: supervisord args
chesedo Jun 16, 2022
3a9020c
bug: supervisord args v2
chesedo Jun 16, 2022
d56800a
fix: test helper
chesedo Jun 16, 2022
93023ea
bug: security group
chesedo Jun 16, 2022
c513347
refactor: tide postgres version
chesedo Jun 16, 2022
eba0d01
refactor: more helper fixes
chesedo Jun 16, 2022
b7e993d
bug: INTERNAL_ADDRESS
chesedo Jun 16, 2022
33bd7b0
refactor: remove redundant security group link
chesedo Jun 16, 2022
fbb1588
bug: PG timeout
chesedo Jun 16, 2022
b54ae81
Apply suggestions from code review
chesedo Jun 16, 2022
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
959 changes: 766 additions & 193 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions api/src/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ impl Deployment {

let mut factory = ShuttleFactory::new(
context.provisioner_client.clone(),
context.provisioner_address.clone(),
meta.project.clone(),
);
let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port);
Expand Down Expand Up @@ -289,7 +288,6 @@ pub(crate) struct DeploymentSystem {
job_queue: JobQueue,
router: Arc<Router>,
fqdn: String,
pub(crate) provisioner_address: String,
}

const JOB_QUEUE_SIZE: usize = 200;
Expand Down Expand Up @@ -340,7 +338,6 @@ pub(crate) struct Context {
build_system: Box<dyn BuildSystem>,
deployments: Arc<RwLock<Deployments>>,
provisioner_client: ProvisionerClient<Channel>,
provisioner_address: String,
}

impl DeploymentSystem {
Expand Down Expand Up @@ -384,7 +381,6 @@ impl DeploymentSystem {
build_system,
deployments: deployments.clone(),
provisioner_client,
provisioner_address: provisioner_address.clone(),
};

let job_queue = JobQueue::new(context, tx).await;
Expand All @@ -400,7 +396,6 @@ impl DeploymentSystem {
job_queue,
router,
fqdn,
provisioner_address,
}
}

Expand Down
21 changes: 13 additions & 8 deletions api/src/factory.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
use async_trait::async_trait;
use proto::provisioner::{provisioner_client::ProvisionerClient, DatabaseRequest};
use proto::provisioner::{
database_request::DbType, provisioner_client::ProvisionerClient, DatabaseRequest,
};
use shuttle_common::{project::ProjectName, DatabaseReadyInfo};
use shuttle_service::Factory;
use shuttle_service::{database::Type, Factory};
use tonic::{transport::Channel, Request};

pub(crate) struct ShuttleFactory {
project_name: ProjectName,
provisioner_client: ProvisionerClient<Channel>,
provisioner_address: String,
info: Option<DatabaseReadyInfo>,
}

impl ShuttleFactory {
pub(crate) fn new(
provisioner_client: ProvisionerClient<Channel>,
provisioner_address: String,
project_name: ProjectName,
) -> Self {
Self {
provisioner_client,
provisioner_address,
project_name,
info: None,
}
Expand All @@ -32,13 +31,19 @@ impl ShuttleFactory {

#[async_trait]
impl Factory for ShuttleFactory {
async fn get_sql_connection_string(&mut self) -> Result<String, shuttle_service::Error> {
async fn get_sql_connection_string(
&mut self,
db_type: Type,
) -> Result<String, shuttle_service::Error> {
if let Some(ref info) = self.info {
return Ok(info.connection_string(&self.provisioner_address));
return Ok(info.connection_string_private());
}

let db_type: DbType = db_type.into();

let request = Request::new(DatabaseRequest {
project_name: self.project_name.to_string(),
db_type: Some(db_type),
});

let response = self
Expand All @@ -49,7 +54,7 @@ impl Factory for ShuttleFactory {
.into_inner();

let info: DatabaseReadyInfo = response.into();
let conn_str = info.connection_string(&self.provisioner_address);
let conn_str = info.connection_string_private();
self.info = Some(info);

debug!("giving a sql connection string: {}", conn_str);
Expand Down
3 changes: 1 addition & 2 deletions api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,7 @@ async fn project_secrets(
.await?;

if let Some(database_deployment) = &deployment.database_deployment {
let conn_str =
database_deployment.connection_string(&state.deployment_manager.provisioner_address);
let conn_str = database_deployment.connection_string_private();
let conn = sqlx::PgPool::connect(&conn_str)
.await
.map_err(|e| DeploymentApiError::Internal(e.to_string()))?;
Expand Down
1 change: 1 addition & 0 deletions api/users.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ projects = [
'authentication-rocket-app',
'hello-world-tide-app',
'hello-world-tower-app',
'postgres-tide-app',
]
163 changes: 134 additions & 29 deletions cargo-shuttle/src/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ use bollard::{
};
use colored::Colorize;
use crossterm::{
cursor::MoveUp,
cursor::{MoveDown, MoveUp},
terminal::{Clear, ClearType},
QueueableCommand,
};
use futures::StreamExt;
use portpicker::pick_unused_port;
use shuttle_common::{project::ProjectName, DatabaseReadyInfo};
use shuttle_service::{error::CustomError, Factory};
use shuttle_common::{database::AwsRdsEngine, project::ProjectName, DatabaseReadyInfo};
use shuttle_service::{database::Type, error::CustomError, Factory};
use std::{collections::HashMap, io::stdout, time::Duration};
use tokio::time::sleep;

Expand All @@ -34,14 +34,26 @@ impl LocalFactory {
}
}

const PG_PASSWORD: &str = "password";
const PG_IMAGE: &str = "postgres:11";

#[async_trait]
impl Factory for LocalFactory {
async fn get_sql_connection_string(&mut self) -> Result<String, shuttle_service::Error> {
async fn get_sql_connection_string(
&mut self,
db_type: Type,
) -> Result<String, shuttle_service::Error> {
trace!("getting sql string for project '{}'", self.project);
let container_name = format!("shuttle_{}_postgres", self.project);

let EngineConfig {
r#type,
image,
engine,
username,
password,
database_name,
port,
env,
is_ready_cmd,
} = db_type_to_config(db_type);
let container_name = format!("shuttle_{}_{}", self.project, r#type);

let container = match self.docker.inspect_container(&container_name, None).await {
Ok(container) => {
Expand All @@ -51,17 +63,15 @@ impl Factory for LocalFactory {
Err(bollard::errors::Error::DockerResponseServerError { status_code, .. })
if status_code == 404 =>
{
self.pull_image(PG_IMAGE)
.await
.expect("failed to pull image");
self.pull_image(&image).await.expect("failed to pull image");
trace!("will create DB container {container_name}");
let options = Some(CreateContainerOptions {
name: container_name.clone(),
});
let mut port_bindings = HashMap::new();
let host_port = pick_unused_port().expect("system to have a free port");
port_bindings.insert(
"5432/tcp".to_string(),
port.clone(),
Some(vec![PortBinding {
host_port: Some(host_port.to_string()),
..Default::default()
Expand All @@ -72,10 +82,9 @@ impl Factory for LocalFactory {
..Default::default()
};

let password_env = format!("POSTGRES_PASSWORD={PG_PASSWORD}");
let config = Config {
image: Some(PG_IMAGE),
env: Some(vec![&password_env]),
image: Some(image),
env,
host_config: Some(host_config),
..Default::default()
};
Expand All @@ -101,10 +110,10 @@ impl Factory for LocalFactory {
.expect("container to have host config")
.port_bindings
.expect("port bindings on container")
.get("5432/tcp")
.expect("a '5432/tcp' port bindings entry")
.get(&port)
.expect("a port bindings entry")
.as_ref()
.expect("a '5432/tcp' port bindings")
.expect("a port bindings")
.first()
.expect("at least one port binding")
.host_port
Expand All @@ -125,16 +134,19 @@ impl Factory for LocalFactory {
.expect("failed to start none running container");
}

self.wait_for_ready(&container_name).await?;
self.wait_for_ready(&container_name, is_ready_cmd).await?;

let db_info = DatabaseReadyInfo::new(
"postgres".to_string(),
PG_PASSWORD.to_string(),
"postgres".to_string(),
engine,
username,
password,
database_name,
port,
"localhost".to_string(),
"localhost".to_string(),
);

let conn_str = db_info.connection_string("localhost");
let conn_str = db_info.connection_string_private();

println!(
"{:>12} can be reached at {}\n",
Expand All @@ -147,13 +159,18 @@ impl Factory for LocalFactory {
}

impl LocalFactory {
async fn wait_for_ready(&self, container_name: &str) -> Result<(), shuttle_service::Error> {
async fn wait_for_ready(
&self,
container_name: &str,
is_ready_cmd: Vec<String>,
) -> Result<(), shuttle_service::Error> {
loop {
trace!("waiting for '{container_name}' to be ready for connections");

let config = CreateExecOptions {
cmd: Some(vec!["pg_isready"]),
cmd: Some(is_ready_cmd.clone()),
attach_stdout: Some(true),
attach_stderr: Some(true),
..Default::default()
};

Expand All @@ -171,12 +188,12 @@ impl LocalFactory {

if let bollard::exec::StartExecResults::Attached { mut output, .. } = ready_result {
while let Some(line) = output.next().await {
if let bollard::container::LogOutput::StdOut { message } =
trace!("line: {:?}", line);

if let bollard::container::LogOutput::StdOut { .. } =
line.expect("output to have a log line")
{
if message.ends_with(b"accepting connections\n") {
return Ok(());
}
return Ok(());
}
}
}
Expand Down Expand Up @@ -213,6 +230,13 @@ impl LocalFactory {
print_layers(&layers);
}

// Undo last MoveUps
stdout()
.queue(MoveDown(
layers.len().try_into().expect("to convert usize to u16"),
))
.expect("to reset cursor position");

Ok(())
}
}
Expand Down Expand Up @@ -254,3 +278,84 @@ fn print_layers(layers: &Vec<CreateImageInfo>) {
))
.expect("to reset cursor position");
}

struct EngineConfig {
r#type: String,
image: String,
engine: String,
username: String,
password: String,
database_name: String,
port: String,
env: Option<Vec<String>>,
is_ready_cmd: Vec<String>,
}

fn db_type_to_config(db_type: Type) -> EngineConfig {
match db_type {
Type::Shared => EngineConfig {
r#type: "shared_postgres".to_string(),
image: "postgres:11".to_string(),
engine: "postgres".to_string(),
username: "postgres".to_string(),
password: "postgres".to_string(),
database_name: "postgres".to_string(),
port: "5432/tcp".to_string(),
env: Some(vec!["POSTGRES_PASSWORD=postgres".to_string()]),
is_ready_cmd: vec![
"/bin/sh".to_string(),
"-c".to_string(),
"pg_isready | grep 'accepting connections'".to_string(),
],
},
Type::AwsRds(AwsRdsEngine::Postgres) => EngineConfig {
r#type: "aws_rds_postgres".to_string(),
image: "postgres:13.4".to_string(),
engine: "postgres".to_string(),
username: "postgres".to_string(),
password: "postgres".to_string(),
database_name: "postgres".to_string(),
port: "5432/tcp".to_string(),
env: Some(vec!["POSTGRES_PASSWORD=postgres".to_string()]),
is_ready_cmd: vec![
"/bin/sh".to_string(),
"-c".to_string(),
"pg_isready | grep 'accepting connections'".to_string(),
],
},
Type::AwsRds(AwsRdsEngine::MariaDB) => EngineConfig {
r#type: "aws_rds_mariadb".to_string(),
image: "mariadb:10.6.7".to_string(),
engine: "mariadb".to_string(),
username: "root".to_string(),
password: "mariadb".to_string(),
database_name: "mysql".to_string(),
port: "3306/tcp".to_string(),
env: Some(vec!["MARIADB_ROOT_PASSWORD=mariadb".to_string()]),
is_ready_cmd: vec![
"mysql".to_string(),
"-pmariadb".to_string(),
"--silent".to_string(),
"-e".to_string(),
"show databases;".to_string(),
],
},
Type::AwsRds(AwsRdsEngine::MySql) => EngineConfig {
r#type: "aws_rds_mysql".to_string(),
image: "mysql:8.0.28".to_string(),
engine: "mysql".to_string(),
username: "root".to_string(),
password: "mysql".to_string(),
database_name: "mysql".to_string(),
port: "3306/tcp".to_string(),
env: Some(vec!["MYSQL_ROOT_PASSWORD=mysql".to_string()]),
is_ready_cmd: vec![
"mysql".to_string(),
"-pmysql".to_string(),
"--silent".to_string(),
"-e".to_string(),
"show databases;".to_string(),
],
},
}
}
4 changes: 3 additions & 1 deletion codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ description = "Proc-macro code generator for the shuttle.rs service"
proc-macro = true

[dependencies]
proc-macro-error = "1.0"
proc-macro2 = "1.0.39"
quote = "1.0.18"
syn = { version = "1.0.96", features = ["full"] }
syn = { version = "1.0.96", features = ["full", "extra-traits"] }

[dev-dependencies]
pretty_assertions = "1.2.1"
trybuild = "1.0"
Loading