Skip to content

Commit 3dfa3d2

Browse files
committed
test: volumes long-form conformance + integration; promote docs
1 parent 6d2ae5b commit 3dfa3d2

4 files changed

Lines changed: 69 additions & 6 deletions

File tree

‎architecture/supported-subset.md‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,26 @@ boolean-typed exception in this group, validated as an actual bool like
755755

756756
## Volumes
757757

758-
Short syntax only; the long mapping form raises. The `volumes` key itself
758+
A `volumes` entry may be the short string syntax or the long-form mapping
759+
`{type, source, target, read_only, consistency}`. `type` must be `bind`,
760+
`volume`, or `tmpfs`; `cluster`, `npipe`, and `image` are refused (the latter
761+
is podman-inexpressible, and `cluster`/`npipe` fall out as an unrecognized
762+
`type` alongside them). Each is emitted as a single `--mount` flag
763+
(`compose2pod/emit.py`'s `_mount_flag`) rather than `-v`: `type=<type>`,
764+
`source=<source>` (a relative bind `source` is resolved against
765+
`--project-dir`, the same as the short form), `target=<target>`, and a
766+
trailing `ro` when `read_only` is truthy — `read_only` accepts the quoted
767+
`"true"`/`"false"` form via the same `is_bool_like` check every other
768+
boolean field uses. `consistency` is accepted and validated as a string but
769+
otherwise ignored — podman's `--mount` has no consistency knob. The nested
770+
`bind:`/`volume:`/`tmpfs:` option maps (`propagation`, `subpath`,
771+
`tmpfs.size`/`tmpfs.mode`, etc.) fall out as unsupported keys and raise;
772+
`nocopy` is podman-inexpressible regardless. A long-form `volume`-type entry
773+
whose `source` is a bare identifier is cross-checked against the top-level
774+
`volumes:` block exactly like a short-form named volume (below) — a
775+
`bind`/`tmpfs` entry's `source`, or an absent one, needs no declaration.
776+
777+
The `volumes` key itself
759778
must be a list — a bare string raises, rather than being destructured one
760779
character at a time. A `source:target` entry is one of two kinds, told apart
761780
by whether `source` matches Docker's own volume-name grammar

‎planning/deferred.md‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ subset, not a bug and not a design position. Each item below is a **form** of a
1111
capability compose2pod already supports, refused only because the parser was
1212
never written. Every one was measured against `docker compose config` v5.1.2.
1313

14-
- **Long-form `volumes`.** The mapping form raises; podman expresses it with
15-
`--mount`.
14+
- **Long-form `volumes` nested option maps.** The mapping form itself
15+
(`type`/`source`/`target`/`read_only`/`consistency`) is now accepted and
16+
emitted as `--mount` (`2026-07-16.05-volumes-long-form`). Still refused:
17+
the nested `bind:`/`volume:`/`tmpfs:` option map a long-form entry may
18+
carry (`propagation`, `subpath`, `tmpfs.size`/`tmpfs.mode`) — podman's
19+
`--mount` can express these, so they are a genuine parser gap, not a
20+
design position; `nocopy` is podman-inexpressible and would stay refused
21+
either way.
1622
- **Windows drive-letter volume source.** `volumes: ["C:\data:/var"]` with no
1723
top-level declaration: Docker ACCEPTS (measured, `docker compose config`
1824
v5.1.2 -- it special-cases a leading `<letter>:\` so the drive letter stays
@@ -29,9 +35,10 @@ never written. Every one was measured against `docker compose config` v5.1.2.
2935
tilde-bind-mount fix that discovered it.
3036

3137
**Revisit trigger:** a user reports a compose file that `docker compose` runs and
32-
compose2pod refuses — most likely long-form `volumes`, a common form in
33-
hand-written and generated compose files alike. The conformance harness reports
34-
these as `over-reject`, so they stay visible rather than forgotten.
38+
compose2pod refuses — most likely a long-form `volumes` entry's nested
39+
`bind:`/`volume:`/`tmpfs:` option map, now that the mapping form itself is
40+
accepted, or the Windows drive-letter bind above. The conformance harness
41+
reports these as `over-reject`, so they stay visible rather than forgotten.
3542

3643
Two other `over-reject` cells the harness reports — `sysctls: ["a"]` and
3744
`volumes: ["a"]` — are *not* deferred parsers: they are measured legitimate

‎tests/conformance/test_corpus.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,18 @@ def test_healthcheck_compound_duration_is_no_longer_an_over_rejection(
8282
"""
8383
path = Path(__file__).parent / "corpus" / "healthcheck_compound_duration.yaml"
8484
assert assert_rule(yaml.safe_load(path.read_text())) == "both-accept"
85+
86+
87+
def test_volumes_long_form_is_no_longer_an_over_rejection(
88+
assert_rule: Callable[[dict[str, Any]], str],
89+
) -> None:
90+
"""The long-syntax (mapping) volume entry now parses instead of raising.
91+
92+
Same reasoning as the over-rejection tests above: the generic corpus run alone
93+
would stay green even pre-fix, filing `volumes_long_form` under the allowed
94+
'over-reject' verdict instead of catching a regression. The stronger claim --
95+
both oracles ACCEPT `{type: bind, source: ./data, target: /data}` -- needs
96+
this dedicated assertion on the verdict itself.
97+
"""
98+
path = Path(__file__).parent / "corpus" / "volumes_long_form.yaml"
99+
assert assert_rule(yaml.safe_load(path.read_text())) == "both-accept"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Long-form volumes: a {type: bind} mapping round-trips a host file into the container."""
2+
3+
from collections.abc import Callable
4+
from pathlib import Path
5+
6+
from tests.integration.conftest import PodRun
7+
8+
9+
def test_long_form_bind_mount_is_read(run_pod: Callable[..., PodRun], tmp_path: Path) -> None:
10+
(tmp_path / "data.txt").write_text("mount-ok-73\n")
11+
compose = {
12+
"services": {
13+
"app": {
14+
"image": "busybox:1.36",
15+
"volumes": [{"type": "bind", "source": "./data.txt", "target": "/data.txt", "read_only": True}],
16+
"command": ["cat", "/data.txt"],
17+
},
18+
},
19+
}
20+
run = run_pod(compose, target="app", project_dir=tmp_path)
21+
assert run.returncode == 0, run.stderr
22+
assert "mount-ok-73" in run.stdout

0 commit comments

Comments
 (0)