Skip to content

Overlay work#738

Open
yktyagi wants to merge 13 commits into
mainfrom
overlay-work
Open

Overlay work#738
yktyagi wants to merge 13 commits into
mainfrom
overlay-work

Conversation

@yktyagi

@yktyagi yktyagi commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merge Checklist

All boxes should be checked before merging the PR

  • The changes in the PR have been built and tested
  • Documentation has been updated to reflect the changes (or no doc update needed)
  • Ready to merge

Description

Builds out overlay mode end-to-end — layering packages onto an existing baseline image instead of building from scratch — and hardens the supporting resolve/preflight/install/SBOM pipeline. Stacks on the merged foundation PRs (#714, #724, #728, #729, #731, #732, #733, #734).

Resolution & operation modes

  • Overlay templates resolve only user-declared packages (plus their transitive dependencies); the create-mode default OS package set is no longer re-seeded into an overlay, which previously dragged in bootloader packages the frozen baseline could not satisfy.
  • New opt-in additive-and-upgrade package-operation mode: permits upgrading a baseline package to a newer repo version while downgrades and removals stay blocked. The upgrade set is bounded and grown to a fixpoint (requested-and-present packages + transitive deps whose baseline copy fails a version pin); all other present packages keep their baseline version.
  • Kernel images remain immutable even in upgrade mode (boot regen refreshes only the initramfs, not bootloader menu entries); rpm Obsoletes: are classified as removals so rpm -U cannot silently erase a baseline package.
  • To-install packages are logged with [requested] vs [dependency] provenance so the transitive closure is explainable in the SBOM/compare.

Preflight gating (fail up-front, avoid false positives)

  • Metadata-based conflict simulation: declared Conflicts:/Breaks: against a present baseline package are caught before install rather than surfacing as an opaque dpkg/rpm abort. Undeclared file-level collisions remain a known gap (still fail loudly at install).
  • Unsatisfiable version-pinned dependencies are gated up front; only the present-but-wrong-version case is flagged (absent/virtual/alternative-satisfied edges are not, to avoid false alarms).
  • Same-batch lockstep upgrades (e.g. vim-tiny/vim-runtime) are evaluated against the post-install version, so an in-batch upgrade past a break range is not falsely flagged; install passes --auto-deconfigure.

Boot & SBOM

  • initramfs regeneration is gated on whether boot-relevant content (kernel module, firmware, initramfs hook) was actually added — avoids failing pure-userspace overlays on read-only-ESP baselines — and fails safe (regenerates) on any uncertainty.
  • Overlay SBOM now embeds the full merged inventory under the baseline's own filename (replacing, not shadowing, the inherited SBOM), so a subsequent compare no longer reports the whole baseline as removed. Falls back to a delta write with a warning if the baseline SBOM is missing/unparseable.
  • compare --mode=spdx classifies same-name version bumps as upgrades, and reads the embedded SBOM from large ext4 RAW images via a read-only loop-device + debugfs reader (no full-partition copy).

Misc

  • Per-stage overlay build-timing table (the create-mode all-zero table is suppressed for overlay builds).
  • deb cache-hit path recovers the package version from the filename, fixing spurious remove+add pairs in compare diffs.

Any Newly Introduced Dependencies

None. go.mod/go.sum are unchanged — no new third-party dependencies. New functionality relies on existing host tooling already used by the project (dpkg, rpm, debugfs, loop devices).

How Has This Been Tested?

  • Unit tests added/extended across every changed area (go test ./internal/... ./cmd/...): overlay resolve, preflight, install, boot update, depcheck, session, SBOM; config merge/baseline; SBOM filesystem + loop-device readers; compare renderers; debutils download. New/updated test files include resolve_test.go, preflight_test.go, depcheck_test.go, bootupdate_test.go, sbom_loopdev_test.go, sbom_fsread_test.go, merge_test.go, and others.
  • Regression tests cover each fix specifically: arch-qualified/already-present resolution, lockstep breaks/upgrades, unsatisfiable pins, conditional initramfs regen, merged-SBOM replacement, and version-bump upgrade classification.
  • Repo coverage gate ratcheted to 69.1%.
  • Reproduce: go build ./internal/... ./cmd/... then go test ./internal/... ./cmd/....

…mode

LoadAndMergeTemplate merges the create-mode default OS template into every
user template, and mergeSystemConfig unions the default package list with the
user's. For an overlay template that requested only "tree", this re-seeded the
entire from-scratch base package set (ubuntu-minimal, systemd-boot, dracut-core,
systemd, cryptsetup-bin, efibootmgr, ...) into the additive overlay set.

Overlay mode layers packages onto an already-complete baseline image, so those
defaults are already present. Re-seeding them dragged in bootloader packages
(systemd-boot) whose strict version pin (libsystemd-shared (= 255.4-1ubuntu8.16))
the frozen baseline (255.4-1ubuntu8.12) cannot satisfy under additive-only
install, blocking the build.

Restrict the merged package set to exactly the user-declared packages when the
template is in overlay mode. Create mode is unchanged. Adds a regression test
covering both modes.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
Copilot AI review requested due to automatic review settings July 9, 2026 07:48
@yktyagi yktyagi requested a review from a team as a code owner July 9, 2026 07:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands “overlay mode” capabilities and diagnostics across the build pipeline: it adds overlay-specific stage timing reporting, introduces an additive-and-upgrade overlay policy with stronger preflight gating (conflicts, unsatisfied pinned deps, rpm obsoletes, kernel immutability), improves SBOM correctness/merge behavior, and enhances SPDX compare/inspection to work directly from embedded SBOMs in images (including large ext4 roots).

Changes:

  • Add overlay stage timings and render them via a shared timing-table helper.
  • Implement additive-and-upgrade overlay policy with bounded upgrade scope + enhanced preflight checks (conflicts, version pins, rpm obsoletes, kernel-image immutability) and install-mode switching (rpm -U, dpkg --auto-deconfigure).
  • Improve SBOM handling: merge overlay contributions into baseline SBOM, preserve versions from cached .deb filenames, and enhance inspection/compare to classify upgrades and to extract SBOMs from images efficiently.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/utils/display/display.go Introduces reusable timing table rendering and exported TimingRow.
internal/provider/ubuntu/ubuntu.go Prints overlay build timing table on successful overlay builds; avoids timing-table misuse.
internal/ospackage/debutils/download.go Preserves package version when reconstructing SBOM inputs from cached .deb files.
internal/ospackage/debutils/download_test.go Adds regression test ensuring cached .deb filename parsing recovers versions.
internal/image/overlay/session.go Adds per-stage overlay timing recording and SBOM merge embedding into baseline.
internal/image/overlay/session_test.go Updates seams + tests overlay stage ordering and failure cutoffs for timings.
internal/image/overlay/sbom_test.go Adds tests for baseline SBOM discovery/picking behavior.
internal/image/overlay/resolve.go Adds upgrade-capable overlay resolution path with bounded upgrade scope and provenance logging.
internal/image/overlay/resolve_test.go Adds coverage for upgrade mode behavior and upgrade-scope boundaries.
internal/image/overlay/preflight.go Extends overlay preflight with upgrade gating, unsatisfied pinned deps, kernel-image immutability, conflicts/obsoletes metadata checks.
internal/image/overlay/preflight_test.go Updates/extends preflight tests for new policy gates and kernel/obsoletes behaviors.
internal/image/overlay/install.go Propagates “upgrade” intent to installer; adds dpkg --auto-deconfigure; switches rpm to -U when upgrades exist.
internal/image/overlay/install_test.go Adds tests for upgrade propagation and --auto-deconfigure usage; updates command expectations.
internal/image/overlay/depcheck.go Adds metadata readers/parsers for deb/rpm depends/conflicts/obsoletes and shared constraint parsing.
internal/image/overlay/depcheck_test.go Adds unit tests for parsing + end-to-end preflight conflict/dep-pin regressions.
internal/image/overlay/bootupdate.go Skips initramfs regeneration for purely userspace overlays via artifact file-list inspection; improves error surfacing.
internal/image/overlay/bootupdate_test.go Adds tests for boot-relevance gate, parsing, and fail-safe behavior.
internal/image/imageinspect/sbom.go Adds “upgraded” classification to SPDX diffs; adds clearer failure notes during SBOM filesystem inspection.
internal/image/imageinspect/sbom_test.go Updates compare expectations and adds direct diff classification coverage.
internal/image/imageinspect/sbom_loopdev.go Adds loop-device + debugfs SBOM extraction strategy for large ext roots (root-only, read-only).
internal/image/imageinspect/sbom_loopdev_test.go Adds tests for loop-device SBOM reader and partition node selection.
internal/image/imageinspect/sbom_fsread_test.go Adds test that in-place go-diskfs extraction works without partition copying.
internal/image/imageinspect/renderer_text.go Renders upgraded packages section in SPDX compare text output.
internal/image/imageinspect/renderer_text_test.go Updates renderer tests to include upgraded package output.
internal/image/imageinspect/imageinspect.go Reorders SBOM extraction strategies (filesystem → loopdev → raw copy) and threads raw path for loop attach.
internal/config/schema/os-image-template.schema.json Extends overlay policy schema to allow additive-and-upgrade.
internal/config/merge.go Prevents overlay mode from inheriting create-mode default package set.
internal/config/merge_test.go Adds regression tests for overlay-vs-create package merge behavior.
internal/config/manifest/manifest.go Factors SPDX package conversion and adds baseline+overlay SBOM merge writer.
internal/config/manifest/manifest_test.go Adds tests for SBOM merge preserving baseline header/metadata and handling malformed baselines.
internal/config/config.go Adds overlay package-operation constant and derives AllowUpgrade gate from validation.
internal/config/baseline_test.go Extends baseline validation tests to cover additive-and-upgrade and derived AllowUpgrade behavior.
cmd/image-composer-tool/compare_cmd.go Allows SPDX compare inputs to be images or JSON; extracts embedded SBOMs from images before comparing.
cmd/image-composer-tool/compare_cmd_test.go Adds tests for image-based SPDX compare extraction and JSON detection.
cmd/image-composer-tool/build.go Skips create-mode timing table for overlay builds (overlay prints its own timings).

Comment thread internal/image/overlay/session.go
Comment thread cmd/image-composer-tool/compare_cmd.go
@yktyagi yktyagi force-pushed the overlay-work branch 2 times, most recently from 71fe45e to 2ced215 Compare July 9, 2026 08:06
@yktyagi yktyagi requested a review from Copilot July 9, 2026 08:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 4 comments.

Comment thread internal/image/overlay/resolve.go Outdated
Comment thread internal/config/manifest/manifest.go Outdated
Comment thread internal/image/imageinspect/imageinspect.go Outdated
Comment thread internal/config/schema/os-image-template.schema.json
@yktyagi yktyagi requested a review from a team as a code owner July 9, 2026 08:26
@yktyagi yktyagi requested a review from Copilot July 9, 2026 08:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

internal/image/overlay/session.go:1

  • Mutating the package-level manifest.DefaultSPDXFile introduces global shared state that can leak across builds/tests in the same process and can cause data races if multiple builds run concurrently. Consider avoiding global mutation by passing the intended SBOM filename explicitly to the embed/sidecar-copy helpers (or restoring the prior value via defer after the overlay postprocess completes).

Comment thread internal/image/overlay/depcheck.go
Comment thread internal/image/imageinspect/imageinspect.go Outdated
…light

An additive-only overlay cannot upgrade a baseline package, so a to-install
artifact that pins an exact version of a package already present at a different
version (e.g. systemd-boot requiring libsystemd-shared (= 255.4-1ubuntu8.16)
against a baseline's 255.4-1ubuntu8.12) fails mid-dpkg with an opaque error.

Read each plan.ToInstall artifact's declared dependencies from its on-disk
control metadata (dpkg -f for deb, rpm -qpR for rpm) and classify edges whose
version constraint cannot be satisfied by the post-install package set. Only
the present-but-wrong-version case is flagged; absent (possibly virtual/Provides)
dependencies and edges rescued by a satisfied alternative or co-install are not,
avoiding false positives. The reader is a best-effort seam: an unreadable
artifact logs a warning and does not fail the preflight.

This converts a mid-install crash into a clear up-front block naming the
offending dependency and versions.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 1 comment.

Comment thread internal/image/imageinspect/sbom.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Comment thread internal/image/overlay/resolve.go
Comment thread internal/config/manifest/manifest.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Comment thread internal/image/overlay/resolve.go
Comment thread internal/image/overlay/resolve.go Outdated
Introduce an opt-in "additive-and-upgrade" packageOperation that permits
upgrading a package already installed in the baseline to a newer version,
while downgrades and removals remain blocked. The new mode is derived into
the internal OverlayPolicy.AllowUpgrade gate during config validation and
exposed through the JSON schema enum.

Resolver: in upgrade mode, requested-and-present packages are re-resolved so
their repository candidate can be compared against the baseline. A bounded
upgrade set (upgradeEligibleNames) is grown to a fixpoint covering
requested-and-present packages and required transitive dependencies whose
baseline copy fails a versioned pin; every other present package keeps its
baseline version.

Preflight: the AllowUpgrade gate now permits classified upgrades; the
unsatisfied-dependency check evaluates against the post-install set so a pin
satisfied by an upgraded package is not flagged. Bootable kernel images are
made immutable (kernel-immutable rule) since boot regeneration refreshes only
the initramfs, not the bootloader menu entries. rpm Obsoletes: declarations
are classified as removals so rpm -U cannot silently erase a baseline package
past the AllowRemoval gate.

Install: the rpm backend switches from `rpm -i` to `rpm -U` when the approved
plan contains upgrades; the deb backend is unchanged (dpkg -i upgrades in
place). The overlay SBOM records contributed packages (added or upgraded).

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
The overlay boot-regen stage previously ran the initramfs generator
unconditionally after any package install. On a systemd-boot/BLS baseline
that is not just wasted work for a pure-userspace overlay: update-initramfs
chains a loader hook that copies the kernel into the ESP, which overlay
mode mounts read-only, so the build fails even though nothing about the
boot-time initramfs changed.

Gate regeneration on whether the overlay actually added boot-relevant
content — a kernel module, firmware, or an initramfs-generator hook — by
reading each installed artifact's file manifest on the host (dpkg -c /
rpm -qlp). Skip regeneration only when every artifact was read and none
carried such content; fail safe (regenerate) on a nil plan, a missing
download dir, an unlocatable artifact, or an unreadable manifest, so an
initramfs that genuinely needs rebuilding is never left stale.

Also surface the generator's captured output in the boot-regen error,
mirroring the deb/rpm installer backends: on its own the wrapped error is
only "exit status 1" and the actual failing-hook diagnostic was otherwise
lost to debug logging.

RegenerateBoot now takes the ResolutionPlan (for the artifact download
dir the gate reads from); the call site and tests are updated.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Comment thread internal/image/overlay/resolve.go
Comment thread internal/image/overlay/resolve.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 1 comment.

Comment thread internal/image/overlay/session.go Outdated
yktyagi and others added 9 commits July 9, 2026 20:28
The overlay pipeline does not run through the create-mode maker/chroot
stages that populate the template's build timers, so the build-timing
table printed on completion was all zeros for an overlay build.

Instrument the overlay Builder to record each stage's wall-clock duration
in execution order (acquire/mount, inspect, resolve, preflight, install,
boot regen, resize, SBOM, emit) via a timeStage helper with a clock seam,
and render them as a dedicated "Overlay Build Timings" table on a clean
build. Extract the table-drawing logic into a generic display.PrintTimingTable
that both the create-mode and overlay paths share. Suppress the all-zero
create-mode table for overlay builds so only the accurate table is shown.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
compare --mode=spdx failed on large ext4 Ubuntu RAW images because the
diskfs-based filesystem reader could not extract the embedded SBOM. Add a
loop-device + debugfs reader that mounts the root partition read-only and
pulls /usr/share/sbom/* in place, with no full-partition copy, so large
images no longer exhaust temp space.

Factor CompareSPDXData out of CompareSPDXFiles so the compare command can
diff SBOM bytes read straight from each image, and surface per-partition
diagnostic notes when SBOM discovery skips a candidate.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
An overlay image is built on a copy of the baseline and inherits its full
embedded SBOM, but overlay mode wrote a delta-only spdx_manifest.json listing
just the added packages. It also used a different filename than the baseline's
timestamped manifest, so both coexisted at /usr/share/sbom and the delta
shadowed the real inventory. A compare of that image then reported the entire
baseline as removed (1710 -> 30).

Read the inherited baseline SBOM from the mounted root during postprocess and
merge the overlay's added/upgraded packages into it, writing under the
baseline's own filename so the merged document replaces (not shadows) the
baseline SBOM. Packages are keyed by name: a matching name is upgraded in
place, a new name is appended; the baseline document header is preserved.
Falls back to a delta write (with a warning) when no baseline SBOM is present
or it cannot be parsed, so the build never fails on SBOM generation.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
When every required .deb is already cached, DownloadPackagesComplete returned
package infos built from filenames via a helper that kept only the name and
discarded the version. That empty version flowed into the overlay SBOM's
versionInfo, so an upgraded package (e.g. linux-libc-dev) appeared in a
name|version|url compare diff as a removed-and-re-added pair instead of an
upgrade.

Build the cached PackageInfo with parseDebFileName, which recovers both name
and version from the name_version_arch.deb filename, and drop the now-unused
name-only helper.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
The overlay install set is the transitive closure of the requested packages
minus what the baseline already satisfies, so it is routinely larger than the
template list. Without a breakdown the extra dependency packages look
unexplained when they later surface in the image SBOM or a compare diff.

After resolution, log each to-be-installed package tagged [requested] or
[dependency] with its version, followed by a summary count of each.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
Reconcile a same-name add+remove pair in the SPDX package diff into a
single upgrade line ("name: fromVer -> toVer") instead of reporting an
unrelated deletion plus addition. A package whose full identity key
(name|version|downloadLocation) changes while its name stays the same is
now surfaced under a dedicated "Upgraded packages" section in both the
text and JSON renderers. Empty baseline versions render as "unknown".

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
The simulateOverlayInstall seam was a no-op, so a to-install package
declaring Conflicts:/Breaks: against a present baseline package was not
caught by the preflight gate: conflictPolicy=fail had no conflict source
to act on, and the clash only surfaced as an opaque dpkg -i / rpm -i
abort at unpack time.

Implement the seam as a metadata-based simulation: read each to-install
artifact's declared conflicts on the host (dpkg -f Conflicts/Breaks, rpm
-qp --conflicts) and classify each one that names a package present in
the baseline (at a version the conflict's range covers) as an
ActionConflict, gated by conflictPolicy. It runs without a chroot or a
real package-manager invocation, so it is deterministic and always
executes. Undeclared file-level collisions are not covered here and
still fail loudly at install time; a future live simulator can augment
this.

Widen the seam signature to (info, baseline, plan) since a metadata
simulation needs the baseline installed set to know what is present.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>
…and preflight

Two related overlay-install correctness fixes for lockstep-versioned package
sets (e.g. vim-runtime/vim-tiny), where a package Breaks: an older version of
another that the overlay upgrades in the same batch:

- install: pass --auto-deconfigure to dpkg -i so a transient Breaks: satisfied
  later in the same batch is deconfigured and reconfigured rather than aborting
  at unpack time, mirroring apt's behavior.

- preflight: evaluate a versioned Conflicts:/Breaks: against the POST-INSTALL
  version of the target, not the baseline version. When the overlay upgrades the
  target past the break range in the same batch there is no real conflict, so it
  must not be flagged. Extract a shared postInstallVersionIndex helper reused by
  classifyConflicts and classifyUnsatisfiedDeps.

Adds regression coverage for both paths.

Signed-off-by: Tyagi, Yogesh <yogesh.tyagi@intel.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated no new comments.

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.

2 participants