Skip to content

Commit 1ec42ed

Browse files
mominulDDtKey
andauthored
feat: platform config passing through ImageExt trait (#838)
~~Alternative approach to #800 #837~~ I think this approach gives more flexibility API-wise. I am happy to add tests if the maintainers think this is a more suitable approach. Fixes #500, #718 Thanks in advance! --------- Co-authored-by: Artem Medvedev <i@ddtkey.com>
1 parent b41cc80 commit 1ec42ed

File tree

4 files changed

+74
-12
lines changed

4 files changed

+74
-12
lines changed

testcontainers/src/core/client.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,18 @@ impl Client {
450450
Ok(())
451451
}
452452

453-
pub(crate) async fn pull_image(&self, descriptor: &str) -> Result<(), ClientError> {
453+
pub(crate) async fn pull_image(
454+
&self,
455+
descriptor: &str,
456+
platform: Option<String>,
457+
) -> Result<(), ClientError> {
454458
let pull_options = CreateImageOptionsBuilder::new()
455459
.from_image(descriptor)
456-
.platform(self.config.platform().unwrap_or_default())
460+
.platform(
461+
platform
462+
.as_deref()
463+
.unwrap_or_else(|| self.config.platform().unwrap_or_default()),
464+
)
457465
.build();
458466

459467
let credentials = self.credentials_for_image(descriptor).await;
@@ -467,7 +475,7 @@ impl Client {
467475
Err(BollardError::DockerResponseServerError {
468476
status_code: _,
469477
message: _,
470-
}) => {
478+
}) if !matches!(platform.as_deref(), Some("linux/amd64")) => {
471479
self.pull_image_linux_amd64(descriptor).await?;
472480
}
473481
_ => {
@@ -731,7 +739,7 @@ mod tests {
731739
)
732740
.await;
733741

734-
client.pull_image(IMAGE).await?;
742+
client.pull_image(IMAGE, None).await?;
735743

736744
let image = client.bollard.inspect_image(IMAGE).await?;
737745

@@ -746,7 +754,7 @@ mod tests {
746754
.remove_image(IMAGE, Option::<RemoveImageOptions>::None, credentials)
747755
.await?;
748756

749-
client.pull_image(IMAGE).await?;
757+
client.pull_image(IMAGE, None).await?;
750758

751759
let image = client.bollard.inspect_image(IMAGE).await?;
752760

testcontainers/src/core/containers/request.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct ContainerRequest<I: Image> {
2626
pub(crate) image_name: Option<String>,
2727
pub(crate) image_tag: Option<String>,
2828
pub(crate) container_name: Option<String>,
29+
pub(crate) platform: Option<String>,
2930
pub(crate) network: Option<String>,
3031
pub(crate) hostname: Option<String>,
3132
pub(crate) labels: BTreeMap<String, String>,
@@ -97,6 +98,10 @@ impl<I: Image> ContainerRequest<I> {
9798
&self.container_name
9899
}
99100

101+
pub fn platform(&self) -> &Option<String> {
102+
&self.platform
103+
}
104+
100105
pub fn env_vars(&self) -> impl Iterator<Item = (Cow<'_, str>, Cow<'_, str>)> {
101106
self.image
102107
.env_vars()
@@ -248,6 +253,7 @@ impl<I: Image> From<I> for ContainerRequest<I> {
248253
image_name: None,
249254
image_tag: None,
250255
container_name: None,
256+
platform: None,
251257
network: None,
252258
hostname: None,
253259
labels: BTreeMap::default(),
@@ -307,6 +313,7 @@ impl<I: Image + Debug> Debug for ContainerRequest<I> {
307313
.field("image_name", &self.image_name)
308314
.field("image_tag", &self.image_tag)
309315
.field("container_name", &self.container_name)
316+
.field("platform", &self.platform)
310317
.field("network", &self.network)
311318
.field("hostname", &self.hostname)
312319
.field("labels", &self.labels)

testcontainers/src/core/image/image_ext.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ pub trait ImageExt<I: Image> {
6868
/// Sets the container name.
6969
fn with_container_name(self, name: impl Into<String>) -> ContainerRequest<I>;
7070

71+
/// Sets the platform the container will be run on.
72+
///
73+
/// Platform in the format `os[/arch[/variant]]` used for image lookup.
74+
///
75+
/// # Examples
76+
///
77+
/// ```rust,no_run
78+
/// use testcontainers::{GenericImage, ImageExt};
79+
///
80+
/// let image = GenericImage::new("image", "tag")
81+
/// .with_platform("linux/amd64");
82+
/// ```
83+
fn with_platform(self, platform: impl Into<String>) -> ContainerRequest<I>;
84+
7185
/// Sets the network the container will be connected to.
7286
fn with_network(self, network: impl Into<String>) -> ContainerRequest<I>;
7387

@@ -283,6 +297,15 @@ impl<RI: Into<ContainerRequest<I>>, I: Image> ImageExt<I> for RI {
283297
}
284298
}
285299

300+
fn with_platform(self, platform: impl Into<String>) -> ContainerRequest<I> {
301+
let container_req = self.into();
302+
303+
ContainerRequest {
304+
platform: Some(platform.into()),
305+
..container_req
306+
}
307+
}
308+
286309
fn with_network(self, network: impl Into<String>) -> ContainerRequest<I> {
287310
let container_req = self.into();
288311
ContainerRequest {

testcontainers/src/runners/async_runner.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,27 @@ where
187187
None
188188
};
189189

190+
let mut options_builder: Option<CreateContainerOptionsBuilder> = None;
191+
190192
// name of the container
191193
if let Some(name) = container_req.container_name() {
192-
let options = CreateContainerOptionsBuilder::new()
193-
.name(name)
194-
.platform(client.config.platform().unwrap_or_default())
195-
.build();
196-
create_options = Some(options)
194+
let options = CreateContainerOptionsBuilder::new().name(name);
195+
196+
options_builder = Some(options);
197+
}
198+
199+
// platform of the container
200+
if let Some(platform) = container_req.platform() {
201+
let options = options_builder.unwrap_or(CreateContainerOptionsBuilder::new());
202+
options_builder = Some(options.platform(platform));
203+
} else {
204+
// set platform from global platform setting if available
205+
let options = options_builder.unwrap_or(CreateContainerOptionsBuilder::new());
206+
options_builder = Some(options.platform(client.config.platform().unwrap_or_default()));
207+
}
208+
209+
if let Some(options) = options_builder {
210+
create_options = Some(options.build());
197211
}
198212

199213
// handle environment variables
@@ -311,7 +325,12 @@ where
311325
status_code: 404, ..
312326
},
313327
)) => {
314-
client.pull_image(&container_req.descriptor()).await?;
328+
client
329+
.pull_image(
330+
&container_req.descriptor(),
331+
container_req.platform().clone(),
332+
)
333+
.await?;
315334
client.create_container(create_options, config).await
316335
}
317336
res => res,
@@ -361,7 +380,12 @@ where
361380
async fn pull_image(self) -> Result<ContainerRequest<I>> {
362381
let container_req = self.into();
363382
let client = Client::lazy_client().await?;
364-
client.pull_image(&container_req.descriptor()).await?;
383+
client
384+
.pull_image(
385+
&container_req.descriptor(),
386+
container_req.platform().clone(),
387+
)
388+
.await?;
365389

366390
Ok(container_req)
367391
}

0 commit comments

Comments
 (0)