Skip to content
Merged
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
113 changes: 113 additions & 0 deletions planning/releases/0.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# compose2pod 0.2.0 — most of the common compose subset, and a real-podman test harness

This release rounds out the per-container and pod-level keys most compose
files actually use — `extends`, `secrets`/`configs`, resource limits,
`ulimits`, and pod-wide `dns`/`sysctls`/`extra_hosts` — and adds a CI-only
integration harness that runs generated scripts against real Podman. That
harness caught a real bug on its first run, fixed in this same release: see
Fix, below.

## Fix

- **`--add-host` moved from `podman run` to `podman pod create`.** Current
Podman rejects `--add-host` on a container joining a pod ("network cannot
be configured when it is shared with a pod") — so any generated script for
a pod with more than one named service **failed at run time**. All
`--add-host` emission (service-name/`hostname`/`container_name`/network
`aliases`, and `extra_hosts`) now lands on the single `podman pod create`
line instead, alongside `dns`/`sysctls`. `extra_hosts` is consequently now
genuinely pod-wide (previously per-service); a host name landing on two
different addresses — across services' `extra_hosts`, or against an
alias's fixed `127.0.0.1` — is refused (`UnsupportedComposeError`) rather
than resolved by guessing, matching the existing `sysctls` conflict rule.
- **`validate()`/`emit_script()` reject malformed input cleanly instead of
crashing.** A malformed `depends_on`, `healthcheck`, `hostname`/
`container_name`, `tmpfs`, or per-service `networks` shape now raises
`UnsupportedComposeError` at the validation/emit boundary instead of an
uncaught `AttributeError`/`ValueError` deeper in the pipeline.
- **`emit_script` validates the pod name itself**, not only the CLI — an
adversarial `EmitOptions.pod` (quotes, spaces, `$(...)`) now raises
cleanly instead of producing a malformed shell trap.

## Feature

- **`extends`.** Same-file Compose `extends: {service: <name>}` is resolved
before validation (transitive, cycle-checked, per-key merge — concat for
list keys like `cap_add`/`volumes`, local-wins merge for `environment`/
`labels`/`healthcheck`, override for scalars and `command`/`entrypoint`).
Cross-file `extends: {file: ...}` stays refused, keeping the
single-document input model.
- **`secrets` and `configs`.** Top-level `secrets:`/`configs:` (`file:`,
`environment:`, and — configs only — inline `content:` sources) compile to
`podman secret create`, mounted via `--secret` at `/run/secrets/<name>`
(secrets) or the container root `/<name>` (configs, absolute `target:`
only). `external: true` is refused; a secret and same-named config don't
collide (distinct store names).
- **Resource limits.** Both the legacy scalar keys (`mem_limit`, `cpus`,
`pids_limit`, `cpu_shares`, `cpuset`, `shm_size`, `oom_score_adj`,
`oom_kill_disable`, ...) and the modern `deploy.resources.limits`/
`.reservations` block map onto `--memory`/`--cpus`/`--pids-limit`/
`--memory-reservation`. A legacy key and its `deploy.resources` counterpart
targeting the same flag is refused rather than picking an undefined
precedence.
- **`ulimits`.** A mapping of limit name to a scalar (`nproc: 65535`) or a
`{soft, hard}` pair, emitted as `--ulimit`.
- **Pod-level `dns`/`sysctls`/`extra_hosts`.** These apply to every
container in the pod (one shared `/etc/resolv.conf`, sysctl set, and
`/etc/hosts`) — `dns`/`dns_search`/`dns_opt` are unioned across the
target's dependency closure; `sysctls` union by key, refusing a same-key
conflict; `extra_hosts` merges the same way (see Fix, above). `validate()`
warns whenever any service in the document declares one of these, even
outside the target's closure.
- **Core process, confinement, and metadata keys**: `entrypoint`, `user`,
`working_dir`, `group_add`, `labels`, `read_only`, `init`, `privileged`,
`cap_add`, `cap_drop`, `security_opt`, `platform`, `devices`,
`annotations`, `pull_policy`. Each is an honest, validated passthrough to
its `podman run` flag.
- **`profiles` and `stop_signal`/`stop_grace_period`** are now accepted and
ignored (with a warning) rather than rejected — compose2pod's run set is
always `--target` plus its `depends_on` closure, and the generated script
always force-removes the pod, so neither key changes anything it can act
on. A target `depends_on` reaching a service outside its declared profile
still runs it (the closure is authoritative — more permissive than
Compose, never a silent drop).

## Packaging

- **`resolve_extends` is exported** from `compose2pod.__all__`, alongside
the existing `EmitOptions`, `UnsupportedComposeError`, `emit_script`,
`to_shell`, and `validate`.

## Why

Prior releases built the core single-pod model and its interpolation
semantics; this one rounds out the compose keys real-world compose files
actually use, so fewer documents need pre-editing before they run.
Discovering the `--add-host` regression only on a real Podman run — not from
any of the 363 existing unit tests, all of which assert on generated
script *text* — is exactly why the integration harness (see Internals) was
worth building: some regressions are only visible to the runtime it targets.

## Downstream

- **Compose files using `extra_hosts` for a service-specific override:** it
is now pod-wide. A host name your `extra_hosts` maps to one address must
not conflict with another service's mapping, or with an alias's fixed
`127.0.0.1` — compose2pod now refuses rather than silently picking one.
- **Any pod that previously failed with `network cannot be configured when
it is shared with a pod`** now works without changes to your compose file.
- **API consumers:** `compose2pod.resolve_extends(compose)` is now
available for flattening `extends` outside the CLI pipeline.

## Internals

- 363 tests at 100% line coverage (enforced); `ruff select=ALL`, `ty`, and
`eof-fixer` clean.
- A new CI-only integration harness (`tests/integration/`, 10 scenarios)
renders real compose documents and runs the generated script against real
Podman on `ubuntu-latest`, asserting on exit code and output — it caught
the `--add-host` regression above on its first run. Kept out of the
100%-coverage gate and the fast suite via a location-based marker.
- `compose2pod/keys.py`'s `SERVICE_KEYS` registry unifies what was
previously three hand-synced tables (validation, emission, and the
supported-key set) into one `(validate, emit)` pair per key.