Skip to content

Commit

Permalink
feat: create/run sample app containers after image built completed (#90)
Browse files Browse the repository at this point in the history
Signed-off-by: lstocchi <[email protected]>
  • Loading branch information
lstocchi committed Jan 19, 2024
1 parent bf1ee55 commit 276cfc2
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 8 deletions.
2 changes: 1 addition & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"simple-git": "^3.22.0"
},
"devDependencies": {
"@podman-desktop/api": "0.0.202401191125-9c1aea6",
"@podman-desktop/api": "0.0.202401191407-14bbe67",
"@types/js-yaml": "^4.0.9",
"@types/node": "^18",
"vitest": "^1.1.0"
Expand Down
71 changes: 68 additions & 3 deletions packages/backend/src/managers/applicationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { RecipeStatusUtils } from '../utils/recipeStatusUtils';
import { getParentDirectory } from '../utils/pathUtils';
import type { LocalModelInfo } from '@shared/src/models/ILocalModelInfo';
import type { ModelInfo } from '@shared/src/models/IModelInfo';
import { getFreePort, getFreePortRange, getPortsInfo } from '../utils/ports';

// TODO: Need to be configured
export const AI_STUDIO_FOLDER = path.join('podman-desktop', 'ai-studio');
Expand Down Expand Up @@ -151,7 +152,7 @@ export class ApplicationManager {
},
});

await this.downloadModelMain(model.id, model.url, taskUtil);
const destinationFolder = await this.downloadModelMain(model.id, model.url, taskUtil);

filteredContainers.forEach(container => {
taskUtil.setTask({
Expand All @@ -162,7 +163,7 @@ export class ApplicationManager {
});

// Promise all the build images
return Promise.all(
await Promise.all(
filteredContainers.map(container => {
// We use the parent directory of our configFile as the rootdir, then we append the contextDir provided
const context = path.join(getParentDirectory(configFile), container.contextdir);
Expand Down Expand Up @@ -204,14 +205,78 @@ export class ApplicationManager {
});
}),
);

// now that the image has been built we run them
filteredContainers.forEach(container => {
taskUtil.setTask({
id: container.name,
state: 'loading',
name: `Running ${container.name}`,
});
});

// if it's a model service we mount the model as volume
await Promise.all(
filteredContainers.map(async container => {
const image = (await containerEngine.listImages()).find(im => {
return im.RepoTags?.some(tag => tag.endsWith(`${container.name}:latest`))
});

if (!image) {
console.error('no image found')
taskUtil.setTaskState(container.name, 'error');
return;
}
console.log(image.engineId);
console.log(image.Id);
const imageInspectInfo = await containerEngine.getImageInspect(image.engineId, image.Id);
console.log(imageInspectInfo);
const exposedPorts = Array.from(Object.keys(imageInspectInfo?.Config?.ExposedPorts || {}));
const portBindings = {};
for (const exposed of exposedPorts) {
const localPorts = await getPortsInfo(exposed);
portBindings[exposed] = [
{
HostPort: localPorts
}
];
}
let hostConfig: unknown;
let envs: string[] = [];
if (container.modelService) {
hostConfig = {
AutoRemove: true,
Mounts: [
{
Target: `/${model.id}`,
Source: destinationFolder,
Type: 'bind',
},
],
PortBindings: portBindings,
};
envs = [`MODEL_PATH=/${model.id}`];
} else {
hostConfig = {
AutoRemove: true,
}
}
return containerEngine.createContainer(image.engineId, {
Image: image.Id,
Detach: true,
HostConfig: hostConfig,
Env: envs,
}).catch(e => console.error(e));
}),
);
}

downloadModelMain(modelId: string, url: string, taskUtil: RecipeStatusUtils, destFileName?: string): Promise<string> {
return new Promise((resolve, reject) => {
const downloadCallback = (result: DownloadModelResult) => {
if (result.result) {
taskUtil.setTaskState(modelId, 'success');
resolve('');
resolve(destFileName);
} else {
taskUtil.setTaskState(modelId, 'error');
reject(result.error);
Expand Down
75 changes: 75 additions & 0 deletions packages/backend/src/utils/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,78 @@ export function isFreePort(port: number): Promise<boolean> {
.listen(port, '127.0.0.1'),
);
}

export async function getPortsInfo(portDescriptor: string): Promise<string | undefined> {
// check if portDescriptor is a range of ports
if (portDescriptor.includes('-')) {
return await getPortRange(portDescriptor);
} else {
const localPort = await getPort(portDescriptor);
if (!localPort) {
return undefined;
}
return `${localPort}`;
}
}

/**
* return a range of the same length as portDescriptor containing free ports
* undefined if the portDescriptor range is not valid
* e.g 5000:5001 -> 9000:9001
*/
async function getPortRange(portDescriptor: string): Promise<string | undefined> {
const rangeValues = getStartEndRange(portDescriptor);
if (!rangeValues) {
return Promise.resolve(undefined);
}

const rangeSize = rangeValues.endRange + 1 - rangeValues.startRange;
try {
// if free port range fails, return undefined
return await getFreePortRange(rangeSize);
} catch (e) {
console.error(e);
return undefined;
}
}

async function getPort(portDescriptor: string): Promise<number | undefined> {
let port: number;
if (portDescriptor.endsWith('/tcp') || portDescriptor.endsWith('/udp')) {
port = parseInt(portDescriptor.substring(0, portDescriptor.length - 4));
} else {
port = parseInt(portDescriptor);
}
// invalid port
if (isNaN(port)) {
return Promise.resolve(undefined);
}
try {
// if getFreePort fails, it returns undefined
return await getFreePort(port);
} catch (e) {
console.error(e);
return undefined;
}
}

function getStartEndRange(range: string) {
if (range.endsWith('/tcp') || range.endsWith('/udp')) {
range = range.substring(0, range.length - 4);
}

const rangeValues = range.split('-');
if (rangeValues.length !== 2) {
return undefined;
}
const startRange = parseInt(rangeValues[0]);
const endRange = parseInt(rangeValues[1]);

if (isNaN(startRange) || isNaN(endRange)) {
return undefined;
}
return {
startRange,
endRange,
};
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,10 @@
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==

"@podman-desktop/[email protected].202401191125-9c1aea6":
version "0.0.202401191125-9c1aea6"
resolved "https://registry.yarnpkg.com/@podman-desktop/api/-/api-0.0.202401191125-9c1aea6.tgz#a6dd84efaa1769cc3eedde320c9d5524e2f920f1"
integrity sha512-4oMQmfCXpnQrnEihe1yn3mGZULle4a0MlXdyOZ4vlKx04e2rZK7jFI45EjU6L64pwN0bGHWLFRI12Ut2sAirHQ==
"@podman-desktop/[email protected].202401191407-14bbe67":
version "0.0.202401191407-14bbe67"
resolved "https://registry.yarnpkg.com/@podman-desktop/api/-/api-0.0.202401191407-14bbe67.tgz#0cd692a05675467253bce4269822549264b6b47a"
integrity sha512-HCSf25+cLiYp906CMaFhs+nmP7YrVM7OMfv2/AvRPATMX+DKzPO8M1hJeL7fzHBlBHXmi22c4LKLAZ6gp5LtpQ==

"@rollup/[email protected]":
version "4.9.1"
Expand Down

0 comments on commit 276cfc2

Please sign in to comment.