From 7cea44b225503ce7c8dc0acf5a39bcd0c3c1f441 Mon Sep 17 00:00:00 2001 From: Florent BENOIT Date: Fri, 26 Jan 2024 10:49:04 +0100 Subject: [PATCH] chore: remove custom utility functions (#29) * chore: remove custom utility functions make usage of Podman Desktop API instead Signed-off-by: Florent Benoit --- package.json | 2 +- src/minikube-installer.spec.ts | 5 +- src/minikube-installer.ts | 6 +- src/util.spec.ts | 197 +++++++++++++++++++++++++++++++++ src/util.ts | 32 ++---- yarn.lock | 8 +- 6 files changed, 219 insertions(+), 31 deletions(-) create mode 100644 src/util.spec.ts diff --git a/package.json b/package.json index c047988..68834e5 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ }, "devDependencies": { "7zip-min": "^1.4.4", - "@podman-desktop/api": "^1.0.1", + "@podman-desktop/api": "^1.6.4", "@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/parser": "^5.59.7", "@vitest/coverage-c8": "^0.31.4", diff --git a/src/minikube-installer.spec.ts b/src/minikube-installer.spec.ts index 83ebcaf..5e05678 100644 --- a/src/minikube-installer.spec.ts +++ b/src/minikube-installer.spec.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (C) 2023 Red Hat, Inc. + * Copyright (C) 2023-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. @@ -32,6 +32,9 @@ vi.mock('@podman-desktop/api', async () => { withProgress: vi.fn(), showNotification: vi.fn(), }, + env: { + isWindows: vi.fn(), + }, ProgressLocation: { APP_ICON: 1, }, diff --git a/src/minikube-installer.ts b/src/minikube-installer.ts index 1399441..e7b536f 100644 --- a/src/minikube-installer.ts +++ b/src/minikube-installer.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (C) 2023 Red Hat, Inc. + * Copyright (C) 2023-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. @@ -21,7 +21,7 @@ import * as os from 'node:os'; import * as fs from 'node:fs'; import * as path from 'node:path'; import { Octokit } from '@octokit/rest'; -import { isWindows, installBinaryToSystem } from './util'; +import { installBinaryToSystem } from './util'; import type { components } from '@octokit/openapi-types'; const githubOrganization = 'kubernetes'; @@ -133,7 +133,7 @@ export class MinikubeInstaller { fs.mkdirSync(this.storagePath); } fs.appendFileSync(destFile, Buffer.from(asset.data as unknown as ArrayBuffer)); - if (!isWindows()) { + if (!extensionApi.env.isWindows) { const stat = fs.statSync(destFile); fs.chmodSync(destFile, stat.mode | fs.constants.S_IXUSR); } diff --git a/src/util.spec.ts b/src/util.spec.ts new file mode 100644 index 0000000..b3b244d --- /dev/null +++ b/src/util.spec.ts @@ -0,0 +1,197 @@ +/********************************************************************** + * 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 + ***********************************************************************/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import * as extensionApi from '@podman-desktop/api'; +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; +import { detectMinikube, getMinikubePath, runCliCommand } from './util'; +import * as childProcess from 'node:child_process'; +import type { MinikubeInstaller } from './minikube-installer'; + +vi.mock('node:child_process'); + +vi.mock('@podman-desktop/api', async () => { + return { + window: { + showInformationMessage: vi.fn().mockReturnValue(Promise.resolve('Yes')), + showErrorMessage: vi.fn(), + withProgress: vi.fn(), + showNotification: vi.fn(), + }, + env: { + isMac: vi.fn(), + isWindows: vi.fn(), + isLinux: vi.fn(), + }, + ProgressLocation: { + APP_ICON: 1, + }, + Disposable: { + from: vi.fn(), + }, + }; +}); + +const originalProcessEnv = process.env; +beforeEach(() => { + vi.clearAllMocks(); + process.env = {}; +}); + +afterEach(() => { + process.env = originalProcessEnv; +}); + +test('getMinikubePath on macOS', async () => { + vi.mocked(extensionApi.env).isMac = true; + + const computedPath = getMinikubePath(); + expect(computedPath).toEqual('/usr/local/bin:/opt/homebrew/bin:/opt/local/bin:/opt/podman/bin'); +}); + +test('getMinikubePath on macOS with existing PATH', async () => { + const existingPATH = '/my-existing-path'; + process.env.PATH = existingPATH; + vi.mocked(extensionApi.env).isMac = true; + + const computedPath = getMinikubePath(); + expect(computedPath).toEqual(`${existingPATH}:/usr/local/bin:/opt/homebrew/bin:/opt/local/bin:/opt/podman/bin`); +}); + +test.each([ + ['macOS', true, false], + ['windows', false, true], +])('detectMinikube on %s', async (operatingSystem, isMac, isWindows) => { + vi.mocked(extensionApi.env).isMac = isMac; + vi.mocked(extensionApi.env).isWindows = isWindows; + + // spy on runCliCommand + const spawnSpy = vi.spyOn(childProcess, 'spawn'); + + const onEventMock = vi.fn(); + + onEventMock.mockImplementation((event: string, callback: (data: string) => void) => { + // delay execution + if (event === 'close') { + setTimeout(() => { + callback(0 as unknown as string); + }, 500); + } + }); + + spawnSpy.mockReturnValue({ + on: onEventMock, + stdout: { setEncoding: vi.fn(), on: vi.fn() }, + stderr: { setEncoding: vi.fn(), on: vi.fn() }, + } as unknown as childProcess.ChildProcessWithoutNullStreams); + + const fakeMinikubeInstaller = { + getAssetInfo: vi.fn(), + } as unknown as MinikubeInstaller; + + const result = await detectMinikube('', fakeMinikubeInstaller); + expect(result).toEqual('minikube'); + + // expect not called getAssetInfo + expect(fakeMinikubeInstaller.getAssetInfo).not.toBeCalled(); + + expect(spawnSpy).toBeCalled(); + // expect right parameters + if (isMac) { + expect(spawnSpy.mock.calls[0][0]).toEqual('minikube'); + } else if (isWindows) { + expect(spawnSpy.mock.calls[0][0]).toEqual('"minikube"'); + } + expect(spawnSpy.mock.calls[0][1]).toEqual(['version']); +}); + +test('runCliCommand/killProcess on macOS', async () => { + vi.mocked(extensionApi.env).isMac = true; + vi.mocked(extensionApi.env).isWindows = false; + + // spy on runCliCommand + const spawnSpy = vi.spyOn(childProcess, 'spawn'); + + const killMock = vi.fn(); + + spawnSpy.mockReturnValue({ + kill: killMock, + on: vi.fn(), + stdout: { setEncoding: vi.fn(), on: vi.fn() }, + stderr: { setEncoding: vi.fn(), on: vi.fn() }, + } as unknown as childProcess.ChildProcessWithoutNullStreams); + + const fakeToken = { + onCancellationRequested: vi.fn(), + } as unknown as extensionApi.CancellationToken; + + vi.mocked(fakeToken.onCancellationRequested).mockImplementation((callback: any): extensionApi.Disposable => { + // abort execution after 500ms + setTimeout(() => { + callback(); + }, 500); + + return extensionApi.Disposable.from({ dispose: vi.fn() }); + }); + + await expect(runCliCommand('fooCommand', [], undefined, fakeToken)).rejects.toThrow('Execution cancelled'); + + expect(spawnSpy.mock.calls[0][0]).toEqual('fooCommand'); + + expect(killMock).toBeCalled(); +}); + +test('runCliCommand/killProcess on Windows', async () => { + vi.mocked(extensionApi.env).isMac = false; + vi.mocked(extensionApi.env).isWindows = true; + + // spy on runCliCommand + const spawnSpy = vi.spyOn(childProcess, 'spawn'); + + const killMock = vi.fn(); + + spawnSpy.mockReturnValue({ + kill: killMock, + pid: 'pid123', + on: vi.fn(), + stdout: { setEncoding: vi.fn(), on: vi.fn() }, + stderr: { setEncoding: vi.fn(), on: vi.fn() }, + } as unknown as childProcess.ChildProcessWithoutNullStreams); + + const fakeToken = { + onCancellationRequested: vi.fn(), + } as unknown as extensionApi.CancellationToken; + + vi.mocked(fakeToken.onCancellationRequested).mockImplementation((callback: any): extensionApi.Disposable => { + // abort execution after 500ms + setTimeout(() => { + callback(); + }, 500); + + return extensionApi.Disposable.from({ dispose: vi.fn() }); + }); + + await expect(runCliCommand('fooCommand', [], undefined, fakeToken)).rejects.toThrow('Execution cancelled'); + + expect(spawnSpy.mock.calls[0][0]).toEqual('"fooCommand"'); + // on windows we don't use killProcess but run taskkill + expect(killMock).not.toBeCalled(); + + expect(spawnSpy.mock.calls[1]).toEqual(['taskkill', ['/pid', 'pid123', '/f', '/t']]); +}); diff --git a/src/util.ts b/src/util.ts index c506bdc..5b799c3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (C) 2023 Red Hat, Inc. + * Copyright (C) 2023-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. @@ -21,22 +21,9 @@ import * as path from 'node:path'; import type { ChildProcess } from 'node:child_process'; import { spawn } from 'node:child_process'; import * as sudo from 'sudo-prompt'; -import type * as extensionApi from '@podman-desktop/api'; +import * as extensionApi from '@podman-desktop/api'; import type { MinikubeInstaller } from './minikube-installer'; -const windows = os.platform() === 'win32'; -export function isWindows(): boolean { - return windows; -} -const mac = os.platform() === 'darwin'; -export function isMac(): boolean { - return mac; -} -const linux = os.platform() === 'linux'; -export function isLinux(): boolean { - return linux; -} - export interface SpawnResult { stdOut: string; stdErr: string; @@ -52,7 +39,7 @@ const macosExtraPath = '/usr/local/bin:/opt/homebrew/bin:/opt/local/bin:/opt/pod export function getMinikubePath(): string { const env = process.env; - if (isMac()) { + if (extensionApi.env.isMac) { if (!env.PATH) { return macosExtraPath; } else { @@ -78,7 +65,9 @@ export async function detectMinikube(pathAddition: string, installer: MinikubeIn await runCliCommand(assetInfo.name, ['version'], { env: { PATH: getMinikubePath().concat(path.delimiter).concat(pathAddition) }, }); - return pathAddition.concat(path.sep).concat(isWindows() ? assetInfo.name + '.exe' : assetInfo.name); + return pathAddition + .concat(path.sep) + .concat(extensionApi.env.isWindows ? assetInfo.name + '.exe' : assetInfo.name); } catch (e) { console.error(e); } @@ -99,9 +88,9 @@ export function runCliCommand( let env = Object.assign({}, process.env); // clone original env object // In production mode, applications don't have access to the 'user' path like brew - if (isMac() || isWindows()) { + if (extensionApi.env.isMac || extensionApi.env.isWindows) { env.PATH = getMinikubePath(); - if (isWindows()) { + if (extensionApi.env.isWindows) { // Escape any whitespaces in command command = `"${command}"`; } @@ -115,8 +104,7 @@ export function runCliCommand( env = Object.assign(env, options.env); } - const spawnProcess = spawn(command, args, { shell: isWindows(), env }); - + const spawnProcess = spawn(command, args, { shell: extensionApi.env.isWindows, env }); // if the token is cancelled, kill the process and reject the promise token?.onCancellationRequested(() => { killProcess(spawnProcess); @@ -232,7 +220,7 @@ export async function installBinaryToSystem(binaryPath: string, binaryName: stri } function killProcess(spawnProcess: ChildProcess) { - if (isWindows()) { + if (extensionApi.env.isWindows) { spawn('taskkill', ['/pid', spawnProcess.pid?.toString(), '/f', '/t']); } else { spawnProcess.kill(); diff --git a/yarn.lock b/yarn.lock index 04d5350..596ae65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -377,10 +377,10 @@ picocolors "^1.0.0" tslib "^2.5.0" -"@podman-desktop/api@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@podman-desktop/api/-/api-1.0.1.tgz#4d3caae333726db1d919e44568b2966d6e33f85d" - integrity sha512-iI4/U+AhaGz4WgcPNT4PjzhL3aHsGLXsgBSnS+BV9hitjNbcVKicCO3kHmPkmjATn0tBDCDhPkUXkI7aCHi01A== +"@podman-desktop/api@^1.6.4": + version "1.6.4" + resolved "https://registry.yarnpkg.com/@podman-desktop/api/-/api-1.6.4.tgz#f6da8228523e787f408d366f1d99d12a7b9e6924" + integrity sha512-8sxPcFvepxVM0iANq9h+QbnxAPAEE03KhrDTUp8AEzMPHZhascBSi11xhaLaDUujXVaKHUysEt0hh+0ccL479w== "@types/chai-subset@^1.3.3": version "1.3.3"