From e12640e533dc90efac11ee27af0def3ae2587d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Tue, 24 Oct 2023 11:43:41 +0200 Subject: [PATCH] feat: allow image push to lima kubernetes cluster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Anders F Björklund --- extensions/lima/package.json | 17 +++++- extensions/lima/src/extension.ts | 21 ++++++- extensions/lima/src/image-handler.ts | 83 ++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 extensions/lima/src/image-handler.ts diff --git a/extensions/lima/package.json b/extensions/lima/package.json index 3ab9d3b776dd8..0e26a967a8458 100644 --- a/extensions/lima/package.json +++ b/extensions/lima/package.json @@ -36,7 +36,21 @@ "description": "Instance name (default is same name as type)" } } - } + }, + "menus": { + "dashboard/image": [ + { + "command": "lima.image.move", + "title": "Push image to Lima cluster" + } + ] + }, + "commands": [ + { + "command": "lima.image.move", + "title": "Lima: Move image to cluster..." + } + ] }, "scripts": { "build": "tsc && node ./scripts/build.js", @@ -50,6 +64,7 @@ "devDependencies": { "7zip-min": "^1.4.4", "mkdirp": "^3.0.1", + "tmp-promise": "^3.0.3", "zip-local": "^0.3.5" } } diff --git a/extensions/lima/src/extension.ts b/extensions/lima/src/extension.ts index 844c4c036cec3..773054d7eda50 100644 --- a/extensions/lima/src/extension.ts +++ b/extensions/lima/src/extension.ts @@ -21,10 +21,16 @@ import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs'; -import { configuration } from '@podman-desktop/api'; +import { configuration, ProgressLocation } from '@podman-desktop/api'; +import { getLimactl } from './limactl'; +import { ImageHandler } from './image-handler'; type limaProviderType = 'docker' | 'podman' | 'kubernetes'; +const LIMA_MOVE_IMAGE_COMMAND = 'lima.image.move'; + +const imageHandler = new ImageHandler(); + function registerProvider( extensionContext: extensionApi.ExtensionContext, provider: extensionApi.Provider, @@ -32,6 +38,7 @@ function registerProvider( ): void { let providerState: extensionApi.ProviderConnectionStatus = 'unknown'; const providerType: limaProviderType = configuration.getConfiguration('lima').get('type'); + const instanceName: string = configuration.getConfiguration('lima').get('name') || providerType; if (providerType === 'podman' || providerType === 'docker') { const connection: extensionApi.ContainerProviderConnection = { name: 'Lima', @@ -57,6 +64,18 @@ function registerProvider( const disposable = provider.registerKubernetesProviderConnection(connection); provider.updateStatus('started'); extensionContext.subscriptions.push(disposable); + extensionContext.subscriptions.push( + extensionApi.commands.registerCommand(LIMA_MOVE_IMAGE_COMMAND, async image => { + return extensionApi.window.withProgress( + { location: ProgressLocation.TASK_WIDGET, title: `Loading ${image.name} to lima.` }, + async progress => { + await imageHandler.moveImage(image, instanceName, getLimactl()); + // Mark the task as completed + progress.report({ increment: -1 }); + }, + ); + }), + ); } console.log('Lima extension is active'); } diff --git a/extensions/lima/src/image-handler.ts b/extensions/lima/src/image-handler.ts new file mode 100644 index 0000000000000..a8bb7248bd6e1 --- /dev/null +++ b/extensions/lima/src/image-handler.ts @@ -0,0 +1,83 @@ +/********************************************************************** + * Copyright (C) 2023 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 * as extensionApi from '@podman-desktop/api'; +import { tmpName } from 'tmp-promise'; +import { getInstallationPath } from './limactl'; +import * as fs from 'node:fs'; + +type ImageInfo = { engineId: string; name?: string; tag?: string }; + +// Handle the image move command when moving from Podman or Docker to Lima +export class ImageHandler { + // Move image from Podman or Docker to Lima + async moveImage(image: ImageInfo, instanceName: string, limactl: string): Promise { + // If there's no image name passed in, we can't do anything + if (!image.name) { + throw new Error('Image selection not supported yet'); + } + + // Only proceed if instance was given + if (instanceName) { + let name = image.name; + let filename: string; + const env = Object.assign({}, process.env); + + // Create a name:tag string for the image + if (image.tag) { + name = name + ':' + image.tag; + } + + env.PATH = getInstallationPath(); + try { + // Create a temporary file to store the image + filename = await tmpName(); + + // Save the image to the temporary file + await extensionApi.containerEngine.saveImage(image.engineId, name, filename); + + // Run the Lima commands to push the image to the cluster + const { stdout: tempname } = await extensionApi.process.exec(limactl, ['shell', instanceName, 'mktemp'], { + env: env, + }); + await extensionApi.process.exec(limactl, ['copy', filename, instanceName + ':' + tempname], { env: env }); + const loadCommand = ['sudo', 'ctr', '-n=k8s.io', 'images', 'import']; // or "sudo nerdctl -n k8s.io load -i" + await extensionApi.process.exec(limactl, ['shell', instanceName, ...loadCommand, tempname], { env: env }); + await extensionApi.process.exec(limactl, ['shell', instanceName, 'rm', tempname], { env: env }); + + // Show a dialog to the user that the image was pushed + // TODO: Change this to taskbar notification when implemented + await extensionApi.window.showInformationMessage( + `Image ${image.name} pushed to Lima instance: ${instanceName}`, + ); + } catch (err) { + // Show a dialog error to the user that the image was not pushed + await extensionApi.window.showErrorMessage( + `Unable to push image ${image.name} to Lima instance: ${instanceName}. Error: ${err}`, + ); + + // Throw the errors to the console aswell + throw new Error(`Unable to push image to Lima instance: ${err}`); + } finally { + // Remove the temporary file if one was created + if (filename !== undefined) { + await fs.promises.rm(filename); + } + } + } + } +}