5656//! }
5757//! ```
5858
59+ use std:: time:: Duration ;
5960use testcontainers:: {
6061 core:: { IntoContainerPort , WaitFor } ,
6162 runners:: SyncRunner ,
@@ -73,6 +74,33 @@ const DEFAULT_IMAGE_NAME: &str = "torrust-provisioned-instance";
7374/// Default Docker image tag for provisioned instances
7475const DEFAULT_IMAGE_TAG : & str = "latest" ;
7576
77+ /// Container timeout configurations for different operations
78+ ///
79+ /// This struct provides configurable timeouts for various container operations
80+ /// to make the system more flexible and adaptable to different environments.
81+ #[ derive( Debug , Clone ) ]
82+ pub struct ContainerTimeouts {
83+ /// Timeout for Docker image build operations
84+ pub docker_build : Duration ,
85+ /// Timeout for container startup operations
86+ pub container_start : Duration ,
87+ /// Timeout for SSH connectivity to become available
88+ pub ssh_ready : Duration ,
89+ /// Timeout for SSH key setup operations
90+ pub ssh_setup : Duration ,
91+ }
92+
93+ impl Default for ContainerTimeouts {
94+ fn default ( ) -> Self {
95+ Self {
96+ docker_build : Duration :: from_secs ( 300 ) , // 5 minutes
97+ container_start : Duration :: from_secs ( 60 ) , // 1 minute
98+ ssh_ready : Duration :: from_secs ( 30 ) , // 30 seconds
99+ ssh_setup : Duration :: from_secs ( 15 ) , // 15 seconds
100+ }
101+ }
102+ }
103+
76104/// Specific error types for provisioned container operations
77105#[ derive( Debug , thiserror:: Error ) ]
78106pub enum ProvisionedContainerError {
@@ -171,18 +199,76 @@ pub type Result<T> = std::result::Result<T, Box<ProvisionedContainerError>>;
171199/// Following the pattern from Torrust Tracker `MySQL` driver, where different states
172200/// have different capabilities enforced at compile time.
173201/// Initial state - container is stopped/not started yet
174- #[ derive( Debug , Default ) ]
175- pub struct StoppedProvisionedContainer { }
202+ #[ derive( Debug ) ]
203+ pub struct StoppedProvisionedContainer {
204+ /// Timeout configurations for container operations
205+ pub timeouts : ContainerTimeouts ,
206+ }
207+
208+ #[ allow( clippy:: derivable_impls) ]
209+ impl Default for StoppedProvisionedContainer {
210+ fn default ( ) -> Self {
211+ Self {
212+ timeouts : ContainerTimeouts :: default ( ) ,
213+ }
214+ }
215+ }
176216
177217impl StoppedProvisionedContainer {
218+ /// Create a new stopped container with custom timeout configurations
219+ ///
220+ /// # Arguments
221+ /// * `timeouts` - Custom timeout configuration for container operations
222+ ///
223+ /// # Example
224+ /// ```rust,no_run
225+ /// use torrust_tracker_deploy::e2e::containers::{StoppedProvisionedContainer, ContainerTimeouts};
226+ /// use std::time::Duration;
227+ ///
228+ /// let mut timeouts = ContainerTimeouts::default();
229+ /// timeouts.ssh_ready = Duration::from_secs(60);
230+ ///
231+ /// let container = StoppedProvisionedContainer::with_timeouts(timeouts);
232+ /// ```
233+ #[ must_use]
234+ pub fn with_timeouts ( timeouts : ContainerTimeouts ) -> Self {
235+ Self { timeouts }
236+ }
237+
238+ /// Create a new stopped container with custom SSH ready timeout
239+ ///
240+ /// This is a convenience method for the most commonly customized timeout.
241+ ///
242+ /// # Arguments
243+ /// * `ssh_ready_timeout` - How long to wait for SSH to become available
244+ ///
245+ /// # Example
246+ /// ```rust,no_run
247+ /// use torrust_tracker_deploy::e2e::containers::StoppedProvisionedContainer;
248+ /// use std::time::Duration;
249+ ///
250+ /// let container = StoppedProvisionedContainer::with_ssh_ready_timeout(
251+ /// Duration::from_secs(60)
252+ /// );
253+ /// ```
254+ #[ must_use]
255+ pub fn with_ssh_ready_timeout ( ssh_ready_timeout : Duration ) -> Self {
256+ let timeouts = ContainerTimeouts {
257+ ssh_ready : ssh_ready_timeout,
258+ ..ContainerTimeouts :: default ( )
259+ } ;
260+ Self { timeouts }
261+ }
262+
178263 /// Build the Docker image if needed using the `ContainerImageBuilder`
179- fn build_image ( ) -> Result < ( ) > {
264+ fn build_image ( docker_build_timeout : Duration ) -> Result < ( ) > {
180265 let builder = ContainerImageBuilder :: new ( )
181266 . with_name ( DEFAULT_IMAGE_NAME )
182267 . with_tag ( DEFAULT_IMAGE_TAG )
183268 . with_dockerfile ( std:: path:: PathBuf :: from (
184269 "docker/provisioned-instance/Dockerfile" ,
185- ) ) ;
270+ ) )
271+ . with_build_timeout ( docker_build_timeout) ;
186272 builder. build ( ) . map_err ( |e| {
187273 Box :: new ( ProvisionedContainerError :: DockerImageBuildFailed { source : * e } )
188274 } ) ?;
@@ -199,7 +285,7 @@ impl StoppedProvisionedContainer {
199285 /// - Container networking setup fails
200286 pub fn start ( self ) -> Result < RunningProvisionedContainer > {
201287 // First build the Docker image if needed
202- Self :: build_image ( ) ?;
288+ Self :: build_image ( self . timeouts . docker_build ) ?;
203289
204290 info ! ( "Starting provisioned instance container" ) ;
205291
0 commit comments