diff --git a/planning/releases/0.2.0.md b/planning/releases/0.2.0.md new file mode 100644 index 0000000..7afac99 --- /dev/null +++ b/planning/releases/0.2.0.md @@ -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: }` 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/` + (secrets) or the container root `/` (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.