diff --git a/packages/backend/package.json b/packages/backend/package.json index ba479fd0..6153407f 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -20,6 +20,15 @@ "default": 30, "maximum": 120, "description": "Build timeout (in minutes)" + }, + "bootc.builder": { + "type": "string", + "default": "CentOS", + "enum": [ + "CentOS", + "RHEL" + ], + "description": "Builder image used to create disk images" } } }, diff --git a/packages/backend/src/build-disk-image.spec.ts b/packages/backend/src/build-disk-image.spec.ts index 0b0f51f6..cc3364cb 100644 --- a/packages/backend/src/build-disk-image.spec.ts +++ b/packages/backend/src/build-disk-image.spec.ts @@ -17,14 +17,22 @@ ***********************************************************************/ 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 { buildExists, createBuilderImageOptions, getBuilder, getUnusedName } from './build-disk-image'; +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: { @@ -33,6 +41,9 @@ vi.mock('@podman-desktop/api', async () => { containerEngine: { listContainers: vi.fn().mockReturnValue([]), }, + configuration: { + getConfiguration: () => config, + }, }; }); @@ -53,7 +64,7 @@ test('check image builder options', async () => { 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/'); @@ -84,7 +95,7 @@ test('check image builder with multiple types', async () => { 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/'); @@ -181,6 +192,18 @@ test('test if blank string is passed into filesystem, it is not included in the expect(options.Cmd).not.toContain('--rootfs'); }); +test('test specified builder is used', async () => { + const builder = 'foo-builder'; + const build = { + image: 'test-image', + type: ['vmdk'], + } as BootcBuildInfo; + const options = createBuilderImageOptions('my-image', build, builder); + + expect(options).toBeDefined(); + expect(options.Image).toEqual(builder); +}); + test('check we pick unused container name', async () => { const basename = 'test'; let name = await getUnusedName(basename); @@ -230,3 +253,21 @@ test('check build exists', async () => { exists = await buildExists(folder, ['iso', 'raw']); expect(exists).toEqual(false); }); + +test('check uses RHEL builder', async () => { + configurationGetConfigurationMock.mockReturnValue('RHEL'); + + const builder = await getBuilder(); + + expect(builder).toBeDefined(); + expect(builder).toEqual(bootcImageBuilderRHEL); +}); + +test('check uses Centos builder', async () => { + configurationGetConfigurationMock.mockReturnValue('CentOS'); + + const builder = await getBuilder(); + + expect(builder).toBeDefined(); + expect(builder).toEqual(bootcImageBuilderCentos); +}); diff --git a/packages/backend/src/build-disk-image.ts b/packages/backend/src/build-disk-image.ts index 6db4ceda..3bf0a7d8 100644 --- a/packages/backend/src/build-disk-image.ts +++ b/packages/backend/src/build-disk-image.ts @@ -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; @@ -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'; @@ -118,11 +118,14 @@ export async function buildDiskImage(build: BootcBuildInfo, history: History, ov fs.unlinkSync(logPath); } + // determine which bootc image builder to use + const builder = await getBuilder(); + // Preliminary Step 0. Create the "bootc-image-builder" container // 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 = createBuilderImageOptions(containerName, build, builder); logData += JSON.stringify(buildImageContainer, undefined, 2); logData += '\n----------\n'; try { @@ -304,8 +307,24 @@ export async function getUnusedName(name: string): Promise { return unusedName; } +export async function getBuilder(): Promise { + // use the preference to decide which builder to use + const buildProp = await getConfigurationValue('builder'); + + if (buildProp === 'RHEL') { + return bootcImageBuilderRHEL; + } + + // always default to centos bib + return bootcImageBuilderCentos; +} + // Create builder options for the "bootc-image-builder" container -export function createBuilderImageOptions(name: string, build: BootcBuildInfo): ContainerCreateOptions { +export function createBuilderImageOptions( + name: string, + build: BootcBuildInfo, + builder?: string, +): ContainerCreateOptions { const cmd = [`${build.image}:${build.tag}`, '--output', '/output/', '--local']; build.type.forEach(t => cmd.push('--type', t)); @@ -323,7 +342,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 ?? bootcImageBuilderCentos, Tty: true, HostConfig: { Privileged: true, diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 00d5d815..6ed9d48a 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -17,5 +17,6 @@ ***********************************************************************/ // 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-1714633180'; +export const bootcImageBuilderRHEL = 'registry.redhat.io/rhel9/bootc-image-builder:9.4';