Skip to content

Commit 655f631

Browse files
committed
feat: [#130] add env var to change the default config path
You can overwrite the default config file path with: ``` TORRUST_IDX_BACK_CONFIG_PATH=./storage/config/config.toml cargo run ``` The default path is `./config.toml`
1 parent f90c1ff commit 655f631

File tree

6 files changed

+53
-22
lines changed

6 files changed

+53
-22
lines changed

src/bootstrap/config.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ use std::env;
66
// Environment variables
77

88
/// The whole `config.toml` file content. It has priority over the config file.
9+
/// Even if the file is not on the default path.
910
pub const ENV_VAR_CONFIG: &str = "TORRUST_IDX_BACK_CONFIG";
1011

12+
/// The `config.toml` file location.
13+
pub const ENV_VAR_CONFIG_PATH: &str = "TORRUST_IDX_BACK_CONFIG_PATH";
14+
1115
// Default values
1216

1317
pub const ENV_VAR_DEFAULT_CONFIG_PATH: &str = "./config.toml";
@@ -25,9 +29,11 @@ pub async fn init_configuration() -> Configuration {
2529

2630
Configuration::load_from_env_var(ENV_VAR_CONFIG).unwrap()
2731
} else {
28-
println!("Loading configuration from config file `{}`", ENV_VAR_DEFAULT_CONFIG_PATH);
32+
let config_path = env::var(ENV_VAR_CONFIG_PATH).unwrap_or_else(|_| ENV_VAR_DEFAULT_CONFIG_PATH.to_string());
33+
34+
println!("Loading configuration from config file `{}`", config_path);
2935

30-
match Configuration::load_from_file(ENV_VAR_DEFAULT_CONFIG_PATH).await {
36+
match Configuration::load_from_file(&config_path).await {
3137
Ok(config) => config,
3238
Err(error) => {
3339
panic!("{}", error)

src/config.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,14 @@ impl Default for AppConfiguration {
151151
#[derive(Debug)]
152152
pub struct Configuration {
153153
pub settings: RwLock<AppConfiguration>,
154+
pub config_path: Option<String>,
154155
}
155156

156157
impl Default for Configuration {
157158
fn default() -> Self {
158159
Self {
159160
settings: RwLock::new(AppConfiguration::default()),
161+
config_path: None,
160162
}
161163
}
162164
}
@@ -188,6 +190,7 @@ impl Configuration {
188190

189191
Ok(Configuration {
190192
settings: RwLock::new(torrust_config),
193+
config_path: Some(config_path.to_string()),
191194
})
192195
}
193196

@@ -207,6 +210,7 @@ impl Configuration {
207210
let torrust_config: AppConfiguration = config_builder.try_deserialize()?;
208211
Ok(Configuration {
209212
settings: RwLock::new(torrust_config),
213+
config_path: None,
210214
})
211215
}
212216
Err(_) => Err(ConfigError::Message(
@@ -215,26 +219,36 @@ impl Configuration {
215219
}
216220
}
217221

218-
pub async fn save_to_file(&self, config_path: &str) -> Result<(), ()> {
222+
pub async fn save_to_file(&self, config_path: &str) {
219223
let settings = self.settings.read().await;
220224

221225
let toml_string = toml::to_string(&*settings).expect("Could not encode TOML value");
222226

223227
drop(settings);
224228

225229
fs::write(config_path, toml_string).expect("Could not write to file!");
226-
Ok(())
227230
}
228231

229-
pub async fn update_settings(&self, new_settings: AppConfiguration, config_path: &str) -> Result<(), ()> {
230-
let mut settings = self.settings.write().await;
231-
*settings = new_settings;
232-
233-
drop(settings);
232+
/// Updates the settings and saves them to the configuration file.
233+
///
234+
/// # Panics
235+
///
236+
/// Will panic if the configuration file path is not defined. That happens
237+
/// when the configuration was loaded from the environment variable.
238+
pub async fn update_settings(&self, new_settings: AppConfiguration) {
239+
match &self.config_path {
240+
Some(config_path) => {
241+
let mut settings = self.settings.write().await;
242+
*settings = new_settings;
234243

235-
let _ = self.save_to_file(config_path).await;
244+
drop(settings);
236245

237-
Ok(())
246+
let _ = self.save_to_file(config_path).await;
247+
}
248+
None => panic!(
249+
"Cannot update settings when the config file path is not defined. For example: when it's loaded from env var."
250+
),
251+
}
238252
}
239253

240254
pub async fn get_public(&self) -> ConfigurationPublic {

src/routes/settings.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use actix_web::{web, HttpRequest, HttpResponse, Responder};
22

3-
use crate::bootstrap::config::ENV_VAR_DEFAULT_CONFIG_PATH;
43
use crate::common::WebAppData;
54
use crate::config::AppConfiguration;
65
use crate::errors::{ServiceError, ServiceResult};
@@ -12,7 +11,7 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) {
1211
.service(
1312
web::resource("")
1413
.route(web::get().to(get_settings))
15-
.route(web::post().to(update_settings)),
14+
.route(web::post().to(update_settings_handler)),
1615
)
1716
.service(web::resource("/name").route(web::get().to(get_site_name)))
1817
.service(web::resource("/public").route(web::get().to(get_public_settings))),
@@ -47,7 +46,16 @@ pub async fn get_site_name(app_data: WebAppData) -> ServiceResult<impl Responder
4746
}))
4847
}
4948

50-
pub async fn update_settings(
49+
/// Update the settings
50+
///
51+
/// # Errors
52+
///
53+
/// Will return an error if:
54+
///
55+
/// - There is no logged-in user.
56+
/// - The user is not an administrator.
57+
/// - The settings could not be updated because they were loaded from env vars.
58+
pub async fn update_settings_handler(
5159
req: HttpRequest,
5260
payload: web::Json<AppConfiguration>,
5361
app_data: WebAppData,
@@ -60,10 +68,7 @@ pub async fn update_settings(
6068
return Err(ServiceError::Unauthorized);
6169
}
6270

63-
let _ = app_data
64-
.cfg
65-
.update_settings(payload.into_inner(), ENV_VAR_DEFAULT_CONFIG_PATH)
66-
.await;
71+
let _ = app_data.cfg.update_settings(payload.into_inner()).await;
6772

6873
let settings = app_data.cfg.settings.read().await;
6974

tests/e2e/contexts/settings/contract.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,14 @@ async fn it_should_allow_admins_to_get_all_the_settings() {
6767
#[tokio::test]
6868
async fn it_should_allow_admins_to_update_all_the_settings() {
6969
let mut env = TestEnv::new();
70+
env.start().await;
7071

7172
if !env.is_isolated() {
7273
// This test can't be executed in a non-isolated environment because
7374
// it will change the settings for all the other tests.
7475
return;
7576
}
7677

77-
env.start().await;
78-
7978
let logged_in_admin = new_logged_in_admin(&env).await;
8079
let client = Client::authenticated(&env.server_socket_addr().unwrap(), &logged_in_admin.token);
8180

tests/environments/app_starter.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use torrust_index_backend::config::{AppConfiguration, Configuration};
88
/// It launches the app and provides a way to stop it.
99
pub struct AppStarter {
1010
configuration: AppConfiguration,
11+
config_path: Option<String>,
1112
/// The application binary state (started or not):
1213
/// - `None`: if the app is not started,
1314
/// - `RunningState`: if the app was started.
@@ -16,9 +17,10 @@ pub struct AppStarter {
1617

1718
impl AppStarter {
1819
#[must_use]
19-
pub fn with_custom_configuration(configuration: AppConfiguration) -> Self {
20+
pub fn with_custom_configuration(configuration: AppConfiguration, config_path: Option<String>) -> Self {
2021
Self {
2122
configuration,
23+
config_path,
2224
running_state: None,
2325
}
2426
}
@@ -29,6 +31,7 @@ impl AppStarter {
2931
pub async fn start(&mut self) {
3032
let configuration = Configuration {
3133
settings: RwLock::new(self.configuration.clone()),
34+
config_path: self.config_path.clone(),
3235
};
3336

3437
// Open a channel to communicate back with this function

tests/environments/isolated.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ impl TestEnv {
2727
let temp_dir = TempDir::new().expect("failed to create a temporary directory");
2828

2929
let configuration = ephemeral(&temp_dir);
30+
// Even if we load the configuration from the environment variable, we
31+
// still need to provide a path to save the configuration when the
32+
// configuration is updated via the `POST /settings` endpoints.
33+
let config_path = format!("{}/config.toml", temp_dir.path().to_string_lossy());
3034

31-
let app_starter = AppStarter::with_custom_configuration(configuration);
35+
let app_starter = AppStarter::with_custom_configuration(configuration, Some(config_path));
3236

3337
Self { app_starter, temp_dir }
3438
}

0 commit comments

Comments
 (0)