Skip to content

Commit

Permalink
feat: add cap_add and cap_drop support (#726)
Browse files Browse the repository at this point in the history
Hi 👋 This PR implements #578 and adds tests for the added
functionality.
Specifically, this PR adds support for adding and dropping capabilities
for containers.
Have a nice weekend!
  • Loading branch information
nicmr authored Oct 27, 2024
1 parent 7711714 commit 3822495
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
14 changes: 14 additions & 0 deletions testcontainers/src/core/containers/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub struct ContainerRequest<I: Image> {
pub(crate) ports: Option<Vec<PortMapping>>,
pub(crate) ulimits: Option<Vec<ResourcesUlimits>>,
pub(crate) privileged: bool,
pub(crate) cap_add: Option<Vec<String>>,
pub(crate) cap_drop: Option<Vec<String>>,
pub(crate) shm_size: Option<u64>,
pub(crate) cgroupns_mode: Option<CgroupnsMode>,
pub(crate) userns_mode: Option<String>,
Expand Down Expand Up @@ -111,6 +113,14 @@ impl<I: Image> ContainerRequest<I> {
self.privileged
}

pub fn cap_add(&self) -> Option<&Vec<String>> {
self.cap_add.as_ref()
}

pub fn cap_drop(&self) -> Option<&Vec<String>> {
self.cap_drop.as_ref()
}

pub fn cgroupns_mode(&self) -> Option<CgroupnsMode> {
self.cgroupns_mode
}
Expand Down Expand Up @@ -187,6 +197,8 @@ impl<I: Image> From<I> for ContainerRequest<I> {
ports: None,
ulimits: None,
privileged: false,
cap_add: None,
cap_drop: None,
shm_size: None,
cgroupns_mode: None,
userns_mode: None,
Expand Down Expand Up @@ -229,6 +241,8 @@ impl<I: Image + Debug> Debug for ContainerRequest<I> {
.field("ports", &self.ports)
.field("ulimits", &self.ulimits)
.field("privileged", &self.privileged)
.field("cap_add", &self.cap_add)
.field("cap_drop", &self.cap_drop)
.field("shm_size", &self.shm_size)
.field("cgroupns_mode", &self.cgroupns_mode)
.field("userns_mode", &self.userns_mode)
Expand Down
26 changes: 26 additions & 0 deletions testcontainers/src/core/image/image_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ pub trait ImageExt<I: Image> {
/// Sets the container to run in privileged mode.
fn with_privileged(self, privileged: bool) -> ContainerRequest<I>;

/// Adds the capabilities to the container
fn with_cap_add(self, capability: impl Into<String>) -> ContainerRequest<I>;

/// Drops the capabilities from the container's capabilities
fn with_cap_drop(self, capability: impl Into<String>) -> ContainerRequest<I>;

/// cgroup namespace mode for the container. Possible values are:
/// - [`CgroupnsMode::Private`]: the container runs in its own private cgroup namespace
/// - [`CgroupnsMode::Host`]: use the host system's cgroup namespace
Expand Down Expand Up @@ -231,6 +237,26 @@ impl<RI: Into<ContainerRequest<I>>, I: Image> ImageExt<I> for RI {
}
}

fn with_cap_add(self, capability: impl Into<String>) -> ContainerRequest<I> {
let mut container_req = self.into();
container_req
.cap_add
.get_or_insert_with(Vec::new)
.push(capability.into());

container_req
}

fn with_cap_drop(self, capability: impl Into<String>) -> ContainerRequest<I> {
let mut container_req = self.into();
container_req
.cap_drop
.get_or_insert_with(Vec::new)
.push(capability.into());

container_req
}

fn with_cgroupns_mode(self, cgroupns_mode: CgroupnsMode) -> ContainerRequest<I> {
let container_req = self.into();
ContainerRequest {
Expand Down
56 changes: 56 additions & 0 deletions testcontainers/src/runners/async_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ where
extra_hosts: Some(extra_hosts),
cgroupns_mode: container_req.cgroupns_mode().map(|mode| mode.into()),
userns_mode: container_req.userns_mode().map(|v| v.to_string()),
cap_add: container_req.cap_add().cloned(),
cap_drop: container_req.cap_drop().cloned(),
..Default::default()
}),
working_dir: container_req.working_dir().map(|dir| dir.to_string()),
Expand Down Expand Up @@ -574,6 +576,60 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn async_run_command_should_have_cap_add() -> anyhow::Result<()> {
let image = GenericImage::new("hello-world", "latest");
let expected_capability = "NET_ADMIN";
let container = image
.with_cap_add(expected_capability.to_string())
.start()
.await?;

let client = Client::lazy_client().await?;
let container_details = client.inspect(container.id()).await?;

let capabilities = container_details
.host_config
.expect("HostConfig")
.cap_add
.expect("CapAdd");

assert_eq!(
expected_capability,
capabilities.get(0).expect("No capabilities added"),
"cap_add must contain {expected_capability}"
);

Ok(())
}

#[tokio::test]
async fn async_run_command_should_have_cap_drop() -> anyhow::Result<()> {
let image = GenericImage::new("hello-world", "latest");
let expected_capability = "AUDIT_WRITE";
let container = image
.with_cap_drop(expected_capability.to_string())
.start()
.await?;

let client = Client::lazy_client().await?;
let container_details = client.inspect(container.id()).await?;

let capabilities = container_details
.host_config
.expect("HostConfig")
.cap_drop
.expect("CapAdd");

assert_eq!(
expected_capability,
capabilities.get(0).expect("No capabilities dropped"),
"cap_drop must contain {expected_capability}"
);

Ok(())
}

#[tokio::test]
async fn async_run_command_should_include_ulimits() -> anyhow::Result<()> {
let image = GenericImage::new("hello-world", "latest");
Expand Down

0 comments on commit 3822495

Please sign in to comment.