Repository navigation
[BUG] Shared base images view lists unshared bases and counts disabled observed images #286
Description
Summary
The shared base images view (/base-images, backed by SharedBaseImageQueryService) lists every base image recorded for any observed image, including bases used by a single observed image, and it counts and lists disabled observed images (and stale image versions) as users of a base. The page is meant to show where a base-image update or vulnerability reaches several tracked things; with single-user and disabled entries it overstates sharing and hides the relevant entries among noise. "Shared" must mean used by at least two enabled observed images and/or runtime containers, with disabled images excluded.
Area
docs/areas/base-images.md(added in [Docs] Describe the current behavior by area #263), Shared base images; alsodocs/areas/web-ui.md(/base-images, dashboard tile, navigation).- Security area: None.
Current behavior
src/DockerUpdateGuard.Data/Queries/SharedBaseImageQueryService.cs:66-106joins allObservedImages(noIsEnabledfilter) with the relationships of their current version, groups them (:108-113) and returns every group; there is no minimum number of users.ObservedImageCount(:137-139) counts disabled images too.src/DockerUpdateGuard/UI/ApplicationViewService.cs:873-947(GetBaseImagesCoreAsync) then loads all relationships pointing at the grouped base versions (:885-893), regardless of whether the child is the current version of an observed image or whether that image is enabled. Parent image references (:935-938) and linked runtime containers (:920-925) are derived from these relationships, so a stale child version of an image that has since moved on, or a disabled image, still appears as a parent.- The same unfiltered list drives the dashboard tile Base Images (
ApplicationViewService.cs:1752,:1793) and whether the navigation shows Base Images (HasBaseImagesAsync,:2164-2172;src/DockerUpdateGuard/Components/Layout/NavMenu.razor.cs:137).
Reproduction:
- Register one observed image
myapp:1.0whose label names basedocker.io/library/nginx:1.27. Run its scan./base-imagesshowsnginx:1.27with 1 observed image, 0 containers — although nothing is shared. - Register a second image built on the same base, then disable it (today only possible through discovery, see [BUG] Observed images cannot be disabled or deleted, and discovery re-enables them #282: a discovered repository that disappears from the account).
/base-imagesstill shows 2 observed images and lists the disabled image's reference as a parent.
Expected behavior
Decision (product manager): "shared" requires at least two users; disabled images do not count.
Definitions (normative):
- Base entry: the grouping stays as today — a base with a digest by
registry/repository@digest(case-insensitive), a base without a digest byregistry/repository:tagtogether with its source reference. - Consuming observed images of a base entry: the distinct enabled observed images whose current image version has a recorded base-image relationship (any depth) to one of the entry's base image versions.
- Consuming runtime containers of a base entry: the distinct runtime containers (Docker instance + container id) in the current snapshot set (the latest runtime scan of each Docker instance, as the view uses today) whose image version is the current image version of one of the consuming observed images.
- Usage count = number of consuming observed images + number of consuming runtime containers.
- A base entry is shared when its usage count is at least 2. Examples: two enabled images on the same base → shared; one enabled image running in two containers → shared (1 + 2); one enabled image running in one container → shared (1 + 1); one enabled image, no container → not shared; one disabled image plus one enabled image, no container → not shared.
/base-imageslists only shared entries. Parent image references, the observed image count and the linked runtime containers of an entry are computed from consuming observed images and containers only (no disabled images, no non-current versions).- Ordering stays: running containers, then observed images (both descending), then reference.
- The dashboard tile Base Images counts shared entries and is labeled Shared base images; the navigation entry is shown when at least one shared entry exists. The page title and description say "Shared base images"; the empty state reads "No base image is used by two or more enabled images or containers yet."
- Unchanged: the base image chain on the observed/own image detail pages shows the full chain of that image, shared or not; the inventory telemetry gauge of deduplicated base images (
ApplicationTelemetry) keeps counting all recorded bases.
Acceptance criteria
- A base used by exactly one enabled observed image and no container is not returned by the shared base images query, not shown on
/base-imagesand not counted on the dashboard. - A base used by two enabled observed images is returned with observed image count 2.
- A base used by one enabled observed image running in at least one container is returned, with those containers listed.
- A disabled observed image neither counts towards the usage count nor appears among parent image references; a base used by one enabled and one disabled image (no containers) is not listed.
- Relationships of non-current image versions of an observed image do not contribute parents or containers.
- Unit tests, e.g.
SharedBaseImageQueryServiceGetBaseImagesSingleObservedImageIsExcluded,SharedBaseImageQueryServiceGetBaseImagesTwoEnabledImagesAreShared,SharedBaseImageQueryServiceGetBaseImagesDisabledImageIsNotCounted(src/Tests/DockerUpdateGuard.Data.Tests/SharedBaseImageQueryServiceTests.cs), andApplicationViewServiceGetBaseImagesOneImageWithContainerIsShared,ApplicationViewServiceGetBaseImagesIgnoresNonCurrentChildVersions,ApplicationViewServiceDashboardCountsOnlySharedBaseImages(src/Tests/DockerUpdateGuard.Tests/ApplicationViewServiceTests.cs); render test for the renamed page/tile. - The area document
docs/areas/base-images.mddescribes the new behavior and its Known gaps entry for this issue is removed;docs/areas/web-ui.mdis updated for the page, tile and navigation.
Implementation notes
- The usage count needs both observed images and containers, but containers live in the host project (
GetLatestContainerSnapshotsAsync,ApplicationViewService.cs:1083). Recommended:SharedBaseImageQueryService.GetBaseImagesAsyncfiltersobservedImage.IsEnabledand returns the consuming observed image ids and their current image version ids per entry (no minimum);ApplicationViewService.GetBaseImagesCoreAsynccomputes containers from those current version ids only and applies the>= 2rule.HasBaseImagesAsyncand the dashboard must use the same filtered result (extract one helper so the three callers cannot diverge). - Replace the broad relationship load at
ApplicationViewService.cs:885-893with one restricted to child versions that are current versions of enabled observed images. - Without a disable action in the UI the disabled case is reachable today only via discovery; [BUG] Observed images cannot be disabled or deleted, and discovery re-enables them #282 adds the user action and must use the same
IsEnabledfilter — whichever lands second keeps both consistent. - Splitting of one base into several entries because of different Docker Hub spellings is [BUG] Docker Hub image references are not normalized to one canonical form #275, not this issue.
- No configuration, migration or entity change.
Related
- [BUG] Docker Hub image references are not normalized to one canonical form #275 (canonical Docker Hub references — makes more bases recognizable as shared).
- [BUG] Observed images cannot be disabled or deleted, and discovery re-enables them #282 (user-controlled enabled state).
- [Docs] Describe the current behavior by area #263 (area documents).
Generated by Claude Code
Activity