Skip to content

Commit 8f27d44

Browse files
committed
refactor: add TestEnvironmentType enum and fix OpenTofu cleanup issue
- Add TestEnvironmentType enum with Container and VirtualMachine variants - Fix Drop implementation to only cleanup OpenTofu for VirtualMachine environments - Add warning when keep_env flag is used with Container environments - Remove confusing --keep CLI flag from config tests since containers auto-cleanup - Extract E2E tasks into modular functions for better code organization - Add comprehensive task modules for container setup, SSH connectivity, and validation This resolves the 'No such file or directory' OpenTofu warnings in Docker-based config tests while maintaining full functionality for VM-based provision tests.
1 parent 26c3f6a commit 8f27d44

11 files changed

+795
-265
lines changed

src/bin/e2e_config_tests.rs

Lines changed: 38 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616
//! Run with custom options:
1717
//!
1818
//! ```bash
19-
//! # Keep test environment after completion
20-
//! cargo run --bin e2e-config-tests -- --keep
21-
//!
2219
//! # Use custom templates directory
2320
//! cargo run --bin e2e-config-tests -- --templates-dir ./custom/templates
2421
//!
@@ -49,32 +46,29 @@
4946
5047
use anyhow::{Context, Result};
5148
use clap::Parser;
52-
use std::net::SocketAddr;
53-
use std::sync::Arc;
5449
use std::time::Instant;
5550
use tracing::{error, info};
5651

57-
use torrust_tracker_deploy::application::commands::ConfigureCommand;
58-
use torrust_tracker_deploy::config::{InstanceName, SshCredentials};
59-
use torrust_tracker_deploy::container::Services;
60-
use torrust_tracker_deploy::e2e::containers::actions::{SshKeySetupAction, SshWaitAction};
61-
use torrust_tracker_deploy::e2e::containers::timeout::ContainerTimeouts;
62-
use torrust_tracker_deploy::e2e::containers::{
63-
RunningProvisionedContainer, StoppedProvisionedContainer,
52+
use torrust_tracker_deploy::config::InstanceName;
53+
use torrust_tracker_deploy::e2e::environment::{TestEnvironment, TestEnvironmentType};
54+
use torrust_tracker_deploy::e2e::tasks::{
55+
container::{
56+
cleanup_docker_container::cleanup_docker_container,
57+
configure_ssh_connectivity::configure_ssh_connectivity,
58+
run_provision_simulation::run_provision_simulation,
59+
setup_docker_container::setup_docker_container,
60+
},
61+
create_test_ssh_credentials::create_test_ssh_credentials,
62+
preflight_cleanup,
63+
run_ansible_configuration::run_ansible_configuration,
64+
run_deployment_validation::run_deployment_validation,
6465
};
65-
use torrust_tracker_deploy::e2e::environment::TestEnvironment;
66-
use torrust_tracker_deploy::e2e::tasks::container::provision_docker_infrastructure::provision_docker_infrastructure;
67-
use torrust_tracker_deploy::e2e::tasks::preflight_cleanup;
6866
use torrust_tracker_deploy::logging::{self, LogFormat};
6967

7068
#[derive(Parser)]
7169
#[command(name = "e2e-config-tests")]
7270
#[command(about = "E2E configuration tests for Torrust Tracker Deploy using Docker containers")]
7371
struct CliArgs {
74-
/// Keep the test environment after completion
75-
#[arg(long)]
76-
keep: bool,
77-
7872
/// Templates directory path (default: ./data/templates)
7973
#[arg(long, default_value = "./data/templates")]
8074
templates_dir: String,
@@ -126,7 +120,7 @@ pub async fn main() -> Result<()> {
126120
InstanceName::new("torrust-tracker-vm".to_string()).expect("Valid hardcoded instance name");
127121

128122
// Setup test environment with preflight cleanup
129-
let test_env = setup_test_environment(cli.templates_dir, instance_name)?;
123+
let test_env = setup_test_environment(false, cli.templates_dir, instance_name)?;
130124

131125
let test_result = run_configuration_tests(&test_env).await;
132126

@@ -163,262 +157,51 @@ pub async fn main() -> Result<()> {
163157

164158
/// Setup test environment with preflight cleanup
165159
fn setup_test_environment(
160+
keep_env: bool,
166161
templates_dir: String,
167162
instance_name: InstanceName,
168163
) -> Result<TestEnvironment> {
169164
info!("Running preflight cleanup for Docker-based E2E tests");
170-
let test_env =
171-
TestEnvironment::with_ssh_user_and_init(false, templates_dir, "torrust", instance_name)
172-
.context("Failed to create test environment")?;
165+
let test_env = TestEnvironment::with_ssh_user_and_init(
166+
keep_env,
167+
templates_dir,
168+
"torrust",
169+
instance_name,
170+
TestEnvironmentType::Container,
171+
)
172+
.context("Failed to create test environment")?;
173173

174174
preflight_cleanup::cleanup_lingering_resources_docker(&test_env)
175175
.context("Failed to complete preflight cleanup")?;
176176

177177
Ok(test_env)
178178
}
179179

180-
/// Setup Docker container and start it
181-
async fn setup_docker_container() -> Result<RunningProvisionedContainer> {
182-
info!("Setting up Docker container");
183-
let stopped_container = StoppedProvisionedContainer::default();
184-
let running_container = stopped_container
185-
.start()
186-
.await
187-
.context("Failed to start provisioned instance container")?;
188-
189-
Ok(running_container)
190-
}
191-
192-
/// Configure SSH connectivity to the running container
193-
async fn configure_ssh_connectivity(
194-
container: &RunningProvisionedContainer,
195-
test_env: &TestEnvironment,
196-
) -> Result<()> {
197-
let socket_addr = container.ssh_socket_addr();
198-
let timeouts = ContainerTimeouts::default();
199-
let ssh_wait_action = SshWaitAction::new(timeouts.ssh_ready, 10);
200-
ssh_wait_action
201-
.execute(socket_addr)
202-
.context("SSH server failed to start")?;
203-
204-
// Get SSH credentials from test environment and setup keys
205-
let ssh_credentials = &test_env.config.ssh_credentials;
206-
let ssh_key_setup_action = SshKeySetupAction::new();
207-
ssh_key_setup_action
208-
.execute(container, ssh_credentials)
209-
.await
210-
.context("Failed to setup SSH authentication")?;
211-
212-
info!(
213-
socket_addr = %socket_addr,
214-
ssh_user = %ssh_credentials.ssh_username,
215-
container_id = %container.container_id(),
216-
"Container ready for Ansible configuration"
217-
);
218-
219-
Ok(())
220-
}
221-
222-
/// Run the complete configuration tests
180+
/// Run the complete configuration tests using extracted tasks
223181
async fn run_configuration_tests(test_env: &TestEnvironment) -> Result<()> {
224182
info!("Starting configuration tests with Docker container");
225183

226-
// Step 1: Setup Docker container - start with stopped state
184+
// Step 1: Setup Docker container
227185
let running_container = setup_docker_container().await?;
186+
let socket_addr = running_container.ssh_socket_addr();
228187

229-
// Step 2: Wait for SSH server and setup connectivity (only available when running)
230-
configure_ssh_connectivity(&running_container, test_env).await?;
231-
232-
// Step 2.5: Run provision simulation to render Ansible templates
233-
info!("Running provision simulation to prepare container configuration");
234-
run_provision_simulation(running_container.ssh_socket_addr(), test_env).await?;
235-
236-
// Step 3: Run configuration tasks (Ansible playbooks)
237-
info!("Running Ansible configuration tasks");
238-
run_ansible_configuration(running_container.ssh_socket_addr(), test_env)?;
239-
240-
// Step 4: Validate deployment
241-
info!("Validating service deployment");
242-
run_deployment_validation(running_container.ssh_socket_addr(), test_env).await?;
243-
244-
// Step 5: Cleanup - transition back to stopped state
245-
cleanup_container(running_container);
246-
247-
info!("Configuration tests completed successfully");
248-
249-
Ok(())
250-
}
251-
252-
/// Cleanup container by stopping it
253-
fn cleanup_container(running_container: RunningProvisionedContainer) {
254-
info!("Cleaning up container");
255-
let _stopped_container = running_container.stop();
256-
info!("Container stopped successfully");
257-
}
258-
259-
/// Run provision simulation to prepare templates for container configuration
260-
async fn run_provision_simulation(
261-
ssh_service_socket_addr: SocketAddr,
262-
test_env: &TestEnvironment,
263-
) -> Result<()> {
264-
info!(
265-
socket_addr = %ssh_service_socket_addr,
266-
"Running provision simulation for container"
267-
);
268-
269-
// Create SSH credentials and use configuration from test environment
270-
let ssh_credentials =
271-
create_container_ssh_credentials(&test_env.config.ssh_credentials.ssh_username)?;
272-
let services = Services::new(&test_env.config);
273-
274-
// Run the Docker infrastructure provision simulation
275-
provision_docker_infrastructure(
276-
Arc::clone(&services.ansible_template_renderer),
277-
ssh_credentials,
278-
ssh_service_socket_addr,
279-
)
280-
.await
281-
.context("Failed to complete Docker infrastructure provision simulation")?;
282-
283-
info!(
284-
status = "complete",
285-
"Provision simulation completed - Ansible templates rendered with container details"
286-
);
287-
288-
Ok(())
289-
}
290-
291-
/// Run Ansible configuration tasks on the container
292-
fn run_ansible_configuration(
293-
ssh_service_socket_addr: SocketAddr,
294-
test_env: &TestEnvironment,
295-
) -> Result<()> {
296-
info!(
297-
socket_addr = %ssh_service_socket_addr,
298-
"Running Ansible configuration on container"
299-
);
300-
301-
// NOTE: This demonstrates the configuration workflow structure, but currently
302-
// the ConfigureCommand uses LXD-based inventory that tries to connect to
303-
// 10.140.190.171 instead of 127.0.0.1:mapped_port for containers.
304-
//
305-
// To fully implement container-based configuration, we need to:
306-
// 1. Create container-specific Ansible inventory templates
307-
// 2. Modify Config/Services to support container-specific templates
308-
// 3. Update template rendering to use container host/port
309-
//
310-
// For now, we'll catch the expected connection error and log it:
311-
312-
let services = Services::new(&test_env.config);
313-
let configure_command = ConfigureCommand::new(Arc::clone(&services.ansible_client));
314-
315-
match configure_command.execute().map_err(anyhow::Error::from) {
316-
Ok(()) => {
317-
info!(
318-
status = "complete",
319-
"Container configuration completed successfully"
320-
);
321-
}
322-
Err(e) => {
323-
// Expected failure due to inventory mismatch - log and return error
324-
info!(
325-
status = "expected_failure",
326-
error = %e,
327-
note = "ConfigureCommand failed as expected - needs container-specific inventory"
328-
);
329-
return Err(
330-
e.context("Configuration failed (expected - needs container-specific inventory)")
331-
);
332-
}
333-
}
334-
335-
info!(
336-
status = "structural_complete",
337-
"Configuration workflow structure implemented"
338-
);
339-
340-
Ok(())
341-
}
342-
343-
/// Run deployment validation tests on the container
344-
async fn run_deployment_validation(
345-
ssh_service_socket_addr: SocketAddr,
346-
test_env: &TestEnvironment,
347-
) -> Result<()> {
348-
info!(
349-
socket_addr = %ssh_service_socket_addr,
350-
"Running deployment validation on container"
351-
);
352-
353-
// Now we can use the proper SSH infrastructure with custom port support
188+
// Step 2: Configure SSH connectivity
354189
let ssh_credentials =
355-
create_container_ssh_credentials(&test_env.config.ssh_credentials.ssh_username)
356-
.context("Failed to create container SSH credentials")?;
190+
create_test_ssh_credentials(&test_env.config.ssh_credentials.ssh_username)?;
191+
configure_ssh_connectivity(socket_addr, &ssh_credentials, Some(&running_container)).await?;
357192

358-
// Create SSH connection with the container's dynamic port
359-
validate_container_deployment_with_port(&ssh_credentials, ssh_service_socket_addr)
360-
.await
361-
.context("Container deployment validation failed")?;
193+
// Step 3: Run provision simulation
194+
run_provision_simulation(socket_addr, &ssh_credentials, test_env).await?;
362195

363-
info!(status = "success", "All deployment validations passed");
196+
// Step 4: Run Ansible configuration (expect failure due to inventory mismatch)
197+
run_ansible_configuration(socket_addr, test_env, false)?;
364198

365-
info!(
366-
status = "success",
367-
"Validation workflow completed successfully"
368-
);
199+
// Step 5: Run deployment validation
200+
run_deployment_validation(socket_addr, &ssh_credentials).await?;
369201

370-
Ok(())
371-
}
372-
373-
/// Create centralized SSH credentials for test purposes
374-
///
375-
/// Uses fixed test SSH keys from fixtures/ directory with provided username.
376-
/// This factory eliminates code duplication across multiple functions that need
377-
/// the same test SSH credentials.
378-
fn create_test_ssh_credentials(ssh_username: &str) -> Result<SshCredentials> {
379-
let project_root = std::env::current_dir().context("Failed to get current directory")?;
380-
Ok(SshCredentials::new(
381-
project_root.join("fixtures/testing_rsa"),
382-
project_root.join("fixtures/testing_rsa.pub"),
383-
ssh_username.to_string(),
384-
))
385-
}
386-
387-
/// Create SSH credentials for connecting to the container
388-
fn create_container_ssh_credentials(ssh_username: &str) -> Result<SshCredentials> {
389-
// Use the centralized test SSH credentials factory
390-
create_test_ssh_credentials(ssh_username)
391-
}
392-
393-
/// Validate container deployment using SSH infrastructure with custom port
394-
async fn validate_container_deployment_with_port(
395-
ssh_credentials: &SshCredentials,
396-
socket_addr: SocketAddr,
397-
) -> Result<()> {
398-
use torrust_tracker_deploy::infrastructure::remote_actions::{
399-
DockerComposeValidator, DockerValidator, RemoteAction,
400-
};
401-
402-
let ip_addr = socket_addr.ip();
403-
404-
// Create SSH connection with the container's dynamic port using the new port support
405-
let ssh_connection = ssh_credentials
406-
.clone()
407-
.with_host_and_port(ip_addr, socket_addr.port());
408-
409-
// Validate Docker installation
410-
let docker_validator = DockerValidator::new(ssh_connection.clone());
411-
docker_validator
412-
.execute(&ip_addr)
413-
.await
414-
.context("Docker validation failed")?;
415-
416-
// Validate Docker Compose installation
417-
let compose_validator = DockerComposeValidator::new(ssh_connection);
418-
compose_validator
419-
.execute(&ip_addr)
420-
.await
421-
.context("Docker Compose validation failed")?;
202+
// Step 6: Cleanup container
203+
cleanup_docker_container(running_container);
422204

205+
info!("Configuration tests completed successfully");
423206
Ok(())
424207
}

0 commit comments

Comments
 (0)