diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fc88846 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +/target +.env +/tests +Dockerfile +/scripts +/migrations diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e2e18f3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM lukemathwalker/cargo-chef:latest-rust-1.89.0 AS chef +WORKDIR /app + +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 --bin zero2prod + +FROM debian:bookworm-slim AS runtime +WORKDIR /app +RUN apt update -y \ +&& apt install -y --no-install-recommends openssl ca-certificates \ +&& apt autoremove -y \ +&& apt clean -y \ +&& rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/release/zero2prod zero2prod +COPY configuration configuration +ENV APP_ENVIRONMENT=production +ENTRYPOINT [ "./zero2prod" ] diff --git a/configuration.yaml b/configuration.yaml deleted file mode 100644 index 85a79ac..0000000 --- a/configuration.yaml +++ /dev/null @@ -1,7 +0,0 @@ -application_port: 8000 -database: - host: "127.0.0.1" - port: 5432 - username: "postgres" - password: "password" - database_name: "newsletter" diff --git a/src/configuration.rs b/src/configuration.rs index 3d95ab5..24b38a6 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -2,19 +2,61 @@ use secrecy::{ExposeSecret, SecretString}; use serde::Deserialize; pub fn get_configuration() -> Result { + let base_path = std::env::current_dir().expect("Failed to determine the current directory"); + let config_dir = base_path.join("configuration"); + let environment: Environment = std::env::var("APP_ENVIRONMENT") + .unwrap_or_else(|_| "local".into()) + .try_into() + .expect("Failed to parse APP_ENVIRONMENT"); + let environment_filename = format!("{}.yaml", environment.as_str()); + let settings = config::Config::builder() - .add_source(config::File::new( - "configuration.yaml", - config::FileFormat::Yaml, - )) + .add_source(config::File::from(config_dir.join("base.yaml"))) + .add_source(config::File::from(config_dir.join(environment_filename))) .build()?; + settings.try_deserialize::() } +pub enum Environment { + Local, + Production, +} + +impl Environment { + pub fn as_str(&self) -> &str { + match self { + Environment::Local => "local", + Environment::Production => "production", + } + } +} + +impl TryFrom for Environment { + type Error = String; + + fn try_from(value: String) -> Result { + match value.to_lowercase().as_str() { + "local" => Ok(Environment::Local), + "production" => Ok(Environment::Production), + other => Err(format!( + "{} is not a supported environment. Use either `local` or `production`.", + other + )), + } + } +} + #[derive(Deserialize)] pub struct Settings { + pub application: ApplicationSettings, pub database: DatabaseSettings, - pub application_port: u16, +} + +#[derive(Deserialize)] +pub struct ApplicationSettings { + pub port: u16, + pub host: String, } #[derive(Deserialize)] diff --git a/src/main.rs b/src/main.rs index 71cb726..b6ac537 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,14 +7,15 @@ use zero2prod::{configuration::get_configuration, startup::run, telemetry::init_ async fn main() { init_subscriber(std::io::stdout); let configuration = get_configuration().expect("Failed to read configuration"); - let listener = TcpListener::bind(format!("127.0.0.1:{}", configuration.application_port)) - .await - .unwrap(); + let listener = TcpListener::bind(format!( + "{}:{}", + configuration.application.host, configuration.application.port + )) + .await + .unwrap(); tracing::debug!("listening on {}", listener.local_addr().unwrap()); let connection_pool = - PgPool::connect(configuration.database.connection_string().expose_secret()) - .await - .unwrap(); + PgPool::connect_lazy(configuration.database.connection_string().expose_secret()).unwrap(); run(listener, connection_pool).await }