Skip to content

Commit

Permalink
feat: error handling
Browse files Browse the repository at this point in the history
Error handling and showing an error dialog.
Check if file exists and warn the user.
Linting and frmatting.

Signed-off-by: Tim deBoer <[email protected]>
  • Loading branch information
deboer-tim committed Jan 10, 2024
1 parent 85f4585 commit ba0a8cb
Showing 1 changed file with 91 additions and 98 deletions.
189 changes: 91 additions & 98 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import type { ContainerCreateOptions, ExtensionContext } from '@podman-desktop/a
import * as extensionApi from '@podman-desktop/api';
import { BootC } from './bootc';
import * as os from 'node:os';
import * as fs from 'node:fs';
import { resolve } from 'node:path';

let bootc: BootC | undefined;
const bootcImageBuilderContainerName = '-bootc-image-builder';
const bootcImageBuilderName = 'quay.io/centos-bootc/bootc-image-builder';
let diskImageBuildingName: string;

export async function activate(extensionContext: ExtensionContext): Promise<void> {

bootc = new BootC(extensionContext);
await bootc?.activate();

Expand All @@ -36,65 +37,75 @@ export async function activate(extensionContext: ExtensionContext): Promise<void
const selectedType = await extensionApi.window.showQuickPick(['qcow2', 'ami', 'iso'], {
placeHolder: 'Select image type',
});
if (!selectedType)
if (!selectedType) {
return;
}

const selectedFolder = await extensionApi.window.showInputBox({
prompt: 'Select the folder to generate disk' + selectedType + ' into',
value: os.homedir(),
ignoreFocusOut: true,
});
if (!selectedFolder)
if (!selectedFolder) {
return;

// TODO: check if file exists and warn or error out
}

// check if the file already exists and warn the user
const imagePath = resolve(selectedFolder, selectedType, 'disk.' + selectedType);
if (
!fs.existsSync(imagePath) &&
(await extensionApi.window.showWarningMessage(
'File already exists, do you want to overwrite?',
'Yes',
'No',
)) === 'No'
) {
return;
}

return extensionApi.window.withProgress(
{ location: extensionApi.ProgressLocation.TASK_WIDGET, title: 'Building disk image ' + image.name },
async progress => {

// ignore everything before last /
// we will use this as the "build" container name
diskImageBuildingName = image.name.split('/').pop() + bootcImageBuilderContainerName,
diskImageBuildingName = image.name.split('/').pop() + bootcImageBuilderContainerName;

// TODO: Make sure that 'image' has been pushed to registry before building it..
// or else it will fail.
// for demo right now, don't bother checking

await pullBootcImageBuilderImage();
progress.report({ increment: 10 });
await removePreviousBuildImage(image);
progress.report({ increment: 15 });
let containerId = await createImage(image, selectedType, selectedFolder);

logContainer(image, containerId, progress);
try {
await pullBootcImageBuilderImage();
progress.report({ increment: 10 });

await removePreviousBuildImage(image);
progress.report({ increment: 15 });

// list containers using extensionApi.containerEngine.listContainers
// and filter
const containerId = await createImage(image, selectedType, selectedFolder);
progress.report({ increment: 16 });

// List containers and filter by the name 'diskImageBuildingName'
await logContainer(image, containerId, progress);

let containerRunning = true;
while (containerRunning) {
extensionApi.containerEngine.listContainers().then((containers) => {
console.log(containers);
containers.forEach((container) => {
if (container.Id === containerId) {
// Wait for container to exit so that the task doesn't end and we can monitor progress
let containerRunning = true;
while (containerRunning) {
await extensionApi.containerEngine.listContainers().then(containers => {
containers.forEach(container => {
// check if container is stopped
if (container.State === 'exited') {
if (container.Id === containerId && container.State === 'exited') {
// remove the container
// Cant do this until we extract the logs
//extensionApi.containerEngine.deleteContainer(image.engineId, container.Id);
containerRunning = false;
}
}
}
);
});
await new Promise(r => setTimeout(r, 2000));
}

});
});
await new Promise(r => setTimeout(r, 1000));
}
} catch (error) {
console.error(error);
await extensionApi.window.showErrorMessage(`Unable to build disk image: ${error}`);
}
// Mark the task as completed
progress.report({ increment: -1 });
},
Expand All @@ -104,7 +115,7 @@ export async function activate(extensionContext: ExtensionContext): Promise<void
}

async function removePreviousBuildImage(image) {
// Ignore if the container doesn't exist
// TODO: Ignore if the container doesn't exist
try {
await extensionApi.containerEngine.deleteContainer(image.engineId, diskImageBuildingName);
} catch (e) {
Expand All @@ -113,40 +124,31 @@ async function removePreviousBuildImage(image) {
}

async function pullBootcImageBuilderImage() {
// get all engines
const providerConnections = extensionApi.provider.getContainerConnections();

// get all engines
const providerConnections = extensionApi.provider.getContainerConnections();

// keep only the podman engine
// TODO: match by engineId from `image.engineId` instead of just looking for the first podman
const podmanConnection = providerConnections.filter(
providerConnection => providerConnection.connection.type === 'podman',
);
// keep only the podman engine
// TODO: match by engineId from `image.engineId` instead of just looking for the first podman
const podmanConnection = providerConnections.filter(
providerConnection => providerConnection.connection.type === 'podman',
);

// engine running
if (podmanConnection.length < 1) {
throw new Error('No podman engine running. Cannot preload images');
}
// engine running
if (podmanConnection.length < 1) {
throw new Error('No podman engine running. Cannot preload images');
}

// get the podman engine
let containerConnection = podmanConnection[0].connection;
// get the podman engine
const containerConnection = podmanConnection[0].connection;

console.log('Pulling ' + bootcImageBuilderName);

try {
await extensionApi.containerEngine.pullImage(containerConnection, bootcImageBuilderName, () =>
console.log("Bootc builder image pulled"),
);
} catch (e) {
console.log(e);
}
await extensionApi.containerEngine.pullImage(containerConnection, bootcImageBuilderName, () => {});
}

async function createImage(image, type, folder: string) {

console.log('Building ' + diskImageBuildingName + ' to ' + type);

/*
/*
// The "raw" CLI command for the below container create
Expand All @@ -160,53 +162,44 @@ quay.io/centos-bootc/bootc-image-builder:latest \
$IMAGE
*/

// Update options with the above values
let options: ContainerCreateOptions = {
name: diskImageBuildingName,
Image: bootcImageBuilderName,
Tty: true,
HostConfig: {
Privileged: true,
SecurityOpt: ['label=type:unconfined_t'],
Binds: [folder + ':/tmp/' + type]
},
// Outputs to:
// <type>/disk.<type>
// in the directory provided
Cmd: [image.name,"--type", type, "--output","/tmp/" + type],
};
try {
let result = await extensionApi.containerEngine.createContainer(image.engineId, options);
// Update options with the above values
const options: ContainerCreateOptions = {
name: diskImageBuildingName,
Image: bootcImageBuilderName,
Tty: true,
HostConfig: {
Privileged: true,
SecurityOpt: ['label=type:unconfined_t'],
Binds: [folder + ':/tmp/' + type],
},
// Outputs to:
// <type>/disk.<type>
// in the directory provided
Cmd: [image.name, '--type', type, '--output', '/tmp/' + type],
};

const result = await extensionApi.containerEngine.createContainer(image.engineId, options);

// return the created container id
return result.id;
} catch (e) {
console.log(e);
}
}

async function logContainer(image, containerId: string, progress): Promise<void> {
try {
await extensionApi.containerEngine.logsContainer(
image.engineId,
containerId,
(_name: string, data: string) => {
if (data) {
if (data.includes('org.osbuild.rpm'))
progress.report({ increment: 40 });
else if (data.includes('org.osbuild.selinux'))
progress.report({ increment: 60 });
else if (data.includes('org.osbuild.deploy.container'))
progress.report({ increment: 80 });
else if (data.includes('org.osbuild.copy'))
progress.report({ increment: 90 });
}
}
);
} catch (err) {
console.error(err);
// propagate the error
// throw err;
}
await extensionApi.containerEngine.logsContainer(image.engineId, containerId, (_name: string, data: string) => {
if (data) {
if (data.includes('org.osbuild.rpm')) {
progress.report({ increment: 40 });
} else if (data.includes('org.osbuild.selinux')) {
progress.report({ increment: 60 });
} else if (data.includes('org.osbuild.deploy.container')) {
progress.report({ increment: 80 });
} else if (data.includes('org.osbuild.copy')) {
progress.report({ increment: 90 });
}
}
});
}

export async function deactivate(): Promise<void> {
await bootc?.deactivate();
}

0 comments on commit ba0a8cb

Please sign in to comment.