Skip to content

Commit

Permalink
feat: use the new function to create a container within a pod
Browse files Browse the repository at this point in the history
Signed-off-by: lstocchi <[email protected]>
  • Loading branch information
lstocchi committed Mar 11, 2024
1 parent 40d97c3 commit ecacc3c
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 61 deletions.
55 changes: 31 additions & 24 deletions packages/backend/src/managers/applicationManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { AIConfig, ContainerConfig } from '../models/AIConfig';
import * as portsUtils from '../utils/ports';
import { goarch } from '../utils/arch';
import * as utils from '../utils/utils';
import * as pathUtils from '../utils/pathUtils';
import type { Webview, TelemetryLogger, PodInfo } from '@podman-desktop/api';
import type { CatalogManager } from './catalogManager';
import type { LocalRepositoryRegistry } from '../registries/LocalRepositoryRegistry';
Expand All @@ -50,10 +51,8 @@ const mocks = vi.hoisted(() => {
getImageInspectMock: vi.fn(),
createPodMock: vi.fn(),
createContainerMock: vi.fn(),
replicatePodmanContainerMock: vi.fn(),
startContainerMock: vi.fn(),
startPod: vi.fn(),
deleteContainerMock: vi.fn(),
inspectContainerMock: vi.fn(),
logUsageMock: vi.fn(),
logErrorMock: vi.fn(),
Expand Down Expand Up @@ -109,10 +108,8 @@ vi.mock('@podman-desktop/api', () => ({
getImageInspect: mocks.getImageInspectMock,
createPod: mocks.createPodMock,
createContainer: mocks.createContainerMock,
replicatePodmanContainer: mocks.replicatePodmanContainerMock,
startContainer: mocks.startContainerMock,
startPod: mocks.startPod,
deleteContainer: mocks.deleteContainerMock,
inspectContainer: mocks.inspectContainerMock,
pullImage: mocks.pullImageMock,
stopContainer: mocks.stopContainerMock,
Expand Down Expand Up @@ -1055,35 +1052,45 @@ describe('createAndAddContainersToPod', () => {
modelService: false,
ports: ['8080', '8081'],
};
test('check that after the creation and copy inside the pod, the container outside the pod is actually deleted', async () => {
const imageInfo2: ImageInfo = {
id: 'id2',
appName: 'appName2',
modelService: true,
ports: ['8085'],
};
test('check that containers are correctly created', async () => {
mocks.createContainerMock.mockResolvedValue({
id: 'container-1',
});
vi.spyOn(pathUtils, 'getMappedPathInPodmanMachine').mockReturnValue('mapped');
vi.spyOn(manager, 'getRandomName').mockReturnValue('name');
await manager.createAndAddContainersToPod(pod, [imageInfo1], 'path');
expect(mocks.createContainerMock).toBeCalledWith('engine', {
await manager.createAndAddContainersToPod(pod, [imageInfo1, imageInfo2], 'path');
expect(mocks.createContainerMock).toHaveBeenNthCalledWith(1, 'engine', {
Image: 'id',
Detach: true,
HostConfig: {
AutoRemove: true,
},
Env: [],
Env: ['MODEL_ENDPOINT=http://localhost:8085'],
start: false,
name: 'name',
pod: 'id',
});
expect(mocks.replicatePodmanContainerMock).toBeCalledWith(
{
id: 'container-1',
engineId: 'engine',
},
{
engineId: 'engine',
},
{
pod: 'id',
name: 'name',
expect(mocks.createContainerMock).toHaveBeenNthCalledWith(2, 'engine', {
Image: 'id2',
Detach: true,
Env: ['MODEL_PATH=/path'],
start: false,
name: 'name',
pod: 'id',
HostConfig: {
Mounts: [
{
Mode: 'Z',
Source: 'mapped',
Target: '/path',
Type: 'bind',
},
],
},
);
expect(mocks.deleteContainerMock).toBeCalledWith('engine', 'container-1');
});
});
});

Expand Down
54 changes: 17 additions & 37 deletions packages/backend/src/managers/applicationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,12 @@ import type { Recipe } from '@shared/src/models/IRecipe';
import type { GitCloneInfo, GitManager } from './gitManager';
import fs from 'fs';
import * as path from 'node:path';
import {
type PodCreatePortOptions,
containerEngine,
type TelemetryLogger,
type PodInfo,
type Webview,
} from '@podman-desktop/api';
import { containerEngine } from '@podman-desktop/api';
import type { HostConfig, PodCreatePortOptions, TelemetryLogger, PodInfo, Webview } from '@podman-desktop/api';
import type { AIConfig, AIConfigFile, ContainerConfig } from '../models/AIConfig';
import { parseYamlFile } from '../models/AIConfig';
import type { Task } from '@shared/src/models/ITask';
import { getParentDirectory } from '../utils/pathUtils';
import { getMappedPathInPodmanMachine, getParentDirectory } from '../utils/pathUtils';
import type { ModelInfo } from '@shared/src/models/IModelInfo';
import type { ModelsManager } from './modelsManager';
import { getPortsInfo } from '../utils/ports';
Expand Down Expand Up @@ -259,62 +254,47 @@ export class ApplicationManager extends Publisher<ApplicationState[]> {
const containers: ContainerAttachedInfo[] = [];
await Promise.all(
images.map(async image => {
let hostConfig: unknown;
let hostConfig: HostConfig;
let envs: string[] = [];
// if it's a model service we mount the model as a volume
if (image.modelService) {
const modelName = path.basename(modelPath);
const mappedMountPath = getMappedPathInPodmanMachine(modelPath);
hostConfig = {
AutoRemove: true,
Mounts: [
{
Target: `/${modelName}`,
Source: modelPath,
Source: mappedMountPath,
Type: 'bind',
Mode: 'Z',
},
],
};
envs = [`MODEL_PATH=/${modelName}`];
} else {
hostConfig = {
AutoRemove: true,
};
// TODO: remove static port
const modelService = images.find(image => image.modelService);
if (modelService && modelService.ports.length > 0) {
const endPoint = `http://localhost:${modelService.ports[0]}`;
envs = [`MODEL_ENDPOINT=${endPoint}`];
}
}
const createdContainer = await containerEngine.createContainer(podInfo.engineId, {

const podifiedName = this.getRandomName(`${image.appName}-podified`);
await containerEngine.createContainer(podInfo.engineId, {
Image: image.id,
name: podifiedName,
Detach: true,
HostConfig: hostConfig,
Env: envs,
start: false,
pod: podInfo.Id,
});
containers.push({
name: podifiedName,
modelService: image.modelService,
ports: image.ports,
});

// now, for each container, put it in the pod
if (createdContainer) {
const podifiedName = this.getRandomName(`${image.appName}-podified`);
await containerEngine.replicatePodmanContainer(
{
id: createdContainer.id,
engineId: podInfo.engineId,
},
{ engineId: podInfo.engineId },
{ pod: podInfo.Id, name: podifiedName },
);
containers.push({
name: podifiedName,
modelService: image.modelService,
ports: image.ports,
});
// remove the external container
await containerEngine.deleteContainer(podInfo.engineId, createdContainer.id);
} else {
throw new Error(`failed at creating container for image ${image.id}`);
}
}),
);
return containers;
Expand Down
46 changes: 46 additions & 0 deletions packages/backend/src/utils/pathUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import { vi, describe, test, expect, beforeEach } from 'vitest';
import { getMappedPathInPodmanMachine } from './pathUtils';
import * as podmanDesktopApi from '@podman-desktop/api';

vi.mock('@podman-desktop/api', async () => {
return {
env: {
isWindows: false,
},
};
});

beforeEach(() => {
vi.resetAllMocks();
});

describe('getMappedPathInPodmanMachine', () => {
test('return original path if env is not Windows', async () => {
vi.mocked(podmanDesktopApi.env).isWindows = false;
const result = getMappedPathInPodmanMachine('path');
expect(result).equals('path');
});
test('return original path if env is Windows', async () => {
vi.mocked(podmanDesktopApi.env).isWindows = true;
const result = getMappedPathInPodmanMachine('C:\\dir1\\dir2\\file');
expect(result).equals('/mnt/c/dir1/dir2/file');
});
});
9 changes: 9 additions & 0 deletions packages/backend/src/utils/pathUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import { env } from '@podman-desktop/api';
import path from 'path';

export function getParentDirectory(filePath: string): string {
Expand All @@ -25,3 +26,11 @@ export function getParentDirectory(filePath: string): string {
// Get the directory name using path.dirname
return path.dirname(normalizedPath);
}

export function getMappedPathInPodmanMachine(path: string): string {
if (env.isWindows) {
path = path.replace(':', '').replace(/\\/g, '/').toLowerCase();
return `/mnt/${path}`;
}
return path;
}

0 comments on commit ecacc3c

Please sign in to comment.