compose2pod 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {compose2pod-0.3.0 → compose2pod-0.3.1}/PKG-INFO +30 -10
- {compose2pod-0.3.0 → compose2pod-0.3.1}/README.md +29 -9
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/emit.py +4 -4
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/parsing.py +26 -8
- {compose2pod-0.3.0 → compose2pod-0.3.1}/pyproject.toml +1 -1
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/__init__.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/__main__.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/cli.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/exceptions.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/extends.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/graph.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/healthcheck.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/keys.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/pod.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/py.typed +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/read.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/resources.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/shell.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/stores.py +0 -0
- {compose2pod-0.3.0 → compose2pod-0.3.1}/compose2pod/values.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compose2pod
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Convert a Docker Compose file into a script that runs its services as a single Podman pod
|
|
5
5
|
Keywords: podman,docker-compose,compose,pod,ci,testing,containers
|
|
6
6
|
Author: Artur Shiriev
|
|
@@ -83,15 +83,35 @@ sh ./run.sh
|
|
|
83
83
|
|
|
84
84
|
## Supported compose subset
|
|
85
85
|
|
|
86
|
-
compose2pod
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
compose2pod refuses **every document `docker compose config` refuses** — a
|
|
87
|
+
measured property, checked continuously by a differential conformance harness
|
|
88
|
+
that runs the real Docker CLI and the real compose2pod pipeline over the same
|
|
89
|
+
YAML. So a file that compiles is a file Docker would run; where compose2pod
|
|
90
|
+
still refuses a form Docker accepts, it is because Podman genuinely cannot
|
|
91
|
+
express it (each such case is documented, not guessed).
|
|
92
|
+
|
|
93
|
+
Within that boundary it covers most of what real compose files use:
|
|
94
|
+
|
|
95
|
+
- **Services** — `image`/`build`, `command`/`entrypoint`, `environment` and
|
|
96
|
+
`env_file` (string and long-form `{path, required, format}`), `volumes`
|
|
97
|
+
(short-form and long-form `--mount`, including the `bind`/`volume`/`tmpfs`
|
|
98
|
+
option maps), `tmpfs`, `healthcheck`, `depends_on` (all conditions), network
|
|
99
|
+
`aliases`, `hostname`/`container_name`.
|
|
100
|
+
- **Confinement & metadata** — `user`, `working_dir`, `read_only`, `init`,
|
|
101
|
+
`privileged`, `cap_add`/`cap_drop`, `security_opt`, `devices`, `group_add`,
|
|
102
|
+
`platform`, `labels`, `annotations`, `pull_policy` (the quoted-boolean and
|
|
103
|
+
YAML-1.1 spellings Docker accepts, too).
|
|
104
|
+
- **Resources** — the legacy keys (`mem_limit`, `cpus`, `pids_limit`,
|
|
105
|
+
`ulimits`, …) and the modern `deploy.resources` block.
|
|
106
|
+
- **Pod-wide** — `dns`/`dns_search`/`dns_opt`, `sysctls`, `extra_hosts`.
|
|
107
|
+
- **Composition** — same-file `extends`, `secrets`/`configs`, `profiles`.
|
|
108
|
+
|
|
109
|
+
Compose extension fields (any `x-`-prefixed key) and YAML anchors are accepted
|
|
110
|
+
as-is, so a top-level `x-*` anchor block for shared config just works.
|
|
111
|
+
`${VAR}`-style variable interpolation is left live in the generated script,
|
|
112
|
+
resolved by its shell against the environment present when the script runs (no
|
|
113
|
+
`.env` file support). See `architecture/supported-subset.md` for the full
|
|
114
|
+
accept/ignore/reject matrix and `planning/decisions/` for the boundary rulings.
|
|
95
115
|
|
|
96
116
|
## Status
|
|
97
117
|
|
|
@@ -57,15 +57,35 @@ sh ./run.sh
|
|
|
57
57
|
|
|
58
58
|
## Supported compose subset
|
|
59
59
|
|
|
60
|
-
compose2pod
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
compose2pod refuses **every document `docker compose config` refuses** — a
|
|
61
|
+
measured property, checked continuously by a differential conformance harness
|
|
62
|
+
that runs the real Docker CLI and the real compose2pod pipeline over the same
|
|
63
|
+
YAML. So a file that compiles is a file Docker would run; where compose2pod
|
|
64
|
+
still refuses a form Docker accepts, it is because Podman genuinely cannot
|
|
65
|
+
express it (each such case is documented, not guessed).
|
|
66
|
+
|
|
67
|
+
Within that boundary it covers most of what real compose files use:
|
|
68
|
+
|
|
69
|
+
- **Services** — `image`/`build`, `command`/`entrypoint`, `environment` and
|
|
70
|
+
`env_file` (string and long-form `{path, required, format}`), `volumes`
|
|
71
|
+
(short-form and long-form `--mount`, including the `bind`/`volume`/`tmpfs`
|
|
72
|
+
option maps), `tmpfs`, `healthcheck`, `depends_on` (all conditions), network
|
|
73
|
+
`aliases`, `hostname`/`container_name`.
|
|
74
|
+
- **Confinement & metadata** — `user`, `working_dir`, `read_only`, `init`,
|
|
75
|
+
`privileged`, `cap_add`/`cap_drop`, `security_opt`, `devices`, `group_add`,
|
|
76
|
+
`platform`, `labels`, `annotations`, `pull_policy` (the quoted-boolean and
|
|
77
|
+
YAML-1.1 spellings Docker accepts, too).
|
|
78
|
+
- **Resources** — the legacy keys (`mem_limit`, `cpus`, `pids_limit`,
|
|
79
|
+
`ulimits`, …) and the modern `deploy.resources` block.
|
|
80
|
+
- **Pod-wide** — `dns`/`dns_search`/`dns_opt`, `sysctls`, `extra_hosts`.
|
|
81
|
+
- **Composition** — same-file `extends`, `secrets`/`configs`, `profiles`.
|
|
82
|
+
|
|
83
|
+
Compose extension fields (any `x-`-prefixed key) and YAML anchors are accepted
|
|
84
|
+
as-is, so a top-level `x-*` anchor block for shared config just works.
|
|
85
|
+
`${VAR}`-style variable interpolation is left live in the generated script,
|
|
86
|
+
resolved by its shell against the environment present when the script runs (no
|
|
87
|
+
`.env` file support). See `architecture/supported-subset.md` for the full
|
|
88
|
+
accept/ignore/reject matrix and `planning/decisions/` for the boundary rulings.
|
|
69
89
|
|
|
70
90
|
## Status
|
|
71
91
|
|
|
@@ -95,15 +95,15 @@ def _env_file_flags(svc: dict[str, Any], project_dir: str) -> list[Token]:
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
def _nested_mount_parts(vtype: str, options: dict[str, Any]) -> list[str]:
|
|
98
|
-
"""Map a long-form volume entry's validated `bind`/`volume`/`tmpfs` sub-map to `--mount` options."""
|
|
98
|
+
"""Map a long-form volume entry's validated `bind`/`volume`/`tmpfs`/`image` sub-map to `--mount` options."""
|
|
99
99
|
if vtype == "bind":
|
|
100
100
|
parts = [f"bind-propagation={options['propagation']}"] if "propagation" in options else []
|
|
101
101
|
if "selinux" in options:
|
|
102
102
|
parts.append(f"relabel={'shared' if options['selinux'] == 'z' else 'private'}")
|
|
103
103
|
return parts
|
|
104
|
-
if vtype
|
|
104
|
+
if vtype in ("volume", "image"):
|
|
105
105
|
return [f"subpath={options['subpath']}"] if "subpath" in options else []
|
|
106
|
-
# vtype == "tmpfs": the gate validates `type` to
|
|
106
|
+
# vtype == "tmpfs": the gate validates `type` to bind/volume/tmpfs/image, so no other branch is reachable.
|
|
107
107
|
parts = [f"tmpfs-size={options['size']}"] if "size" in options else []
|
|
108
108
|
if "mode" in options:
|
|
109
109
|
parts.append(f"tmpfs-mode={options['mode']}")
|
|
@@ -119,7 +119,7 @@ def _mount_flag(entry: dict[str, Any], project_dir: str) -> list[Token]:
|
|
|
119
119
|
source = str(Path(project_dir, source))
|
|
120
120
|
parts.append(f"source={source}")
|
|
121
121
|
parts.append(f"target={entry['target']}")
|
|
122
|
-
if as_bool(entry.get("read_only", False)):
|
|
122
|
+
if entry["type"] != "image" and as_bool(entry.get("read_only", False)):
|
|
123
123
|
parts.append("ro")
|
|
124
124
|
vtype = entry["type"]
|
|
125
125
|
parts += _nested_mount_parts(vtype, entry.get(vtype, {}))
|
|
@@ -160,7 +160,7 @@ def _classify_volume(volume: str) -> tuple[str, str | None]:
|
|
|
160
160
|
return "bind", None
|
|
161
161
|
|
|
162
162
|
|
|
163
|
-
_VOLUME_LONG_TYPES = ("bind", "volume", "tmpfs")
|
|
163
|
+
_VOLUME_LONG_TYPES = ("bind", "volume", "tmpfs", "image")
|
|
164
164
|
_VOLUME_LONG_KEYS = {"type", "source", "target", "read_only", "consistency"}
|
|
165
165
|
# Docker's own per-type nested option map keys (measured, docker compose config
|
|
166
166
|
# v5.1.2). `create_host_path`/`nocopy` are real docker keys, so they land here
|
|
@@ -171,6 +171,7 @@ _VOLUME_OPTION_KEYS = {
|
|
|
171
171
|
"bind": {"propagation", "selinux", "create_host_path"},
|
|
172
172
|
"volume": {"subpath", "nocopy"},
|
|
173
173
|
"tmpfs": {"size", "mode"},
|
|
174
|
+
"image": {"subpath"},
|
|
174
175
|
}
|
|
175
176
|
_PROPAGATION_VALUES = {"private", "rprivate", "shared", "rshared", "slave", "rslave"}
|
|
176
177
|
_SELINUX_VALUES = {"z", "Z"}
|
|
@@ -205,10 +206,11 @@ def _validate_service_volumes(name: str, svc: dict[str, Any]) -> None:
|
|
|
205
206
|
def _validate_volume_long_form(name: str, entry: dict[str, Any]) -> None:
|
|
206
207
|
"""Check one long-syntax volume mapping against Docker's strict schema (measured, v5.1.2).
|
|
207
208
|
|
|
208
|
-
type (bind/volume/tmpfs), source, target, read_only, consistency,
|
|
209
|
-
one nested option map matching `type` (a mismatched sub-map is
|
|
210
|
-
a deliberate stricter-than-docker check; docker
|
|
211
|
-
cluster/npipe
|
|
209
|
+
type (bind/volume/tmpfs/image), source, target, read_only, consistency,
|
|
210
|
+
plus the one nested option map matching `type` (a mismatched sub-map is
|
|
211
|
+
refused -- a deliberate stricter-than-docker check; docker
|
|
212
|
+
accepts-and-ignores it). cluster/npipe types are refused (podman cannot
|
|
213
|
+
express them).
|
|
212
214
|
|
|
213
215
|
`type` is validated before the unknown-key check (unlike every other field
|
|
214
216
|
here) because the check itself needs `vtype` to know which sub-map key --
|
|
@@ -303,10 +305,26 @@ def _validate_tmpfs_options(name: str, options: dict[str, Any]) -> None:
|
|
|
303
305
|
raise UnsupportedComposeError(msg)
|
|
304
306
|
|
|
305
307
|
|
|
308
|
+
def _validate_image_options(name: str, options: dict[str, Any]) -> None:
|
|
309
|
+
"""Check an image entry's nested `image:` option map (measured: strict, `subpath` only, absolute)."""
|
|
310
|
+
if "subpath" not in options:
|
|
311
|
+
return
|
|
312
|
+
subpath = options["subpath"]
|
|
313
|
+
if not isinstance(subpath, str):
|
|
314
|
+
msg = f"service {name!r}: image 'subpath' must be a string"
|
|
315
|
+
raise UnsupportedComposeError(msg)
|
|
316
|
+
if not subpath.startswith("/") and not values.has_variable(subpath):
|
|
317
|
+
# podman requires an absolute image subpath (`must be an absolute path`);
|
|
318
|
+
# docker accepts a relative one -> a rule-two narrowing. A ${VAR} is host-dependent.
|
|
319
|
+
msg = f"service {name!r}: image 'subpath' must be an absolute path"
|
|
320
|
+
raise UnsupportedComposeError(msg)
|
|
321
|
+
|
|
322
|
+
|
|
306
323
|
_VOLUME_OPTION_VALIDATORS: dict[str, Callable[[str, dict[str, Any]], None]] = {
|
|
307
324
|
"bind": _validate_bind_options,
|
|
308
325
|
"volume": _validate_volume_type_options,
|
|
309
326
|
"tmpfs": _validate_tmpfs_options,
|
|
327
|
+
"image": _validate_image_options,
|
|
310
328
|
}
|
|
311
329
|
|
|
312
330
|
|
|
@@ -324,10 +342,10 @@ def _validate_volume_options(name: str, vtype: str, options: Any) -> None: # no
|
|
|
324
342
|
|
|
325
343
|
|
|
326
344
|
def _validate_volume_long_form_source(name: str, vtype: str, source: Any) -> None: # noqa: ANN401 - Compose values are untyped YAML/JSON
|
|
327
|
-
"""Check a long-form volume entry's 'source': required for bind, refused for tmpfs, optional
|
|
328
|
-
if vtype
|
|
345
|
+
"""Check a long-form volume entry's 'source': required for bind/image, refused for tmpfs, optional for volume."""
|
|
346
|
+
if vtype in ("bind", "image"):
|
|
329
347
|
if not isinstance(source, str):
|
|
330
|
-
msg = f"service {name!r}:
|
|
348
|
+
msg = f"service {name!r}: {vtype} volume 'source' must be a string"
|
|
331
349
|
raise UnsupportedComposeError(msg)
|
|
332
350
|
elif vtype == "tmpfs":
|
|
333
351
|
if source is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|