diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5d2a7ec --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.env +target/ +tests/ +Dockerfile +scripts/ +migrations/ diff --git a/Dockerfile b/Dockerfile index 8e69287..088d069 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,28 @@ -FROM rust:1.76.0 - +FROM lukemathwalker/cargo-chef:latest-rust-1.76.0-bookworm as chef WORKDIR /app - RUN apt update && apt install lld clang -y + +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json COPY . . ENV SQLX_OFFLINE true RUN cargo build --release + + +FROM debian:bookworm-slim AS runner +WORKDIR /app +RUN apt-get update -y \ + && apt-get install -y --no-install-recommends openssl ca-certificates \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/release/zero2prod zero2prod +COPY configuration configuration ENV APP_ENVIRONMENT production -ENTRYPOINT ["./target/release/zero2prod"] +ENTRYPOINT ["./zero2prod"] diff --git a/configuration/development.yaml b/configuration/development.yaml index c464c2f..8fd67fa 100644 --- a/configuration/development.yaml +++ b/configuration/development.yaml @@ -1,2 +1,4 @@ application: host: 127.0.0.1 +database: + require_ssl: false diff --git a/configuration/production.yaml b/configuration/production.yaml index b936a88..cd4608a 100644 --- a/configuration/production.yaml +++ b/configuration/production.yaml @@ -1,2 +1,4 @@ application: host: 0.0.0.0 +database: + require_ssl: true diff --git a/src/configuration.rs b/src/configuration.rs index cfbb890..2e58116 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,5 +1,6 @@ use config::Config; use secrecy::{ExposeSecret, Secret}; +use sqlx::{postgres::{PgConnectOptions, PgSslMode}, ConnectOptions}; #[derive(serde::Deserialize)] pub struct Settings { @@ -20,21 +21,27 @@ pub struct DatabaseSettings { pub port: u16, pub host: String, pub database_name: String, + pub require_ssl: bool, } impl DatabaseSettings { - pub fn connection_string(&self) -> Secret { - Secret::new(format!( - "postgres://{}:{}@{}:{}/{}", - self.username, self.password.expose_secret(), self.host, self.port, self.database_name - )) + pub fn without_db(&self) -> PgConnectOptions { + let ssl_mode = if self.require_ssl { + PgSslMode::Require + } else { + PgSslMode::Prefer + }; + PgConnectOptions::new() + .host(&self.host) + .username(&self.username) + .password(self.password.expose_secret()) + .port(self.port) + .ssl_mode(ssl_mode) } - pub fn connection_string_without_db(&self) -> Secret { - Secret::new(format!( - "postgres://{}:{}@{}:{}", - self.username, self.password.expose_secret(), self.host, self.port - )) + pub fn with_db(&self) -> PgConnectOptions { + let options = self.without_db().database(&self.database_name); + options.log_statements(tracing::log::LevelFilter::Trace) } } @@ -51,6 +58,7 @@ pub fn get_configuration() -> Result { let settings = Config::builder() .add_source(config::File::with_name(base_config_file).required(true)) .add_source(config::File::with_name(environment_config_file).required(true)) + .add_source(config::Environment::with_prefix("APP").try_parsing(true).separator("_")) .build()?; settings.try_deserialize() diff --git a/src/main.rs b/src/main.rs index 591129c..a6bf203 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use std::net::TcpListener; -use secrecy::ExposeSecret; use sqlx::postgres::PgPoolOptions; use zero2prod::configuration::get_configuration; use zero2prod::startup::run; @@ -11,8 +10,9 @@ async fn main() -> std::io::Result<()> { init_subscriber(subscriber); let config = get_configuration().expect("Failed to read configuration"); - let connection_pool = PgPoolOptions::new() - .max_connections(10).connect_lazy(config.database.connection_string().expose_secret()).expect("Failed to connect to Postgres."); + println!("Application configuration: {}", config.application.port); + let connection_pool = PgPoolOptions::new().acquire_timeout(std::time::Duration::from_secs(2)) + .max_connections(10).connect_lazy_with(config.database.with_db()); let address = format!("{}:{}", config.application.host, config.application.port); let listener = TcpListener::bind(address).expect("Failed to bind random port"); diff --git a/tests/health_check.rs b/tests/health_check.rs index 5abe054..5cee432 100644 --- a/tests/health_check.rs +++ b/tests/health_check.rs @@ -1,5 +1,4 @@ use once_cell::sync::Lazy; -use secrecy::ExposeSecret; use sqlx::{postgres::PgPoolOptions, query, Connection, Executor, PgConnection, Pool, Postgres}; use uuid::Uuid; use std:: net::TcpListener; @@ -44,7 +43,7 @@ async fn spawn_app() -> TestApp { } pub async fn configure_database(config: &DatabaseSettings) -> Pool { - let mut connection = PgConnection::connect(config.connection_string_without_db().expose_secret()) + let mut connection = PgConnection::connect_with(&config.without_db()) .await .expect("Failed to connect to Postgres."); @@ -54,7 +53,7 @@ pub async fn configure_database(config: &DatabaseSettings) -> Pool { let connection_pool = PgPoolOptions::new() .max_connections(10) - .connect(config.connection_string().expose_secret()) + .connect_with(config.with_db()) .await .expect("Failed to connect to Postgres.");