testcontainers_ext/
lib.rs1#![crate_name = "testcontainers_ext"]
2
3use bollard::container::ListContainersOptions;
4use std::future::Future;
5use testcontainers::{ContainerRequest, Image, ImageExt, TestcontainersError};
6
7pub trait ImagePruneExistedLabelExt<I>: Sized + ImageExt<I> + Send
8where
9 I: Image,
10{
11 fn with_prune_existed_label(
36 self,
37 scope: &str,
38 container_label: &str,
39 prune: bool,
40 force: bool,
41 ) -> impl Future<Output = Result<ContainerRequest<I>, TestcontainersError>> + Send {
42 use std::collections::HashMap;
43
44 use bollard::container::PruneContainersOptions;
45 use testcontainers::core::client::docker_client_instance;
46
47 let testcontainers_project_key = format!("{scope}.testcontainers.scope");
48 let testcontainers_container_key = format!("{scope}.testcontainers.container");
49 let testcontainers_prune_key = format!("{scope}.testcontainers.prune");
50
51 async move {
52 if prune {
53 let client = docker_client_instance().await?;
54
55 let mut filters = HashMap::<String, Vec<String>>::new();
56
57 filters.insert(
58 String::from("label"),
59 vec![
60 format!("{testcontainers_prune_key}=true"),
61 format!("{}={}", testcontainers_project_key, scope),
62 format!("{}={}", testcontainers_container_key, container_label),
63 ],
64 );
65
66 if force {
67 let result = client
68 .list_containers(Some(ListContainersOptions {
69 all: false,
70 filters: filters.clone(),
71 ..Default::default()
72 }))
73 .await
74 .map_err(|err| TestcontainersError::Other(Box::new(err)))?;
75
76 let remove_containers = result
77 .iter()
78 .filter(|c| matches!(c.state.as_deref(), Some("running")))
79 .flat_map(|c| c.id.as_deref())
80 .collect::<Vec<_>>();
81
82 futures::future::try_join_all(
83 remove_containers
84 .iter()
85 .map(|c| client.stop_container(c, None)),
86 )
87 .await
88 .map_err(|error| TestcontainersError::Other(Box::new(error)))?;
89
90 #[cfg(feature = "tracing")]
91 if !remove_containers.is_empty() {
92 tracing::warn!(name = "stop running containers", result = ?remove_containers);
93 }
94 }
95
96 let _result = client
97 .prune_containers(Some(PruneContainersOptions { filters }))
98 .await
99 .map_err(|err| TestcontainersError::Other(Box::new(err)))?;
100
101 #[cfg(feature = "tracing")]
102 if _result
103 .containers_deleted
104 .as_ref()
105 .is_some_and(|c| !c.is_empty())
106 {
107 tracing::warn!(name = "prune existed containers", result = ?_result);
108 }
109 }
110
111 let result = self.with_labels([
112 (testcontainers_prune_key, "true"),
113 (testcontainers_project_key, scope),
114 (testcontainers_container_key, container_label),
115 ]);
116
117 Ok(result)
118 }
119 }
120}
121
122impl<R, I> ImagePruneExistedLabelExt<I> for R
123where
124 R: Sized + ImageExt<I> + Send,
125 I: Image,
126{
127}
128
129pub trait ImageDefaultLogConsumerExt<I>: Sized + ImageExt<I>
130where
131 I: Image,
132{
133 fn with_default_log_consumer(self) -> ContainerRequest<I> {
157 use testcontainers::core::logs::consumer::logging_consumer::LoggingConsumer;
158
159 self.with_log_consumer(
160 LoggingConsumer::new()
161 .with_stdout_level(log::Level::Info)
162 .with_stderr_level(log::Level::Error),
163 )
164 }
165}
166
167impl<R, I> ImageDefaultLogConsumerExt<I> for R
168where
169 R: Sized + ImageExt<I>,
170 I: Image,
171{
172}