release: 3.7.0 - #1399
Closed
Jaro-c wants to merge 22 commits into
Closed
Conversation
…e reporting (#1365) (#1375) Closes #1365. I bound the libpod log/stream parsers to a 1 MiB cap, enforced before any allocation: `parse_frame` rejects a hostile size field with `StreamTooLarge` before splitting the buffer, and `parse_multiplexed` / `parse_json_lines` use a cumulative-bytes counter so many small frames cannot grow the buffered slice past the cap between checks. The wire-format correction is the bigger behavioural fix: the logs frame header is **8 bytes** (4 bytes stream type + 4 bytes size big-endian uint32), the daemon does not cap frame sizes, and **stats/events are NDJSON rather than frame-stream** — three different parsers, not one. The trailing-bytes path of `parse_json_lines` now reports `StreamEndedEarly` when the remainder does not parse, instead of a serde error that hid the cause. I also rewrote the hijack response-head reader to drain the head bytes first before deciding the error, with a coverage test that runs against a peer that never sends the terminator. New fuzz target `stream_ndjson` exercises the cumulative cap; the existing `stream_frame` target now seeds an oversized header and expects `StreamTooLarge` without an OOM. ## Validation - `cargo build`, `cargo test --lib` (1540+ tests), `cargo test --bins`, `cargo fmt --check`, `cargo clippy -- -D warnings`: all green on the branch. - Fuzz target updates: `fuzz/fuzz_targets/stream_frame.rs` already exercised `parse_frame`; I added the 8-byte header size, a 4 GiB hostile header that must error without OOM, and a new `stream_ndjson` target for the cumulative-cap path. - Lane: real-Podman 5 and 6 on this PR. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com> Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…64 padding, ed25519 error classification (#1359) (#1376) ## Test plan - **Fixture release** under `tests/fixtures/releases/` with three rotation slots populated in `install.sh`. The CI step iterates every `*.sig` and asserts verification. - **Shell test for the padding fix**: a known-old fixture and a known-mis-padded fixture, both should fail verification with the correct error class. - **CI**: extend `.github/workflows/installer-contract.yml` with a step that runs the shell fixtures on Linux, a Windows-runner step for `install.ps1`, and the existing Rust reference test for parity. Closes #1359. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…nd events (#1366) (#1378) Closes #1366. Five sites in `internal/engine/build/mod.rs` and one in `internal/engine/events.rs` called `serde_json::to_string(...).unwrap_or_default()` and dropped the result of a serialisation failure as the empty string. An empty `buildargs=` reached libpod as a no-arg query parameter and the build silently ran with image defaults; an empty events row corrupted the NDJSON stream a parser was reading line-by-line. All six sites now flow through one `to_query_json` helper (`internal/engine/mod.rs`) that returns `Result<String>` and surfaces the field name and the underlying error in a `ComposeError::Build` message. The events side logs the cause at `debug` and drops the single row, so the stream stays well-formed. Unit tests: a synthetic `Serialize` impl that always errors is run through `to_query_json` with each of the five build-side labels and the events-side label, asserting `Err` and the message. The happy path is pinned to the actual types each call site serialises. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com> Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…1362) (#1377) Closes #1362. ## What changed I added a per-socket HTTP/1.1 connection pool in `internal/libpod/client/pool.rs` keyed by the Podman socket path. Buffered calls (`get_json`, `post_json`, `post_json_ok`, `post_empty_ok`, `post_empty_json`, `post_empty_json_unbounded`, `post_bytes_json`, `put_bytes_ok`, `head_container_path_stat`, `delete_existed`, `ping`) now acquire a connection, issue one request, and release it on completion. A response carrying `Connection: close` is dropped proactively so the next acquire opens a fresh one instead of paying the wait for a half-closed socket. The default cap is **8 connections**, tunable via `Client::with_pool_size` or the new `--connection-pool-size` / `PODUP_LIBCOD_POOL` global CLI flag. Streaming endpoints (`get_stream`, `post_json_stream`, `post_empty_stream`, `post_bytes_stream`, `post_stream_body`, `post_json_stream_within`) now take a **dedicated** connection outside the buffered pool and hold it for the lifetime of the response body; the connection is released when the `Client` is dropped. That keeps the public streaming-helper interface (`Result<Response<Incoming>>`) intact while ensuring a long-lived `logs -f` or interactive `exec` cannot share its socket with a buffered caller mid-stream and corrupt the wire. `Client::new`'s signature is unchanged (so the public surface of `podup::Client` is not a breaking change). The new public items are `Client::with_pool_size`, `Client::pool_size`, `Client::DEFAULT_POOL_SIZE`, `podman::connect_with_pool_size`, and `podman::connect_from_env_with_pool_size` — all additive. The `Client::new` doc comment is updated to describe the pool contract. Every existing doc comment on the touched items is preserved. ## Why `Client::connect` (`internal/libpod/client/mod.rs`) opened a brand-new hyper HTTP/1.1 connection over the Unix socket for *every* request. The doc comment justified it as "correct for a CLI tool where API calls are sequential and infrequent", but `up` is fan-out by construction: a 100-service compose file issues ~5 libpod calls per service plus a per-level bulk — easily **600+ connections per `up`**. Validation against the libpod server source confirmed the server DOES allow connection reuse; the bug was the client side opening a fresh connection per request. ## Test plan - **Unit test** with a fake-libpod server that counts incoming connections (`internal/libpod/client/pool/tests.rs`): - Sequential 100 requests → 1 connection. - Concurrent 16 requests (pool size 4) → ≤ 4 connections. - A forcibly-closed (poisoned) connection is replaced on the next acquire. - A new `Client` after a `Drop` opens a fresh connection. - Pool size is what `Client::pool_size` reports; a cap of 0 is floored to 1. - A `Drop` on the `Client` closes the pool. - **Existing fake-podman harness tests** (1500+ in `cargo test --lib --features test-helpers`): all still pass. The harness sends `Connection: close` on every response, and the new pool discards those proactively so the second request lands on a fresh socket. - **CLI**: `podup --help` lists `--connection-pool-size <N>` and the `PODUP_LIBCOD_POOL` env var; the global flag is forwarded through `main.rs` and `autostart_cmd.rs` to every `podman::connect_with_pool_size` call site. - **Lane**: real-Podman 5 and 6, no regression (run on this PR). ## Validation - `cargo build --bin podup --features test-helpers` — green. - `cargo test --lib --features test-helpers` — 1550 tests, all green. - `cargo test --bins --features test-helpers` — 83 tests, all green. - `cargo fmt --check` — clean. - `cargo clippy --bin podup --features test-helpers -- -D warnings` — clean. ## Limits (intentional) - Streaming connections are released when the **Client** drops, not when the stream body drops. The streaming helpers' return type is fixed at `Response<Incoming>` (per the public-surface constraint), so the body does not carry a hook to release the underlying connection. In the CLI, the `Client` lifetime matches the command lifetime, so the two converge in practice; an embedder holding a `Client` across many operations will retain streaming connections until the `Client` is dropped. - The pool is **per-socket-path**. A `Client` instance holds one pool; the `socket_path` field is immutable after `Client::new`. The "keyed by socket path" requirement is satisfied because the pool is constructed from the socket path itself. --------- Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
… unify clap error formatting (#1368) (#1374) ## Test plan - **Exhaustiveness test**: a compile-time check that every `Commands` variant appears in either `dispatch.rs` or `rest.rs`, with no catch-all. The `#[deny(unreachable_patterns)]` lint gives this for free. - **Integration**: `podup logs --wrong-flag` exits non-zero with the `podup:` prefix. - **Unit test** for `is_label_only` future-proofing (covered here as part of the same shape concern): a `#[non_exhaustive]` annotation on the enum would catch it at compile time. --------- Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
Closes #1326. The nightly lane run on Podman 6 already measures coverage (`cargo llvm-cov --all-features`) and prints `PODUP_COVERAGE=N.MM`, but the value is only reported to the job summary — never gated. The owner recommended a 88% nightly floor in #1326 (3 samples 91.5-91.6%, room for natural drift above the level where a real regression hides), with PR runs deliberately not gated (a second instrumented build costs ~30 min per leg, and the nightly already pays for it). This commit adds the gate to the lane's existing verify step: - Schedule + Podman 6 only (the leg that represents the latest stable major and the one that produces a coverage number under `cargo-llvm-cov`). - Parses `PODUP_COVERAGE=N.MM`, strips the trailing `%`, and compares as an integer against 88. Below the floor, the verify step exits non-zero with a clear error naming #1326. - PR runs are untouched (they never set `COVERAGE=1`, so the gate condition is false and the existing path is unchanged). - workflow_dispatch is also left untouched (a manual run might want to bypass the gate to test the threshold itself; the nightly schedule is the gating path). The lane's matrix condition for `COVERAGE=1` already gates the measurement to `schedule|workflow_dispatch` (line 279), so this commit is purely additive: it parses an existing emission and adds a comparison. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com> Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…s label (#1361) (#1382) The label_file reader split each line on '=' and inserted the (key, value) pair into SpecGenerator.labels with no control-character or size check. Podman JSON-encodes the value on the wire, so wire injection was never the concern — the concern is downstream consumers that re-parse the label value (logs, ls/inspect UIs, label-based filters), where a control character in either side lets one entry break out of its context. The 16 MiB read cap on the file also left the resulting HashMap size, key length, and value length unconstrained. This change adds `sanitize_kv_pair`, which validates a single key/value pair: - key: non-empty, no ASCII control characters, length ≤ 253 bytes (Podman's per-label cap); - value: no ASCII control characters, length ≤ 4 KiB; - map: capped at 64 distinct entries, with overwrites of an existing key still allowed at the cap (the cap bounds distinct keys, not insertions). `build_label_file_labels` now returns `Result<HashMap<String, String>>` so a rejection surfaces as an `Unsupported` error naming the file and line, rather than a silently-truncated map. The `podup.config-files` label joins compose-file paths with `,` and so could visually merge with the next entry when a path itself contained a `,`. Each path is now passed through `encode_path_for_label` which replaces `,` with `%2C`, making the round-trip through any downstream `,`-split unambiguous. ## Test plan - Unit tests: `sanitize_kv_pair` — bad input (control chars, oversize key/value, too many entries) → reject, good input → pass. - Integration: a 1000-line label file → capped at 64 entries; a label file with control characters → reject at parse with a clear error; a path containing `,` in `compose_files` → URL-encoded in the `podup.config-files` label. - Live-Podman: the lane (Podman 5 + 6). Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…1356) (#1383) ## Test plan - **Shell fixture**: a stub `podup` binary that prints different `--version` outputs. Two cases: - Reports the resolved tag (with or without leading `v`) → installer proceeds. - Reports anything else (older tag, `-dev` suffix, garbage) → installer refuses with non-zero exit and the staged file is removed. - **CI**: extend `.github/workflows/asset-contract.yml` (or its underlying reusable in `Glyndor/.github`) with a step that runs the shell fixture on Linux, a Windows-runner step for `install.ps1`, and the existing Rust reference test for parity. The fixture should live in `tests/fixtures/releases/` or similar. ## Validation - `shellcheck install.sh` clean. - `PSScriptAnalyzer install.ps1` clean. - The new CI job passes on push. - The Rust reference test (`internal/update/install.rs`) still passes — no regression. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.22.1 to 0.23.1. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md">base64's changelog</a>.</em></p> <blockquote> <h1>0.23.1</h1> <ul> <li>Make the tests build again on non-SIMD architectures</li> </ul> <h1>0.23.0</h1> <ul> <li>Added more consts for preconfigured configs and engines</li> <li>Make DecodeError::InvalidLastSymbol more clear by including the decoded value</li> <li>Added SIMD-accelerated engines behind the default-on <code>simd-unsafe</code> feature: <code>Simd</code> picks the best instruction set at runtime (AVX2 on <code>x86_64</code>, NEON on <code>aarch64</code>) and falls back to the scalar <code>GeneralPurpose</code> engine, while <code>Avx2</code> and <code>Neon</code> target one instruction set with no runtime detection and work in <code>no_std</code>. The engines support the standard and URL-safe alphabets.</li> <li>Update MSRV to 1.71.0</li> <li>Add support for custom padding symbols</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/marshallpierce/rust-base64/commit/069bf7067b949f5c0a92b6ceb82492920502f2c2"><code>069bf70</code></a> v0.23.1</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/6ab1fb0a5843013557a52c45c84b91f5d1bb87af"><code>6ab1fb0</code></a> Merge pull request <a href="https://redirect.github.com/marshallpierce/rust-base64/issues/310">#310</a> from musicinmybrain/test-on-non-simd-arches</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/7cffce6f971acdf48f83112cbdd63bd61125ba06"><code>7cffce6</code></a> Fix testing on architectures without unsafe SIMD support</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/e34f9a08c5c89a4641350ac22033f3fa4f5d4d97"><code>e34f9a0</code></a> Merge pull request <a href="https://redirect.github.com/marshallpierce/rust-base64/issues/308">#308</a> from atouchet/com</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/e9240c9a01e0a4934c5619e74740caa6d1f67ce9"><code>e9240c9</code></a> Remove outdated comment</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/9e9220a4166f628de7c8803289e120ae1e944f78"><code>9e9220a</code></a> v0.23.0</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/870326ec592eebde9d6bfe4c5d8130c591273e9c"><code>870326e</code></a> Merge pull request <a href="https://redirect.github.com/marshallpierce/rust-base64/issues/306">#306</a> from marshallpierce/mp/trailing-bits-docs</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/fbec5f1050f9fc16e6a826ebabaa2b7b0644bd67"><code>fbec5f1</code></a> Document no trailing trailing bits</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/0a23549968f059b53cf39e96eba8f46779f322a7"><code>0a23549</code></a> Merge pull request <a href="https://redirect.github.com/marshallpierce/rust-base64/issues/305">#305</a> from marshallpierce/mp/edition-2021</li> <li><a href="https://github.com/marshallpierce/rust-base64/commit/f10b7e20614135aa61289140683fc93e5a45d338"><code>f10b7e2</code></a> Update deps & edition</li> <li>Additional commits viewable in <a href="https://github.com/marshallpierce/rust-base64/compare/v0.22.1...v0.23.1">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…sed, file: secret cleanup) (#1360) (#1384) I tightened install path integrity across the shell and Rust update paths. The change preserves target modes from private staging, unifies Unix and Windows recovery on `target.old`, cleans podup-created native secrets during `down`, and makes the `dpkg-query` ownership path skip manifest scanning when the helper is unavailable. Closes #1360 Validation: `cargo build --locked`, `cargo test --locked`, `shellcheck install.sh`. PSScriptAnalyzer was not run because `pwsh` is unavailable in this environment. The Podman 5 and 6 lane remains covered by CI. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
… install (#1367) (#1387) Refs #1367 (not closing per the issue owner's instruction) ## What's in this PR Five sub-claims from the issue, addressed as documented: 1. **`cp_to_container` (item 1):** documented the per-project lock + libpod-validation contract in a new doc comment on `cp_to_container` at `internal/engine/copy.rs:132-156`. The lock closes the cross-invocation case (two `podup` processes), libpod's own extraction-directory validation closes the within-invocation case (a foreign `rm -rf` racing in is rejected by the second PUT, not silently succeeded). No code change. 2. **`run_attached` (item 2):** on `post_empty_ok(start_path)` returning `Err`, drain the hijacked socket's kernel buffer before returning the error. The drain is bounded by a 2 s budget (`START_FAILURE_DRAIN_BUDGET`) so a wedged peer cannot pin the CLI. The drain does not enter raw mode and does not read from stdin (that is the whole reason `pump_terminal` is gated on a successful start). The success path is unchanged. 3. **`Engine::new` (item 3):** added a `tracing::warn!` when `std::env::current_dir()` fails. The CLI already uses `with_base_dir` exclusively (`internal/main.rs`), so this surfaces the silent loss for library callers and embedders without changing the CLI's hard-error-on-missing-base-dir behaviour. 4. **`create_secret` (item 4):** documented the lock + project-scoped naming contract on the inspect → delete → create sequence at `internal/engine/secrets/create.rs:103-141`. The cross-invocation case is closed by the per-project lock; the within-invocation case is closed by the project-scoped naming (`<project>_...`) and the inspect-time foreign-secret refusal. No code change. 5. **`install_binary` (item 5):** the L5 fix the issue calls for was already addressed in #1360 (`chore: tighten install path integrity`). The current `install_binary` uses `move_target_aside` to rename the target to a `.old` sibling before the swap and `restore_from_backup` on self-test failure; the self-test reads from the on-disk backup. Added one regression test for the chmod-0000 case the issue's test plan calls out: a binary the operator can no longer read or execute is the canonical "I have a backup question for the rollback" case, and the L5 path must leave the previous binary in place. ## Test plan - New unit test: `install_binary_rolls_back_when_the_target_is_unreadable` at `internal/update/install/tests.rs` exercises the chmod-0000 → self-test fails → rollback path. - 1580 tests pass on `cargo test --lib --features=watch,completions,update` (1579 + 1 new). - `cargo build`, `cargo fmt --all -- --check`, `cargo clippy --lib` all clean. The two pre-existing `-D warnings` dead-code errors on `internal/libpod/types/container/response.rs` are present on `origin/develop` and are not introduced by this PR. ## Note on the issue The issue describes `install_binary` reading the current binary with `.ok()` and having no on-disk backup. That state was addressed in #1360 (`move_target_aside` + `restore_from_backup`); the current code in this PR preserves the on-disk `.old` backup through the swap window. The new chmod-0000 test pins the rollback behaviour for the read-forbidden case. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…art/kill/rm/pause/unpause (#1363) (#1388) Closes #1363. ## What's wrong `stop`, `start`, `restart`, `kill`, `rm`, `pause`, `unpause` each call `live_service_containers(name)` or `live_replica_names(name, service)` once **per service** before acting on it. Each call is one `GET /containers/json` filtered by `podup.service={name}`. A bulk `list_project_containers_by_service()` exists at `internal/engine/lifecycle/scale.rs:275-302` and `down` already uses it; the per-service lifecycle commands do not. ## What I changed - Renamed the bulk helper to `Engine::live_project_replicas` (the same shape `down` was already using), and updated its doc comment to pin down the `all=true` requirement — libpod's container-list defaults to `runningOnly` when no `status=` filter is supplied, so a bare project GET would silently drop the very containers `start`/`restart`/ `kill`/`rm`/`pause`/`unpause` need to act on. - Switched the seven per-service lifecycle commands to prefetch the project's containers once at the top via the new helper, then read per-service slices from the in-memory `HashMap`. The per-service `*_one_service` functions now take `Vec<String>` instead of doing their own query. For a 100-service project the container-list GET count drops from S+1 to 1. - `stop_container` now returns `Result<bool>` (transitioned vs. already-stopped/gone) so the `acted` flag in `stop_one_service` stays accurate now that we no longer filter by per-container state client-side — the bulk helper returns names without states. The existing drop-recheck tests still match the Ok/Err shape and now exercise the new bool via the existing "lost stop response" fixtures. - `state_is_active` (formerly the `stop` state filter) is no longer used in production code. Kept it with `#[allow(dead_code)]` for the unit test that pins its truth table — the test is the only thing that still documents the running/paused-vs-anything-else split. ## Tests - New unit test: `live_project_replicas_returns_every_project_container_including_stopped` in `internal/engine/lifecycle/scale.rs`. Synthesises a 100-service project with 5 stopped replicas, asserts the returned map has every service (100 entries, 100 container names, the 5 stopped services included), AND asserts the bulk GET request shape (carries `all=true`, no `status=` filter workaround). - All 1580 unit tests pass (`cargo test --all-features --lib`). - All integration tests pass (`cargo test --all-features`). - `cargo fmt --all -- --check` and `cargo clippy --all-features --all-targets -- -D warnings` are green. ## Test plan (per the issue) - **Integration**: a 100-service project with 5 stopped services; `stop` issues 1 bulk GET (with `all=true`) and 100 stop POSTs (vs. 200 GETs + 100 stop POSTs today). The unit test pins both halves of that on a fake-libpod server. - **Lane**: needs real-Podman 5 and 6 to confirm the wire shape matches what the fake produced. Tagged `prio:P1` so the live lane runs the full lifecycle command surface against the changed code paths. ## Out of scope - The libpod connection pool is a separate issue (#1362). - The per-service `create`/`start`/`healthcheck` fan-out is not addressable in this shape (different concern, different filter). - Query commands (`logs`, `inspect`, `ps`) still use the per-service `live_replica_names`. They could fold into the same bulk helper later, but the issue explicitly scopes this PR to the seven state-changing lifecycle commands. ## Lane I have not run the binary against real Podman in this session — the fix is unverified at the runtime/lane level. The static review, unit tests (against a fake libpod shaped per the libpod source), the full integration suite and clippy are green. The live lane is the place where the wire-shape match against real libpod 5/6 (which the unit test pins against the fake) actually gets exercised end-to-end. Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
#1389) Closes #1358. Three compose fields collapse the isolation between a container and its host, and `podup up` accepted them in the live engine path with no warning. Quadlet warned-and-dropped; the live engine did not, so the same compose file behaved differently between `podup up` and `podup generate quadlet`. Validation against `podman`'s source surfaced two additional modes (`userns_mode: host`, `container:<id>` namespace sharing) that podup should warn on. A pure `check_host_mode(service) -> Vec<ModeWarning>` helper in `internal/engine/container/host_mode.rs` scans every active mode and returns one warning per mode. `Engine::up` (and the `run`/`exec` paths that surface container config) call it after the spec is built and emit a `tracing::warn!` per warning. The Quadlet path keeps warn-and-drop for `pid`/`ipc`/`uts`/`cgroup` (where the unit file semantics require the drop) and adds a `tracing::warn!` on the host-network and privileged arms before the emit. The same detector feeds both paths so the messages match. A new global `--no-warn` flag suppresses these warnings on `up`/`create`/`run`/`exec` and on `generate quadlet` — operators who wrote the compose file deliberately use it to silence the per-run copy. Default: warn. The live path honours the flag via `Engine::with_no_warn`; the Quadlet path uses a `NoWarnGuard` thread-local around `write_quadlet` so `generate_at` stays a non-breaking free function. `podup config` still surfaces the active modes at the default log level — that command is the "show me what will happen" path, where the warning is the whole point. Tests: each mode present triggers a warning, each mode absent stays silent, `container:<id>` namespace sharing triggers a warning, `userns_mode: host` triggers a warning, `--no-warn` suppresses the engine and Quadlet warnings, and `--no-warn` against `podup config` still surfaces the modes. Unit coverage is in `internal/engine/container/host_mode.rs` (9 cases) and `internal/quadlet/mod.rs::no_warn_tests` (3 cases); CLI end-to-end coverage is in `tests/host_binding_warnings.rs` (9 cases). --------- Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## Summary I split the `walk_dir` / `walk_collect` filesystem helpers out of `internal/engine/mod.rs` into a new `internal/engine/walk.rs` module so `mod.rs` falls under the 500-line hard cap the org's `line-limit` reusable enforces (#1386 follow-up; this same check was the one that blocked merging #1390 — the per-PR lane flagged the regression after the `Engine` field additions in the #1364 cache PR pushed it to 503 code lines). The change is mechanical: the two functions move verbatim, with the same semantics and the same private-in-crate visibility, and one new unit test pins the sorted order on a tempdir. `internal/engine/build/context.rs` and `internal/engine/watch/sync.rs` updated to import from the new module. ## Validation - `cargo build --locked --bin podup --features test-helpers` - `cargo test --locked --lib --features test-helpers` (1594 passed, 0 failed) - `cargo test --locked --bins --features test-helpers` (87 passed, 0 failed) - `cargo fmt --all --check` - The `line-limit` check the per-PR lane runs now reports `mod.rs` at 486 code lines (under the 500 hard cap), down from 503. The real-Podman integration lane is the final runtime check for both supported Podman majors. Closes the merge blocker on #1390 (the `Engine` field additions for the cached project label filter in the #1364 PR pushed `mod.rs` over the line cap, which the per-PR lane then flagged as a required-check failure). ## Out of scope (deferred) The next-largest files — `internal/engine/lifecycle/mod.rs` (497) and `internal/engine/copy/archive.rs` (478) — are within the hard cap; they stay. Signed-off-by: Jaro-c <jaroc@glyndor.net> Co-authored-by: Jaro-c <jaroc@glyndor.net>
## Summary I split the libpod client methods into focused HTTP verb modules while keeping the client state, transport plumbing, and status handling in `internal/libpod/client/mod.rs`. The public `Client` API remains unchanged and every production file stays below the repository's hard line limit. ## Validation - `cargo build --locked --bin podup --features test-helpers` - `cargo test --locked --lib --features test-helpers` - `cargo test --locked --bins --features test-helpers` - `rustfmt --check --edition 2021 internal/libpod/client/mod.rs internal/libpod/client/get.rs internal/libpod/client/post.rs internal/libpod/client/put.rs internal/libpod/client/misc.rs internal/libpod/client/delete.rs` The real-Podman integration lane remains the final runtime check for both supported Podman majors. Closes #1386 Signed-off-by: Jaro-c <jaroc@glyndor.net> Co-authored-by: Jaro-c <jaroc@glyndor.net>
## Summary I closed the correctness findings called out in #1369, each as its own commit so the diff matches the original list: - `confirm_lost_response` compared `state == Some("running")` literally — switched to `eq_ignore_ascii_case` so a future libpod returning `Running` or `RUNNING` does not produce a false negative. The state field is lowercase today (validated against `libpod/define/containerstate.go::ContainerStatus.String()`); the case-insensitive test is forward-compat. - `is_exec_teardown_noise` swallowed stderr frames on the bare substring conjunction `unixpacket` + `connection reset by peer` — narrowed to `read unixpacket` + `connection reset by peer` so a real Go program that logs `dial unixpacket: connection reset by peer` is no longer suppressed. The system prefix is the discriminator. - `libpod_pull_policy` warned and silently fell back to `missing` for an unknown value — a typo'd `pull_policy: alaways` previously meant the user got a different artefact than the compose file asked for on every `up`. The warn-and-default policy was a footgun on the only knob the user can set here, so an unknown value is now a hard error with the accepted list in the message. - `Engine::run_lifecycle_hook` held the stdout lock for the whole stream — that serialised the hook behind any other writer for its entire lifetime. Switched to a per-frame lock symmetric with stderr. ## Validation - `cargo build --locked --bin podup --features test-helpers` - `cargo test --locked --lib --features test-helpers` (1594 passed, 0 failed; new test `resolved_pull_policy_rejects_an_unknown_value` covers the typo case) - `cargo test --locked --bins --features test-helpers` (87 passed, 0 failed) - `cargo fmt --all --check` - `cargo clippy --locked --all-targets --all-features -- -D warnings` The real-Podman integration lane is the final runtime check for both supported Podman majors. Closes #1369 ## Out of scope (deliberately deferred) The remaining items in #1369 are tracked separately: - `XDG_CONFIG_HOME` / `HOME` for Quadlet install trust — see #1356 / #1360 already in flight; a separate refactor is needed for the install trust path. - `PodmanError::Hyper(e).source()` not surfacing the underlying io cause — already addressed indirectly by `stream_end_kind` and the existing `body_ended_early` chain walker; the generic `Display` could be widened but is a larger API change. - `read_capped` errors losing the cap context — already named by a separate issue and the `Error` enum change has broader call-site impact than this PR can carry. - `DOCKER_HOST` precedence — the existing docs page already lists the precedence; tightening to a warning would add noise to every CI run that exports `DOCKER_HOST` and gain little. - `is_label_only` `#[non_exhaustive]` — clippy is not currently catching the omission, and `#[non_exhaustive]` is a public-API change that needs its own issue. --------- Signed-off-by: Jaro-c <jaroc@glyndor.net> Co-authored-by: Jaro-c <jaroc@glyndor.net>
…w cannot say (#1370) (#1393) ## Summary I extended `docs/security-model.md` to codify the trust boundaries, the limits of static review, and what the live integration lane actually catches — the four sections the static-review audit flagged as missing in the public docs (#1370): - **What podup trusts libpod to defend** — the in-crate validators that filter every cross-layer transition (names, project names, URL paths, Quadlet values, signal names, pull policies, timeouts, self-update bytes). - **What podup does not defend** — the conscious gaps: `cap_add`/`pid: host`/`network_mode: host`/`runtime:` forwarded to libpod; compose-sourced paths unconfined by design (Makefile posture); the point-in-time lifetime of `file:` secrets; the limits static review cannot close. - **What the live integration lane validates** — Fedora qemu VM with full systemd, per-major matrix, `PODUP_REQUIRE_PODMAN=1` to close the skip-as-pass green path, the per-major `podman-known-failures-<major>` as a classification not a count, the `--test-threads` cap that trades wall-clock for flake control. - **What static review cannot tell us** — the questions only live testing answers: whether libpod's per-field validators actually reject what podup forwards (the integration lane); whether the CDN re-sign rollback attack works (`install.sh:verify_version_self_test` / `install.ps1:Test-StagedVersion`); whether the streaming race conditions are reachable (fuzz targets and the live lane). ## Validation - The page renders without broken links (`docs/security-model.md` is already linked from `README.md` line 168). - A reviewer who has not read the codebase can read the page and know what podup does and does not defend. - `cargo fmt --all --check`, `cargo test --locked --lib --features test-helpers` (1593 passed, 0 failed). The change is documentation only; no code or test shifts. The README's existing link to the page is unchanged. Closes #1370 Signed-off-by: Jaro-c <jaroc@glyndor.net> Co-authored-by: Jaro-c <jaroc@glyndor.net>
## Summary I added a scheduled workflow that runs the live-Podman coverage path on the `develop` branch and applies the same 88% floor the `main` nightly uses (#1380). The merge path `task branch → develop → release PR → main` made a regression landing on `develop` invisible to the `main` nightly until the next release PR; this is the early-warning signal the issue asked for. The new file is a thin copy of `.github/workflows/podman-lane.yml` with three differences: - `ref: develop` instead of the default branch. - Cron `0 21 * * *` (12h after the `main` nightly's `0 9 * * *`) so the two never collide on runner capacity. - Matrix pinned to the Podman 6 leg only (the full-support cross-check is still the per-PR lane; Podman 5 and 6 both measure the suite at ~91.5%, so gating the older major would be redundant per #1326's own recommendation). ## What this is NOT - Not a replacement for the `main` nightly. The `main` nightly is the production-truth signal; this is the early-warning signal. - Not a status check on PRs. PR coverage stays off (a second instrumented build per PR is too expensive per #1326). - Not a per-package floor. The lane measures the full suite; per-package floors would require a different gate shape. - Not auto-merge of regression fixes from the alert. The floor's job is to surface the alert, not to fix it. ## Validation - `cargo fmt --all --check`, `cargo test --locked --lib --features test-helpers` (1593 passed, 0 failed), `cargo test --locked --bins --features test-helpers` (87 passed, 0 failed), `cargo clippy --locked --all-targets --all-features -- -D warnings` — the workflow file is the only change. - The workflow itself is verified by the schedule and by the `workflow_dispatch` path the `main` nightly already uses. The first nightly fires at 21:00 UTC. Closes #1380 Signed-off-by: Jaro-c <jaroc@glyndor.net> Co-authored-by: Jaro-c <jaroc@glyndor.net>
## Summary I closed the hot-path allocation cleanups called out in the static review (#1364), each as its own commit so the diff can be reviewed in the same shape as the original list: - `interpolate_value` rebuilds every YAML `Mapping` unconditionally (#1364 — gate the rebuild on real work). - The progress ticker re-probes terminal capabilities every 100 ms (cache once at `begin`). - `identity_style` allocates a `format!("{p}-")` per call (cache the prefix in `PROJECT_PREFIX`). - `config_hash` double-serializes the Service via `to_value` then `to_vec` (load-bearing for map field order — see the `to_vec` test; the canonical-serialise is required to keep the hash stable). - `restart_service_set` double-clones service names (return `Arc<HashSet<String>>` and share the empty-targets half). - `join_bounded` sorts per dependency level (documented as sub-microsecond at 100 services). - `from_utf8_lossy` per log frame in `run` (use `std::str::from_utf8` first; `write_frame` helper). - `progress_line` takes a global mutex per emission (`SESSION_OPEN` `AtomicBool` short-circuit). - `read_body` copies `Bytes` into a `Vec` (not changed — kept as a `Vec` because the call site already needs owned bytes for the next step; see the comment in `read_body`). - Filter JSON serialized fresh at every list call (cache the URL-encoded `{"label":["podup.project={name}"]}` on `Engine`; add `project_label_filter_with` for the dynamic sites that need a second label). - `resolve_levels` / `resolve_order` rebuild the same HashMaps per command (not changed — out of scope for this iteration; left as a follow-up if the per-call work ever shows up in a profile). ## Validation - `cargo build --locked --bin podup --features test-helpers` - `cargo test --locked --lib --features test-helpers` (1602 passed, 0 failed) - `cargo test --locked --bins --features test-helpers` (87 passed, 0 failed) - `cargo fmt --all --check` - `cargo clippy --locked --all-targets --all-features -- -D warnings` The real-Podman integration lane is the final runtime check for both supported Podman majors. No production file exceeds the 500-code-line hard limit; every committed file matches the formatting standard. Closes #1364 --------- Signed-off-by: Jaro-c <jaroc@glyndor.net> Co-authored-by: Jaro-c <jaroc@glyndor.net>
## Summary I re-validated podup against the Podman 6.0.2 baseline the auto-filer flagged in #1396, then bumped the version files. Validation: - Dispatched the `podman-lane` workflow against `develop` (run 31313391002). The Podman 6 leg ran the full integration suite on a fresh Fedora rawhide cloud image (the `Podman 6.0.2` source packages are now in rawhide; the lane pulled `Fedora-Cloud-Base-Generic-Rawhide-20260809.n.0.x86_64.qcow2`). The VM resolved to `podman 6.1.0-rc1` (rawhide moves fast — the same OS image carried `6.0.2` a few days ago, per the issue's note). - Result: `PODUP_SUMMARY pass=179 fail=0 flaky=0` on the Podman 6 leg, identical to the `6.0.1` baseline. No regressions, no new identities on the per-major known-failures list. - The Podman 5 leg also passed (parallel run; no separate test plan needed — `5.x` is still in support and the per-PR lane runs both). Bumps: - `.github/podman-baseline`: `6.0.1` → `6.0.2`. - `.github/workflows/podman-lane.yml` comment: the nested-virt measurement note (which the lane itself walks back from) is bumped to `6.0.2` so the next person who reads the file sees the right baseline. ## Validation - The lane ran end-to-end green (Pods 5 + 6); the per-PR lane on the next PR will rerun it for the integration diff, and the `podman-lane-develop-nightly` (#1394) keeps the `develop` baseline covered every 24h. - No code changes; the only files in the diff are the two version bumps. Closes #1396 Signed-off-by: Jaro-c <jaroc@glyndor.net> Co-authored-by: Jaro-c <jaroc@glyndor.net>
…mber (#1398) ## Summary The `podman-lane-develop-nightly` workflow (#1394) runs the full suite on Podman 6 every 24h, but the coverage number never reached the gate step. The cause: the script that runs in the VM emits the coverage only as the last column of the `tail -20 /tmp/cov.log` output, while the gate's `grep -oE 'PODUP_COVERAGE=[^ ]+'` is looking for a `PODUP_COVERAGE=<pct>` line of its own. The grep misses, the gate treats the run as a missing number, and exits 0 with a `::warning::` — so the early-warning signal #1380 added is silently no-op'd on every run. Discovered by dispatching the workflow manually after the Podman 6.0.2 baseline bump: the lane reported `success` but the warning was there in the logs, and the `awk` extracted `91.31%` correctly from the cargo-llvm-cov summary — the number was computed, just never emitted in the format the gate parses. The fix is one `print` and one comment: the `awk` now emits `PODUP_COVERAGE=91.31%` (not just `91.31%`) so the gate's regex matches, and the comment names the failure mode so the next person who reads the file sees it. ## Validation - Manual dispatch of the workflow after the fix would show the gate reading the number; the auto-nightly at 21:00 UTC is the next non-manual test. - The change is in a workflow that previously exited `success` while signalling a `::warning::` — the new behaviour is "exit `success` because the gate ran and read the number", which is the contract #1380 intended. - No code or test changes; the only file is the workflow YAML. This is a follow-up to the `develop nightly` workflow from #1394 / PR #1394, not a change to the per-PR `podman-lane`. Signed-off-by: Jaro-c <jaroc@glyndor.net>
Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bump podup to 3.7.0.
Version bumps in lockstep (the release workflow's
verifyjob fails closed if any of the three drifts):Cargo.toml:3.6.1→3.7.0Cargo.lock: regenerated to matchdebian/changelog: new entry at the top with the same version, full changelog for the cycle.Changelog covers everything merged into
developsince the 3.6.1 release:podman-lane-develop-nightlyworkflow with the 88% coverage floor on the Podman 6 leg; per-engine project-label filter cache (Engine::project_label_filter_encoded/…_raw/…_with);write_frameshared helper on theEngine.interpolate_valuegates the parent mapping rebuild onmapping_needs_interp; progress ticker cachesis_terminal+ window size atbegin;progress_lineshort-circuits on aSESSION_OPENAtomicBool;identity_stylecaches thePROJECT_PREFIX;restart_service_setreturnsArc<HashSet<String>>halves;is_exec_teardown_noiseanchors on theread unixpacketsystem prefix;confirm_lost_responseuseseq_ignore_ascii_case;libpod_pull_policyhard-errors on unknown values;Engine::run_lifecycle_hooktakes the stdout lock per-frame, symmetric with stderr;libpod/client/mod.rsandengine/mod.rsare both under the 500-line hard cap (the per-PR lane is no longer red on theline-limitcheck).PODUP_SUMMARY pass=179 fail=0 flaky=0on the Podman 6 leg).Validation
cargo build --locked --bin podup --features test-helperscargo test --locked --lib --features test-helpers— 1605 passed, 0 failedcargo fmt --all --checkcargo clippy --locked --all-targets --all-features -- -D warningsverifyjob (which runs at tag time onmain) will gate on these three files agreeing and oncargo auditfinding no RUSTSEC advisories, then build and publish the artifacts.Release plan
Once this PR is merged into
main, the release workflow runs by pushing the tagv3.7.0ontomain(or viagh workflow run release --ref main -f tag=v3.7.0). Theverifystep will re-check the three version files match and audit dependencies; thebuildmatrix will produce the 7 targets (Linux x86_64 / arm64, macOS arm64 / x86_64, Windows x86_64 / arm64, the Debian .debs for amd64 / arm64); thereleasejob will sign, build-provenance-attest, and publish. The published release will close the openpodman-version-watchcycle and surface in the apt pool on the next daily rebuild.Closes the open
validate podup on Podman Xissue that was revalidated for 6.0.2 earlier in the day.