feat: read and load configuration
This commit is contained in:
parent
df4c07344d
commit
5d56cf132c
6 changed files with 342 additions and 0 deletions
11
.env.docker-compose
Normal file
11
.env.docker-compose
Normal file
|
@ -0,0 +1,11 @@
|
|||
VANIGAM_debug=true
|
||||
VANIGAM_source_code="https://git.batsense.net/libre-solutions/vanigam"
|
||||
VANIGAM_allow_registration=true
|
||||
|
||||
#DATABASE_URL=
|
||||
VANIGAM_database_POOL=4
|
||||
|
||||
PORT=7000
|
||||
VANIGAM_server_DOMAIN="localhost:7000"
|
||||
#VANIGAM_server_COOKIE_SECRET=
|
||||
VANIGAM_server_IP="127.0.0.1"
|
1
.env_sample
Normal file
1
.env_sample
Normal file
|
@ -0,0 +1 @@
|
|||
export POSTGRES_DATABASE_URL="postgres://postgres:password@localhost:5432/postgres"
|
26
config/default.toml
Normal file
26
config/default.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
debug = true
|
||||
source_code = "https://git.batsense.net/libre-solutions/vanigam"
|
||||
allow_registration = true
|
||||
|
||||
[server]
|
||||
# Please set a unique value, your mCaptcha instance's security depends on this being
|
||||
# unique
|
||||
#cookie_secret = "Zae0OOxf^bOJ#zN^&k7VozgW&QAx%n02TQFXpRMG4cCU0xMzgu3dna@tQ9dvc&TlE6p*n#kXUdLZJCQsuODIV%r$@o4%770ePQB7m#dpV!optk01NpY0@615w5e2Br4d"
|
||||
# The port at which you want authentication to listen to
|
||||
# takes a number, choose from 1000-10000 if you dont know what you are doing
|
||||
port = 7000
|
||||
#IP address. Enter 0.0.0.0 to listen on all available addresses
|
||||
ip= "0.0.0.0"
|
||||
# enter your hostname, eg: example.com
|
||||
domain = "localhost:7000"
|
||||
#cookie_secret = ""
|
||||
|
||||
[database]
|
||||
# This section deals with the database location and how to access it
|
||||
# Please note that at the moment, we have support for only postgresqa.
|
||||
# Example, if you are Batman, your config would be:
|
||||
# url = "postgres://batman:password@batcave.org:5432/batcave"
|
||||
# database_type = "postgres"
|
||||
# pool = 4
|
||||
url = "postgres://example.org" # hack for tests to run successfully
|
||||
pool = 4
|
77
src/settings/database.rs
Normal file
77
src/settings/database.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::env;
|
||||
|
||||
use config::{builder::DefaultState, ConfigBuilder, ConfigError};
|
||||
use derive_more::Display;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Serialize, Display, Eq, PartialEq, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DBType {
|
||||
#[display(fmt = "postgres")]
|
||||
Postgres,
|
||||
}
|
||||
|
||||
impl DBType {
|
||||
fn from_url(url: &Url) -> Result<Self, ConfigError> {
|
||||
match url.scheme() {
|
||||
// "mysql" => Ok(Self::Maria),
|
||||
"postgres" => Ok(Self::Postgres),
|
||||
_ => Err(ConfigError::Message("Unknown database type".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
pub struct Database {
|
||||
pub url: String,
|
||||
pub pool: u32,
|
||||
pub database_type: DBType,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||
for (parameter, env_var_name) in [
|
||||
("database.url", "DATABASE_URL"),
|
||||
("database.pool", "FORGEFLUX_database_POOL"),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
if let Ok(val) = env::var(env_var_name) {
|
||||
log::debug!("Overriding [{parameter}] with environment variable {env_var_name}");
|
||||
s = s.set_override(parameter, val).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn set_database_type(&mut self) {
|
||||
let url =
|
||||
Url::parse(&self.url).expect("couldn't parse Database URL and detect database type");
|
||||
self.database_type = DBType::from_url(&url).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::env_helper;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_db_env_override() {
|
||||
let init_settings = crate::settings::Settings::new().unwrap();
|
||||
env_helper!(
|
||||
init_settings,
|
||||
"DATABASE_URL",
|
||||
"postgres://test_db_env_override",
|
||||
database.url
|
||||
);
|
||||
env_helper!(init_settings, "FORGEFLUX_database_POOL", 99, database.pool);
|
||||
}
|
||||
}
|
162
src/settings/mod.rs
Normal file
162
src/settings/mod.rs
Normal file
|
@ -0,0 +1,162 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::path::Path;
|
||||
use std::{env, fs};
|
||||
|
||||
use config::builder::DefaultState;
|
||||
use config::{Config, ConfigBuilder, ConfigError, File};
|
||||
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
pub mod database;
|
||||
pub mod server;
|
||||
|
||||
use database::{DBType, Database};
|
||||
use server::Server;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
pub struct Settings {
|
||||
pub debug: bool,
|
||||
pub log: String,
|
||||
pub source_code: String,
|
||||
pub allow_registration: bool,
|
||||
pub database: Database,
|
||||
pub server: Server,
|
||||
// pub smtp: Option<Smtp>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||
for (parameter, env_var_name) in [
|
||||
("debug", "VANIGAM_debug"),
|
||||
("source_code", "VANIGAM_source_code"),
|
||||
("allow_registration", "VANIGAM_allow_registration"),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
if let Ok(val) = env::var(env_var_name) {
|
||||
log::debug!("Overriding [{parameter}] with environment variable {env_var_name}");
|
||||
s = s.set_override(parameter, val).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
s = Database::env_override(s);
|
||||
Server::env_override(s)
|
||||
}
|
||||
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let mut s = Config::builder();
|
||||
|
||||
const CURRENT_DIR: &str = "./config/default.toml";
|
||||
const ETC: &str = "/etc/forgeflux/config.toml";
|
||||
|
||||
// Will be overridden after config is parsed and loaded into Settings by
|
||||
// Settings::set_database_type.
|
||||
// This parameter is not ergonomic for users, but it is required and can be programatically
|
||||
// inferred. But we need a default value for config lib to parse successfully, since it is
|
||||
// DBType and not Option<DBType>
|
||||
s = s
|
||||
.set_default("database.database_type", DBType::Postgres.to_string())
|
||||
.expect("unable to set database.database_type default config");
|
||||
|
||||
s = s
|
||||
.set_default("log", "INFO")
|
||||
.expect("unable to set log default config");
|
||||
|
||||
if let Ok(path) = env::var("VANIGAM_CONFIG") {
|
||||
let absolute_path = Path::new(&path).canonicalize().unwrap();
|
||||
log::info!(
|
||||
"Loading config file from {}",
|
||||
absolute_path.to_str().unwrap()
|
||||
);
|
||||
s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
|
||||
} else if Path::new(CURRENT_DIR).exists() {
|
||||
let absolute_path = fs::canonicalize(CURRENT_DIR).unwrap();
|
||||
log::info!(
|
||||
"Loading config file from {}",
|
||||
absolute_path.to_str().unwrap()
|
||||
);
|
||||
// merging default config from file
|
||||
s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
|
||||
} else if Path::new(ETC).exists() {
|
||||
log::info!("{}", format!("Loading config file from {}", ETC));
|
||||
s = s.add_source(File::with_name(ETC));
|
||||
} else {
|
||||
log::warn!("Configuration file not found");
|
||||
}
|
||||
|
||||
s = Self::env_override(s);
|
||||
|
||||
let mut settings = s.build()?.try_deserialize::<Settings>()?;
|
||||
settings.check_url();
|
||||
|
||||
settings.database.set_database_type();
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn check_url(&self) {
|
||||
Url::parse(&self.source_code).expect("Please enter a URL for source_code in settings");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use url::Url;
|
||||
|
||||
use super::Settings;
|
||||
use crate::db::create_database::CreateDatabase;
|
||||
use crate::db::delete_database::DeleteDatabase;
|
||||
use crate::db::migrate::RunMigrations;
|
||||
use crate::db::sqlx_postgres::Postgres;
|
||||
use crate::utils::random_string::{GenerateRandomString, GenerateRandomStringInterface};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! env_helper {
|
||||
($init_settings:ident, $env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
|
||||
println!("Setting env var {} to {} for test", $env, $val);
|
||||
env::set_var($env, $val);
|
||||
{
|
||||
let new_settings = $crate::settings::Settings::new().unwrap();
|
||||
assert_eq!(new_settings.$($param).+, $val_typed, "should match");
|
||||
assert_ne!(new_settings.$($param).+, $init_settings.$($param).+);
|
||||
}
|
||||
env::remove_var($env);
|
||||
};
|
||||
|
||||
|
||||
($init_settings:ident, $env:expr, $val:expr, $($param:ident).+) => {
|
||||
env_helper!($init_settings, $env, $val.to_string(), $val, $($param).+);
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn get_settings() -> Settings {
|
||||
let mut settings = Settings::new().unwrap();
|
||||
let mut db_url = Url::parse(&settings.database.url).unwrap();
|
||||
db_url.set_path(&GenerateRandomString.get_random(12));
|
||||
settings.database.url = db_url.to_string();
|
||||
|
||||
crate::db::sqlx_postgres::PostgresDatabase
|
||||
.create_database(&db_url)
|
||||
.await;
|
||||
let db = Postgres::new(
|
||||
sqlx::postgres::PgPool::connect(&settings.database.url)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
db.migrate().await;
|
||||
|
||||
settings
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub async fn drop_db(&self) {
|
||||
crate::db::sqlx_postgres::PostgresDatabase
|
||||
.delete_database(&Url::parse(&self.database.url).unwrap())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
65
src/settings/server.rs
Normal file
65
src/settings/server.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use config::{builder::DefaultState, ConfigBuilder};
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
pub struct Server {
|
||||
pub port: u32,
|
||||
pub domain: String,
|
||||
pub ip: String,
|
||||
pub cookie_secret: String,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn get_ip(&self) -> String {
|
||||
format!("{}:{}", self.ip, self.port)
|
||||
}
|
||||
|
||||
pub fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||
for (parameter, env_var_name) in [
|
||||
("server.port", "PORT"),
|
||||
("server.domain", "VANIGAM_server_DOMAIN"),
|
||||
("server.cookie_secret", "VANIGAM_server_COOKIE_SECRET"),
|
||||
("server.ip", "VANIGAM_server_IP"),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
if let Ok(val) = env::var(env_var_name) {
|
||||
log::debug!("Overriding [{parameter}] with environment variable {env_var_name}");
|
||||
s = s.set_override(parameter, val).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::env_helper;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_server_env_override() {
|
||||
let init_settings = crate::settings::Settings::new().unwrap();
|
||||
env_helper!(init_settings, "PORT", 22, server.port);
|
||||
env_helper!(
|
||||
init_settings,
|
||||
"VANIGAM_server_DOMAIN",
|
||||
"test_server_env_override.org",
|
||||
server.domain
|
||||
);
|
||||
env_helper!(init_settings, "VANIGAM_server_IP", "1.1.1.1", server.ip);
|
||||
env_helper!(
|
||||
init_settings,
|
||||
"VANIGAM_server_COOKIE_SECRET",
|
||||
"asdfasdflkjhasdlkfhalksdf",
|
||||
server.cookie_secret
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue