Skip to content

Commit a540a12

Browse files
committed
Merge #237: feat: [#236] make Docker Compose tracker ports dynamic
95d5e13 refactor: [#236] fix clippy too_many_arguments error with TrackerPorts struct (Jose Celano) 150f382 test: [#236] verify dynamic ports are rendered in docker-compose.yml (Jose Celano) b902262 feat: [#236] make Docker Compose ports dynamic using Tera variables (Jose Celano) 5909c0b feat: [#236] extend DockerComposeContext with tracker port fields (Jose Celano) Pull request description: ## Description This PR implements dynamic tracker ports in Docker Compose templates, replacing hardcoded port values with Tera template variables extracted from the tracker configuration. Closes #236 ## Changes ### Core Implementation - **Extended `DockerComposeContext`** with tracker port fields (udp_tracker_ports, http_tracker_ports, http_api_port) - **Created `TrackerPorts` struct** to group port parameters and resolve clippy `too_many_arguments` warning - **Implemented `extract_tracker_ports()`** helper to extract ports from `TrackerConfig` domain model - **Updated `docker-compose.yml.tera`** template with Tera loops for dynamic port rendering ### Files Modified - `src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context.rs` - Added TrackerPorts struct and port fields - `src/application/steps/rendering/docker_compose_templates.rs` - Implemented port extraction logic - `templates/docker-compose/docker-compose.yml.tera` - Replaced hardcoded ports with dynamic Tera variables - Updated 27+ test call sites across context.rs, template.rs, docker_compose.rs, project_generator.rs ## Testing ### Automated Tests ✅ - **1473 unit tests** pass (11.95s) - **8 E2E integration tests** pass - **371 doc tests** pass (22.20s) - All linters pass (markdown, YAML, TOML, clippy, rustfmt, shellcheck, cspell) - Pre-commit validation: 6/6 steps pass (4m 58s) ### Manual E2E Test ✅ Successfully completed full deployment workflow with custom port configuration. **Test Configuration:** - 3 UDP Tracker Ports: 8080, 8081, 8082 (instead of default 6868, 6969) - 2 HTTP Tracker Ports: 9090, 9091 (instead of default 7070) - 1 HTTP API Port: 3030 (instead of default 1212) **Test Results:** 1. ✅ Environment created successfully 2. ✅ Infrastructure provisioned with LXD 3. ✅ Instance configured with Ansible 4. ✅ docker-compose.yml generated with correct dynamic ports: ```yaml ports: # UDP Tracker Ports (dynamically configured) - 8080:8080/udp - 8081:8081/udp - 8082:8082/udp # HTTP Tracker Ports (dynamically configured) - 9090:9090 - 9091:9091 # HTTP API Port (dynamically configured) - 3030:3030 ``` 5. ✅ Tracker started successfully 6. ✅ Container verified running with correct port mappings: ``` 0.0.0.0:8080-8082->8080-8082/udp 0.0.0.0:9090-9091->9090-9091/tcp 0.0.0.0:3030->3030/tcp ``` 7. ✅ Services verified accessible from outside VM (IP: 10.140.190.152): - HTTP API: `curl http://10.140.190.152:3030/api/health_check` → `{"status":"Ok"}` - HTTP Tracker 1: `curl http://10.140.190.152:9090/announce` → Responding - HTTP Tracker 2: `curl http://10.140.190.152:9091/announce` → Responding 8. ✅ Environment destroyed cleanly **Key Achievements:** - ✅ Multiple UDP trackers (3 ports) working simultaneously - ✅ Multiple HTTP trackers (2 ports) working simultaneously - ✅ Custom API port responding correctly - ✅ All services accessible on configured custom ports ## Implementation Details **Before (Hardcoded):** ```yaml ports: - "6868:6868/udp" - "6969:6969/udp" - "7070:7070" - "1212:1212" ``` **After (Dynamic):** ```yaml ports: {% for port in udp_tracker_ports %} - "{{ port }}:{{ port }}/udp" {% endfor %} {% for port in http_tracker_ports %} - "{{ port }}:{{ port }}" {% endfor %} - "{{ http_api_port }}:{{ http_api_port }}" ``` ## Architecture The implementation follows the existing pattern established by `AnsibleVariablesContext`: 1. Port extraction in `RenderDockerComposeTemplatesStep` 2. Domain model (`TrackerConfig`) as source of truth 3. Infrastructure layer handles serialization 4. Tera template rendering with dynamic loops ## Commits - docs: [#236] add issue specification for dynamic tracker ports - feat: [#236] extend DockerComposeContext with tracker port fields - feat: [#236] make Docker Compose ports dynamic using Tera variables - test: [#236] verify dynamic ports are rendered in docker-compose.yml - refactor: [#236] fix clippy too_many_arguments error with TrackerPorts struct ## Checklist - [x] Code follows DDD layer placement guidelines - [x] All automated tests pass - [x] Pre-commit validation passes - [x] Conventional commit format used - [x] Issue number referenced in commits - [x] Manual E2E test completed - [x] Feature verified working in real deployment ACKs for top commit: josecelano: ACK 95d5e13 Tree-SHA512: bf83696b8be104f2929b82245028c6bc12a0ba4b00f2bda21898bb1571a8dcf7ce088bcdc1d47eccfa2e066c6df0f2266b72640a20ef2a6b864d4107f3c85bed
2 parents e575e74 + 95d5e13 commit a540a12

File tree

7 files changed

+219
-20
lines changed

7 files changed

+219
-20
lines changed

src/application/steps/rendering/docker_compose_templates.rs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ use tracing::{info, instrument};
3131

3232
use crate::domain::environment::Environment;
3333
use crate::domain::template::TemplateManager;
34-
use crate::domain::tracker::DatabaseConfig;
35-
use crate::infrastructure::templating::docker_compose::template::wrappers::docker_compose::DockerComposeContext;
34+
use crate::domain::tracker::{DatabaseConfig, TrackerConfig};
35+
use crate::infrastructure::templating::docker_compose::template::wrappers::docker_compose::{
36+
DockerComposeContext, TrackerPorts,
37+
};
3638
use crate::infrastructure::templating::docker_compose::template::wrappers::env::EnvContext;
3739
use crate::infrastructure::templating::docker_compose::{
3840
DockerComposeProjectGenerator, DockerComposeProjectGeneratorError,
@@ -70,6 +72,30 @@ impl<S> RenderDockerComposeTemplatesStep<S> {
7072
}
7173
}
7274

75+
/// Extract port numbers from tracker configuration
76+
///
77+
/// Returns a tuple of (`udp_ports`, `http_ports`, `api_port`)
78+
fn extract_tracker_ports(tracker_config: &TrackerConfig) -> (Vec<u16>, Vec<u16>, u16) {
79+
// Extract UDP tracker ports
80+
let udp_ports: Vec<u16> = tracker_config
81+
.udp_trackers
82+
.iter()
83+
.map(|tracker| tracker.bind_address.port())
84+
.collect();
85+
86+
// Extract HTTP tracker ports
87+
let http_ports: Vec<u16> = tracker_config
88+
.http_trackers
89+
.iter()
90+
.map(|tracker| tracker.bind_address.port())
91+
.collect();
92+
93+
// Extract HTTP API port
94+
let api_port = tracker_config.http_api.bind_address.port();
95+
96+
(udp_ports, http_ports, api_port)
97+
}
98+
7399
/// Execute the template rendering step
74100
///
75101
/// This will render Docker Compose templates to the build directory.
@@ -113,12 +139,23 @@ impl<S> RenderDockerComposeTemplatesStep<S> {
113139
.admin_token
114140
.clone();
115141

142+
// Extract tracker ports from configuration
143+
let tracker_config = &self.environment.context().user_inputs.tracker;
144+
let (udp_tracker_ports, http_tracker_ports, http_api_port) =
145+
Self::extract_tracker_ports(tracker_config);
146+
147+
let ports = TrackerPorts {
148+
udp_tracker_ports,
149+
http_tracker_ports,
150+
http_api_port,
151+
};
152+
116153
// Create contexts based on database configuration
117154
let database_config = &self.environment.context().user_inputs.tracker.core.database;
118155
let (env_context, docker_compose_context) = match database_config {
119156
DatabaseConfig::Sqlite { .. } => {
120157
let env_context = EnvContext::new(admin_token);
121-
let docker_compose_context = DockerComposeContext::new_sqlite();
158+
let docker_compose_context = DockerComposeContext::new_sqlite(ports);
122159
(env_context, docker_compose_context)
123160
}
124161
DatabaseConfig::Mysql {
@@ -145,6 +182,7 @@ impl<S> RenderDockerComposeTemplatesStep<S> {
145182
username.clone(),
146183
password.clone(),
147184
*port,
185+
ports,
148186
);
149187

150188
(env_context, docker_compose_context)
@@ -248,5 +286,25 @@ mod tests {
248286
// Verify it contains expected content from embedded template
249287
assert!(output_content.contains("torrust/tracker"));
250288
assert!(output_content.contains("./storage/tracker/lib:/var/lib/torrust/tracker"));
289+
290+
// Verify dynamic ports are rendered (default TrackerConfig has 6969 UDP, 7070 HTTP, 1212 API)
291+
assert!(
292+
output_content.contains("6969:6969/udp"),
293+
"Should contain UDP tracker port 6969"
294+
);
295+
assert!(
296+
output_content.contains("7070:7070"),
297+
"Should contain HTTP tracker port 7070"
298+
);
299+
assert!(
300+
output_content.contains("1212:1212"),
301+
"Should contain HTTP API port 1212"
302+
);
303+
304+
// Verify hardcoded ports are NOT present
305+
assert!(
306+
!output_content.contains("6868:6868"),
307+
"Should not contain hardcoded UDP port 6868"
308+
);
251309
}
252310
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ mod tests {
188188
use super::*;
189189
use tempfile::TempDir;
190190

191-
use crate::infrastructure::templating::docker_compose::template::wrappers::docker_compose::DockerComposeContext;
191+
use crate::infrastructure::templating::docker_compose::template::wrappers::docker_compose::{
192+
DockerComposeContext, TrackerPorts,
193+
};
192194

193195
#[test]
194196
fn it_should_create_renderer_with_template_manager() {
@@ -212,12 +214,18 @@ mod tests {
212214
let temp_dir = TempDir::new().unwrap();
213215
let template_manager = Arc::new(TemplateManager::new(temp_dir.path()));
214216

217+
let ports = TrackerPorts {
218+
udp_tracker_ports: vec![6868, 6969],
219+
http_tracker_ports: vec![7070],
220+
http_api_port: 1212,
221+
};
215222
let mysql_context = DockerComposeContext::new_mysql(
216223
"rootpass123".to_string(),
217224
"tracker_db".to_string(),
218225
"tracker_user".to_string(),
219226
"userpass123".to_string(),
220227
3306,
228+
ports,
221229
);
222230

223231
let renderer = DockerComposeRenderer::new(template_manager);
@@ -294,7 +302,12 @@ mod tests {
294302
let temp_dir = TempDir::new().unwrap();
295303
let template_manager = Arc::new(TemplateManager::new(temp_dir.path()));
296304

297-
let sqlite_context = DockerComposeContext::new_sqlite();
305+
let ports = TrackerPorts {
306+
udp_tracker_ports: vec![6868, 6969],
307+
http_tracker_ports: vec![7070],
308+
http_api_port: 1212,
309+
};
310+
let sqlite_context = DockerComposeContext::new_sqlite(ports);
298311

299312
let renderer = DockerComposeRenderer::new(template_manager);
300313
let output_dir = TempDir::new().unwrap();

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ mod tests {
185185
use tempfile::TempDir;
186186

187187
use super::*;
188+
use crate::infrastructure::templating::docker_compose::template::wrappers::docker_compose::TrackerPorts;
188189
use crate::infrastructure::templating::docker_compose::DOCKER_COMPOSE_SUBFOLDER;
189190

190191
/// Creates a `TemplateManager` that uses the embedded templates
@@ -205,7 +206,13 @@ mod tests {
205206

206207
/// Helper function to create a test docker-compose context with `SQLite`
207208
fn create_test_docker_compose_context_sqlite() -> DockerComposeContext {
208-
DockerComposeContext::new_sqlite()
209+
// Use default test ports (matching TrackerConfig::default())
210+
let ports = TrackerPorts {
211+
udp_tracker_ports: vec![6969],
212+
http_tracker_ports: vec![7070],
213+
http_api_port: 1212,
214+
};
215+
DockerComposeContext::new_sqlite(ports)
209216
}
210217

211218
#[tokio::test]

0 commit comments

Comments
 (0)