Skip to content

Commit c26c7d5

Browse files
author
vsilent
committed
:ro/:rw volume mode suffixes were being embedded into the container path instead of being extracted as a separate read_only
1 parent 40f55a2 commit c26c7d5

File tree

3 files changed

+51
-15
lines changed

3 files changed

+51
-15
lines changed

src/cli/stacker_client.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -997,12 +997,21 @@ fn parse_port_mapping(port_str: &str) -> (String, String) {
997997
}
998998

999999
/// Parse a volume mapping string like "./dist:/usr/share/nginx/html" or "data:/var/lib/db"
1000-
/// into (host_path, container_path) tuple.
1001-
fn parse_volume_mapping(vol_str: &str) -> (String, String) {
1002-
if let Some((host, container)) = vol_str.split_once(':') {
1003-
(host.to_string(), container.to_string())
1004-
} else {
1005-
(vol_str.to_string(), vol_str.to_string())
1000+
/// into (host_path, container_path, read_only) tuple.
1001+
/// Handles optional `:ro` / `:rw` suffix (e.g. "/var/run/docker.sock:/var/run/docker.sock:ro").
1002+
fn parse_volume_mapping(vol_str: &str) -> (String, String, bool) {
1003+
let parts: Vec<&str> = vol_str.split(':').collect();
1004+
match parts.len() {
1005+
// "source:target:mode" (e.g. "/host:/container:ro")
1006+
3 => (
1007+
parts[0].to_string(),
1008+
parts[1].to_string(),
1009+
parts[2] == "ro",
1010+
),
1011+
// "source:target"
1012+
2 => (parts[0].to_string(), parts[1].to_string(), false),
1013+
// bare path
1014+
_ => (vol_str.to_string(), vol_str.to_string(), false),
10061015
}
10071016
}
10081017

@@ -1028,10 +1037,11 @@ fn service_to_app_json(svc: &ServiceDefinition, network_ids: &[String]) -> serde
10281037
.volumes
10291038
.iter()
10301039
.map(|v| {
1031-
let (host, container) = parse_volume_mapping(v);
1040+
let (host, container, read_only) = parse_volume_mapping(v);
10321041
serde_json::json!({
10331042
"host_path": host,
10341043
"container_path": container,
1044+
"read_only": read_only,
10351045
})
10361046
})
10371047
.collect();
@@ -1116,10 +1126,11 @@ fn app_source_to_app_json(
11161126
.volumes
11171127
.iter()
11181128
.map(|v| {
1119-
let (host, container) = parse_volume_mapping(v);
1129+
let (host, container, read_only) = parse_volume_mapping(v);
11201130
serde_json::json!({
11211131
"host_path": host,
11221132
"container_path": container,
1133+
"read_only": read_only,
11231134
})
11241135
})
11251136
.collect();

src/forms/project/volume.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,23 @@ impl TryInto<dctypes::AdvancedVolumes> for &Volume {
2727
type Error = String;
2828
fn try_into(self) -> Result<dctypes::AdvancedVolumes, Self::Error> {
2929
let source = self.host_path.clone();
30-
let target = self.container_path.clone();
30+
let raw_target = self.container_path.clone().unwrap_or_default();
31+
32+
// Strip `:ro` / `:rw` suffix from container_path and extract read_only flag.
33+
// Data may arrive with the mode embedded (e.g. "/var/run/docker.sock:ro").
34+
let (target, read_only) = if raw_target.ends_with(":ro") {
35+
(raw_target.trim_end_matches(":ro").to_string(), true)
36+
} else if raw_target.ends_with(":rw") {
37+
(raw_target.trim_end_matches(":rw").to_string(), false)
38+
} else {
39+
(raw_target, false)
40+
};
41+
3142
tracing::debug!(
32-
"Volume conversion result: source: {:?} target: {:?}",
43+
"Volume conversion result: source: {:?} target: {:?} read_only: {}",
3344
source,
34-
target
45+
target,
46+
read_only
3547
);
3648

3749
let _type = if self.is_named_docker_volume() {
@@ -42,9 +54,9 @@ impl TryInto<dctypes::AdvancedVolumes> for &Volume {
4254

4355
Ok(dctypes::AdvancedVolumes {
4456
source: source,
45-
target: target.unwrap_or("".to_string()),
57+
target: target,
4658
_type: _type.to_string(),
47-
read_only: false,
59+
read_only,
4860
bind: None,
4961
volume: None,
5062
tmpfs: None,

src/services/config_renderer.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,20 +344,33 @@ impl ConfigRenderer {
344344
let mut result = Vec::new();
345345
for item in arr {
346346
if let Value::Object(map) = item {
347+
// Support both "source"/"target" and "host_path"/"container_path" keys
347348
let source = map
348349
.get("source")
350+
.or_else(|| map.get("host_path"))
349351
.and_then(|v| v.as_str())
350352
.unwrap_or("")
351353
.to_string();
352-
let target = map
354+
let raw_target = map
353355
.get("target")
356+
.or_else(|| map.get("container_path"))
354357
.and_then(|v| v.as_str())
355358
.unwrap_or("")
356359
.to_string();
360+
361+
// Strip `:ro` / `:rw` suffix that may be embedded in the target path
362+
let (target, suffix_ro) = if raw_target.ends_with(":ro") {
363+
(raw_target.trim_end_matches(":ro").to_string(), true)
364+
} else if raw_target.ends_with(":rw") {
365+
(raw_target.trim_end_matches(":rw").to_string(), false)
366+
} else {
367+
(raw_target, false)
368+
};
369+
357370
let read_only = map
358371
.get("read_only")
359372
.and_then(|v| v.as_bool())
360-
.unwrap_or(false);
373+
.unwrap_or(suffix_ro);
361374
if !source.is_empty() && !target.is_empty() {
362375
result.push(VolumeMount {
363376
source,

0 commit comments

Comments
 (0)