From 710671e2c05643686d36d9160cc4506048eb7b54 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 18 May 2022 13:20:14 -0700 Subject: [PATCH] Fix downloading cluster specific kubectl (#5413) --- src/common/__tests__/cluster-store.test.ts | 6 +++ ...rectory-for-bundled-binaries.injectable.ts | 13 ------ src/common/vars.ts | 15 +++---- .../base-bundled-binaries-dir.injectable.ts | 18 ++++++++ ...led-binaries-normalized-arch.injectable.ts | 27 +++++++++++ .../vars/bundled-resources-dir.injectable.ts | 22 +++++++++ src/common/vars/is-production.injectable.ts | 13 ++++++ .../vars/normalized-platform.injectable.ts | 24 ++++++++++ src/main/__test__/cluster.test.ts | 7 +++ src/main/__test__/kube-auth-proxy.test.ts | 6 +++ src/main/__test__/kubeconfig-manager.test.ts | 8 +++- .../__test__/kubeconfig-sync.test.ts | 13 ++++-- src/main/getDiForUnitTesting.ts | 10 ++--- .../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 | 45 ++++++++++--------- .../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 | 9 +++- 23 files changed, 270 insertions(+), 67 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/is-production.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 ccc2d897b9..2bbaee13b1 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -24,6 +24,9 @@ import directoryForUserDataInjectable from "../app-paths/directory-for-user-data import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../get-configuration-file-model/app-version/app-version.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); @@ -86,6 +89,9 @@ describe("cluster-store", () => { mainDi.override(clusterStoreInjectable, (di) => ClusterStore.createInstance({ createCluster: di.inject(createClusterInjectionToken) })); mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + 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 52c9a15156..dc4e52d181 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -29,6 +29,9 @@ export const defaultTheme = "lens-dark" as string; 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": @@ -41,6 +44,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": @@ -91,15 +97,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)); export const staticFilesDirectory = path.resolve( !isProduction ? process.cwd() 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/is-production.injectable.ts b/src/common/vars/is-production.injectable.ts new file mode 100644 index 0000000000..68fcfb3d49 --- /dev/null +++ b/src/common/vars/is-production.injectable.ts @@ -0,0 +1,13 @@ +/** + * 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 isProductionInjectable = getInjectable({ + id: "is-production", + instantiate: () => process.env.NODE_ENV === "production", + causesSideEffects: true, +}); + +export default isProductionInjectable; 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 d570919f3e..e6fe16742b 100644 --- a/src/main/__test__/cluster.test.ts +++ b/src/main/__test__/cluster.test.ts @@ -43,6 +43,9 @@ import { createClusterInjectionToken } from "../../common/cluster/create-cluster import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.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 @@ -81,6 +84,10 @@ describe("create clusters", () => { await di.runSetups(); + 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, () => () => { diff --git a/src/main/__test__/kube-auth-proxy.test.ts b/src/main/__test__/kube-auth-proxy.test.ts index 36af031ecf..14c0375553 100644 --- a/src/main/__test__/kube-auth-proxy.test.ts +++ b/src/main/__test__/kube-auth-proxy.test.ts @@ -57,6 +57,9 @@ import path from "path"; import spawnInjectable from "../child-process/spawn.injectable"; import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import appVersionInjectable from "../../common/get-configuration-file-model/app-version/app-version.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); @@ -100,6 +103,9 @@ describe("kube auth proxy tests", () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); 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 91792bfaf8..50c38e1757 100644 --- a/src/main/__test__/kubeconfig-manager.test.ts +++ b/src/main/__test__/kubeconfig-manager.test.ts @@ -43,18 +43,24 @@ import { createClusterInjectionToken } from "../../common/cluster/create-cluster import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable"; import type { DiContainer } from "@ogre-tools/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 describe("kubeconfig manager tests", () => { let clusterFake: Cluster; let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager; - let di: DiContainer; + let di: DiContainer; beforeEach(async () => { di = getDiForUnitTesting({ doGeneralOverrides: true }); di.override(directoryForTempInjectable, () => "some-directory-for-temp"); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); mockFs({ "minikube-config.yml": JSON.stringify({ diff --git a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index 782c6e5950..ec351e3a2d 100644 --- a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -16,10 +16,11 @@ import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import { ClusterStore } from "../../../common/cluster-store/cluster-store"; -import getConfigurationFileModelInjectable - from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; -import appVersionInjectable - from "../../../common/get-configuration-file-model/app-version/app-version.injectable"; +import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; +import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.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 +48,10 @@ describe("kubeconfig-sync.source tests", () => { await di.runSetups(); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); + computeDiff = computeDiffFor({ directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable), createCluster: di.inject(createClusterInjectionToken), diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index b1306457c5..d67d6ce59b 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -4,9 +4,8 @@ */ import glob from "glob"; -import { kebabCase, memoize, noop } from "lodash/fp"; +import { kebabCase, memoize } from "lodash/fp"; import { createContainer } from "@ogre-tools/injectable"; - import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import getElectronAppPathInjectable from "./app-paths/get-electron-app-path/get-electron-app-path.injectable"; import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable"; @@ -15,7 +14,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"; @@ -35,6 +33,9 @@ import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake import joinPathsInjectable from "../common/path/join-paths.injectable"; import { joinPathsFake } from "../common/test-utils/join-paths-fake"; import hotbarStoreInjectable from "../common/hotbar-store.injectable"; +import { noop } from "../common/utils"; +import baseBundeledBinariesDirectoryInjectable from "../common/vars/base-bundled-binaries-dir.injectable"; + export const getDiForUnitTesting = ( { doGeneralOverrides } = { doGeneralOverrides: false }, @@ -82,8 +83,7 @@ export const getDiForUnitTesting = ( di.override(setElectronAppPathInjectable, () => () => undefined); di.override(appNameInjectable, () => "some-electron-app-name"); di.override(registerChannelInjectable, () => () => undefined); - di.override(directoryForBundledBinariesInjectable, () => "some-bin-directory"); - + 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 9d62a04cef..6b9fcf0af3 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,9 +9,9 @@ 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 baseBundeledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable"; export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy; @@ -24,7 +24,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), }; 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 d947b5a4ae..5f3857623b 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,28 +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 directory: 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: string = bundledVersion; public static invalidBundle = false; - constructor(private dependencies: Dependencies, clusterVersion: string) { + constructor(protected readonly dependencies: KubectlDependencies, clusterVersion: string) { let version: SemVer; try { @@ -82,13 +84,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() { @@ -278,12 +280,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, @@ -296,7 +297,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,/,}"`, @@ -327,7 +328,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 96f0587163..ecf472802f 100644 --- a/src/main/router/router.test.ts +++ b/src/main/router/router.test.ts @@ -14,6 +14,9 @@ import asyncFn from "@async-fn/jest"; import parseRequestInjectable from "./parse-request.injectable"; import { contentTypes } from "./router-content-types"; import mockFs from "mock-fs"; +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; @@ -27,6 +30,9 @@ describe("router", () => { mockFs(); di.override(parseRequestInjectable, () => () => Promise.resolve({ payload: "some-payload" })); + di.override(kubectlBinaryNameInjectable, () => "kubectl"); + di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + di.override(normalizedPlatformInjectable, () => "darwin"); await di.runSetups(); 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 314f8700ec..8f7008a80c 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 @@ -45,11 +45,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 831c24bc02..1cc8cd8cce 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -313,7 +313,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 1fe1d6e339..66ad76319e 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 @@ -21,6 +21,10 @@ import type { DeleteClusterDialogModel } from "../delete-cluster-dialog-model/de import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; +import createKubeconfigManagerInjectable from "../../../../main/kubeconfig-manager/create-kubeconfig-manager.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: { @@ -102,7 +106,10 @@ describe("", () => { render = renderFor(rendererDi); mainDi.override(createContextHandlerInjectable, () => () => undefined); - + mainDi.override(createKubeconfigManagerInjectable, () => () => undefined); + mainDi.override(kubectlBinaryNameInjectable, () => "kubectl"); + mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); + mainDi.override(normalizedPlatformInjectable, () => "darwin"); mockFs(); rendererDi.override(hotbarStoreInjectable, () => ({}));