diff --git a/packages/backend/src/managers/applicationManager.spec.ts b/packages/backend/src/managers/applicationManager.spec.ts index e6f34d9f9..1fa27be15 100644 --- a/packages/backend/src/managers/applicationManager.spec.ts +++ b/packages/backend/src/managers/applicationManager.spec.ts @@ -244,6 +244,7 @@ describe('pullApplication', () => { mockForPullApplication({ recipeFolderExists: false, }); + mocks.listPodsMock.mockResolvedValue([]); vi.spyOn(modelsManager, 'isModelOnDisk').mockReturnValue(false); mocks.performDownloadMock.mockResolvedValue('path'); const recipe: Recipe = { @@ -306,6 +307,7 @@ describe('pullApplication', () => { mockForPullApplication({ recipeFolderExists: true, }); + mocks.listPodsMock.mockResolvedValue([]); vi.spyOn(modelsManager, 'isModelOnDisk').mockReturnValue(false); mocks.performDownloadMock.mockResolvedValue('path'); const recipe: Recipe = { @@ -334,6 +336,7 @@ describe('pullApplication', () => { mockForPullApplication({ recipeFolderExists: true, }); + mocks.listPodsMock.mockResolvedValue([]); vi.spyOn(modelsManager, 'isModelOnDisk').mockReturnValue(true); vi.spyOn(modelsManager, 'getLocalModelPath').mockReturnValue('path'); const recipe: Recipe = { diff --git a/packages/backend/src/managers/applicationManager.ts b/packages/backend/src/managers/applicationManager.ts index 7edf13db8..389f7c82b 100644 --- a/packages/backend/src/managers/applicationManager.ts +++ b/packages/backend/src/managers/applicationManager.ts @@ -78,6 +78,8 @@ export class ApplicationManager { // Map recipeId => EnvironmentState #environments: Map; + protectTasks: Set = new Set(); + constructor( private appUserDirectory: string, private git: GitManager, @@ -137,6 +139,11 @@ export class ApplicationManager { taskUtil, ); + // first delete any existing pod with matching labels + if (await this.hasEnvironmentPod(recipe.id)) { + await this.deleteEnvironment(recipe.id); + } + // create a pod containing all the containers to run the application const podInfo = await this.createApplicationPod(recipe, model, images, modelPath, taskUtil); @@ -607,6 +614,16 @@ export class ApplicationManager { this.podmanConnection.onMachineStop(() => { // Podman Machine has been stopped, we consider all recipe pods are stopped + + for (const recipeId of this.#environments.keys()) { + const taskUtil = new RecipeStatusUtils(recipeId, this.recipeStatusRegistry); + taskUtil.setTask({ + id: `stopped-${recipeId}`, + state: 'success', + name: `Application stopped manually`, + }); + } + this.#environments.clear(); this.sendEnvironmentState(); }); @@ -649,6 +666,13 @@ export class ApplicationManager { } this.#environments.delete(recipeId); this.sendEnvironmentState(); + + const taskUtil = new RecipeStatusUtils(recipeId, this.recipeStatusRegistry); + taskUtil.setTask({ + id: `stopped-${recipeId}`, + state: 'success', + name: `Application stopped manually`, + }); } forgetPodById(podId: string) { @@ -665,6 +689,18 @@ export class ApplicationManager { } this.#environments.delete(recipeId); this.sendEnvironmentState(); + + const protect = this.protectTasks.has(podId); + if (!protect) { + const taskUtil = new RecipeStatusUtils(recipeId, this.recipeStatusRegistry); + taskUtil.setTask({ + id: `stopped-${recipeId}`, + state: 'success', + name: `Application stopped manually`, + }); + } else { + this.protectTasks.delete(podId); + } } updateEnvironmentState(recipeId: string, state: EnvironmentState): void { @@ -700,45 +736,31 @@ export class ApplicationManager { const envPod = await this.getEnvironmentPod(recipeId); try { await containerEngine.stopPod(envPod.engineId, envPod.Id); - taskUtil.setTask({ - id: `stopping-${recipeId}`, - state: 'success', - name: `Application stopped`, - }); } catch (err: unknown) { // continue when the pod is already stopped if (!String(err).includes('pod already stopped')) { taskUtil.setTask({ id: `stopping-${recipeId}`, state: 'error', - error: 'error stopping the pod. Please try to remove the pod manually', + error: 'error stopping the pod. Please try to stop and remove the pod manually', name: `Error stopping application`, }); throw err; } - taskUtil.setTask({ - id: `stopping-${recipeId}`, - state: 'success', - name: `Application stopped`, - }); } - taskUtil.setTask({ - id: `removing-${recipeId}`, - state: 'loading', - name: `Removing application`, - }); + this.protectTasks.add(envPod.Id); await containerEngine.removePod(envPod.engineId, envPod.Id); taskUtil.setTask({ - id: `removing-${recipeId}`, + id: `stopping-${recipeId}`, state: 'success', - name: `Application removed`, + name: `Application stopped`, }); } catch (err: unknown) { taskUtil.setTask({ id: `removing-${recipeId}`, state: 'error', error: 'error removing the pod. Please try to remove the pod manually', - name: `Error removing application`, + name: `Error stopping application`, }); throw err; } @@ -754,16 +776,25 @@ export class ApplicationManager { } async getEnvironmentPod(recipeId: string): Promise { + const envPod = await this.queryPod(recipeId); + if (!envPod) { + throw new Error(`no pod found with recipe Id ${recipeId}`); + } + return envPod; + } + + async hasEnvironmentPod(recipeId: string): Promise { + const envPod = await this.queryPod(recipeId); + return !!envPod; + } + + async queryPod(recipeId: string): Promise { if (!containerEngine.listPods || !containerEngine.stopPod || !containerEngine.removePod) { // TODO(feloy) this check can be safely removed when podman desktop 1.8 is released // and the extension minimal version is set to 1.8 return; } const pods = await containerEngine.listPods(); - const envPod = pods.find(pod => LABEL_RECIPE_ID in pod.Labels && pod.Labels[LABEL_RECIPE_ID] === recipeId); - if (!envPod) { - throw new Error(`no pod found with recipe Id ${recipeId}`); - } - return envPod; + return pods.find(pod => LABEL_RECIPE_ID in pod.Labels && pod.Labels[LABEL_RECIPE_ID] === recipeId); } } diff --git a/packages/backend/src/studio-api-impl.ts b/packages/backend/src/studio-api-impl.ts index a1a1193d6..ed053ead7 100644 --- a/packages/backend/src/studio-api-impl.ts +++ b/packages/backend/src/studio-api-impl.ts @@ -161,7 +161,7 @@ export class StudioApiImpl implements StudioAPI { // Do not wait on the promise as the api would probably timeout before the user answer. podmanDesktopApi.window .showWarningMessage( - `Delete the environment "${recipe.name}"? This will delete the containers running the application and model.`, + `Stop the environment "${recipe.name}"? This will delete the containers running the application and model.`, 'Confirm', 'Cancel', ) diff --git a/packages/frontend/src/lib/EnvironmentActions.svelte b/packages/frontend/src/lib/EnvironmentActions.svelte index 3d602b45d..ba323b676 100644 --- a/packages/frontend/src/lib/EnvironmentActions.svelte +++ b/packages/frontend/src/lib/EnvironmentActions.svelte @@ -1,5 +1,5 @@ -{#if task} -
+
+ {#if task} -
-{/if} + {:else if !!object.envState.pod} + Pod running + {/if} +