Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow image push to lima kubernetes cluster #4487

Merged
merged 2 commits into from Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 23 additions & 2 deletions extensions/lima/package.json
Expand Up @@ -14,6 +14,12 @@
"configuration": {
"title": "Lima",
"properties": {
"lima.binary.path": {
"type": "string",
"format": "file",
"default": "",
"description": "Custom path to limactl binary (Default is blank)"
},
"lima.type": {
"type": "string",
"default": "podman",
Expand All @@ -30,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": "vite build && node ./scripts/build.js",
Expand All @@ -45,6 +65,7 @@
"adm-zip": "^0.5.10",
"mkdirp": "^3.0.1",
"vite": "^5.0.10",
"vitest": "^1.1.0"
"vitest": "^1.1.0",
"tmp-promise": "^3.0.3"
}
}
21 changes: 20 additions & 1 deletion extensions/lima/src/extension.ts
Expand Up @@ -21,17 +21,24 @@ 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,
providerPath: string,
): 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',
Expand All @@ -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');
}
Expand Down
83 changes: 83 additions & 0 deletions 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<void> {
// 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);
}
}
}
}
}
52 changes: 52 additions & 0 deletions extensions/lima/src/limactl.ts
@@ -0,0 +1,52 @@
/**********************************************************************
* 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';

const macosExtraPath = '/usr/local/bin:/opt/homebrew/bin:/opt/local/bin';

export function getInstallationPath(): string {
const env = process.env;
if (extensionApi.env.isMac) {
if (!env.PATH) {
return macosExtraPath;
} else {
return env.PATH.concat(':').concat(macosExtraPath);
}
} else {
return env.PATH;
}
}

export function getLimactl(): string {
// If we have a custom binary path regardless if we are running Windows or not
const customBinaryPath = getCustomBinaryPath();
if (customBinaryPath) {
return customBinaryPath;
}

if (extensionApi.env.isWindows) {
return 'limactl.exe';
}
return 'limactl';
}

// Get the limactl binary path from configuration lima.binary.path
// return string or undefined
export function getCustomBinaryPath(): string | undefined {
return extensionApi.configuration.getConfiguration('lima').get('binary.path');
}