Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion architecture/compute-runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ but currently ignores them.
Docker and Podman also accept per-sandbox driver-config mounts for existing
runtime-managed named volumes and tmpfs mounts. Podman additionally accepts
image mounts through its image-volume API. User-supplied bind and volume mounts
default to read-only. Direct host bind mounts, and Docker local-driver
default to read-only. Direct host bind mounts, and Docker or Podman local-driver
bind-backed named volumes, are available only when explicitly enabled in the
active local driver table of `gateway.toml`. Host bind mounts are an unsafe
operator override because they place gateway-host filesystem state inside the
Expand Down
7 changes: 4 additions & 3 deletions crates/openshell-driver-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1821,9 +1821,10 @@ fn docker_tmpfs_option(option: &str) -> Result<Vec<String>, Status> {
fn docker_volume_is_bind_backed(volume: &bollard::models::Volume) -> bool {
volume.driver == "local"
&& volume.options.get("o").is_some_and(|options| {
options
.split(',')
.any(|option| option.trim().eq_ignore_ascii_case("bind"))
options.split(',').any(|option| {
let option = option.trim();
option.eq_ignore_ascii_case("bind") || option.eq_ignore_ascii_case("rbind")
})
})
}

Expand Down
14 changes: 14 additions & 0 deletions crates/openshell-driver-docker/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,20 @@ fn docker_local_volume_with_bind_option_is_bind_backed() {
assert!(docker_volume_is_bind_backed(&volume));
}

#[test]
fn docker_local_volume_with_rbind_option_is_bind_backed() {
let volume = inspected_volume(
"local",
HashMap::from([
("type".to_string(), "none".to_string()),
("o".to_string(), "rw,rbind".to_string()),
("device".to_string(), "/tmp/openshell".to_string()),
]),
);

assert!(docker_volume_is_bind_backed(&volume));
}

#[test]
fn docker_local_volume_without_bind_option_is_not_bind_backed() {
let volume = inspected_volume(
Expand Down
4 changes: 3 additions & 1 deletion crates/openshell-driver-podman/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ mount types:
- `bind`: mounts an absolute host path when `[openshell.drivers.podman]`
has `enable_bind_mounts = true`.
- `volume`: mounts an existing Podman named volume. The driver validates that
the volume exists before provisioning and never creates or removes it.
the volume exists before provisioning and never creates or removes it. Podman
local-driver volumes created with bind options are treated as host bind
mounts and require `enable_bind_mounts = true`.
- `tmpfs`: mounts an in-memory filesystem with optional `options`,
`size_bytes`, and `mode`.
- `image`: mounts an OCI image through Podman's image-volume API. The driver
Expand Down
63 changes: 49 additions & 14 deletions crates/openshell-driver-podman/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ pub struct PortMappingEntry {
pub host_ip: String,
}

/// A named Podman volume returned by the libpod inspect API.
#[derive(Debug, Clone, Default, serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct VolumeInspect {
#[serde(default)]
pub driver: String,
#[serde(default)]
pub options: HashMap<String, String>,
}

/// A Podman event from the events stream.
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
Expand Down Expand Up @@ -497,21 +507,15 @@ impl PodmanClient {
}
}

/// Return whether a named volume exists. Does not create the volume.
pub async fn volume_exists(&self, name: &str) -> Result<bool, PodmanApiError> {
/// Inspect a named volume. Does not create the volume.
pub async fn inspect_volume(&self, name: &str) -> Result<VolumeInspect, PodmanApiError> {
validate_name(name)?;
match self
.request_json::<Value>(
hyper::Method::GET,
&format!("/libpod/volumes/{name}/json"),
None,
)
.await
{
Ok(_) => Ok(true),
Err(PodmanApiError::NotFound(_)) => Ok(false),
Err(e) => Err(e),
}
self.request_json(
hyper::Method::GET,
&format!("/libpod/volumes/{name}/json"),
None,
)
.await
}

// ── Network operations ───────────────────────────────────────────────
Expand Down Expand Up @@ -755,6 +759,8 @@ fn url_encode(s: &str) -> String {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{StubResponse, spawn_podman_stub};
use hyper::StatusCode;

#[test]
fn url_encode_encodes_special_characters() {
Expand Down Expand Up @@ -794,4 +800,33 @@ mod tests {
let exact_name = "a".repeat(MAX_NAME_LEN);
assert!(validate_name(&exact_name).is_ok());
}

#[tokio::test]
async fn inspect_volume_parses_driver_options() {
let (socket_path, request_log, handle) = spawn_podman_stub(
"inspect-volume",
vec![StubResponse::new(
StatusCode::OK,
r#"{"Name":"work-bind","Driver":"local","Options":{"type":"none","o":"rw,bind","device":"/srv/work"}}"#,
)],
);
let client = PodmanClient::new(socket_path.clone());

let volume = client
.inspect_volume("work-bind")
.await
.expect("volume inspect should parse");

assert_eq!(volume.driver, "local");
assert_eq!(volume.options.get("o").map(String::as_str), Some("rw,bind"));
handle.await.expect("stub task should finish");
assert_eq!(
request_log
.lock()
.expect("request log lock should not be poisoned")
.as_slice(),
["GET /v5.0.0/libpod/volumes/work-bind/json"]
);
let _ = std::fs::remove_file(socket_path);
}
}
Loading
Loading