chore: load app settings via app ctx and do away with global static loader

This commit is contained in:
Aravinth Manivannan 2022-04-27 10:52:24 +05:30
parent 624646f836
commit 68d63bba07
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
7 changed files with 77 additions and 51 deletions

View File

@ -21,7 +21,7 @@ my-codegen = {package = "actix-web-codegen", git ="https://github.com/realaravin
config = "0.13" config = "0.13"
git2 = "0.14.2" git2 = "0.14.2"
serde = { version = "1", features = ["derive"]} serde = { version = "1", features = ["derive", "rc"]}
serde_json = "1" serde_json = "1"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"

30
src/ctx.rs Normal file
View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::sync::Arc;
use crate::settings::Settings;
#[derive(Clone)]
pub struct Ctx {
pub settings: Settings,
}
impl Ctx {
pub fn new(settings: Settings) -> Arc<Self> {
Arc::new(Self { settings })
}
}

View File

@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use crate::errors::*; use crate::errors::*;
use crate::SETTINGS; use crate::AppCtx;
pub mod routes { pub mod routes {
pub struct Deploy { pub struct Deploy {
@ -42,11 +42,12 @@ pub struct DeployEvent {
} }
#[my_codegen::post(path = "crate::V1_API_ROUTES.deploy.update")] #[my_codegen::post(path = "crate::V1_API_ROUTES.deploy.update")]
async fn update(payload: web::Json<DeployEvent>) -> ServiceResult<impl Responder> { async fn update(payload: web::Json<DeployEvent>, ctx: AppCtx) -> ServiceResult<impl Responder> {
for page in SETTINGS.pages.iter() { for page in ctx.settings.pages.iter() {
if page.secret == payload.secret { if page.secret == payload.secret {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
web::block(|| { let page = page.clone();
web::block(move || {
tx.send(page.update()).unwrap(); tx.send(page.update()).unwrap();
}) })
.await .await
@ -65,18 +66,18 @@ pub fn services(cfg: &mut web::ServiceConfig) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::{http::StatusCode, test, App}; use actix_web::{http::StatusCode, test};
use crate::services; use crate::tests;
use crate::*; use crate::*;
use super::*; use super::*;
#[actix_rt::test] #[actix_rt::test]
async fn deploy_update_works() { async fn deploy_update_works() {
let app = test::init_service(App::new().configure(services)).await; let ctx = tests::get_data().await;
let app = get_app!(ctx).await;
let page = SETTINGS.pages.get(0); let page = ctx.settings.pages.get(0);
let page = page.unwrap(); let page = page.unwrap();
let mut payload = DeployEvent { let mut payload = DeployEvent {
@ -86,10 +87,7 @@ mod tests {
let resp = test::call_service( let resp = test::call_service(
&app, &app,
test::TestRequest::post() post_request!(&payload, V1_API_ROUTES.deploy.update).to_request(),
.uri(V1_API_ROUTES.deploy.update)
.set_json(&payload)
.to_request(),
) )
.await; .await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -98,10 +96,7 @@ mod tests {
let resp = test::call_service( let resp = test::call_service(
&app, &app,
test::TestRequest::post() post_request!(&payload, V1_API_ROUTES.deploy.update).to_request(),
.uri(V1_API_ROUTES.deploy.update)
.set_json(&payload)
.to_request(),
) )
.await; .await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);

View File

@ -17,6 +17,7 @@
//! represents all the ways a trait can fail using this crate //! represents all the ways a trait can fail using this crate
use std::convert::From; use std::convert::From;
use std::io::Error as FSErrorInner; use std::io::Error as FSErrorInner;
use std::sync::Arc;
use actix_web::{ use actix_web::{
error::ResponseError, error::ResponseError,
@ -90,7 +91,7 @@ pub enum ServiceError {
_0, _0,
_1 _1
)] )]
PathTaken(Page, Page), PathTaken(Arc<Page>, Arc<Page>),
/// when the a Secret configured for a page is already taken /// when the a Secret configured for a page is already taken
#[display( #[display(
@ -98,7 +99,7 @@ pub enum ServiceError {
_0, _0,
_1 _1
)] )]
SecretTaken(Page, Page), SecretTaken(Arc<Page>, Arc<Page>),
/// when the a Repository URL configured for a page is already taken /// when the a Repository URL configured for a page is already taken
#[display( #[display(
@ -106,7 +107,7 @@ pub enum ServiceError {
_0, _0,
_1 _1
)] )]
DuplicateRepositoryURL(Page, Page), DuplicateRepositoryURL(Arc<Page>, Arc<Page>),
#[display(fmt = "File System Error {}", _0)] #[display(fmt = "File System Error {}", _0)]
FSError(FSError), FSError(FSError),

View File

@ -15,28 +15,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::env; use std::env;
use std::sync::Arc;
use actix_web::{ use actix_web::{
error::InternalError, http::StatusCode, middleware as actix_middleware, web::JsonConfig, App, error::InternalError, http::StatusCode, middleware as actix_middleware, web::Data as WebData,
HttpServer, web::JsonConfig, App, HttpServer,
}; };
use lazy_static::lazy_static;
use log::info; use log::info;
mod ctx;
mod deploy; mod deploy;
mod errors; mod errors;
mod meta; mod meta;
mod page; mod page;
mod routes; mod routes;
mod settings; mod settings;
#[cfg(test)]
mod tests;
pub use routes::ROUTES as V1_API_ROUTES; pub use routes::ROUTES as V1_API_ROUTES;
pub use settings::Settings; pub use settings::Settings;
lazy_static! {
pub static ref SETTINGS: Settings = Settings::new().unwrap();
}
pub const CACHE_AGE: u32 = 604800; pub const CACHE_AGE: u32 = 604800;
pub const GIT_COMMIT_HASH: &str = env!("GIT_HASH"); pub const GIT_COMMIT_HASH: &str = env!("GIT_HASH");
@ -45,6 +44,8 @@ pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
pub const PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE"); pub const PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE");
pub type AppCtx = WebData<Arc<ctx::Ctx>>;
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
@ -55,6 +56,9 @@ async fn main() -> std::io::Result<()> {
} }
} }
let settings = Settings::new().unwrap();
let ctx = WebData::new(ctx::Ctx::new(settings.clone()));
pretty_env_logger::init(); pretty_env_logger::init();
info!( info!(
@ -62,12 +66,13 @@ async fn main() -> std::io::Result<()> {
PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, GIT_COMMIT_HASH PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, GIT_COMMIT_HASH
); );
println!("Starting server on: http://{}", SETTINGS.server.get_ip()); info!("Starting server on: http://{}", settings.server.get_ip());
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(actix_middleware::Logger::default()) .wrap(actix_middleware::Logger::default())
.wrap(actix_middleware::Compress::default()) .wrap(actix_middleware::Compress::default())
.app_data(ctx.clone())
.app_data(get_json_err()) .app_data(get_json_err())
.wrap( .wrap(
actix_middleware::DefaultHeaders::new() actix_middleware::DefaultHeaders::new()
@ -78,8 +83,8 @@ async fn main() -> std::io::Result<()> {
)) ))
.configure(services) .configure(services)
}) })
.workers(SETTINGS.server.workers.unwrap_or_else(num_cpus::get)) .workers(settings.server.workers.unwrap_or_else(num_cpus::get))
.bind(SETTINGS.server.get_ip()) .bind(settings.server.get_ip())
.unwrap() .unwrap()
.run() .run()
.await .await

