diff --git a/packages/backend/src/managers/applicationManager.spec.ts b/packages/backend/src/managers/applicationManager.spec.ts index 871f86393..2f1af8417 100644 --- a/packages/backend/src/managers/applicationManager.spec.ts +++ b/packages/backend/src/managers/applicationManager.spec.ts @@ -318,6 +318,7 @@ describe('pullApplication', () => { labels: { [LABEL_RECIPE_ID]: 'recipe1', }, + abortController: expect.anything(), }, ); expect(mocks.logUsageMock).toHaveBeenNthCalledWith(1, 'recipe.pull', { @@ -326,6 +327,85 @@ describe('pullApplication', () => { durationSeconds: 99, }); }); + + test('pullApplication should clone repository and call downloadModelMain and fail on buildImage', async () => { + mockForPullApplication({ + recipeFolderExists: false, + }); + mocks.buildImageMock.mockRejectedValue(new Error('Build failed')); + mocks.listPodsMock.mockResolvedValue([]); + vi.spyOn(podman, 'isQEMUMachine').mockResolvedValue(false); + vi.spyOn(modelsManager, 'isModelOnDisk').mockReturnValue(false); + vi.spyOn(modelsManager, 'uploadModelToPodmanMachine').mockResolvedValue('path'); + mocks.performDownloadMock.mockResolvedValue('path'); + const recipe: Recipe = { + id: 'recipe1', + name: 'Recipe 1', + categories: [], + description: '', + ref: '000000', + readme: '', + repository: 'repo', + }; + const model: ModelInfo = { + id: 'model1', + description: '', + hw: '', + license: '', + name: 'Model 1', + registry: '', + url: '', + memory: 1000, + }; + mocks.inspectContainerMock.mockResolvedValue({ + State: { + Running: true, + }, + }); + vi.spyOn(utils, 'getDurationSecondsSince').mockReturnValue(99); + let error: unknown = undefined; + try { + await manager.pullApplication(recipe, model); + } catch (err: unknown) { + error = err; + } + expect(error).toBeDefined(); + const gitCloneOptions = { + repository: 'repo', + ref: '000000', + targetDirectory: '\\home\\user\\aistudio\\recipe1', + }; + if (process.platform === 'win32') { + expect(processCheckoutMock).toHaveBeenNthCalledWith(1, gitCloneOptions); + } else { + gitCloneOptions.targetDirectory = '/home/user/aistudio/recipe1'; + expect(processCheckoutMock).toHaveBeenNthCalledWith(1, gitCloneOptions); + } + expect(mocks.performDownloadMock).toHaveBeenCalledOnce(); + expect(mocks.buildImageMock).toHaveBeenCalledOnce(); + expect(mocks.buildImageMock).toHaveBeenCalledWith( + `${gitCloneOptions.targetDirectory}${path.sep}contextdir1`, + expect.anything(), + { + containerFile: 'Containerfile', + tag: 'recipe1-container1:latest', + labels: { + [LABEL_RECIPE_ID]: 'recipe1', + }, + abortController: expect.anything(), + }, + ); + expect(mocks.logErrorMock).toHaveBeenNthCalledWith( + 1, + 'recipe.pull', + expect.objectContaining({ + 'recipe.id': 'recipe1', + 'recipe.name': 'Recipe 1', + durationSeconds: 99, + message: 'error pulling application', + }), + ); + }); test('pullApplication should not download model if already on disk', async () => { mockForPullApplication({ recipeFolderExists: true, diff --git a/packages/backend/src/managers/applicationManager.ts b/packages/backend/src/managers/applicationManager.ts index 31a9e93e4..53c6f5f79 100644 --- a/packages/backend/src/managers/applicationManager.ts +++ b/packages/backend/src/managers/applicationManager.ts @@ -409,50 +409,64 @@ export class ApplicationManager extends Publisher implements const imageInfoList: ImageInfo[] = []; // Promise all the build images - await Promise.all( - containers.map(container => { - const task = containerTasks[container.name]; + const abortController = new AbortController(); + try { + await Promise.all( + containers.map(container => { + const task = containerTasks[container.name]; - // We use the parent directory of our configFile as the rootdir, then we append the contextDir provided - const context = path.join(getParentDirectory(configPath), container.contextdir); - console.log(`Application Manager using context ${context} for container ${container.name}`); + // We use the parent directory of our configFile as the rootdir, then we append the contextDir provided + const context = path.join(getParentDirectory(configPath), container.contextdir); + console.log(`Application Manager using context ${context} for container ${container.name}`); - // Ensure the context provided exist otherwise throw an Error - if (!fs.existsSync(context)) { - task.error = 'The context provided does not exist.'; - this.taskRegistry.updateTask(task); - throw new Error('Context configured does not exist.'); - } + // Ensure the context provided exist otherwise throw an Error + if (!fs.existsSync(context)) { + task.error = 'The context provided does not exist.'; + this.taskRegistry.updateTask(task); + throw new Error('Context configured does not exist.'); + } - const imageTag = this.getImageTag(recipe, container); - const buildOptions = { - containerFile: container.containerfile, - tag: imageTag, - labels: { - [LABEL_RECIPE_ID]: labels !== undefined && 'recipe-id' in labels ? labels['recipe-id'] : undefined, - }, - }; - - return containerEngine - .buildImage( - context, - (event, data) => { - // todo: do something with the event - if (event === 'error' || (event === 'finish' && data !== '')) { - console.error('Something went wrong while building the image: ', data); - task.error = `Something went wrong while building the image: ${data}`; - this.taskRegistry.updateTask(task); - } + const imageTag = this.getImageTag(recipe, container); + const buildOptions = { + containerFile: container.containerfile, + tag: imageTag, + labels: { + [LABEL_RECIPE_ID]: labels !== undefined && 'recipe-id' in labels ? labels['recipe-id'] : undefined, }, - buildOptions, - ) - .catch((err: unknown) => { - task.error = `Something went wrong while building the image: ${String(err)}`; - this.taskRegistry.updateTask(task); - throw new Error(`Something went wrong while building the image: ${String(err)}`); - }); - }), - ); + abortController: abortController, + }; + + let error = false; + return containerEngine + .buildImage( + context, + (event, data) => { + // todo: do something with the event + if (event === 'error' || (event === 'finish' && data !== '')) { + console.error('Something went wrong while building the image: ', data); + task.error = `Something went wrong while building the image: ${data}`; + this.taskRegistry.updateTask(task); + error = true; + } + }, + buildOptions, + ) + .catch((err: unknown) => { + task.error = `Something went wrong while building the image: ${String(err)}`; + this.taskRegistry.updateTask(task); + throw new Error(`Something went wrong while building the image: ${String(err)}`); + }) + .then(() => { + if (error) { + throw new Error(`Something went wrong while building the image: ${imageTag}`); + } + }); + }), + ); + } catch (err: unknown) { + abortController.abort(); + throw err; + } // after image are built we return their data const images = await containerEngine.listImages();