Skip to content

Commit 4d53c20

Browse files
committed
refactor: [#232] Split EnvContext into service-specific types
- Create TrackerServiceConfig and MySqlServiceConfig types to mirror .env template structure - Update .env.tera template to use nested variable syntax (e.g., {{ tracker.api_admin_token }}, {{ mysql.root_password }}) - Add backward-compatible getter methods on EnvContext for existing code - Update all template tests to use new nested variable syntax - Add comprehensive tests for context serialization and MySQL configuration - Benefits: Better separation of concerns, more maintainable code, clearer template variable organization
1 parent dc98108 commit 4d53c20

File tree

4 files changed

+163
-47
lines changed

4 files changed

+163
-47
lines changed

src/infrastructure/templating/docker_compose/template/renderer/env.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ mod tests {
218218
TORRUST_TRACKER_CONFIG_TOML_PATH=/etc/torrust/tracker/tracker.toml
219219
220220
# Override the admin token for the tracker HTTP API
221-
TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN={{ tracker_api_admin_token }}
221+
TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN={{ tracker.api_admin_token }}
222222
";
223223

224224
fs::write(docker_compose_dir.join(".env.tera"), template_content)?;

src/infrastructure/templating/docker_compose/template/wrappers/env/context.rs

Lines changed: 152 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,52 @@
22
//!
33
//! This module defines the structure and validation for environment variables
44
//! that will be rendered into the .env file for Docker Compose.
5+
//!
6+
//! The context is organized by service to mirror the structure of the .env template:
7+
//! - Tracker service configuration
8+
//! - `MySQL` service configuration (optional)
59
610
use serde::Serialize;
711

8-
/// Context for rendering the .env template
12+
/// Configuration for the Tracker service
913
///
10-
/// Contains all variables needed for the Docker Compose environment configuration.
14+
/// Contains environment variables for the Torrust Tracker container.
1115
#[derive(Serialize, Debug, Clone)]
12-
pub struct EnvContext {
16+
pub struct TrackerServiceConfig {
1317
/// The admin token for the Torrust Tracker HTTP API
14-
tracker_api_admin_token: String,
18+
pub api_admin_token: String,
1519
/// Database driver type ("sqlite3" or "mysql")
1620
/// Controls which config template the container entrypoint uses
17-
database_driver: String,
18-
/// `MySQL` root password (only used when `MySQL` driver is configured)
19-
#[serde(skip_serializing_if = "Option::is_none")]
20-
mysql_root_password: Option<String>,
21-
/// `MySQL` database name (only used when `MySQL` driver is configured)
22-
#[serde(skip_serializing_if = "Option::is_none")]
23-
mysql_database: Option<String>,
24-
/// `MySQL` user (only used when `MySQL` driver is configured)
25-
#[serde(skip_serializing_if = "Option::is_none")]
26-
mysql_user: Option<String>,
27-
/// `MySQL` password (only used when `MySQL` driver is configured)
21+
pub database_driver: String,
22+
}
23+
24+
/// Configuration for the `MySQL` service
25+
///
26+
/// Contains environment variables for the `MySQL` container.
27+
/// Only included when `MySQL` driver is configured.
28+
#[derive(Serialize, Debug, Clone)]
29+
pub struct MySqlServiceConfig {
30+
/// `MySQL` root password
31+
pub root_password: String,
32+
/// `MySQL` database name
33+
pub database: String,
34+
/// `MySQL` user
35+
pub user: String,
36+
/// `MySQL` password
37+
pub password: String,
38+
}
39+
40+
/// Context for rendering the .env template
41+
///
42+
/// Contains all variables needed for the Docker Compose environment configuration,
43+
/// organized by service to mirror the template structure.
44+
#[derive(Serialize, Debug, Clone)]
45+
pub struct EnvContext {
46+
/// Tracker service configuration
47+
pub tracker: TrackerServiceConfig,
48+
/// `MySQL` service configuration (only present when `MySQL` driver is configured)
2849
#[serde(skip_serializing_if = "Option::is_none")]
29-
mysql_password: Option<String>,
50+
pub mysql: Option<MySqlServiceConfig>,
3051
}
3152

3253
impl EnvContext {
@@ -42,19 +63,21 @@ impl EnvContext {
4263
/// use torrust_tracker_deployer_lib::infrastructure::templating::docker_compose::template::wrappers::env::EnvContext;
4364
///
4465
/// let context = EnvContext::new("MySecretToken123".to_string());
45-
/// assert_eq!(context.tracker_api_admin_token(), "MySecretToken123");
66+
/// assert_eq!(context.tracker.api_admin_token, "MySecretToken123");
67+
/// assert_eq!(context.tracker.database_driver, "sqlite3");
68+
/// assert!(context.mysql.is_none());
4669
/// ```
4770
#[must_use]
4871
pub fn new(tracker_api_admin_token: String) -> Self {
4972
Self {
50-
tracker_api_admin_token,
51-
database_driver: "sqlite3".to_string(),
52-
mysql_root_password: None,
53-
mysql_database: None,
54-
mysql_user: None,
55-
mysql_password: None,
73+
tracker: TrackerServiceConfig {
74+
api_admin_token: tracker_api_admin_token,
75+
database_driver: "sqlite3".to_string(),
76+
},
77+
mysql: None,
5678
}
5779
}
80+
5881
/// Creates a new `EnvContext` with `MySQL` credentials
5982
///
6083
/// # Arguments
@@ -64,6 +87,22 @@ impl EnvContext {
6487
/// * `mysql_database` - `MySQL` database name
6588
/// * `mysql_user` - `MySQL` user
6689
/// * `mysql_password` - `MySQL` password
90+
///
91+
/// # Examples
92+
///
93+
/// ```rust
94+
/// use torrust_tracker_deployer_lib::infrastructure::templating::docker_compose::template::wrappers::env::EnvContext;
95+
///
96+
/// let context = EnvContext::new_with_mysql(
97+
/// "MySecretToken123".to_string(),
98+
/// "root_pass".to_string(),
99+
/// "tracker_db".to_string(),
100+
/// "tracker_user".to_string(),
101+
/// "user_pass".to_string(),
102+
/// );
103+
/// assert_eq!(context.tracker.database_driver, "mysql");
104+
/// assert!(context.mysql.is_some());
105+
/// ```
67106
#[must_use]
68107
pub fn new_with_mysql(
69108
tracker_api_admin_token: String,
@@ -73,43 +112,53 @@ impl EnvContext {
73112
mysql_password: String,
74113
) -> Self {
75114
Self {
76-
tracker_api_admin_token,
77-
database_driver: "mysql".to_string(),
78-
mysql_root_password: Some(mysql_root_password),
79-
mysql_database: Some(mysql_database),
80-
mysql_user: Some(mysql_user),
81-
mysql_password: Some(mysql_password),
115+
tracker: TrackerServiceConfig {
116+
api_admin_token: tracker_api_admin_token,
117+
database_driver: "mysql".to_string(),
118+
},
119+
mysql: Some(MySqlServiceConfig {
120+
root_password: mysql_root_password,
121+
database: mysql_database,
122+
user: mysql_user,
123+
password: mysql_password,
124+
}),
82125
}
83126
}
84127

85128
/// Get the tracker API admin token
86129
#[must_use]
87130
pub fn tracker_api_admin_token(&self) -> &str {
88-
&self.tracker_api_admin_token
131+
&self.tracker.api_admin_token
132+
}
133+
134+
/// Get the database driver type
135+
#[must_use]
136+
pub fn database_driver(&self) -> &str {
137+
&self.tracker.database_driver
89138
}
90139

91140
/// Get the `MySQL` root password (if configured)
92141
#[must_use]
93142
pub fn mysql_root_password(&self) -> Option<&str> {
94-
self.mysql_root_password.as_deref()
143+
self.mysql.as_ref().map(|m| m.root_password.as_str())
95144
}
96145

97146
/// Get the `MySQL` database name (if configured)
98147
#[must_use]
99148
pub fn mysql_database(&self) -> Option<&str> {
100-
self.mysql_database.as_deref()
149+
self.mysql.as_ref().map(|m| m.database.as_str())
101150
}
102151

103152
/// Get the `MySQL` user (if configured)
104153
#[must_use]
105154
pub fn mysql_user(&self) -> Option<&str> {
106-
self.mysql_user.as_deref()
155+
self.mysql.as_ref().map(|m| m.user.as_str())
107156
}
108157

109158
/// Get the `MySQL` password (if configured)
110159
#[must_use]
111160
pub fn mysql_password(&self) -> Option<&str> {
112-
self.mysql_password.as_deref()
161+
self.mysql.as_ref().map(|m| m.password.as_str())
113162
}
114163
}
115164

@@ -122,7 +171,30 @@ mod tests {
122171
let token = "TestToken123".to_string();
123172
let context = EnvContext::new(token.clone());
124173

125-
assert_eq!(context.tracker_api_admin_token(), "TestToken123");
174+
assert_eq!(context.tracker.api_admin_token, "TestToken123");
175+
assert_eq!(context.tracker.database_driver, "sqlite3");
176+
assert!(context.mysql.is_none());
177+
}
178+
179+
#[test]
180+
fn it_should_create_context_with_mysql_configuration() {
181+
let context = EnvContext::new_with_mysql(
182+
"AdminToken456".to_string(),
183+
"root_pass".to_string(),
184+
"tracker_db".to_string(),
185+
"tracker_user".to_string(),
186+
"user_pass".to_string(),
187+
);
188+
189+
assert_eq!(context.tracker.api_admin_token, "AdminToken456");
190+
assert_eq!(context.tracker.database_driver, "mysql");
191+
assert!(context.mysql.is_some());
192+
193+
let mysql_config = context.mysql.as_ref().unwrap();
194+
assert_eq!(mysql_config.root_password, "root_pass");
195+
assert_eq!(mysql_config.database, "tracker_db");
196+
assert_eq!(mysql_config.user, "tracker_user");
197+
assert_eq!(mysql_config.password, "user_pass");
126198
}
127199

128200
#[test]
@@ -131,7 +203,51 @@ mod tests {
131203

132204
// Verify it can be serialized (needed for Tera template rendering)
133205
let serialized = serde_json::to_string(&context).unwrap();
134-
assert!(serialized.contains("tracker_api_admin_token"));
206+
assert!(serialized.contains("tracker"));
207+
assert!(serialized.contains("api_admin_token"));
135208
assert!(serialized.contains("AdminToken456"));
136209
}
210+
211+
#[test]
212+
fn it_should_serialize_mysql_config_when_present() {
213+
let context = EnvContext::new_with_mysql(
214+
"Token123".to_string(),
215+
"root".to_string(),
216+
"db".to_string(),
217+
"user".to_string(),
218+
"pass".to_string(),
219+
);
220+
221+
let serialized = serde_json::to_string(&context).unwrap();
222+
assert!(serialized.contains("mysql"));
223+
assert!(serialized.contains("root_password"));
224+
}
225+
226+
#[test]
227+
fn it_should_not_serialize_mysql_config_when_absent() {
228+
let context = EnvContext::new("Token123".to_string());
229+
230+
let serialized = serde_json::to_string(&context).unwrap();
231+
// MySQL section should not be present when None
232+
assert!(!serialized.contains("mysql"));
233+
}
234+
235+
#[test]
236+
fn it_should_provide_backward_compatible_getters() {
237+
let context = EnvContext::new_with_mysql(
238+
"Token123".to_string(),
239+
"root_pass".to_string(),
240+
"tracker_db".to_string(),
241+
"tracker_user".to_string(),
242+
"user_pass".to_string(),
243+
);
244+
245+
// Backward compatible getter methods
246+
assert_eq!(context.tracker_api_admin_token(), "Token123");
247+
assert_eq!(context.database_driver(), "mysql");
248+
assert_eq!(context.mysql_root_password(), Some("root_pass"));
249+
assert_eq!(context.mysql_database(), Some("tracker_db"));
250+
assert_eq!(context.mysql_user(), Some("tracker_user"));
251+
assert_eq!(context.mysql_password(), Some("user_pass"));
252+
}
137253
}

src/infrastructure/templating/docker_compose/template/wrappers/env/template.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ mod tests {
8585

8686
#[test]
8787
fn it_should_create_env_template_successfully() {
88-
let template_content = "TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN={{ tracker_api_admin_token }}\n";
88+
let template_content = "TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN={{ tracker.api_admin_token }}\n";
8989

9090
let template_file = File::new(".env.tera", template_content.to_string()).unwrap();
9191

@@ -97,7 +97,7 @@ mod tests {
9797

9898
#[test]
9999
fn it_should_render_template_with_substituted_variables() {
100-
let template_content = "TOKEN={{ tracker_api_admin_token }}\n";
100+
let template_content = "TOKEN={{ tracker.api_admin_token }}\n";
101101

102102
let template_file = File::new(".env.tera", template_content.to_string()).unwrap();
103103

@@ -141,7 +141,7 @@ mod tests {
141141
fn it_should_render_to_file() {
142142
use tempfile::TempDir;
143143

144-
let template_content = "ADMIN_TOKEN={{ tracker_api_admin_token }}\n";
144+
let template_content = "ADMIN_TOKEN={{ tracker.api_admin_token }}\n";
145145
let template_file = File::new(".env.tera", template_content.to_string()).unwrap();
146146

147147
let env_context = EnvContext::new("FileTestToken".to_string());

templates/docker-compose/.env.tera

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ TORRUST_TRACKER_CONFIG_TOML_PATH='/etc/torrust/tracker/tracker.toml'
1111
# Database driver type - tells the container entrypoint which config template to use
1212
# Must match the driver specified in tracker.toml
1313
# Uses standardized TORRUST_TRACKER_CONFIG_OVERRIDE_* naming convention
14-
TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER='{{ database_driver }}'
14+
TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER='{{ tracker.database_driver }}'
1515

1616
# Admin API token for tracker HTTP API access
1717
# This overrides the admin token in the tracker configuration file
18-
TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN='{{ tracker_api_admin_token }}'
18+
TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN='{{ tracker.api_admin_token }}'
1919

20-
{% if database_driver == "mysql" %}
20+
{% if mysql %}
2121
# =============================================================================
2222
# MySQL Service Configuration
2323
# =============================================================================
2424

2525
# These variables are only needed when MySQL driver is configured
26-
MYSQL_ROOT_PASSWORD='{{ mysql_root_password }}'
27-
MYSQL_DATABASE='{{ mysql_database }}'
28-
MYSQL_USER='{{ mysql_user }}'
29-
MYSQL_PASSWORD='{{ mysql_password }}'
26+
MYSQL_ROOT_PASSWORD='{{ mysql.root_password }}'
27+
MYSQL_DATABASE='{{ mysql.database }}'
28+
MYSQL_USER='{{ mysql.user }}'
29+
MYSQL_PASSWORD='{{ mysql.password }}'
3030
{% endif %}

0 commit comments

Comments
 (0)