Skip to content

Commit

Permalink
fix: exclude image pulling time from startup timeout (#687)
Browse files Browse the repository at this point in the history
The main purpose of this timeout is to ensure that the wait strategies
do not get stuck. But image pulling should not be timed out.
If necessary, users can always use a external timeout or explicit
[pull_image](https://docs.rs/testcontainers/latest/testcontainers/runners/trait.AsyncRunner.html#tymethod.pull_image)
call
  • Loading branch information
DDtKey authored Jul 7, 2024
1 parent 73792f9 commit f0d418a
Showing 1 changed file with 137 additions and 136 deletions.
273 changes: 137 additions & 136 deletions testcontainers/src/runners/async_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,156 +53,157 @@ where
{
async fn start(self) -> Result<ContainerAsync<I>> {
let container_req = self.into();
let startup_timeout = container_req
.startup_timeout()
.unwrap_or(DEFAULT_STARTUP_TIMEOUT);

tokio::time::timeout(startup_timeout, async {
let client = Client::lazy_client().await?;
let mut create_options: Option<CreateContainerOptions<String>> = None;
let client = Client::lazy_client().await?;
let mut create_options: Option<CreateContainerOptions<String>> = None;

let extra_hosts: Vec<_> = container_req
.hosts()
.map(|(key, value)| format!("{key}:{value}"))
.collect();

let mut config: Config<String> = Config {
image: Some(container_req.descriptor()),
host_config: Some(HostConfig {
privileged: Some(container_req.privileged()),
extra_hosts: Some(extra_hosts),
cgroupns_mode: container_req.cgroupns_mode().map(|mode| mode.into()),
userns_mode: container_req.userns_mode().map(|v| v.to_string()),
..Default::default()
}),
..Default::default()
};

let extra_hosts: Vec<_> = container_req
.hosts()
.map(|(key, value)| format!("{key}:{value}"))
.collect();
// shared memory
if let Some(bytes) = container_req.shm_size() {
config.host_config = config.host_config.map(|mut host_config| {
host_config.shm_size = Some(bytes as i64);
host_config
});
}

let mut config: Config<String> = Config {
image: Some(container_req.descriptor()),
host_config: Some(HostConfig {
privileged: Some(container_req.privileged()),
extra_hosts: Some(extra_hosts),
cgroupns_mode: container_req.cgroupns_mode().map(|mode| mode.into()),
userns_mode: container_req.userns_mode().map(|v| v.to_string()),
..Default::default()
}),
..Default::default()
};

// shared memory
if let Some(bytes) = container_req.shm_size() {
config.host_config = config.host_config.map(|mut host_config| {
host_config.shm_size = Some(bytes as i64);
host_config
});
}
// create network and add it to container creation
let network = if let Some(network) = container_req.network() {
config.host_config = config.host_config.map(|mut host_config| {
host_config.network_mode = Some(network.to_string());
host_config
});
Network::new(network, client.clone()).await?
} else {
None
};

// create network and add it to container creation
let network = if let Some(network) = container_req.network() {
config.host_config = config.host_config.map(|mut host_config| {
host_config.network_mode = Some(network.to_string());
host_config
});
Network::new(network, client.clone()).await?
} else {
None
};

// name of the container
if let Some(name) = container_req.container_name() {
create_options = Some(CreateContainerOptions {
name: name.to_owned(),
platform: None,
})
}
// name of the container
if let Some(name) = container_req.container_name() {
create_options = Some(CreateContainerOptions {
name: name.to_owned(),
platform: None,
})
}

// handle environment variables
let envs: Vec<String> = container_req
.env_vars()
.map(|(k, v)| format!("{k}={v}"))
.collect();
config.env = Some(envs);
// handle environment variables
let envs: Vec<String> = container_req
.env_vars()
.map(|(k, v)| format!("{k}={v}"))
.collect();
config.env = Some(envs);

// mounts and volumes
let mounts: Vec<_> = container_req.mounts().map(Into::into).collect();

if !mounts.is_empty() {
config.host_config = config.host_config.map(|mut host_config| {
host_config.mounts = Some(mounts);
host_config
});
}

// mounts and volumes
let mounts: Vec<_> = container_req.mounts().map(Into::into).collect();
// entrypoint
if let Some(entrypoint) = container_req.entrypoint() {
config.entrypoint = Some(vec![entrypoint.to_string()]);
}

if !mounts.is_empty() {
config.host_config = config.host_config.map(|mut host_config| {
host_config.mounts = Some(mounts);
host_config
});
}
let is_container_networked = container_req
.network()
.as_ref()
.map(|network| network.starts_with("container:"))
.unwrap_or(false);

// expose ports
if !is_container_networked {
let mapped_ports = container_req
.ports()
.map(|ports| ports.iter().map(|p| p.container_port).collect::<Vec<_>>())
.unwrap_or_default();

let ports_to_expose = container_req
.expose_ports()
.iter()
.copied()
.chain(mapped_ports)
.map(|p| (format!("{p}"), HashMap::new()))
.collect();

// entrypoint
if let Some(entrypoint) = container_req.entrypoint() {
config.entrypoint = Some(vec![entrypoint.to_string()]);
}
// exposed ports of the image + mapped ports
config.exposed_ports = Some(ports_to_expose);
}

let is_container_networked = container_req
.network()
.as_ref()
.map(|network| network.starts_with("container:"))
.unwrap_or(false);

// expose ports
if !is_container_networked {
let mapped_ports = container_req
.ports()
.map(|ports| ports.iter().map(|p| p.container_port).collect::<Vec<_>>())
.unwrap_or_default();

let ports_to_expose = container_req
.expose_ports()
.iter()
.copied()
.chain(mapped_ports)
.map(|p| (format!("{p}"), HashMap::new()))
.collect();

// exposed ports of the image + mapped ports
config.exposed_ports = Some(ports_to_expose);
}
// ports
if container_req.ports().is_some() {
let empty: Vec<_> = Vec::new();
let bindings = container_req.ports().unwrap_or(&empty).iter().map(|p| {
(
format!("{}", p.container_port),
Some(vec![PortBinding {
host_ip: None,
host_port: Some(p.host_port.to_string()),
}]),
)
});

config.host_config = config.host_config.map(|mut host_config| {
host_config.port_bindings = Some(bindings.collect());
host_config
});
} else if !is_container_networked {
config.host_config = config.host_config.map(|mut host_config| {
host_config.publish_all_ports = Some(true);
host_config
});
}

// ports
if container_req.ports().is_some() {
let empty: Vec<_> = Vec::new();
let bindings = container_req.ports().unwrap_or(&empty).iter().map(|p| {
(
format!("{}", p.container_port),
Some(vec![PortBinding {
host_ip: None,
host_port: Some(p.host_port.to_string()),
}]),
)
});

config.host_config = config.host_config.map(|mut host_config| {
host_config.port_bindings = Some(bindings.collect());
host_config
});
} else if !is_container_networked {
config.host_config = config.host_config.map(|mut host_config| {
host_config.publish_all_ports = Some(true);
host_config
});
}
let cmd: Vec<_> = container_req.cmd().map(|v| v.to_string()).collect();
if !cmd.is_empty() {
config.cmd = Some(cmd);
}

let cmd: Vec<_> = container_req.cmd().map(|v| v.to_string()).collect();
if !cmd.is_empty() {
config.cmd = Some(cmd);
// create the container with options
let create_result = client
.create_container(create_options.clone(), config.clone())
.await;
let container_id = match create_result {
Ok(id) => Ok(id),
Err(ClientError::CreateContainer(
bollard::errors::Error::DockerResponseServerError {
status_code: 404, ..
},
)) => {
client.pull_image(&container_req.descriptor()).await?;
client.create_container(create_options, config).await
}
res => res,
}?;

// create the container with options
let create_result = client
.create_container(create_options.clone(), config.clone())
.await;
let container_id = match create_result {
Ok(id) => Ok(id),
Err(ClientError::CreateContainer(
bollard::errors::Error::DockerResponseServerError {
status_code: 404, ..
},
)) => {
client.pull_image(&container_req.descriptor()).await?;
client.create_container(create_options, config).await
}
res => res,
}?;

#[cfg(feature = "watchdog")]
if client.config.command() == crate::core::env::Command::Remove {
crate::watchdog::register(container_id.clone());
}
#[cfg(feature = "watchdog")]
if client.config.command() == crate::core::env::Command::Remove {
crate::watchdog::register(container_id.clone());
}

let startup_timeout = container_req
.startup_timeout()
.unwrap_or(DEFAULT_STARTUP_TIMEOUT);

tokio::time::timeout(startup_timeout, async {
client.start_container(&container_id).await?;

let container =
Expand Down

0 comments on commit f0d418a

Please sign in to comment.