Skip to content

Commit cfd3d35

Browse files
test(haos-e2e): trim cache-save race, compress GHCR qcow2, eval boot snapshot (homeassistant-ai#1428)
* ci(haos-e2e): drop redundant cache save in inaddon lane homeassistant-ai#1407 made both HAOS lanes share the same actions/cache key for the 12 GB qcow2. Both jobs start in parallel on the same PR push, both try to actions/cache/save@v5 with the same key, and the loser hits the cache service's reservation guard: Failed to save: Unable to reserve cache with key haos-image-XXXXXXXXXXXXXXXX, another job may be creating this cache. But not before the loser spends ~25s zstdmt-tar'ing the 12 GB qcow2 just to be told no. Remove the save step from the inaddon lane and let the external lane own the save — both lanes still restore from the shared key on the next run. Observed in run homeassistant-ai#283 (job 77593061250): 11:26:17 cache save started 11:26:42 Failed to save: ... another job may be creating this cache 11:26:43 Cache save failed. Wall-time win in steady state: ~25s on every cache-miss run for the inaddon lane. No change to cache contents or hit rates. * perf(haos-e2e): compress qcow2 in-format before oras push + add tag_suffix dispatch input The publish workflow currently pushes the post-bake qcow2 raw via ORAS (``haos-test-image.qcow2:application/octet-stream``), which is ~12 GB on disk and downloads at GHCR's ~32 MB/s single-stream cap → 6m 24s on every cache-miss e2e run (run homeassistant-ai#283 job 77593061250). The image is dominated by sparse-zeroed space from ``qemu-img resize 32G`` plus addon Docker layers; ``qemu-img convert -c -O qcow2`` re-packs the file with the format's native zlib compression and typically shrinks it ~3–5x. The result stays a standard qcow2 (same OCI artifact-type, same consumer code path). qemu decompresses sectors lazily during VM I/O when the e2e workflow boots it — no upfront xz-style decompress step on the pull side. Workflow changes ---------------- * New ``Compress qcow2 in-format (qemu-img convert -c)`` step before the artifact upload + GHCR push. Logs ``before / after / ratio`` so the size win is visible in the workflow output. * ``workflow_dispatch`` gains a ``tag_suffix`` input (default ``latest``) so perf-iteration branches can publish to a non-``latest`` moving tag (e.g. ``:17.3-haose2eefficiency``) without disturbing the master-served image other PRs pull from. Pull-side validation -------------------- This commit only changes how the image is *published*; the e2e workflows still pull ``:HAOS_VERSION-latest`` (which is still the old uncompressed image) until commit 3 in this series flips them to the new tag. The e2e test workflows are not in this workflow file's trigger ``paths`` and are not in this PR's commit yet either, so this push does not auto-trigger an e2e run that would pull a not-yet-existing image. The next step is a manual ``gh workflow run build-haos-test-image.yml --ref perf/haos-e2e-improvements -f tag_suffix=haose2eefficiency`` to publish the compressed image at ``:17.3-haose2eefficiency``. * test(haos-e2e): TEMP point both lanes at :17.3-haose2eefficiency (perf measurement) Temporary scaffolding for PR homeassistant-ai#1428 only. The publish workflow on this branch (dispatched with tag_suffix=haose2eefficiency) just published the in-format compressed qcow2 to ``ghcr.io/homeassistant-ai/haos-test-image:17.3-haose2eefficiency`` — size 5.1 GB down from 12 GB (2.3x ratio, see run 26361263298 step 11). Pointing both test lanes at the new tag lets this PR's e2e CI exercise the compressed pull path so we can measure GHCR-pull wall-time end-to-end. The shared actions/cache entry for the existing key was deleted before this push so both lanes miss the restore step and fall through to the GHCR fetch. Final cleanup commit before this PR is marked ready will revert both ``tag=`` lines back to ``-latest``. Do not merge in this state. * revert: restore :17.3-latest tag pointer in HAOS e2e workflows Reverts the temporary tag pointer added in commit 0132186 ("test(haos-e2e): TEMP point both lanes at :17.3-haose2eefficiency"). The compressed-qcow2 measurement is done — both lanes pulled :17.3-haose2eefficiency on commit 2b's run with GHCR pull dropping from 5m 39s (commit #1, uncompressed) to 47-58s (compressed). See runs 26361656754 + 26361656780. After merge, ``build-haos-test-image.yml``'s next master-push run republishes ``:17.3-latest`` through the compression step (added in commit ba97395), so the master-served image gets the same treatment and every future PR's GHCR-pull path benefits. The ``:17.3-haose2eefficiency`` GHCR tag itself can be deleted post-merge (or left as a perf-iteration artifact — it doesn't hurt anything). This commit's CI run is expected to be SLOWER than commit 2b's because it pulls the old uncompressed ``:17.3-latest`` — that's the validation that the master-served path still works end to end, not a regression. * test(haos-e2e): address PR review feedback (correctness + comment accuracy) Aggregated fixes from three PR review agents on PR homeassistant-ai#1428 (comment-analyzer, silent-failure-hunter, code-reviewer). Correctness / safety: * ``build-haos-test-image.yml`` compression step now runs ``qemu-img check`` on the compressed file before consumers (artifact upload + oras push) see it. Catches torn writes and refcount-table corruption that ``set -e`` alone misses — a ``qemu-img convert`` killed mid-write can produce an exit-0 truncated file via the shell wrapper. * Added a 1 GiB sanity floor on the compressed file size: the baked HAOS image is gigabytes; anything smaller is corruption ``qemu-img check`` didn't catch, and we fail before the moving tag points at bad bytes. * Added a ``TAG_SUFFIX`` regex guard (``^[A-Za-z0-9._-]+$``, the OCI tag character set) in the ``Push image to GHCR`` step. A typo like ``foo/bar``, ``:evil``, or a trailing space would otherwise surface as a confused ``oras`` error several lines deep. * Dropped ``-p`` from ``qemu-img convert``: the progress bar renders as one very long line of carriage-return-overwrites in non-TTY GHA logs without adding signal the ``before/after/ratio`` echoes don't already give. Comment accuracy: * ``build_image.py`` had a stale comment claiming ``qemu-img convert -c`` only shrinks ``~7 GB → ~7 GB`` and adds 9 min — that was from a smaller pre-homeassistant-ai#1379 addon set on a non-resized qcow2. Replaced with the current state: workflow step does the compress at publish time, measured at 12 GB → 5.1 GB (2.3x) in 6m 15s on publish run 26361263298 step 11. * ``build-haos-test-image.yml`` compression step comment: fixed "sectors" → "clusters" (qcow2 unit is the 64 KiB cluster); reframed the "sparse-zeroed space" line (sparse means *absent*, not zero-filled — the wire-bytes win comes from ORAS serialising sparse holes as actual zero bytes, which the dense compressed output skips); added the "writes re-allocate uncompressed" trade-off; cited the specific publish + e2e runs that produced the measured numbers. * ``haos-e2e-inaddon-tests.yml`` no-cache-save comment: added one paragraph making the byte-identical-qcow2 invariant explicit (both lanes consume the same publish-time bake; any PR-level overrides happen in-VM on the per-worker overlay, never on the cached base). * ``tag_suffix`` dispatch input description: previously said *what* the override is for, now also says *how* — the consumer edit (``tag=`` lines in the two e2e workflow files) is a separate manual step, and the allowed character set is named. * Consolidated the duplicate ``TAG_SUFFIX`` env-var explanation in the ``Push image to GHCR`` step down to a pointer back at the input description. No changes to the productive perf wins from this PR series (commits 8655bd8 + ba97395): cache-save still removed from inaddon lane, qcow2 still compressed before oras push, tag_suffix input still wired through. * revert: don't touch build_image.py in this PR (would force local build) Reverts the build_image.py stale-comment fix from 897dfd1. That change invalidated the e2e workflows' cache key (``git ls-tree`` over ``tests/haos_image_build/``) AND tripped the ``Detect PR-modified bake inputs`` gate that skips GHCR pull and forces a local image build on cache miss. Local build then filled the runner's 14 GB SSD before the test suite could run — the exact failure mode homeassistant-ai#1407 was originally designed to mitigate. External lane run 26362625939 hit: System.IO.IOException: No space left on device The stale comment in build_image.py:1400 about ``qemu-img convert -c`` is still wrong, but addressing it in the same PR as a cache-key-invalidating side effect isn't worth it. Will follow up separately if needed; the workflow step's own comment is now the source of truth for the compression numbers. * fix(haos-e2e): free disk space before local-build path + re-apply build_image.py comment fix Re-applies the build_image.py stale-comment fix that was reverted in 65cba9c, paired with a workflow-level fix for the disk-fill the reverted attempt exposed. What hit: * The original review-fix commit (897dfd1) modified ``tests/haos_image_build/build_image.py`` for a docstring update. * That path is in the cache-key hash (``git ls-tree -r HEAD tests/haos_image_build``) AND in the ``Detect PR-modified bake inputs`` gate's regex, so the change correctly invalidated the cache key AND triggered ``bake_inputs_changed=true``. * With GHCR fallback intentionally suppressed on bake-input changes (would otherwise serve master's stale image), the workflow fell through to the local-build path. * Local build then tripped ``No space left on device`` mid-bake (external lane run 26362625939) — the qcow2 download + decompress + boot + addon-install + ``cp`` peaks against the ~14 GB usable SSD. Two fixes, one commit: 1. **Workflow-level disk prune** before the local-build path on both e2e lanes (``haos-e2e-tests.yml`` and ``haos-e2e-inaddon-tests.yml``). Removes the unused ``/usr/share/dotnet``, ``/usr/share/swift``, ``/opt/ghc``, ``/usr/local/lib/android``, ``/usr/local/.ghcup`` trees (~20 GB combined on a standard ubuntu-22.04 runner) plus ``docker system prune`` plus ``apt-get clean``. Only runs on the conditions that lead into local build — ``cache-hit != 'true' && ghcr-pull.outcome != 'success'`` — so cache-hit and GHCR-pull paths are unaffected. Without this, ANY PR that legitimately changes bake inputs (the point of the cache-invalidation logic) would trip the same disk-fill that just took out PR homeassistant-ai#1428. 2. **Re-apply the stale-comment fix** in ``build_image.py`` with a shorter, less narrative version: drops the old incorrect numbers (``~7 GB → ~7 GB``, ``+9 min``) and replaces them with a pointer to the workflow step that does the actual compression (homeassistant-ai#1428, measured ``12 GB → 5.1 GB`` / ``2.3x``). The build_image.py change still invalidates the cache key, forcing this commit's CI runs through the local-build path again — which now has the disk prune to survive. * ci(haos-e2e): note that external lane is sole cache writer (sanity-check + nav aid) Adds a three-line comment on the external lane's ``Save image to cache`` step cross-referencing the inaddon lane's no-save block. Together with the matching comment in haos-e2e-inaddon-tests.yml (also homeassistant-ai#1428), a future maintainer reading either side now sees the full design: shared cache key (homeassistant-ai#1407), single writer (this PR). Also serves as the cache-hit-path sanity-check for homeassistant-ai#1428: this commit doesn't touch any bake-input path, so cache key ``b11cff145b55bc4b``-or-equivalent stays valid and the e2e workflows will restore from cache rather than re-running the local build that the previous commit (2772942) exercised. CI should land at ~6-7 min per lane vs the local-build path's ~11-12 min, confirming the common cache-hit path is unaffected by this PR. --------- Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>
1 parent 31eb00e commit cfd3d35

4 files changed

Lines changed: 159 additions & 18 deletions

File tree

.github/workflows/build-haos-test-image.yml

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ name: Build HAOS Test Image (Publish to GHCR)
2121

2222
on:
2323
workflow_dispatch:
24+
inputs:
25+
tag_suffix:
26+
description: >-
27+
Moving-tag suffix (default: ``latest``). Override to publish
28+
to ``:HAOS_VERSION-<suffix>`` instead of ``:HAOS_VERSION-latest``
29+
— used by perf-iteration branches that need a non-latest tag
30+
they can point the e2e test workflows at without touching the
31+
master-served image. Pointing the e2e lanes at the override is
32+
a SEPARATE manual edit on the branch: change the ``tag=`` line
33+
in the ``Try pulling from GHCR`` step of both
34+
``haos-e2e-tests.yml`` and ``haos-e2e-inaddon-tests.yml``.
35+
Allowed character set is ``[A-Za-z0-9._-]+`` (OCI tag rules);
36+
the ``Push image to GHCR`` step validates and fails fast on
37+
anything else.
38+
type: string
39+
required: false
40+
default: 'latest'
2441
push:
2542
branches: [master]
2643
paths:
@@ -146,9 +163,68 @@ jobs:
146163
if-no-files-found: warn
147164
retention-days: 14
148165

166+
- name: Compress qcow2 in-format (qemu-img convert -c)
167+
# Re-pack the qcow2 with qcow2-native zlib *cluster* compression
168+
# before upload. The post-bake file is ~12 GB *apparent* size on
169+
# disk — sparse, with ~5 GB actually allocated; the rest is
170+
# un-allocated holes from ``qemu-img resize 32G`` in
171+
# ``fetch_haos_qcow2``. ORAS pushes/pulls
172+
# ``application/octet-stream`` as a raw byte stream, so the
173+
# sparse holes get serialised over the wire as actual zero bytes
174+
# — the *apparent* size, not the allocated size, is what GHCR
175+
# pull pays for.
176+
#
177+
# ``qemu-img convert -c -O qcow2`` re-encodes the data densely
178+
# at the cluster level (default 64 KiB). The output stays a
179+
# standard qcow2 (same OCI media-type, same consumer code path);
180+
# QEMU decompresses clusters on-demand during VM I/O when the
181+
# e2e workflow boots, so no upfront decompress step on the pull
182+
# side. Trade-off worth knowing about: qcow2 cluster compression
183+
# is read-only — the first write to any compressed cluster makes
184+
# QEMU re-allocate it uncompressed in place. The e2e workflow's
185+
# boot is read-heavy (HA starts, recorder DB rebuilds, addon
186+
# containers come up), so write-side re-allocation cost is small
187+
# relative to the pull-time saved, but it's why this convert
188+
# pass lives at publish time and not at every consumer.
189+
#
190+
# Measured at publish run 26361263298 step 11:
191+
# 12 GB → 5.1 GB (2.3x ratio), convert pass took 6m 15s.
192+
# First e2e run that consumed the result (runs 26361656780 +
193+
# 26361656754) saw GHCR pull drop from ~6 min (uncompressed
194+
# baseline) to 47–58 s.
195+
#
196+
# Cost: the convert pass adds ~6 min to the publish runner —
197+
# paid only on master push / weekly cron / manual dispatch,
198+
# not on PR runs.
199+
run: |
200+
orig_bytes=$(stat -c%s haos-test-image.qcow2)
201+
qemu-img convert -c -O qcow2 \
202+
haos-test-image.qcow2 haos-test-image.qcow2.compressed
203+
mv haos-test-image.qcow2.compressed haos-test-image.qcow2
204+
# Integrity gate before consumers see the file: catch torn
205+
# writes (e.g. ``qemu-img convert`` killed mid-run after the
206+
# output file already exists, exit-0 from the shell wrapper
207+
# but file is truncated) and broken refcount tables.
208+
# ``qemu-img check`` reads metadata + L1/L2 tables only — it
209+
# does not stream the full image — so this is fast.
210+
qemu-img check haos-test-image.qcow2
211+
new_bytes=$(stat -c%s haos-test-image.qcow2)
212+
# Sanity floor: the baked HAOS image is gigabytes; anything
213+
# under 1 GiB after compress is corruption ``qemu-img check``
214+
# didn't catch. Fail before the moving tag points at bad bytes.
215+
if [ "$new_bytes" -lt 1073741824 ]; then
216+
echo "::error::compressed qcow2 suspiciously small: ${new_bytes} bytes"
217+
exit 1
218+
fi
219+
ratio=$(awk -v o="$orig_bytes" -v n="$new_bytes" 'BEGIN{printf "%.1f", o/n}')
220+
echo "qcow2 size before: $(numfmt --to=iec "$orig_bytes")"
221+
echo "qcow2 size after : $(numfmt --to=iec "$new_bytes")"
222+
echo "compression ratio: ${ratio}x"
223+
149224
- name: Upload built image as workflow artifact
150225
# Always upload — even on PRs that don't push to GHCR — so reviewers
151-
# can pull the artifact and verify locally.
226+
# can pull the artifact and verify locally. (Now also smaller thanks
227+
# to the in-format compression step above.)
152228
uses: actions/upload-artifact@v7
153229
with:
154230
name: haos-test-image-${{ steps.haos_version.outputs.version }}-${{ github.sha }}
@@ -166,13 +242,27 @@ jobs:
166242
env:
167243
HAOS_VERSION: ${{ steps.haos_version.outputs.version }}
168244
SHORT_SHA: ${{ github.sha }}
245+
# See the ``tag_suffix`` workflow_dispatch input at the top of
246+
# this file for the override semantics and required edits on
247+
# the consumer side. The ``|| 'latest'`` fallback covers the
248+
# ``push:`` / ``schedule:`` triggers where
249+
# ``github.event.inputs`` is null.
250+
TAG_SUFFIX: ${{ github.event.inputs.tag_suffix || 'latest' }}
169251
run: |
252+
# Validate the suffix against the OCI tag character set before
253+
# building any tag strings — a typo like ``foo/bar``,
254+
# ``:evil``, or a trailing space would otherwise surface as a
255+
# confusing ``oras push`` error ten lines later.
256+
if [[ ! "$TAG_SUFFIX" =~ ^[A-Za-z0-9._-]+$ ]]; then
257+
echo "::error::TAG_SUFFIX must match [A-Za-z0-9._-]+ (OCI tag chars), got: '$TAG_SUFFIX'"
258+
exit 1
259+
fi
170260
short_sha="${SHORT_SHA:0:7}"
171261
versioned="${IMAGE_REPO}:${HAOS_VERSION}-${short_sha}"
172-
moving="${IMAGE_REPO}:${HAOS_VERSION}-latest"
262+
moving="${IMAGE_REPO}:${HAOS_VERSION}-${TAG_SUFFIX}"
173263
oras push "$versioned" \
174264
--artifact-type application/vnd.homeassistant-ai.haos-test-image.v1 \
175265
haos-test-image.qcow2:application/octet-stream
176-
oras tag "$versioned" "${HAOS_VERSION}-latest"
266+
oras tag "$versioned" "${HAOS_VERSION}-${TAG_SUFFIX}"
177267
echo "Pushed $versioned"
178268
echo "Pushed $moving"

.github/workflows/haos-e2e-inaddon-tests.yml

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,20 @@ jobs:
142142
(cd /tmp/haos-pull && /tmp/oras pull "$tag")
143143
mv /tmp/haos-pull/haos-test-image.qcow2 /tmp/haos-test-image.qcow2
144144
145+
- name: Free disk space for local build (cache miss + GHCR miss only)
146+
# See ``haos-e2e-tests.yml`` for the full rationale — same prune
147+
# set, same condition. PRs touching ``tests/haos_image_build/``
148+
# force the local-build path on both lanes and would otherwise
149+
# trip "No space left on device" mid-bake on the 14 GB SSD.
150+
if: steps.restore-cache.outputs.cache-hit != 'true' && steps.ghcr-pull.outcome != 'success'
151+
run: |
152+
df -h /
153+
sudo rm -rf /usr/share/dotnet /usr/share/swift /opt/ghc \
154+
/usr/local/lib/android /usr/local/.ghcup
155+
sudo apt-get clean
156+
docker system prune -af --volumes || true
157+
df -h /
158+
145159
- name: Install build-script Python deps (cache miss + GHCR miss)
146160
if: steps.restore-cache.outputs.cache-hit != 'true' && steps.ghcr-pull.outcome != 'success'
147161
run: pip install -r tests/haos_image_build/requirements.txt
@@ -152,16 +166,26 @@ jobs:
152166
python3 tests/haos_image_build/build_image.py --verbose \
153167
--output /tmp/haos-test-image.qcow2
154168
155-
- name: Save image to cache (cache miss only)
156-
# Save whenever the runtime cache missed — covers both the
157-
# local-build path AND a successful GHCR pull. Without saving on
158-
# the GHCR-served branch, the actions/cache entry stays empty
159-
# forever and every future run pays the GHCR pull cost again.
160-
if: steps.restore-cache.outputs.cache-hit != 'true'
161-
uses: actions/cache/save@v5
162-
with:
163-
path: /tmp/haos-test-image.qcow2
164-
key: ${{ steps.key.outputs.cache-key }}
169+
# No cache-save step here on purpose. The external lane
170+
# (haos-e2e-tests.yml) computes the same cache key (#1407) and
171+
# always tries to save the same qcow2; both lanes start in
172+
# parallel on the same PR push, so whichever lost the save race
173+
# was burning ~25s on a tar+zstd-write of a 12 GB qcow2 that
174+
# the actions/cache service then refused with
175+
# "another job may be creating this cache." Letting only the
176+
# external lane save eliminates the wasted work without
177+
# changing what gets cached — both lanes still restore from the
178+
# shared key on the next run.
179+
#
180+
# The shared-key bet is safe because both lanes consume the SAME
181+
# byte-identical qcow2: the cache key hashes the bake-input
182+
# paths only, the qcow2 is built once at publish time and
183+
# pulled identically by both lanes, and PR-level addon/source
184+
# overrides happen *in-VM at test time* (see
185+
# ``refresh_dev_addon_source_in_qcow2`` /
186+
# ``refresh_recorder_in_qcow2`` in conftest.py) on each
187+
# worker's per-worker overlay — never on the base qcow2 that
188+
# gets cached.
165189

166190
- name: Install uv
167191
uses: astral-sh/setup-uv@v7

.github/workflows/haos-e2e-tests.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,29 @@ jobs:
157157
# files into a subdirectory of pwd doesn't silently no-op the mv.
158158
mv /tmp/haos-pull/haos-test-image.qcow2 /tmp/haos-test-image.qcow2
159159
160+
- name: Free disk space for local build (cache miss + GHCR miss only)
161+
# GitHub-hosted Linux runners ship with ~30 GB of pre-installed
162+
# tooling (dotnet, swift, ghc, android sdk) that the HAOS bake
163+
# doesn't use, and only ~14 GB of usable SSD. The local-build
164+
# path's qcow2 download (~1.5 GB xz) + decompress (~7 GB qcow2)
165+
# + boot+addon-install (qcow2 grows to ~12 GB apparent / ~5 GB
166+
# allocated) + ``cp`` to ``/tmp/haos-test-image.qcow2`` (another
167+
# ~5 GB if reflink fails) peaks at the edge of available disk.
168+
# Without this prune, PRs that touch ``tests/haos_image_build/``
169+
# (which forces the local-build path via the ``Detect PR-modified
170+
# bake inputs`` gate above) trip "No space left on device"
171+
# mid-bake. The directories below are well-known unused on
172+
# standard runners; we deliberately keep ``/opt/hostedtoolcache``
173+
# because Python from there is still on PATH for build_image.py.
174+
if: steps.restore-cache.outputs.cache-hit != 'true' && steps.ghcr-pull.outcome != 'success'
175+
run: |
176+
df -h /
177+
sudo rm -rf /usr/share/dotnet /usr/share/swift /opt/ghc \
178+
/usr/local/lib/android /usr/local/.ghcup
179+
sudo apt-get clean
180+
docker system prune -af --volumes || true
181+
df -h /
182+
160183
- name: Install build-script Python deps (cache miss + GHCR miss)
161184
if: steps.restore-cache.outputs.cache-hit != 'true' && steps.ghcr-pull.outcome != 'success'
162185
run: pip install -r tests/haos_image_build/requirements.txt
@@ -172,6 +195,10 @@ jobs:
172195
# local-build path AND a successful GHCR pull. Without saving on
173196
# the GHCR-served branch, the actions/cache entry stays empty
174197
# forever and every future run pays the GHCR pull cost again.
198+
# This is the sole writer for the shared cache key (#1407 +
199+
# #1428) — the inaddon lane intentionally doesn't save, see the
200+
# block comment at the same position in
201+
# ``haos-e2e-inaddon-tests.yml``.
175202
if: steps.restore-cache.outputs.cache-hit != 'true'
176203
uses: actions/cache/save@v5
177204
with:

tests/haos_image_build/build_image.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,11 +1397,11 @@ def build(work_dir: Path, output: Path) -> None:
13971397
# HAOS is shut down — safe to open the qcow2 with libguestfs and bake
13981398
# the testcontainer's seed state into /config/ for the e2e suite.
13991399
bake_test_state(qcow2)
1400-
# Skip post-build compression for now: empirically qemu-img convert -c
1401-
# only shrinks ~7 GB → ~7 GB (Docker layer contents don't compress well
1402-
# with zlib) but adds 9 min, and xz -9 -T0 adds >25 min. Just sparse-copy
1403-
# the raw qcow2. Image-size optimization (zstd? strip unused docker
1404-
# layers? slim addons?) is tracked separately as a follow-up.
1400+
# Output uncompressed: nothing downstream of this script on the
1401+
# developer iteration path benefits from a smaller file, and the
1402+
# convert pass adds ~6 min. GHCR-served image is compressed at
1403+
# publish time by build-haos-test-image.yml's ``Compress qcow2
1404+
# in-format`` step (#1428, measured 12 GB → 5.1 GB / 2.3x).
14051405
LOG.info("Copying qcow2 to %s (uncompressed)", output)
14061406
output.parent.mkdir(parents=True, exist_ok=True)
14071407
_run(["cp", "--reflink=auto", str(qcow2), str(output)])

0 commit comments

Comments
 (0)