View File

@ -17,13 +17,13 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{GIT_COMMIT_HASH, VERSION}; use crate::{AppCtx, GIT_COMMIT_HASH, VERSION};
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BuildDetails { pub struct BuildDetails<'a> {
pub version: &'static str, pub version: &'a str,
pub git_commit_hash: &'static str, pub git_commit_hash: &'a str,
pub source_code: &'static str, pub source_code: &'a str,
} }
pub mod routes { pub mod routes {
@ -44,11 +44,11 @@ pub mod routes {
/// emmits build details of the bninary /// emmits build details of the bninary
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")] #[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")]
async fn build_details() -> impl Responder { async fn build_details(ctx: AppCtx) -> impl Responder {
let build = BuildDetails { let build = BuildDetails {
version: VERSION, version: VERSION,
git_commit_hash: GIT_COMMIT_HASH, git_commit_hash: GIT_COMMIT_HASH,
source_code: &crate::SETTINGS.source_code, source_code: &ctx.settings.source_code,
}; };
HttpResponse::Ok().json(build) HttpResponse::Ok().json(build)
} }
@ -59,14 +59,14 @@ pub fn services(cfg: &mut web::ServiceConfig) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::{http::StatusCode, test, App}; use actix_web::{http::StatusCode, test};
use crate::services;
use crate::*; use crate::*;
#[actix_rt::test] #[actix_rt::test]
async fn build_details_works() { async fn build_details_works() {
let app = test::init_service(App::new().configure(services)).await; let ctx = tests::get_data().await;
let app = get_app!(ctx).await;
let resp = test::call_service( let resp = test::call_service(
&app, &app,

View File

@ -16,6 +16,7 @@
*/ */
use std::env; use std::env;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use config::{Config, Environment, File}; use config::{Config, Environment, File};
use log::warn; use log::warn;
@ -43,7 +44,7 @@ impl Server {
pub struct Settings { pub struct Settings {
pub server: Server, pub server: Server,
pub source_code: String, pub source_code: String,
pub pages: Vec<Page>, pub pages: Vec<Arc<Page>>,
} }
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
@ -101,20 +102,14 @@ impl Settings {
continue; continue;
} }
if page.secret == page2.secret { if page.secret == page2.secret {
log::error!( log::error!("{}", ServiceError::SecretTaken(page.clone(), page2.clone()));
"{}",
ServiceError::SecretTaken(page.to_owned(), page2.to_owned())
);
} else if page.repo == page2.repo { } else if page.repo == page2.repo {
log::error!( log::error!(
"{}", "{}",
ServiceError::DuplicateRepositoryURL(page.to_owned(), page2.to_owned(),) ServiceError::DuplicateRepositoryURL(page.clone(), page2.clone(),)
); );
} else if page.path == page2.path { } else if page.path == page2.path {
log::error!( log::error!("{}", ServiceError::PathTaken(page.clone(), page2.clone()));
"{}",
ServiceError::PathTaken(page.to_owned(), page2.to_owned())
);
} }
} }
if let Err(e) = page.update() { if let Err(e) = page.update() {