Skip to content

feat: resolve same-file compose extends by flattening inheritance#33

Merged
lesnik512 merged 5 commits into
mainfrom
feat/extends
Jul 11, 2026
Merged

feat: resolve same-file compose extends by flattening inheritance#33
lesnik512 merged 5 commits into
mainfrom
feat/extends

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

Support the common form of compose extends — a service inheriting another service's config within the same document. A new pure module compose2pod/extends.py exposes resolve_extends(compose) -> compose that flattens every service's extends away (transitive, cycle-checked, per-key merge) before validate() runs, so the rest of the pipeline never sees extends. Cross-file file: extends is refused loudly, keeping compose2pod's single-document input model intact.

Design: planning/changes/2026-07-11.01-extends.md. This is the largest Bucket B item from the coverage audit.

What's supported

  • Mapping form only (extends: {service: <name>}); service must name a service in the same document. Transitive, with cycle detection.
  • Resolved as a pre-validation step in cli.py (resolve_extendsvalidateemit_script); also exported as public API for library callers.
  • Cross-file file:, bare-string extends, unknown extends keys, non-string service, unknown referenced service, and incompatible-form merges are each refused with a distinct UnsupportedComposeError.

Merge semantics (per-key category, matching the spec for the supported subset)

  • Mapping-merge (local wins): environment, labels, annotations, extra_hosts, ulimits, healthcheck, depends_on. List-form environment/depends_on are normalized to mappings first.
  • Sequence-concatenate (base then local): cap_add, cap_drop, security_opt, devices, group_add, secrets, configs, volumes, tmpfs, env_file. Scalar tmpfs/env_file normalized to a one-element list first.
  • Override (local replaces): everything else, including command/entrypoint (argv, replaced not concatenated) and unknown keys (which validate() then rejects).

Where a faithful merge is impossible (a mapping-merge key that is neither a mapping nor a list-form environment/depends_on, or a concat key that is neither a list nor a scalar string), resolution refuses loudly rather than guessing.

Documented divergences

  • Only environment/depends_on accept list form for the mapping-merge; other mapping-merge keys in list form on a merged side are refused (not silently coerced).
  • Short-form volumes are concatenated rather than merged by target path (podman resolves duplicate mounts).
  • Referenced resources are not auto-imported — as in Compose, the extending service must declare what it needs.

Robustness

A whole-branch review found that a service extending a non-dict base (e.g. a null service body) crashed raw. Fixed at the root: parsing.validate() now rejects a non-dict service value ("service X must be a mapping"), and resolve_extends defers such a base to validation instead of crashing — both paths now return a clean exit 2 with no traceback.

Testing

just test-ci at 100% coverage (293 tests), just lint-ci clean, just check-planning OK. tests/test_extends.py covers each merge category, list/scalar normalization, transitive + diamond resolution, cycle detection, and every refusal; tests/test_cli.py proves the end-to-end pipeline (a merged doc emits correctly; a file: extends and a non-dict base each return a clean error). architecture/supported-subset.md gains an ## Extends section.

A non-dict service value (e.g. `services: {web: null}`) crashed with an
uncaught TypeError (exit 1) instead of a clean UnsupportedComposeError
(exit 2). Root-cause fix in two places: validate() now guards non-dict
service values before iterating them, and resolve_extends() defers to
validate() instead of crashing when a service's extends base is not a
mapping.
@lesnik512 lesnik512 merged commit cbaac3f into main Jul 11, 2026
6 checks passed
@lesnik512 lesnik512 deleted the feat/extends branch July 11, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant