Skip to content

Commit

Permalink
feat: add manifest / multi-arch selection support
Browse files Browse the repository at this point in the history
### What does this PR do?

* Manifests are now listed, inspected and propagated within the Build
  page
* Simply select a manifest, select the arch and it will build

### Screenshot / video of UI

<!-- If this PR is changing UI, please include
screenshots or screencasts showing the difference -->

### What issues does this PR fix or reference?

<!-- Include any related issues from Podman Desktop
repository (or from another issue tracker). -->

Closes podman-desktop#324

### How to test this PR?

<!-- Please explain steps to reproduce -->

Follow the video above, or do the following tests:

1. Build a manifest within Podman Desktop (select two architectures, and
   build a bootc image). Must use the latest Podman Desktop
2. Select the manifest within the extension and see it build.

Signed-off-by: Charlie Drage <[email protected]>
  • Loading branch information
cdrage committed Apr 25, 2024
1 parent 01b9233 commit ce1a842
Show file tree
Hide file tree
Showing 5 changed files with 387 additions and 17 deletions.
28 changes: 23 additions & 5 deletions packages/backend/src/api-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,30 @@ export class BootcApiImpl implements BootcApi {
async listBootcImages(): Promise<ImageInfo[]> {
let images: ImageInfo[] = [];
try {
const retrieveImages = await podmanDesktopApi.containerEngine.listImages();
images = retrieveImages.filter(image => {
if (image.Labels) {
return image.Labels['bootc'] ?? image.Labels['containers.bootc'];
const retrievedImages = await podmanDesktopApi.containerEngine.listImages();
const filteredImages: ImageInfo[] = [];
for (const image of retrievedImages) {
let includeImage = false;

// The image must have RepoTags and Labels to be considered a bootc image
if (image.RepoTags && image.Labels) {
// Convert to boolean by checking the string is non-empty
includeImage = !!(image.Labels['bootc'] ?? image.Labels['containers.bootc']);
} else if (image?.isManifest) {
// Manifests **usually** do not have any labels. If this is the case, we must find the images associated to the
// manifest in order to determine if we are going to return the manifest or not.
const manifestImages = await containerUtils.getImagesFromManifest(image, retrievedImages);
// Checking if any associated image has a non-empty label
includeImage = manifestImages.some(
manifestImage => !!(manifestImage.Labels['bootc'] ?? manifestImage.Labels['containers.bootc']),
);
}
});

if (includeImage) {
filteredImages.push(image);
}
}
images = filteredImages;
} catch (err) {
await podmanDesktopApi.window.showErrorMessage(`Error listing images: ${err}`);
console.error('Error listing images: ', err);
Expand Down
6 changes: 5 additions & 1 deletion packages/backend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@

// Image related
export const bootcImageBuilderContainerName = '-bootc-image-builder';
export const bootcImageBuilderName = 'quay.io/centos-bootc/bootc-image-builder:latest-1713548482';

// CHANGE IN THE FUTURE... MUST BE THE ONE WITH MANIFEST IMPLEMENTATION
// export const bootcImageBuilderName = 'quay.io/centos-bootc/bootc-image-builder:latest-1712917745';
// Below only works for arm64 M1 testing
export const bootcImageBuilderName = 'quay.io/bootc-extension/test-arm64-bib-image:latest';
30 changes: 30 additions & 0 deletions packages/backend/src/container-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,33 @@ export async function removeContainerAndVolumes(engineId: string, container: str
throw new Error('There was an error removing the container and volumes: ' + e);
}
}

// Image retrieval for manifest
// Params: Pass in ImageInfo
// 1. Check if isManifest for ImageInfo is true
// 2. If true, perform a inspectManifest to get the manifest object
// 3. Go through the array of manifests and get the digest values's (sha256)
// 4. do listImages and filter out the images that have the same digest value
// 5. Return the imageInfo of all matching images (these are the images that are part of the manifest)
export async function getImagesFromManifest(
image: extensionApi.ImageInfo,
images: extensionApi.ImageInfo[],
): Promise<extensionApi.ImageInfo[]> {
if (!image.isManifest) {
throw new Error('Image is not a manifest');
}

// Get the manifest
const manifest = await inspectManifest(image.engineId, image.Id);

if (manifest.manifests === undefined) {
return [];
}

// Get the digest values, if there are no digests, make sure array is [] so we don't run into
// issues with the filter
const digestValues = manifest.manifests.map(manifest => manifest.digest).filter(digest => digest !== undefined);

// Filter out the images that have the same digest value
return images.filter(image => image.Digest && digestValues.includes(image.Digest));
}
Loading

0 comments on commit ce1a842

Please sign in to comment.