Skip to content

Commit

Permalink
add extension traits to run container without explicitly creating a c…
Browse files Browse the repository at this point in the history
…lient
  • Loading branch information
Ender Tunc committed Jun 24, 2023
1 parent 0f2c985 commit b534af7
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 258 deletions.
2 changes: 1 addition & 1 deletion testcontainers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"
keywords = [ "docker", "testcontainers" ]
license = "MIT OR Apache-2.0"
repository = "https://github.com/testcontainers/testcontainers-rs"
rust-version = "1.60.0"
rust-version = "1.70.0"
description = "A library for integration-testing against docker containers from within Rust."

[dependencies]
Expand Down
4 changes: 2 additions & 2 deletions testcontainers/src/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod cli;
#[cfg(feature = "experimental")]
mod http;

pub use self::cli::Cli;
pub use self::cli::RunViaCli;

#[cfg(feature = "experimental")]
pub use self::http::Http;
pub use self::http::RunViaHttp;
157 changes: 69 additions & 88 deletions testcontainers/src/clients/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,75 +7,20 @@ use std::{
collections::HashMap,
ffi::{OsStr, OsString},
process::{Child, Command, Stdio},
sync::{Arc, RwLock},
sync::{OnceLock, RwLock},
thread::sleep,
time::{Duration, Instant},
};

const ONE_SECOND: Duration = Duration::from_secs(1);
const ZERO: Duration = Duration::from_secs(0);

/// Implementation of the Docker client API using the docker cli.
///
/// This (fairly naive) implementation of the Docker client API simply creates `Command`s to the `docker` CLI. It thereby assumes that the `docker` CLI is installed and that it is in the PATH of the current execution environment.
#[derive(Debug)]
pub struct Cli {
inner: Arc<Client>,
static CLI_DOCKER: OnceLock<Client> = OnceLock::new();
fn docker_client() -> &'static Client {
CLI_DOCKER.get_or_init(|| Client::new::<env::Os>())
}

impl Cli {
pub fn run<I: Image>(&self, image: impl Into<RunnableImage<I>>) -> Container<'_, I> {
let image = image.into();

if let Some(network) = image.network() {
if self.inner.create_network_if_not_exists(network) {
let mut guard = self
.inner
.created_networks
.write()
.expect("failed to lock RwLock");

guard.push(network.to_owned());
}
}

let mut command = Client::build_run_command(&image, self.inner.command());

log::debug!("Executing command: {:?}", command);

let output = command.output().expect("Failed to execute docker command");

assert!(output.status.success(), "failed to start container");
let container_id = String::from_utf8(output.stdout)
.expect("output is not valid utf8")
.trim()
.to_string();

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

self.inner.register_container_started(container_id.clone());

self.block_until_ready(&container_id, image.ready_conditions());

let client = Cli {
inner: self.inner.clone(),
};

let container = Container::new(container_id, client, image, self.inner.command);

for cmd in container
.image()
.exec_after_start(ContainerState::new(container.ports()))
{
container.exec(cmd);
}

container
}
}
// const docker_client: Arc<Client> = Arc::new(Client::new::<env::Os>());

#[derive(Debug)]
struct Client {
Expand Down Expand Up @@ -241,35 +186,25 @@ impl Client {
}
}

impl Default for Cli {
fn default() -> Self {
Self::new::<env::Os>()
}
}

impl Cli {
impl Client {
pub fn new<E>() -> Self
where
E: GetEnvValue,
{
Self {
inner: Arc::new(Client {
container_startup_timestamps: Default::default(),
created_networks: Default::default(),
binary: "docker".into(),
command: env::command::<E>().unwrap_or_default(),
}),
container_startup_timestamps: Default::default(),
created_networks: Default::default(),
binary: "docker".into(),
command: env::command::<E>().unwrap_or_default(),
}
}
}

impl Docker for Cli {
impl Docker for &Client {
fn stdout_logs(&self, id: &str) -> LogStream {
self.inner
.wait_at_least_one_second_after_container_was_started(id);
self.wait_at_least_one_second_after_container_was_started(id);

let child = self
.inner
.command()
.arg("logs")
.arg("-f")
Expand All @@ -283,11 +218,9 @@ impl Docker for Cli {
}

fn stderr_logs(&self, id: &str) -> LogStream {
self.inner
.wait_at_least_one_second_after_container_was_started(id);
self.wait_at_least_one_second_after_container_was_started(id);

let child = self
.inner
.command()
.arg("logs")
.arg("-f")
Expand All @@ -311,7 +244,6 @@ impl Docker for Cli {

fn inspect(&self, id: &str) -> ContainerInspectResponse {
let output = self
.inner
.command()
.arg("inspect")
.arg(id)
Expand All @@ -335,7 +267,6 @@ impl Docker for Cli {

fn rm(&self, id: &str) {
let output = self
.inner
.command()
.arg("rm")
.arg("-f")
Expand All @@ -356,8 +287,7 @@ impl Docker for Cli {
}

fn stop(&self, id: &str) {
self.inner
.command()
self.command()
.arg("stop")
.arg(id)
.stdout(Stdio::piped())
Expand All @@ -368,8 +298,7 @@ impl Docker for Cli {
}

fn start(&self, id: &str) {
self.inner
.command()
self.command()
.arg("start")
.arg(id)
.stdout(Stdio::piped())
Expand All @@ -381,7 +310,6 @@ impl Docker for Cli {

fn exec(&self, id: &str, cmd: String) -> std::process::Output {
let exec_output = self
.inner
.command()
.arg("exec")
.arg(id)
Expand Down Expand Up @@ -410,7 +338,7 @@ impl Docker for Cli {
self.stderr_logs(id).wait_for_message(&message).unwrap()
}
WaitFor::Duration { length } => {
std::thread::sleep(length);
sleep(length);
}
WaitFor::Healthcheck => loop {
use HealthStatusEnum::*;
Expand Down Expand Up @@ -464,6 +392,59 @@ impl Drop for Client {
}
}

pub trait RunViaCli<I: Image> {
fn run(self) -> Container<I>;
}

impl<I: Image> RunViaCli<I> for RunnableImage<I> {
fn run(self) -> Container<I> {
let docker = docker_client();
if let Some(network) = self.network() {
if docker.create_network_if_not_exists(network) {
let mut guard = docker
.created_networks
.write()
.expect("failed to lock RwLock");

guard.push(network.to_owned());
}
}

let mut command = Client::build_run_command(&self, docker.command());

log::debug!("Executing command: {:?}", command);

let output = command.output().expect("Failed to execute docker command");

assert!(output.status.success(), "failed to start container");
let container_id = String::from_utf8(output.stdout)
.expect("output is not valid utf8")
.trim()
.to_string();

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

docker.register_container_started(container_id.clone());

docker.block_until_ready(&container_id, self.ready_conditions());

let container: Container<I> =
Container::new(container_id, docker.clone(), self, docker.command);

for cmd in container
.image()
.exec_after_start(ContainerState::new(container.ports()))
{
container.exec(cmd);
}

container
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit b534af7

Please sign in to comment.