Skip to content

[BUG] Shared base images view lists unshared bases and counts disabled observed images #286

Open

Description

@LarsLaskowski

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

Current behavior

  • src/DockerUpdateGuard.Data/Queries/SharedBaseImageQueryService.cs:66-106 joins all ObservedImages (no IsEnabled filter) 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:

  1. Register one observed image myapp:1.0 whose label names base docker.io/library/nginx:1.27. Run its scan. /base-images shows nginx:1.27 with 1 observed image, 0 containers — although nothing is shared.
  2. 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-images still 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 by registry/repository:tag together 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-images lists 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-images and 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), and ApplicationViewServiceGetBaseImagesOneImageWithContainerIsShared, ApplicationViewServiceGetBaseImagesIgnoresNonCurrentChildVersions, ApplicationViewServiceDashboardCountsOnlySharedBaseImages (src/Tests/DockerUpdateGuard.Tests/ApplicationViewServiceTests.cs); render test for the renamed page/tile.
  • The area document docs/areas/base-images.md describes the new behavior and its Known gaps entry for this issue is removed; docs/areas/web-ui.md is 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.GetBaseImagesAsync filters observedImage.IsEnabled and returns the consuming observed image ids and their current image version ids per entry (no minimum); ApplicationViewService.GetBaseImagesCoreAsync computes containers from those current version ids only and applies the >= 2 rule. HasBaseImagesAsync and 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-893 with 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 IsEnabled filter — 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


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions