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
68 changes: 68 additions & 0 deletions planning/releases/0.1.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# compose2pod 0.1.8 — interpolation moves to run time

This release reverses 0.1.7: Compose `${VAR}` interpolation now happens when
the *generated script* runs, not when compose2pod generates it. compose2pod
emits a script that runs later — typically in CI, where the real secrets and
config live — so resolving against compose2pod's own environment at generation
time baked any variable that was unset then to an empty string. References are
now emitted as live POSIX-shell fragments that the runtime shell expands.

## Change

- **`${VAR}` is interpolated at script-run time, not generation time.**
`to_shell()` (new `compose2pod/shell.py`) re-encodes every compose-derived
value (`environment`, `image`, `command`, `volumes`, `tmpfs`, `env_file`,
healthcheck `test`) into a double-quoted shell fragment whose variable
references stay live, so the generated script's own shell resolves them
against its runtime environment. All the 0.1.7 forms still work — `$VAR`,
`${VAR}`, `${VAR:-default}`, `${VAR-default}`, `${VAR:?msg}`, `${VAR?msg}`,
`${VAR:+alt}`, `${VAR+alt}`, `$$` — mapping onto identical POSIX `sh`
parameter expansion. A bare `$VAR`/`${VAR}` is emitted as `${VAR-}` so an
unset variable expands to empty under the script's `set -eu` instead of
aborting; `${VAR:?msg}` now fails the script at run time (not at generation).
Command substitution and other shell metacharacters in literal text are
escaped, so nothing but a variable reference is ever live. The CLI prints one
informational note listing the variables the script references at run time.

## Fix

- **`environment` null value is host passthrough.** A null mapping value
(`KEY:`) now emits a bare `-e KEY` — passing `KEY` through from the host,
identical to the list form `- KEY` — instead of the literal `-e KEY=None`.
- **Malformed braced references are rejected.** A braced reference whose text
after the name is not a valid operator (e.g. `${FOO!bar}`) now raises a clear
`UnsupportedComposeError` instead of silently dropping the trailing text.

## Packaging

- Public API: the generation-time `interpolate` function is **removed**;
`to_shell` is exported in its place. `to_shell(value)` returns a
runtime-expandable shell fragment, not a resolved value.

## Why

The 0.1.7 model resolved `${VAR}` against `os.environ` at generation time. For
compose2pod's actual use — generate a script now, run it later in CI — that was
the wrong moment: the variables are supposed to be unset at generation and set
at run time, so 0.1.7 baked them to empty and emitted a spurious "variable not
set" warning. Deferring to the runtime shell resolves them where they exist.

## Downstream

- **Compose files:** set `${VAR}` values in the *runtime* environment of the
generated script (e.g. your CI job), not in the environment that runs
compose2pod. An unset bare `${VAR}` now expands to empty at run time; write
`${VAR:?message}` where you want a missing variable to fail the run.
- **API consumers:** `compose2pod.interpolate` is gone. Use
`compose2pod.to_shell(value)`, which emits a runtime-expandable shell fragment
rather than resolving the value in-process.

## Internals

- `architecture/supported-subset.md` rewritten to describe run-time expansion,
the `${VAR-}`/`$$`/`${VAR:?}` semantics, `env_file` interpolation, the
reference note, null-value passthrough, and malformed-reference rejection.
- 123 tests at 100% line coverage (enforced); `ruff select=ALL`, `ty`, and
`eof-fixer` clean. `test_shell.py` executes emitted fragments under real
`sh -euc` to prove run-time expansion, the `${VAR:?}` abort, and
command-substitution inertness.