Skip to content

Commit

Permalink
feat: support rhel bib
Browse files Browse the repository at this point in the history
Adds a new preference that allows you to switch between:
- 'image' - the default, picks up builder from image label (or use centos
  if none is defined)
- 'centos' - always use Centos builder
- 'rhel' - always use RHEL builder

createBuilderImageOptions() is made async to allow it to check the image
and switch the builder if necessary.

Signed-off-by: Tim deBoer <[email protected]>
  • Loading branch information
deboer-tim committed May 6, 2024
1 parent bd1c8d5 commit 59d00bc
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 20 deletions.
10 changes: 10 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
"default": 30,
"maximum": 120,
"description": "Build timeout (in minutes)"
},
"bootc.builder": {
"type": "string",
"default": "image",
"enum": [
"image",
"centos",
"rhel"
],
"description": "Builder image"
}
}
},
Expand Down
98 changes: 86 additions & 12 deletions packages/backend/src/build-disk-image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@

import { beforeEach, expect, test, vi } from 'vitest';
import { buildExists, createBuilderImageOptions, getUnusedName } from './build-disk-image';
import { bootcImageBuilderName } from './constants';
import type { ContainerInfo } from '@podman-desktop/api';
import { bootcImageBuilderCentos, bootcImageBuilderRHEL } from './constants';
import type { ContainerInfo, Configuration } from '@podman-desktop/api';
import { containerEngine } from '@podman-desktop/api';
import type { BootcBuildInfo } from '/@shared/src/models/bootc';
import * as fs from 'node:fs';
import { resolve } from 'node:path';

const configurationGetConfigurationMock = vi.fn();

const config: Configuration = {
get: configurationGetConfigurationMock,
has: () => true,
update: vi.fn(),
};

