Skip to content

Commit 1643442

Browse files
authored
feat: add user configuration for container commands (#784)
This was inspired by #762 and my project's requirement to use an image that requires running with root user inside the container. This pull request introduces the ability to configure a user that commands are run as inside a container in the `testcontainers` library. The most important changes include adding a new `user` field to the `ContainerRequest` struct, implementing methods to set and retrieve the user, and updating relevant tests to ensure this functionality works correctly. Enhancements to `ContainerRequest`: * [`testcontainers/src/core/containers/request.rs`](diffhunk://#diff-450493263e745439645d3062a1defe3bdedcadacabb123fe7664244e42570049R46): Added a new `user` field to the `ContainerRequest` struct and provided a method to retrieve the user. [[1]](diffhunk://#diff-450493263e745439645d3062a1defe3bdedcadacabb123fe7664244e42570049R46) [[2]](diffhunk://#diff-450493263e745439645d3062a1defe3bdedcadacabb123fe7664244e42570049R196-R200) [[3]](diffhunk://#diff-450493263e745439645d3062a1defe3bdedcadacabb123fe7664244e42570049R230) [[4]](diffhunk://#diff-450493263e745439645d3062a1defe3bdedcadacabb123fe7664244e42570049L268-R276) Enhancements to `ImageExt`: * [`testcontainers/src/core/image/image_ext.rs`](diffhunk://#diff-5d7331a740328676b08bc371893a5aa3e743d351bccafc3da0e2c55ffbf2004fR169-R171): Added a new method `with_user` to the `ImageExt` trait to set the user for the container. [[1]](diffhunk://#diff-5d7331a740328676b08bc371893a5aa3e743d351bccafc3da0e2c55ffbf2004fR169-R171) [[2]](diffhunk://#diff-5d7331a740328676b08bc371893a5aa3e743d351bccafc3da0e2c55ffbf2004fR386-R393) Updates to `async_runner`: * [`testcontainers/src/runners/async_runner.rs`](diffhunk://#diff-24518a824e97296d94c621275ed17e1f3fa9ae37e0b66158ee4dcf549c21b868R152): Updated the `async_runner` to include the user configuration and added a new test to verify that the user is correctly set inside the container. [[1]](diffhunk://#diff-24518a824e97296d94c621275ed17e1f3fa9ae37e0b66158ee4dcf549c21b868R152) [[2]](diffhunk://#diff-24518a824e97296d94c621275ed17e1f3fa9ae37e0b66158ee4dcf549c21b868R877-R894)
1 parent 8168a82 commit 1643442

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

testcontainers/src/core/containers/request.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub struct ContainerRequest<I: Image> {
4343
pub(crate) log_consumers: Vec<Box<dyn LogConsumer + 'static>>,
4444
#[cfg(feature = "reusable-containers")]
4545
pub(crate) reuse: crate::ReuseDirective,
46+
pub(crate) user: Option<String>,
4647
}
4748

4849
/// Represents a port mapping between a host's external port and the internal port of a container.
@@ -192,6 +193,11 @@ impl<I: Image> ContainerRequest<I> {
192193
pub fn reuse(&self) -> crate::ReuseDirective {
193194
self.reuse
194195
}
196+
197+
/// Returns the configured user that commands are run as inside the container.
198+
pub fn user(&self) -> Option<&str> {
199+
self.user.as_deref()
200+
}
195201
}
196202

197203
impl<I: Image> From<I> for ContainerRequest<I> {
@@ -221,6 +227,7 @@ impl<I: Image> From<I> for ContainerRequest<I> {
221227
log_consumers: vec![],
222228
#[cfg(feature = "reusable-containers")]
223229
reuse: crate::ReuseDirective::Never,
230+
user: None,
224231
}
225232
}
226233
}
@@ -265,7 +272,8 @@ impl<I: Image + Debug> Debug for ContainerRequest<I> {
265272
.field("cgroupns_mode", &self.cgroupns_mode)
266273
.field("userns_mode", &self.userns_mode)
267274
.field("startup_timeout", &self.startup_timeout)
268-
.field("working_dir", &self.working_dir);
275+
.field("working_dir", &self.working_dir)
276+
.field("user", &self.user);
269277

270278
#[cfg(feature = "reusable-containers")]
271279
repr.field("reusable", &self.reuse);

testcontainers/src/core/image/image_ext.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ pub trait ImageExt<I: Image> {
166166
/// `Container` or `ContainerAsync` is dropped.
167167
#[cfg(feature = "reusable-containers")]
168168
fn with_reuse(self, reuse: ReuseDirective) -> ContainerRequest<I>;
169+
170+
/// Sets the user that commands are run as inside the container.
171+
fn with_user(self, user: impl Into<String>) -> ContainerRequest<I>;
169172
}
170173

171174
/// Implements the [`ImageExt`] trait for the every type that can be converted into a [`ContainerRequest`].
@@ -380,4 +383,12 @@ impl<RI: Into<ContainerRequest<I>>, I: Image> ImageExt<I> for RI {
380383
..self.into()
381384
}
382385
}
386+
387+
fn with_user(self, user: impl Into<String>) -> ContainerRequest<I> {
388+
let container_req = self.into();
389+
ContainerRequest {
390+
user: Some(user.into()),
391+
..container_req
392+
}
393+
}
383394
}

testcontainers/src/runners/async_runner.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ where
149149
..Default::default()
150150
}),
151151
working_dir: container_req.working_dir().map(|dir| dir.to_string()),
152+
user: container_req.user().map(|user| user.to_string()),
152153
..Default::default()
153154
};
154155

@@ -873,4 +874,22 @@ mod tests {
873874
);
874875
Ok(())
875876
}
877+
878+
#[tokio::test]
879+
async fn async_run_command_should_have_user() -> anyhow::Result<()> {
880+
let image = GenericImage::new("simple_web_server", "latest");
881+
let expected_user = "root";
882+
let container = image.with_user(expected_user).start().await?;
883+
884+
let client = Client::lazy_client().await?;
885+
let container_details = client.inspect(container.id()).await?;
886+
887+
let user = container_details
888+
.config
889+
.expect("ContainerConfig")
890+
.user
891+
.expect("User");
892+
assert_eq!(expected_user, &user, "user must be `root`");
893+
Ok(())
894+
}
876895
}

0 commit comments

Comments
 (0)