From e1c1e00a2bbc843d499ea37d3f390d7f89bfdb81 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 2 Jun 2022 18:40:30 -0400 Subject: [PATCH] Fix downloading cluster specific kubectl (#5399) --- src/common/__tests__/cluster-store.test.ts | 6 +++ ...rectory-for-bundled-binaries.injectable.ts | 13 ------ src/common/vars.ts | 16 +++---- .../base-bundled-binaries-dir.injectable.ts | 18 ++++++++ ...led-binaries-normalized-arch.injectable.ts | 27 ++++++++++++ .../vars/bundled-resources-dir.injectable.ts | 22 ++++++++++ .../vars/normalized-platform.injectable.ts | 24 ++++++++++ src/main/__test__/cluster.test.ts | 6 +++ src/main/__test__/kube-auth-proxy.test.ts | 6 +++ src/main/__test__/kubeconfig-manager.test.ts | 6 +++ .../__test__/kubeconfig-sync.test.ts | 6 +++ src/main/getDiForUnitTesting.ts | 9 ++-- .../create-kube-auth-proxy.injectable.ts | 4 +- src/main/kubectl/binary-name.injectable.ts | 19 ++++++++ .../kubectl/bundled-binary-path.injectable.ts | 18 ++++++++ src/main/kubectl/create-kubectl.injectable.ts | 21 ++++++--- src/main/kubectl/kubectl.ts | 44 ++++++++++--------- .../kubectl/normalized-arch.injectable.ts | 27 ++++++++++++ src/main/router/router.test.ts | 6 +++ .../local-shell-session.ts | 4 +- src/main/shell-session/shell-session.ts | 2 +- .../__tests__/delete-cluster-dialog.test.tsx | 10 ++++- 22 files changed, 251 insertions(+), 63 deletions(-) delete mode 100644 src/common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable.ts create mode 100644 src/common/vars/base-bundled-binaries-dir.injectable.ts create mode 100644 src/common/vars/bundled-binaries-normalized-arch.injectable.ts create mode 100644 src/common/vars/bundled-resources-dir.injectable.ts create mode 100644 src/common/vars/normalized-platform.injectable.ts create mode 100644 src/main/kubectl/binary-name.injectable.ts create mode 100644 src/main/kubectl/bundled-binary-path.injectable.ts create mode 100644 src/main/kubectl/normalized-arch.injectable.ts diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index 4f836d5566..82d7b97638 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -22,6 +22,9 @@ import getConfigurationFileModelInjectable from "../get-configuration-file-model import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.injectable"; import assert from "assert"; import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable"; +import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable"; +import normalizedPlatformInjectable from "../vars/normalized-platform.injectable"; console = new Console(stdout, stderr); @@ -84,6 +87,9 @@ describe("cluster-store", () => { mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); mainDi.override(directoryForTempInjectable, () => "some-temp-directory"); + mainDi.override(kubectlBinaryNameInjectable, () => "kubectl"); + mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + mainDi.override(normalizedPlatformInjectable, () => "darwin"); mainDi.permitSideEffects(getConfigurationFileModelInjectable); mainDi.permitSideEffects(appVersionInjectable); diff --git a/src/common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable.ts b/src/common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable.ts deleted file mode 100644 index 9c25407d4d..0000000000 --- a/src/common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { baseBinariesDir } from "../../vars"; - -const directoryForBundledBinariesInjectable = getInjectable({ - id: "directory-for-bundled-binaries", - instantiate: () => baseBinariesDir.get(), -}); - -export default directoryForBundledBinariesInjectable; diff --git a/src/common/vars.ts b/src/common/vars.ts index efbf32ee67..1d47eac9d8 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -57,6 +57,9 @@ export const defaultThemeId: ThemeId = "lens-dark"; export const defaultFontSize = 12; export const defaultTerminalFontFamily = "RobotoMono"; export const defaultEditorFontFamily = "RobotoMono"; +/** + * @deprecated use `di.inject(normalizedPlatformInjectable)` instead + */ export const normalizedPlatform = (() => { switch (process.platform) { case "darwin": @@ -69,6 +72,9 @@ export const normalizedPlatform = (() => { throw new Error(`platform=${process.platform} is unsupported`); } })(); +/** + * @deprecated use `di.inject(bundledBinariesNormalizedArchInjectable)` instead + */ export const normalizedArch = (() => { switch (process.arch) { case "arm64": @@ -119,16 +125,6 @@ export const helmBinaryName = getBinaryName("helm"); */ export const helmBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), helmBinaryName)); -/** - * @deprecated for being explicit side effect. - */ -export const kubectlBinaryName = getBinaryName("kubectl"); - -/** - * @deprecated for being explicit side effect. - */ -export const kubectlBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), kubectlBinaryName)); - // Apis export const apiPrefix = "/api"; // local router apis export const apiKubePrefix = "/api-kube"; // k8s cluster apis diff --git a/src/common/vars/base-bundled-binaries-dir.injectable.ts b/src/common/vars/base-bundled-binaries-dir.injectable.ts new file mode 100644 index 0000000000..41f5a5e0a3 --- /dev/null +++ b/src/common/vars/base-bundled-binaries-dir.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import path from "path"; +import bundledBinariesNormalizedArchInjectable from "./bundled-binaries-normalized-arch.injectable"; +import bundledResourcesDirectoryInjectable from "./bundled-resources-dir.injectable"; + +const baseBundeledBinariesDirectoryInjectable = getInjectable({ + id: "base-bundeled-binaries-directory", + instantiate: (di) => path.join( + di.inject(bundledResourcesDirectoryInjectable), + di.inject(bundledBinariesNormalizedArchInjectable), + ), +}); + +export default baseBundeledBinariesDirectoryInjectable; diff --git a/src/common/vars/bundled-binaries-normalized-arch.injectable.ts b/src/common/vars/bundled-binaries-normalized-arch.injectable.ts new file mode 100644 index 0000000000..3c838d626c --- /dev/null +++ b/src/common/vars/bundled-binaries-normalized-arch.injectable.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; + +const bundledBinariesNormalizedArchInjectable = getInjectable({ + id: "bundled-binaries-normalized-arch", + instantiate: () => { + switch (process.arch) { + case "arm64": + return "arm64"; + case "x64": + case "amd64": + return "x64"; + case "386": + case "x32": + case "ia32": + return "ia32"; + default: + throw new Error(`arch=${process.arch} is unsupported`); + } + }, + causesSideEffects: true, +}); + +export default bundledBinariesNormalizedArchInjectable; diff --git a/src/common/vars/bundled-resources-dir.injectable.ts b/src/common/vars/bundled-resources-dir.injectable.ts new file mode 100644 index 0000000000..a73ff98e78 --- /dev/null +++ b/src/common/vars/bundled-resources-dir.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import path from "path"; +import isProductionInjectable from "./is-production.injectable"; +import normalizedPlatformInjectable from "./normalized-platform.injectable"; + +const bundledResourcesDirectoryInjectable = getInjectable({ + id: "bundled-resources-directory", + instantiate: (di) => { + const isProduction = di.inject(isProductionInjectable); + const normalizedPlatform = di.inject(normalizedPlatformInjectable); + + return isProduction + ? process.resourcesPath + : path.join(process.cwd(), "binaries", "client", normalizedPlatform); + }, +}); + +export default bundledResourcesDirectoryInjectable; diff --git a/src/common/vars/normalized-platform.injectable.ts b/src/common/vars/normalized-platform.injectable.ts new file mode 100644 index 0000000000..7177678407 --- /dev/null +++ b/src/common/vars/normalized-platform.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; + +const normalizedPlatformInjectable = getInjectable({ + id: "normalized-platform", + instantiate: () => { + switch (process.platform) { + case "darwin": + return "darwin"; + case "linux": + return "linux"; + case "win32": + return "windows"; + default: + throw new Error(`platform=${process.platform} is unsupported`); + } + }, + causesSideEffects: true, +}); + +export default normalizedPlatformInjectable; diff --git a/src/main/__test__/cluster.test.ts b/src/main/__test__/cluster.test.ts index 0d76513a8f..4086adefbd 100644 --- a/src/main/__test__/cluster.test.ts +++ b/src/main/__test__/cluster.test.ts @@ -21,6 +21,9 @@ import type { ClusterContextHandler } from "../context-handler/context-handler"; import { parse } from "url"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; +import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; +import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; console = new Console(process.stdout, process.stderr); // fix mockFS @@ -59,6 +62,9 @@ describe("create clusters", () => { di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(directoryForTempInjectable, () => "some-directory-for-temp"); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true)); di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ])); di.override(createContextHandlerInjectable, () => (cluster) => ({ diff --git a/src/main/__test__/kube-auth-proxy.test.ts b/src/main/__test__/kube-auth-proxy.test.ts index d08dadaa4e..298c8f351b 100644 --- a/src/main/__test__/kube-auth-proxy.test.ts +++ b/src/main/__test__/kube-auth-proxy.test.ts @@ -59,6 +59,9 @@ import getConfigurationFileModelInjectable from "../../common/get-configuration- import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.injectable"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; +import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; +import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; console = new Console(stdout, stderr); @@ -105,6 +108,9 @@ describe("kube auth proxy tests", () => { di.override(directoryForTempInjectable, () => "some-directory-for-temp"); di.override(spawnInjectable, () => mockSpawn); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(appVersionInjectable); diff --git a/src/main/__test__/kubeconfig-manager.test.ts b/src/main/__test__/kubeconfig-manager.test.ts index 436a815686..604608bb25 100644 --- a/src/main/__test__/kubeconfig-manager.test.ts +++ b/src/main/__test__/kubeconfig-manager.test.ts @@ -20,6 +20,9 @@ import loggerInjectable from "../../common/logger.injectable"; import type { Logger } from "../../common/logger"; import assert from "assert"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; +import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; console = new Console(process.stdout, process.stderr); // fix mockFS @@ -34,6 +37,9 @@ describe("kubeconfig manager tests", () => { di.override(directoryForTempInjectable, () => "some-directory-for-temp"); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); loggerMock = { warn: jest.fn(), diff --git a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index 55f318697b..5946a113a0 100644 --- a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -20,6 +20,9 @@ import appVersionInjectable from "../../../common/get-configuration-file-model/a import clusterManagerInjectable from "../../cluster-manager.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; +import kubectlBinaryNameInjectable from "../../kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../../kubectl/normalized-arch.injectable"; +import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable"; jest.mock("electron", () => ({ app: { @@ -47,6 +50,9 @@ describe("kubeconfig-sync.source tests", () => { di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); di.override(directoryForTempInjectable, () => "some-directory-for-temp"); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); di.override(clusterStoreInjectable, () => ClusterStore.createInstance({ createCluster: () => null as never }), diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 50b5b05e3d..5889e30090 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -4,7 +4,7 @@ */ import glob from "glob"; -import { kebabCase, memoize, noop } from "lodash/fp"; +import { kebabCase, memoize } from "lodash/fp"; import type { DiContainer } from "@ogre-tools/injectable"; import { createContainer } from "@ogre-tools/injectable"; import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; @@ -13,7 +13,6 @@ import registerChannelInjectable from "./app-paths/register-channel/register-cha import writeJsonFileInjectable from "../common/fs/write-json-file.injectable"; import readJsonFileInjectable from "../common/fs/read-json-file.injectable"; import readFileInjectable from "../common/fs/read-file.injectable"; -import directoryForBundledBinariesInjectable from "../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable"; import loggerInjectable from "../common/logger.injectable"; import spawnInjectable from "./child-process/spawn.injectable"; import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable"; @@ -76,6 +75,8 @@ import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectab import getElectronThemeInjectable from "./electron-app/features/get-electron-theme.injectable"; import syncThemeFromOperatingSystemInjectable from "./electron-app/features/sync-theme-from-operating-system.injectable"; import platformInjectable from "../common/vars/platform.injectable"; +import { noop } from "../renderer/utils"; +import baseBundeledBinariesDirectoryInjectable from "../common/vars/base-bundled-binaries-dir.injectable"; export function getDiForUnitTesting(opts: GetDiForUnitTestingOptions = {}) { const { @@ -126,12 +127,10 @@ export function getDiForUnitTesting(opts: GetDiForUnitTestingOptions = {}) { di.override(appNameInjectable, () => "some-app-name"); di.override(registerChannelInjectable, () => () => undefined); - di.override(directoryForBundledBinariesInjectable, () => "some-bin-directory"); - di.override(broadcastMessageInjectable, () => (channel) => { throw new Error(`Tried to broadcast message to channel "${channel}" over IPC without explicit override.`); }); - + di.override(baseBundeledBinariesDirectoryInjectable, () => "some-bin-directory"); di.override(spawnInjectable, () => () => { return { stderr: { on: jest.fn(), removeAllListeners: jest.fn() }, diff --git a/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts b/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts index c3706f7c08..07bb87bd63 100644 --- a/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts +++ b/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts @@ -9,10 +9,10 @@ import type { Cluster } from "../../common/cluster/cluster"; import path from "path"; import selfsigned from "selfsigned"; import { getBinaryName } from "../../common/vars"; -import directoryForBundledBinariesInjectable from "../../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable"; import spawnInjectable from "../child-process/spawn.injectable"; import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate"; import loggerInjectable from "../../common/logger.injectable"; +import baseBundeledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable"; export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy; @@ -25,7 +25,7 @@ const createKubeAuthProxyInjectable = getInjectable({ return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => { const clusterUrl = new URL(cluster.apiUrl); const dependencies: KubeAuthProxyDependencies = { - proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), binaryName), + proxyBinPath: path.join(di.inject(baseBundeledBinariesDirectoryInjectable), binaryName), proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate), spawn: di.inject(spawnInjectable), logger: di.inject(loggerInjectable), diff --git a/src/main/kubectl/binary-name.injectable.ts b/src/main/kubectl/binary-name.injectable.ts new file mode 100644 index 0000000000..66b42a6007 --- /dev/null +++ b/src/main/kubectl/binary-name.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; + +const kubectlBinaryNameInjectable = getInjectable({ + id: "kubectl-binary-name", + instantiate: (di) => { + const platform = di.inject(normalizedPlatformInjectable); + + return platform === "windows" + ? "kubectl.exe" + : "kubectl"; + }, +}); + +export default kubectlBinaryNameInjectable; diff --git a/src/main/kubectl/bundled-binary-path.injectable.ts b/src/main/kubectl/bundled-binary-path.injectable.ts new file mode 100644 index 0000000000..99cb7f2e7e --- /dev/null +++ b/src/main/kubectl/bundled-binary-path.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import path from "path"; +import baseBundeledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable"; +import kubectlBinaryNameInjectable from "./binary-name.injectable"; + +const bundledKubectlBinaryPathInjectable = getInjectable({ + id: "bundled-kubectl-binary-path", + instantiate: (di) => path.join( + di.inject(baseBundeledBinariesDirectoryInjectable), + di.inject(kubectlBinaryNameInjectable), + ), +}); + +export default bundledKubectlBinaryPathInjectable; diff --git a/src/main/kubectl/create-kubectl.injectable.ts b/src/main/kubectl/create-kubectl.injectable.ts index 931f777535..d670e8e665 100644 --- a/src/main/kubectl/create-kubectl.injectable.ts +++ b/src/main/kubectl/create-kubectl.injectable.ts @@ -3,24 +3,31 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import type { KubectlDependencies } from "./kubectl"; import { Kubectl } from "./kubectl"; import directoryForKubectlBinariesInjectable from "../../common/app-paths/directory-for-kubectl-binaries/directory-for-kubectl-binaries.injectable"; import userStoreInjectable from "../../common/user-store/user-store.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "./normalized-arch.injectable"; +import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; +import kubectlBinaryNameInjectable from "./binary-name.injectable"; +import bundledKubectlBinaryPathInjectable from "./bundled-binary-path.injectable"; +import baseBundeledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable"; const createKubectlInjectable = getInjectable({ id: "create-kubectl", instantiate: (di) => { - const dependencies = { + const dependencies: KubectlDependencies = { userStore: di.inject(userStoreInjectable), - - directoryForKubectlBinaries: di.inject( - directoryForKubectlBinariesInjectable, - ), + directoryForKubectlBinaries: di.inject(directoryForKubectlBinariesInjectable), + normalizedDownloadArch: di.inject(kubectlDownloadingNormalizedArchInjectable), + normalizedDownloadPlatform: di.inject(normalizedPlatformInjectable), + kubectlBinaryName: di.inject(kubectlBinaryNameInjectable), + bundledKubectlBinaryPath: di.inject(bundledKubectlBinaryPathInjectable), + baseBundeledBinariesDirectory: di.inject(baseBundeledBinariesDirectoryInjectable), }; - return (clusterVersion: string) => - new Kubectl(dependencies, clusterVersion); + return (clusterVersion: string) => new Kubectl(dependencies, clusterVersion); }, }); diff --git a/src/main/kubectl/kubectl.ts b/src/main/kubectl/kubectl.ts index d2bc5697fc..55f7e4675d 100644 --- a/src/main/kubectl/kubectl.ts +++ b/src/main/kubectl/kubectl.ts @@ -10,7 +10,6 @@ import logger from "../logger"; import { ensureDir, pathExists } from "fs-extra"; import * as lockFile from "proper-lockfile"; import { getBundledKubectlVersion } from "../../common/utils/app-version"; -import { normalizedPlatform, normalizedArch, kubectlBinaryName, kubectlBinaryPath, baseBinariesDir } from "../../common/vars"; import { SemVer } from "semver"; import { defaultPackageMirror, packageMirrors } from "../../common/user-store/preferences-helpers"; import got from "got/dist/source"; @@ -40,27 +39,31 @@ const kubectlMap: Map = new Map([ ]); const initScriptVersionString = "# lens-initscript v3"; -interface Dependencies { - directoryForKubectlBinaries: string; - - userStore: { - kubectlBinariesPath?: string; - downloadBinariesPath?: string; - downloadKubectlBinaries: boolean; - downloadMirror: string; +export interface KubectlDependencies { + readonly directoryForKubectlBinaries: string; + readonly normalizedDownloadPlatform: "darwin" | "linux" | "windows"; + readonly normalizedDownloadArch: "amd64" | "arm64" | "386"; + readonly kubectlBinaryName: string; + readonly bundledKubectlBinaryPath: string; + readonly baseBundeledBinariesDirectory: string; + readonly userStore: { + readonly kubectlBinariesPath?: string; + readonly downloadBinariesPath?: string; + readonly downloadKubectlBinaries: boolean; + readonly downloadMirror: string; }; } export class Kubectl { - public kubectlVersion: string; - protected url: string; - protected path: string; - protected dirname: string; + public readonly kubectlVersion: string; + protected readonly url: string; + protected readonly path: string; + protected readonly dirname: string; public static readonly bundledKubectlVersion = bundledVersion; public static invalidBundle = false; - constructor(private dependencies: Dependencies, clusterVersion: string) { + constructor(protected readonly dependencies: KubectlDependencies, clusterVersion: string) { let version: SemVer; try { @@ -83,13 +86,13 @@ export class Kubectl { logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using fallback`); } - this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${normalizedPlatform}/${normalizedArch}/${kubectlBinaryName}`; + this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${this.dependencies.normalizedDownloadPlatform}/${this.dependencies.normalizedDownloadArch}/${this.dependencies.kubectlBinaryName}`; this.dirname = path.normalize(path.join(this.getDownloadDir(), this.kubectlVersion)); - this.path = path.join(this.dirname, kubectlBinaryName); + this.path = path.join(this.dirname, this.dependencies.kubectlBinaryName); } public getBundledPath() { - return kubectlBinaryPath.get(); + return this.dependencies.bundledKubectlBinaryPath; } public getPathFromPreferences() { @@ -279,12 +282,11 @@ export class Kubectl { } protected async writeInitScripts() { + const binariesDir = this.dependencies.baseBundeledBinariesDirectory; const kubectlPath = this.dependencies.userStore.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences()); - const binariesDir = baseBinariesDir.get(); - const bashScriptPath = path.join(this.dirname, ".bash_set_path"); const bashScript = [ initScriptVersionString, @@ -297,7 +299,7 @@ export class Kubectl { "elif test -f \"$HOME/.profile\"; then", " . \"$HOME/.profile\"", "fi", - `export PATH="${binariesDir}:${kubectlPath}:$PATH"`, + `export PATH="${kubectlPath}:${binariesDir}:$PATH"`, 'export KUBECONFIG="$tempkubeconfig"', `NO_PROXY=",\${NO_PROXY:-localhost},"`, `NO_PROXY="\${NO_PROXY//,localhost,/,}"`, @@ -328,7 +330,7 @@ export class Kubectl { "d=\":$PATH:\"", `d=\${d//$p/:}`, `d=\${d/#:/}`, - `export PATH="$binariesDir:$kubectlpath:\${d/%:/}"`, + `export PATH="$kubectlpath:$binariesDir:\${d/%:/}"`, "export KUBECONFIG=\"$tempkubeconfig\"", `NO_PROXY=",\${NO_PROXY:-localhost},"`, `NO_PROXY="\${NO_PROXY//,localhost,/,}"`, diff --git a/src/main/kubectl/normalized-arch.injectable.ts b/src/main/kubectl/normalized-arch.injectable.ts new file mode 100644 index 0000000000..88ec6b1067 --- /dev/null +++ b/src/main/kubectl/normalized-arch.injectable.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; + +const kubectlDownloadingNormalizedArchInjectable = getInjectable({ + id: "kubectl-downloading-normalized-arch", + instantiate: () => { + switch (process.arch) { + case "arm64": + return "arm64"; + case "x64": + case "amd64": + return "amd64"; + case "386": + case "x32": + case "ia32": + return "386"; + default: + throw new Error(`arch=${process.arch} is unsupported`); + } + }, + causesSideEffects: true, +}); + +export default kubectlDownloadingNormalizedArchInjectable; diff --git a/src/main/router/router.test.ts b/src/main/router/router.test.ts index 20b8022efc..20d5f4f526 100644 --- a/src/main/router/router.test.ts +++ b/src/main/router/router.test.ts @@ -17,6 +17,9 @@ import mockFs from "mock-fs"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import type { Route } from "./route"; import type { SetRequired } from "type-fest"; +import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; +import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; describe("router", () => { let router: Router; @@ -31,6 +34,9 @@ describe("router", () => { di.override(parseRequestInjectable, () => () => Promise.resolve({ payload: "some-payload" })); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); const injectable = getInjectable({ id: "some-route", diff --git a/src/main/shell-session/local-shell-session/local-shell-session.ts b/src/main/shell-session/local-shell-session/local-shell-session.ts index bced1e52f6..9f42c7f242 100644 --- a/src/main/shell-session/local-shell-session/local-shell-session.ts +++ b/src/main/shell-session/local-shell-session/local-shell-session.ts @@ -50,11 +50,11 @@ export class LocalShellSession extends ShellSession { switch(path.basename(shell)) { case "powershell.exe": - return ["-NoExit", "-command", `& {$Env:PATH="${baseBinariesDir.get()};${kubectlPathDir};$Env:PATH"}`]; + return ["-NoExit", "-command", `& {$Env:PATH="${kubectlPathDir};${baseBinariesDir.get()};$Env:PATH"}`]; case "bash": return ["--init-file", path.join(await this.kubectlBinDirP, ".bash_set_path")]; case "fish": - return ["--login", "--init-command", `export PATH="${baseBinariesDir.get()}:${kubectlPathDir}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`]; + return ["--login", "--init-command", `export PATH="${kubectlPathDir}:${baseBinariesDir.get()}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`]; case "zsh": return ["--login"]; default: diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index 93fcb55dd1..59827b2314 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -311,7 +311,7 @@ export abstract class ShellSession { protected async getShellEnv() { const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv()))); - const pathStr = [...this.getPathEntries(), await this.kubectlBinDirP, process.env.PATH].join(path.delimiter); + const pathStr = [await this.kubectlBinDirP, ...this.getPathEntries(), process.env.PATH].join(path.delimiter); const shell = UserStore.getInstance().resolvedShell; delete env.DEBUG; // don't pass DEBUG into shells diff --git a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx index a2a0e6a301..ded5cf8098 100644 --- a/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx +++ b/src/renderer/components/delete-cluster-dialog/__tests__/delete-cluster-dialog.test.tsx @@ -26,6 +26,9 @@ import { computed } from "mobx"; import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token"; import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token"; import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; +import normalizedPlatformInjectable from "../../../../common/vars/normalized-platform.injectable"; +import kubectlBinaryNameInjectable from "../../../../main/kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../../../../main/kubectl/normalized-arch.injectable"; jest.mock("electron", () => ({ app: { @@ -101,8 +104,11 @@ describe("", () => { applicationBuilder = getApplicationBuilder(); applicationBuilder.beforeApplicationStart(({ mainDi, rendererDi }) => { - mainDi.override(createContextHandlerInjectable, () => () => undefined as any); - mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as any); + mainDi.override(createContextHandlerInjectable, () => () => undefined as never); + mainDi.override(createKubeconfigManagerInjectable, () => () => undefined as never); + mainDi.override(kubectlBinaryNameInjectable, () => "kubectl"); + mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + mainDi.override(normalizedPlatformInjectable, () => "darwin"); rendererDi.override(hotbarStoreInjectable, () => ({})); rendererDi.override(storesAndApisCanBeCreatedInjectable, () => true);