diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 819f3fa..8cc8e84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ name: Continuous integration env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: 0 - MSRV: 1.75.0 + MSRV: 1.80.0 jobs: tests: @@ -87,7 +87,7 @@ jobs: docker_in_docker: runs-on: ubuntu-latest container: - image: public.ecr.aws/docker/library/rust:1.76 + image: public.ecr.aws/docker/library/rust:1.80 steps: - uses: actions/checkout@v4 diff --git a/rustainers/Cargo.toml b/rustainers/Cargo.toml index a13540f..7b3af84 100644 --- a/rustainers/Cargo.toml +++ b/rustainers/Cargo.toml @@ -13,7 +13,7 @@ description = "A simple, opinionated way to run containers for tests." readme = "README.md" repository = "https://github.com/wefoxplatform/rustainers" -rust-version = "1.75.0" +rust-version = "1.80.0" [features] default = ["native-tls"] diff --git a/rustainers/src/runner/inner.rs b/rustainers/src/runner/inner.rs index 176bd3c..3b1b1bb 100644 --- a/rustainers/src/runner/inner.rs +++ b/rustainers/src/runner/inner.rs @@ -20,7 +20,7 @@ use crate::{ RunnableContainer, Volume, WaitStrategy, }; -use super::{ContainerError, RunOption}; +use super::{ContainerError, RunOption, StopOption}; pub(crate) trait InnerRunner: Display + Debug + Send + Sync { fn command(&self) -> Cmd<'static>; @@ -71,6 +71,12 @@ pub(crate) trait InnerRunner: Display + Debug + Send + Sync { cmd.push_arg("--rm"); } + // Stop timeout + if let Some(stop_timeout) = option.stop_timeout { + let secs = stop_timeout.as_secs(); + cmd.push_args(["--stop-timeout", &secs.to_string()]); + } + // Name if let Some(name) = option.name { cmd.push_args(["--name", name]); @@ -562,10 +568,16 @@ pub(crate) trait InnerRunner: Display + Debug + Send + Sync { } #[tracing::instrument(skip(self, id), fields(runner = %self, id = %id))] - fn stop(&self, id: ContainerId) -> Result<(), ContainerError> { + fn stop(&self, id: ContainerId, options: StopOption) -> Result<(), ContainerError> { let mut cmd = self.command(); cmd.push_arg("stop"); cmd.push_arg(id); + + if let Some(timeout) = options.timeout { + let secs = timeout.as_secs(); + cmd.push_args(["--timeout", &secs.to_string()]); + } + let status = cmd.status_blocking()?; if status.success() { info!(%id, "🛑 Container stopped"); @@ -588,6 +600,7 @@ pub(crate) struct CreateAndStartOption<'a> { health_check: Option<&'a HealthCheck>, ports: &'a [ExposedPort], remove: bool, + stop_timeout: Option, name: Option<&'a str>, network: Cow<'a, Network>, volumes: &'a [Volume], @@ -604,6 +617,7 @@ impl<'a> CreateAndStartOption<'a> { } else { None }; + let stop_timeout = option.stop_timeout; let ports = &image.port_mappings; let remove = option.remove; let name = option.name(); @@ -635,6 +649,7 @@ impl<'a> CreateAndStartOption<'a> { health_check, ports, remove, + stop_timeout, name, network, volumes, diff --git a/rustainers/src/runner/mod.rs b/rustainers/src/runner/mod.rs index efd160e..f3d6bdb 100644 --- a/rustainers/src/runner/mod.rs +++ b/rustainers/src/runner/mod.rs @@ -328,6 +328,25 @@ impl Runner { /// /// Fail if we cannot launch the container pub fn stop(&self, container: &Container) -> Result<(), RunnerError> + where + I: ToRunnableContainer, + { + let options = StopOption::default(); + self.stop_with_options(container, options) + } + + /// Stop the container + /// + /// This method is call during the [`crate::Container`] drop if it's not detached + /// + /// # Errors + /// + /// Fail if we cannot launch the container + pub fn stop_with_options( + &self, + container: &Container, + options: StopOption, + ) -> Result<(), RunnerError> where I: ToRunnableContainer, { @@ -335,9 +354,9 @@ impl Runner { let id = container.id; match self { - Self::Docker(runner) => runner.stop(id), - Self::Podman(runner) => runner.stop(id), - Self::Nerdctl(runner) => runner.stop(id), + Self::Docker(runner) => runner.stop(id, options), + Self::Podman(runner) => runner.stop(id, options), + Self::Nerdctl(runner) => runner.stop(id, options), } .map_err(|source| RunnerError::StopError { runner: self.clone(), diff --git a/rustainers/src/runner/options.rs b/rustainers/src/runner/options.rs index 55fb2a0..8784b2e 100644 --- a/rustainers/src/runner/options.rs +++ b/rustainers/src/runner/options.rs @@ -11,6 +11,7 @@ use crate::{Network, Volume}; /// /// * `wait_interval`: wait until re-check a container state (default 500ms) /// * `remove`: if we remove the container after the stop (`--rm` flag, default false) +/// * `stop_timeout`: wait after the stop before killing the container /// * `name`: provide the container name (default unnamed, use the runner name) /// * `network`: define the network /// * `volumes`: set some volumes @@ -25,6 +26,9 @@ pub struct RunOption { /// Automatically remove the container when it exits pub(super) remove: bool, + /// The duration to wait after sending the first signal to stop before killing the container + pub(super) stop_timeout: Option, + /// Assign a name to the container #[builder(setter(into, strip_option))] pub(super) name: Option, @@ -69,3 +73,15 @@ impl Default for RunOption { RunOption::builder().build() } } + +/// Stop options +/// +/// Available options: +/// +/// * `timeout`: wait after the stop before killing the container +#[derive(Debug, Clone, Default, TypedBuilder)] +#[builder(field_defaults(default, setter(prefix = "with_")))] +pub struct StopOption { + /// The duration to wait after sending the first signal to stop before killing the container + pub(super) timeout: Option, +}