From 006e65bfb6f5191b184f47a82b83773c2e1f4ead Mon Sep 17 00:00:00 2001 From: Charlie Drage Date: Mon, 8 Apr 2024 17:47:01 -0400 Subject: [PATCH] chore: add isManifest function to determine a manifest ### What does this PR do? Adds a guessIsManifest function to try and determine if an image is a manifest or not: - By checking the repotags and digests - Checking labels does not exist, as labels are not added when creating a manifest - VirtualSize is under 1MB as a manifest list is just text that points to other images We are unable to check the history (manifest commonly has no history) as Dockerode API does not support getting the history of the image. ### Screenshot / video of UI N/A ### What issues does this PR fix or reference? Closes containers#6690 ### How to test this PR? - [X] Tests are covering the bug fix or the new feature Signed-off-by: Charlie Drage --- packages/main/src/plugin/api/image-info.ts | 1 + .../src/plugin/container-registry.spec.ts | 70 +++++++++++++++++++ .../main/src/plugin/container-registry.ts | 5 ++ 3 files changed, 76 insertions(+) diff --git a/packages/main/src/plugin/api/image-info.ts b/packages/main/src/plugin/api/image-info.ts index 33a2cf4f862..771fac9ee64 100644 --- a/packages/main/src/plugin/api/image-info.ts +++ b/packages/main/src/plugin/api/image-info.ts @@ -25,6 +25,7 @@ export interface ImageInfo extends Dockerode.ImageInfo { engineId: string; engineName: string; History?: string[]; + isManifest?: boolean; } export interface BuildImageOptions { diff --git a/packages/main/src/plugin/container-registry.spec.ts b/packages/main/src/plugin/container-registry.spec.ts index 20b6eabb59a..3e0de774406 100644 --- a/packages/main/src/plugin/container-registry.spec.ts +++ b/packages/main/src/plugin/container-registry.spec.ts @@ -4391,3 +4391,73 @@ describe('loadImages', () => { ); }); }); + +test('manifest is listed as true with podmanListImages correctly', async () => { + const manifestImage: ImageInfo = { + Id: 'manifestImage', + Labels: {}, + engineId: 'engine1', + engineName: 'podman', + ParentId: '', + RepoTags: ['manifestTag'], + RepoDigests: ['manifestDigest'], + Created: 0, + Size: 0, + VirtualSize: 40 * 1024, // 40KB (less than 50KB threshold) + SharedSize: 0, + Containers: 0, + }; + + const regularImage: ImageInfo = { + Id: 'ee301c921b8aadc002973b2e0c3da17d701dcd994b606769a7e6eaa100b81d44', + Labels: {}, + engineId: 'engine5', // Assuming 'engineId' and 'engineName' are part of your ImageInfo but not relevant here + engineName: 'podman', + ParentId: '', + RepoTags: ['testdomain.io/library/hello:latest'], + RepoDigests: [ + 'testdomain.io/library/hello@sha256:2d4e459f4ecb5329407ae3e47cbc107a2fbace221354ca75960af4c047b3cb13', + 'testdomain.io/library/hello@sha256:53641cd209a4fecfc68e21a99871ce8c6920b2e7502df0a20671c6fccc73a7c6', + ], + Created: 1683046167, + Size: 23301, + VirtualSize: 23301, // Directly matches Size in this case + SharedSize: 0, + Containers: 0, + History: ['testdomain.io/library/hello:latest'], + }; + + const imagesList = [manifestImage, regularImage]; + nock('http://localhost').get('/v4.2.0/libpod/images/json').reply(200, imagesList); + const api = new Dockerode({ protocol: 'http', host: 'localhost' }); + + // set provider + containerRegistry.addInternalProvider('podman', { + name: 'podman', + id: 'podman1', + api, + libpodApi: api, + connection: { + type: 'podman', + }, + } as unknown as InternalContainerProvider); + + const images = await containerRegistry.podmanListImages(); + // ensure the field are correct + expect(images).toBeDefined(); + expect(images).toHaveLength(2); + + // Check the first image + const image = images[0]; + expect(image.engineId).toBe('podman1'); + expect(image.engineName).toBe('podman'); + expect(image.Id).toBe('manifestImage'); + expect(image.isManifest).toBe(true); + + // Check the second image + const image2 = images[1]; + expect(image2.engineId).toBe('podman1'); + expect(image2.engineName).toBe('podman'); + expect(image2.Id).toBe('ee301c921b8aadc002973b2e0c3da17d701dcd994b606769a7e6eaa100b81d44'); + expect(image2.isManifest).toBe(false); +}); diff --git a/packages/main/src/plugin/container-registry.ts b/packages/main/src/plugin/container-registry.ts index 599a8a93fe7..a19f4d661a6 100644 --- a/packages/main/src/plugin/container-registry.ts +++ b/packages/main/src/plugin/container-registry.ts @@ -75,6 +75,7 @@ import { Emitter } from './events/emitter.js'; import type { ImageRegistry } from './image-registry.js'; import type { Telemetry } from './telemetry/telemetry.js'; import { Disposable } from './types/disposable.js'; +import { guessIsManifest } from './util/manifest.js'; const tar: { pack: (dir: string) => NodeJS.ReadableStream } = require('tar-fs'); @@ -626,6 +627,10 @@ export class ContainerProviderRegistry { ...image, engineName: provider.name, engineId: provider.id, + // Using guessIsManifest, determine if the image is a manifest and set isManifest accordingly + // NOTE: This is a workaround until we have a better way to determine if an image is a manifest + // and may result in false positives until issue: https://github.com/containers/podman/issues/22184 is resolved + isManifest: guessIsManifest(image), })); }), );