Skip to main content

torrust_tracker_deployer_lib/testing/e2e/
container.rs

1//! Dependency injection container for testing services
2//!
3//! This module provides the `Services` struct that acts as a dependency injection container,
4//! holding all the service clients and template renderers needed for E2E testing operations.
5//! It centralizes service construction and makes them easily accessible throughout tests.
6//!
7//! ## Services Included
8//!
9//! - **Command clients**: `OpenTofu`, LXD, Ansible clients for external tool interaction
10//! - **Template services**: Template manager and specialized renderers for different tools
11//! - **Configuration**: Centralized configuration management
12//!
13//! ## Usage in Tests
14//!
15//! This container is primarily used in E2E tests to create all necessary service dependencies
16//! in a consistent way. In production, services are created on-demand depending on which
17//! command the user is executing.
18
19use std::sync::Arc;
20use std::time::Duration;
21
22use crate::adapters::ansible::AnsibleClient;
23use crate::adapters::lxd::LxdClient;
24use crate::adapters::ssh::SshCredentials;
25use crate::adapters::tofu::OpenTofuClient;
26use crate::config::Config;
27use crate::domain::provider::ProviderConfig;
28use crate::domain::template::TemplateManager;
29use crate::domain::InstanceName;
30use crate::infrastructure::persistence::file_repository_factory::FileRepositoryFactory;
31use crate::infrastructure::templating::ansible::AnsibleProjectGenerator;
32use crate::infrastructure::templating::ansible::ANSIBLE_SUBFOLDER;
33use crate::infrastructure::templating::tofu::TofuProjectGenerator;
34use crate::shared::Clock;
35use crate::testing::e2e::LXD_OPENTOFU_SUBFOLDER;
36use crate::testing::mock_clock::MockClock;
37use chrono::DateTime;
38
39/// Default lock timeout for repository operations
40///
41/// This timeout controls how long repository operations will wait to acquire
42/// file locks before giving up. This prevents operations from hanging indefinitely
43/// if another process has locked the state file.
44///
45/// TODO: Make this configurable via Config in the future
46const REPOSITORY_LOCK_TIMEOUT_SECS: u64 = 30;
47
48/// Service clients and renderers for performing actions in tests
49pub struct Services {
50    // Command wrappers
51    pub opentofu_client: Arc<OpenTofuClient>,
52    pub lxd_client: Arc<LxdClient>,
53    pub ansible_client: Arc<AnsibleClient>,
54
55    // Template related services
56    pub template_manager: Arc<TemplateManager>,
57    pub tofu_template_renderer: Arc<TofuProjectGenerator>,
58    pub ansible_project_generator: Arc<AnsibleProjectGenerator>,
59
60    // Infrastructure services
61    /// Clock service for testable time management
62    pub clock: Arc<dyn Clock>,
63
64    // Persistence layer
65    /// Factory for creating environment-specific repositories
66    pub file_repository_factory: Arc<FileRepositoryFactory>,
67}
68
69impl Services {
70    /// Create a new services container using the provided configuration
71    #[must_use]
72    pub fn new(
73        config: &Config,
74        ssh_credentials: SshCredentials,
75        instance_name: InstanceName,
76        provider_config: ProviderConfig,
77    ) -> Self {
78        // Create template manager
79        let template_manager = TemplateManager::new(config.templates_dir.clone());
80        let template_manager = Arc::new(template_manager);
81
82        // Create OpenTofu client pointing to build/opentofu_subfolder directory
83        let opentofu_client = OpenTofuClient::new(config.build_dir.join(LXD_OPENTOFU_SUBFOLDER));
84
85        // Create LXD client for instance management
86        let lxd_client = LxdClient::new();
87
88        // Create Ansible client pointing to build/ansible_subfolder directory
89        let ansible_client = AnsibleClient::new(config.build_dir.join(ANSIBLE_SUBFOLDER));
90
91        // Create provision template renderer
92        let clock = Arc::new(MockClock::new(DateTime::UNIX_EPOCH));
93        let tofu_template_renderer = TofuProjectGenerator::new(
94            template_manager.clone(),
95            config.build_dir.clone(),
96            ssh_credentials,
97            22, // Default SSH port for tests
98            instance_name,
99            provider_config,
100            clock,
101        );
102
103        // Create configuration template renderer
104        let ansible_project_generator =
105            AnsibleProjectGenerator::new(config.build_dir.clone(), template_manager.clone());
106
107        // Create repository factory
108        let file_repository_factory =
109            FileRepositoryFactory::new(Duration::from_secs(REPOSITORY_LOCK_TIMEOUT_SECS));
110
111        // Create clock service (production implementation uses system time)
112        let clock: Arc<dyn Clock> = Arc::new(crate::shared::SystemClock);
113
114        Self {
115            // Command wrappers
116            opentofu_client: Arc::new(opentofu_client),
117            lxd_client: Arc::new(lxd_client),
118            ansible_client: Arc::new(ansible_client),
119
120            // Template related services
121            template_manager: template_manager.clone(),
122            tofu_template_renderer: Arc::new(tofu_template_renderer),
123            ansible_project_generator: Arc::new(ansible_project_generator),
124
125            // Infrastructure services
126            clock,
127
128            // Persistence layer
129            file_repository_factory: Arc::new(file_repository_factory),
130        }
131    }
132}