vi.mock('@podman-desktop/api', async () => {
return {
env: {
Expand All @@ -33,6 +41,9 @@ vi.mock('@podman-desktop/api', async () => {
containerEngine: {
listContainers: vi.fn().mockReturnValue([]),
},
configuration: {
getConfiguration: () => config,
},
};
});

Expand All @@ -49,11 +60,11 @@ test('check image builder options', async () => {
arch: 'amd',
folder: '/output-folder',
} as BootcBuildInfo;
const options = createBuilderImageOptions(name, build);
const options = await createBuilderImageOptions(name, build);

expect(options).toBeDefined();
expect(options.name).toEqual(name);
expect(options.Image).toEqual(bootcImageBuilderName);
expect(options.Image).toEqual(bootcImageBuilderCentos);
expect(options.HostConfig).toBeDefined();
if (options.HostConfig?.Binds) {
expect(options.HostConfig.Binds[0]).toEqual(build.folder + ':/output/');
Expand All @@ -80,11 +91,11 @@ test('check image builder with multiple types', async () => {
arch: 'amd',
folder: '/output-folder',
} as BootcBuildInfo;
const options = createBuilderImageOptions(name, build);
const options = await createBuilderImageOptions(name, build);

expect(options).toBeDefined();
expect(options.name).toEqual(name);
expect(options.Image).toEqual(bootcImageBuilderName);
expect(options.Image).toEqual(bootcImageBuilderCentos);
expect(options.HostConfig).toBeDefined();
if (options.HostConfig?.Binds) {
expect(options.HostConfig.Binds[0]).toEqual(build.folder + ':/output/');
Expand All @@ -109,7 +120,7 @@ test('check image builder does not include target arch', async () => {
image: 'test-image',
type: ['vmdk'],
} as BootcBuildInfo;
const options = createBuilderImageOptions('my-image', build);
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Cmd).not.toContain('--target-arch');
Expand All @@ -121,7 +132,7 @@ test('check image builder includes target arch for iso', async () => {
type: ['iso'],
arch: 'amd',
} as BootcBuildInfo;
const options = createBuilderImageOptions('my-image', build);
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Cmd).toContain('--target-arch');
Expand All @@ -134,7 +145,7 @@ test('check that if xfs is passed into filesystem, it is included in the command
arch: 'amd',
filesystem: 'xfs',
} as BootcBuildInfo;
const options = createBuilderImageOptions('my-image', build);
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Cmd).toContain('--rootfs');
Expand All @@ -148,7 +159,7 @@ test('check that if ext4 is passed into the filesystem, it is included in the co
arch: 'amd',
filesystem: 'ext4',
} as BootcBuildInfo;
const options = createBuilderImageOptions('my-image', build);
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Cmd).toContain('--rootfs');
Expand All @@ -162,7 +173,7 @@ test('test if a fake filesystem foobar is passed into filesystem, it is not incl
arch: 'amd',
filesystem: 'foobar',
} as BootcBuildInfo;
const options = createBuilderImageOptions('my-image', build);
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Cmd).not.toContain('--rootfs');
Expand All @@ -175,7 +186,7 @@ test('test if blank string is passed into filesystem, it is not included in the
arch: 'amd',
filesystem: '',
} as BootcBuildInfo;
const options = createBuilderImageOptions('my-image', build);
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Cmd).not.toContain('--rootfs');
Expand Down Expand Up @@ -230,3 +241,66 @@ test('check build exists', async () => {
exists = await buildExists(folder, ['iso', 'raw']);
expect(exists).toEqual(false);
});

test('check switches to RHEL builder', async () => {
configurationGetConfigurationMock.mockReturnValue('rhel');

const build = {
image: 'test-image',
type: ['ami'],
} as BootcBuildInfo;
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Image).toEqual(bootcImageBuilderRHEL);
});

test('check switches to image preferred builder (RHEL)', async () => {
configurationGetConfigurationMock.mockReturnValue('image');

const listImagesMock = vi.fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(containerEngine as any).listImages = listImagesMock;
listImagesMock.mockResolvedValue([
{
RepoTags: ['test-image:latest'],
Labels: { 'bootc.diskimage-builder': 'registry.redhat.io/rhel9/bootc-image-builder' },
},
]);

//
const build = {
image: 'test-image',
tag: 'latest',
type: ['iso'],
} as BootcBuildInfo;
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Image).toEqual(bootcImageBuilderRHEL);
});

test('check switches to image preferred builder (Centos)', async () => {
configurationGetConfigurationMock.mockReturnValue('image');

const listImagesMock = vi.fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(containerEngine as any).listImages = listImagesMock;
listImagesMock.mockResolvedValue([
{
RepoTags: ['test-image:latest'],
Labels: { 'bootc.diskimage-builder': 'quay.io/centos-bootc/bootc-image-builder' },
},
]);

//
const build = {
image: 'test-image',
tag: 'latest',
type: ['iso'],
} as BootcBuildInfo;
const options = await createBuilderImageOptions('my-image', build);

expect(options).toBeDefined();
expect(options.Image).toEqual(bootcImageBuilderCentos);
});
26 changes: 20 additions & 6 deletions packages/backend/src/build-disk-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import * as extensionApi from '@podman-desktop/api';
import * as fs from 'node:fs';
import { resolve } from 'node:path';
import * as containerUtils from './container-utils';
import { bootcImageBuilderContainerName, bootcImageBuilderName } from './constants';
import { bootcImageBuilder, bootcImageBuilderCentos, bootcImageBuilderRHEL } from './constants';
import type { BootcBuildInfo, BuildType } from '/@shared/src/models/bootc';
import type { History } from './history';
import * as machineUtils from './machine-utils';
import { telemetryLogger } from './extension';
import { getConfigurationValue, telemetryLogger } from './extension';

export async function buildExists(folder: string, types: BuildType[]) {
let exists = false;
Expand Down Expand Up @@ -100,7 +100,7 @@ export async function buildDiskImage(build: BootcBuildInfo, history: History, ov
.withProgress(
{ location: extensionApi.ProgressLocation.TASK_WIDGET, title: `Building disk image ${build.image}` },
async progress => {
const buildContainerName = build.image.split('/').pop() + bootcImageBuilderContainerName;
const buildContainerName = build.image.split('/').pop() + '-' + bootcImageBuilder;
let successful: boolean = false;
let logData: string = 'Build Image Log --------\n';
logData += 'ID: ' + build.id + '\n';
Expand All @@ -122,7 +122,7 @@ export async function buildDiskImage(build: BootcBuildInfo, history: History, ov
// options that we will use to build the image. This will help with debugging
// as well as making sure we delete the previous build, etc.
const containerName = await getUnusedName(buildContainerName);
const buildImageContainer = createBuilderImageOptions(containerName, build);
const buildImageContainer = await createBuilderImageOptions(containerName, build);
logData += JSON.stringify(buildImageContainer, undefined, 2);
logData += '\n----------\n';
try {
Expand Down Expand Up @@ -305,7 +305,21 @@ export async function getUnusedName(name: string): Promise<string> {
}

// Create builder options for the "bootc-image-builder" container
export function createBuilderImageOptions(name: string, build: BootcBuildInfo): ContainerCreateOptions {
export async function createBuilderImageOptions(name: string, build: BootcBuildInfo): Promise<ContainerCreateOptions> {
// check image for builder to use
const buildProp = await getConfigurationValue<string>('builder');

// default to centos builder, but switch to RHEL based on preference
let builder = bootcImageBuilderCentos;
if (buildProp === 'rhel') {
builder = bootcImageBuilderRHEL;
} else if (buildProp === 'image') {
const buildLabel = await containerUtils.getImageBuilderLabel(image);

Check failure on line 317 in packages/backend/src/build-disk-image.ts

View workflow job for this annotation

GitHub Actions / linter, formatter, and tests / windows-2022

src/build-disk-image.spec.ts > check switches to image preferred builder (RHEL)

ReferenceError: image is not defined ❯ Module.createBuilderImageOptions src/build-disk-image.ts:317:66 ❯ src/build-disk-image.spec.ts:277:19

Check failure on line 317 in packages/backend/src/build-disk-image.ts

View workflow job for this annotation

GitHub Actions / linter, formatter, and tests / windows-2022

src/build-disk-image.spec.ts > check switches to image preferred builder (Centos)

ReferenceError: image is not defined ❯ Module.createBuilderImageOptions src/build-disk-image.ts:317:66 ❯ src/build-disk-image.spec.ts:302:19

Check failure on line 317 in packages/backend/src/build-disk-image.ts

View workflow job for this annotation

GitHub Actions / linter, formatter, and tests / ubuntu-22.04

src/build-disk-image.spec.ts > check switches to image preferred builder (RHEL)

ReferenceError: image is not defined ❯ Module.createBuilderImageOptions src/build-disk-image.ts:317:66 ❯ src/build-disk-image.spec.ts:277:19

Check failure on line 317 in packages/backend/src/build-disk-image.ts

View workflow job for this annotation

GitHub Actions / linter, formatter, and tests / ubuntu-22.04

src/build-disk-image.spec.ts > check switches to image preferred builder (Centos)

ReferenceError: image is not defined ❯ Module.createBuilderImageOptions src/build-disk-image.ts:317:66 ❯ src/build-disk-image.spec.ts:302:19

Check failure on line 317 in packages/backend/src/build-disk-image.ts

View workflow job for this annotation

GitHub Actions / linter, formatter, and tests / macos-14

src/build-disk-image.spec.ts > check switches to image preferred builder (RHEL)

ReferenceError: image is not defined ❯ Module.createBuilderImageOptions src/build-disk-image.ts:317:66 ❯ src/build-disk-image.spec.ts:277:19

Check failure on line 317 in packages/backend/src/build-disk-image.ts

View workflow job for this annotation

GitHub Actions / linter, formatter, and tests / macos-14

src/build-disk-image.spec.ts > check switches to image preferred builder (Centos)

ReferenceError: image is not defined ❯ Module.createBuilderImageOptions src/build-disk-image.ts:317:66 ❯ src/build-disk-image.spec.ts:302:19
if (buildLabel === 'registry.redhat.io/rhel9/bootc-image-builder') {
builder = bootcImageBuilderRHEL;
}
}

const cmd = [`${build.image}:${build.tag}`, '--output', '/output/', '--local'];

build.type.forEach(t => cmd.push('--type', t));
Expand All @@ -323,7 +337,7 @@ export function createBuilderImageOptions(name: string, build: BootcBuildInfo):
// Create the image options for the "bootc-image-builder" container
const options: ContainerCreateOptions = {
name: name,
Image: bootcImageBuilderName,
Image: builder,
Tty: true,
HostConfig: {
Privileged: true,
Expand Down
6 changes: 4 additions & 2 deletions packages/backend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@
***********************************************************************/

// Image related
export const bootcImageBuilderContainerName = '-bootc-image-builder';
export const bootcImageBuilderName = 'quay.io/centos-bootc/bootc-image-builder:latest-1714633180';
export const bootcImageBuilder = 'bootc-image-builder';
export const bootcImageBuilderCentos = 'quay.io/centos-bootc/bootc-image-builder:latest-1714474808';
export const bootcImageBuilderRHEL = 'registry.redhat.io/rhel9/bootc-image-builder:9.4';
export const bootcImageBuilderLabel = 'bootc.diskimage-builder';
17 changes: 17 additions & 0 deletions packages/backend/src/container-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
deleteOldImages,
inspectImage,
inspectManifest,
getImageBuilderLabel,
} from './container-utils';

const mocks = vi.hoisted(() => ({
Expand Down Expand Up @@ -265,3 +266,19 @@ test('test running inspectManifest', async () => {
expect(result.engineName).toBe('podman');
expect(result.manifests).toBeDefined();
});

test('test image builder label', async () => {
const listImagesMock = vi.fn();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(extensionApi.containerEngine as any).listImages = listImagesMock;
listImagesMock.mockResolvedValue([
{ RepoTags: ['test.io/name:1'] },
{ RepoTags: ['test.io/name:2'], Labels: { 'bootc.diskimage-builder': 'foo' } },
{ RepoTags: ['test.io/name:3'] },
{ RepoTags: ['test.io/name:4', 'keep-me'] },
]);

// Test that it'll find the right image and label
const result = await getImageBuilderLabel('test.io/name:2');
expect(result).toBe('foo');
});
14 changes: 14 additions & 0 deletions packages/backend/src/container-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import type { ContainerCreateOptions } from '@podman-desktop/api';
import * as extensionApi from '@podman-desktop/api';
import { getConfigurationValue, telemetryLogger } from './extension';
import { bootcImageBuilderLabel } from './constants';

// Get the running container engine
export async function getContainerEngine(): Promise<extensionApi.ContainerProviderConnection> {
Expand Down Expand Up @@ -347,3 +348,16 @@ export async function getImagesFromManifest(
// Filter out the images that have the same digest value
return images.filter(image => image.Digest && digestValues.includes(image.Digest));
}

// Return the image builder label from the given image
export async function getImageBuilderLabel(imageId: string): Promise<string | undefined> {
try {
const images = await extensionApi.containerEngine.listImages();

const foundImage = images.find(i => i.RepoTags?.find(tag => tag === imageId));
return foundImage?.Labels[bootcImageBuilderLabel];
} catch (err) {
console.error('Error getting image label: ', err);
}
return undefined;
}

0 comments on commit 59d00bc

Please sign in to comment.