Skip to content

Commit

Permalink
Add integration test support for Docker bind mounts (#871)
Browse files Browse the repository at this point in the history
  • Loading branch information
runesoerensen authored Oct 23, 2024
1 parent a3174bc commit 7f089eb
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `libcnb-test`:
- Added `ContainerConfig::bind_mount` to support mounting a host machine file or directory into a container. ([#871](https://github.com/heroku/libcnb.rs/pull/871))

## [0.24.0] - 2024-10-17

Expand Down
33 changes: 33 additions & 0 deletions libcnb-test/src/container_config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;

/// Config used when starting a container.
///
Expand Down Expand Up @@ -31,6 +32,7 @@ pub struct ContainerConfig {
pub(crate) command: Option<Vec<String>>,
pub(crate) env: HashMap<String, String>,
pub(crate) exposed_ports: HashSet<u16>,
pub(crate) bind_mounts: HashMap<PathBuf, PathBuf>,
}

impl ContainerConfig {
Expand Down Expand Up @@ -169,6 +171,37 @@ impl ContainerConfig {
self
}

/// Mount a host file or directory `source` into the container `target`. Useful for
/// integration tests that depend on persistent storage shared between container executions.
///
/// See: [Docker Engine: Bind Mounts](https://docs.docker.com/engine/storage/bind-mounts/)
///
/// # Example
/// ```no_run
/// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
///
/// TestRunner::default().build(
/// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
/// |context| {
/// // ...
/// context.start_container(
/// ContainerConfig::new().bind_mount("/shared/cache", "/workspace/cache"),
/// |container| {
/// // ...
/// },
/// );
/// },
/// );
/// ```
pub fn bind_mount(
&mut self,
source: impl Into<PathBuf>,
target: impl Into<PathBuf>,
) -> &mut Self {
self.bind_mounts.insert(source.into(), target.into());
self
}

/// Adds or updates multiple environment variable mappings for the container.
///
/// # Example
Expand Down
25 changes: 25 additions & 0 deletions libcnb-test/src/docker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::process::Command;

/// Represents a `docker run` command.
Expand All @@ -13,6 +14,7 @@ pub(crate) struct DockerRunCommand {
image_name: String,
platform: Option<String>,
remove: bool,
bind_mounts: BTreeMap<PathBuf, PathBuf>,
}

impl DockerRunCommand {
Expand All @@ -27,6 +29,7 @@ impl DockerRunCommand {
image_name: image_name.into(),
platform: None,
remove: false,
bind_mounts: BTreeMap::new(),
}
}

Expand Down Expand Up @@ -67,6 +70,11 @@ impl DockerRunCommand {
self.remove = remove;
self
}

pub(crate) fn bind_mount<P: Into<PathBuf>>(&mut self, source: P, target: P) -> &mut Self {
self.bind_mounts.insert(source.into(), target.into());
self
}
}

impl From<DockerRunCommand> for Command {
Expand Down Expand Up @@ -98,6 +106,17 @@ impl From<DockerRunCommand> for Command {
command.args(["--publish", &format!("127.0.0.1::{port}")]);
}

for (source, target) in &docker_run_command.bind_mounts {
command.args([
"--mount",
&format!(
"type=bind,source={},target={}",
source.to_string_lossy(),
target.to_string_lossy()
),
]);
}

command.arg(docker_run_command.image_name);

if let Some(container_command) = docker_run_command.command {
Expand Down Expand Up @@ -315,6 +334,8 @@ mod tests {
docker_run_command.expose_port(55555);
docker_run_command.platform("linux/amd64");
docker_run_command.remove(true);
docker_run_command.bind_mount(PathBuf::from("./test-cache"), PathBuf::from("/cache"));
docker_run_command.bind_mount("foo", "/bar");

let command: Command = docker_run_command.clone().into();
assert_eq!(
Expand All @@ -337,6 +358,10 @@ mod tests {
"127.0.0.1::12345",
"--publish",
"127.0.0.1::55555",
"--mount",
"type=bind,source=./test-cache,target=/cache",
"--mount",
"type=bind,source=foo,target=/bar",
"my-image",
"echo",
"hello",
Expand Down
4 changes: 4 additions & 0 deletions libcnb-test/src/test_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ impl<'a> TestContext<'a> {
docker_run_command.expose_port(*port);
});

config.bind_mounts.iter().for_each(|(source, target)| {
docker_run_command.bind_mount(source, target);
});

// We create the ContainerContext early to ensure the cleanup in ContainerContext::drop
// is still performed even if the Docker command panics.
let container_context = ContainerContext {
Expand Down

0 comments on commit 7f089eb

Please sign in to comment.