diff --git a/package.json b/package.json index f134c13d..b2c607c4 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,11 @@ "command": "bootc.image.build", "title": "Build Disk Image", "when": "ostree.bootable in imageLabelKeys" + }, + { + "command": "bootc.vfkit", + "title": "Launch as VM", + "when": "ostree.bootable in imageLabelKeys" } ], "dashboard/container": [ diff --git a/src/extension.ts b/src/extension.ts index 662f7352..0ec8794f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -30,7 +30,7 @@ export async function activate(extensionContext: ExtensionContext): Promise { - await launchVFKit(container); + await launchVFKit(container, history); }), extensionApi.commands.registerCommand('bootc.image.build', async image => { diff --git a/src/history.spec.ts b/src/history.spec.ts index 70fc375b..3d3d1c78 100644 --- a/src/history.spec.ts +++ b/src/history.spec.ts @@ -58,3 +58,27 @@ test('check get returns latest after multiple adds', async () => { expect(history.getLastLocation()).toEqual('c2'); }); + +test('check lastBuild get updated', async () => { + vi.mock('node:fs', async () => { + return { + readFile: vi.fn().mockImplementation(() => '[]'), + writeFile: vi.fn().mockImplementation(() => Promise.resolve()), + existsSync: vi.fn().mockImplementation(() => true), + }; + }); + + const history: History = new History('test'); + + await history.addImageBuild('a0', 'b0', 'c0'); + await history.addImageBuild('a1', 'b1', 'c1'); + + expect(history.getLastBuildFor('a0').type).toEqual('b0'); + expect(history.getLastBuildFor('a0').location).toEqual('c0'); + + await history.addImageBuild('a0', 'b0b', 'c0b'); + await history.addImageBuild('a2', 'b2', 'c2'); + + expect(history.getLastBuildFor('a0').type).toEqual('b0b'); + expect(history.getLastBuildFor('a0').location).toEqual('c0b'); +}); diff --git a/src/history.ts b/src/history.ts index b80f8344..b7d0c1da 100644 --- a/src/history.ts +++ b/src/history.ts @@ -54,9 +54,17 @@ export class History { public getLastLocation(): string | undefined { if (this.infos.length === 0) { return undefined; - } else { - return this.infos[0].location; } + return this.infos[0].location; + } + + public getLastBuildFor(image: string): { type: string; location: string } | undefined { + if (this.infos.length === 0) { + return undefined; + } + + // returns the details of the last build for this image + return this.infos.find((_value, index, info) => info[index].image === image); } public async addImageBuild(image: string, type: string, location: string) { diff --git a/src/launch-vfkit.ts b/src/launch-vfkit.ts index 21191a92..5f69cc21 100644 --- a/src/launch-vfkit.ts +++ b/src/launch-vfkit.ts @@ -18,17 +18,39 @@ import * as extensionApi from '@podman-desktop/api'; import * as fs from 'node:fs'; +import type { History } from './history'; const telemetryLogger = extensionApi.env.createTelemetryLogger(); // eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function launchVFKit(container: any): Promise { - const imageLocation = container.labels['bootc.build.image.location']; - - // Check that vfkit is installed and error if it isn't before executing +export async function launchVFKit(target: any, history: History): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const telemetryData: Record = {}; - telemetryData.imageType = container.labels['bootc.build.type']; + + let imageType: string; + let imageLocation: string; + if ('tag' in target) { + // launched on an image + const image = target as { name: string }; + const lastBuild = history.getLastBuildFor(image.name); + + if (!lastBuild) { + await extensionApi.window.showErrorMessage(`Unable to launch ${image.name} because there was no previous build`); + telemetryData.error = 'no-prior-build'; + telemetryLogger.logUsage('launchVfkit', telemetryData); + return; + } + imageType = lastBuild.type; + imageLocation = lastBuild.location; + } else { + // launched on the builder container + const container = target as { labels: string[] }; + imageType = container.labels['bootc.build.type']; + imageLocation = container.labels['bootc.build.image.location']; + } + + // Check that vfkit is installed and error if it isn't before executing + telemetryData.imageType = imageType; try { await extensionApi.process.exec('vfkit', ['--version']); } catch (error) { @@ -50,9 +72,9 @@ export async function launchVFKit(container: any): Promise { // Check container.labels['bootc.build.type'] to see if it is ami or raw // if it is not raw or ami, we cannot launch with vfkit - if (container.labels['bootc.build.type'] !== 'ami' && container.labels['bootc.build.type'] !== 'raw') { + if (imageType !== 'ami' && imageType !== 'raw') { await extensionApi.window.showErrorMessage( - `Unable to launch ${imageLocation} with vfkit: ${container.labels['bootc.build.type']} is not supported`, + `Unable to launch ${imageLocation} with vfkit: ${imageType} is not supported`, ); telemetryData.error = 'unsupported-type'; telemetryLogger.logUsage('launchVfkit', telemetryData);