Remove a lot of usages of legacy globals (#6825)

* Remove all usages of legacy global logger

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global storageClassApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global serviceApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global secretApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global resourceQuotaApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global persistentVolumeClaimApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global nodeApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global networkPolicyApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix formatting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global configMapApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global jobApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global cronJobApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global podApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global roleApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove exports of NonInjected components
- To improve auto-complete

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global catalogEntityRegistry

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add lint against using legacy global works

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint in KubeObject file

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global catalogCategoryRegistry

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global navigation

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global navigate

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unneeded index file

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global customResourceDefinitionStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global eventStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global podSecurityPolicyStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global storageClassStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global persistentVolumeStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global clusterRoleBindingStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global clusterRoleStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global roleBindingStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move files around

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global roleStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global serviceAccountStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usages of legacy global podStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove final uses of asLegacyGlobalForExtensionApi in application code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint rule and start fixing lint errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make listHelmReleases injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make installHelmChart injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make deleteHelmRelease injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make getHelmReleaseValues injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make getHelmReleaseHistory injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make rollbackHelmRelease injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unnecessary overrides from getDiForUnitTesting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for logger

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for history

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup typing around getting injectables within jest

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move some more overrides to global if needed

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for forcusWindow

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint in KubeApi

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint in catalog/index.ts

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove last usages of legacy global getDetailsUrl

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove last usages of legacy global showDetails

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add functions back to extension API

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove all uses of legacy global Notification functions

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove no longer existing export

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unnecessary overrides

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix injecting side effects by using more injectables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster role binding dialog

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster role dialog

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix create service account dialog

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix history override

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make KubeApi consume its dependencies while not breaking the extension API

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix CephFs tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix user store tests by using override of config

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in hotbar store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add direct devDep of memfs

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in cluster store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in router tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in kube auth proxy tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in log resource selector tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in hotbar remove command tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in the select component tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove mock-fs as a dependency

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove no longer necessary mocks

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix namespace select filter tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of mockFs in the extension loader tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fixing edit-namespace-from-new-tab test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix app paths tests by making the tests run in "production" mode

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove last vestiges of isTestEnv and the is* globals

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix delete-cluster-dialog tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix install-helm-chart-from-previously-opened-tab tests
- Split out storage initialization to a runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unnecessary override of non side effect injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix disable-kube-object-detail-items-when-cluster-is-not-relevant tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix reactively-hide-kube-object-detail-item tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix sidebar-and-tab-navigation-for-core tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove need to override lensLocalStoragePath in tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace more overrides with global ones

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix typings

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove no longer needed technical test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2023-01-09 16:15:37 -05:00
1 parent 900a8bbda3
commit 2b33d5eed6
530 files changed
+11320 -5399

No files matched your search

-3
View File
@@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
import { Console } from "console";
import type { Cluster } from "../../common/cluster/cluster";
import { Kubectl } from "../kubectl/kubectl";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
@@ -25,8 +24,6 @@ import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable";
import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable";
console = new Console(process.stdout, process.stderr); // fix mockFS
describe("create clusters", () => {
let cluster: Cluster;
let createCluster: CreateCluster;
+36 -54
View File
@@ -12,28 +12,21 @@ import type { DeepMockProxy } from "jest-mock-extended";
import { mockDeep, mock } from "jest-mock-extended";
import type { Readable } from "stream";
import { EventEmitter } from "stream";
import { Console } from "console";
import { stdout, stderr } from "process";
import mockFs from "mock-fs";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import type { CreateCluster } from "../../common/cluster/create-cluster-injection-token";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
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 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";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable";
import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable";
import pathExistsSyncInjectable from "../../common/fs/path-exists-sync.injectable";
console = new Console(stdout, stderr);
import ensureDirInjectable from "../../common/fs/ensure-dir.injectable";
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
const clusterServerUrl = "https://192.168.64.3:8443";
@@ -43,44 +36,42 @@ describe("kube auth proxy tests", () => {
let spawnMock: jest.Mock;
let waitUntilPortIsUsedMock: jest.Mock;
let broadcastMessageMock: jest.Mock;
let getBasenameOfPath: GetBasenameOfPath;
beforeEach(async () => {
jest.clearAllMocks();
const mockMinikubeConfig = {
"minikube-config.yml": JSON.stringify({
apiVersion: "v1",
clusters: [{
name: "minikube",
cluster: {
server: clusterServerUrl,
},
}],
"current-context": "minikube",
contexts: [{
context: {
cluster: "minikube",
user: "minikube",
},
name: "minikube",
}],
users: [{
name: "minikube",
}],
kind: "Config",
preferences: {},
}),
"tmp": {},
};
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "/some-directory-for-temp");
const writeJsonSync = di.inject(writeJsonSyncInjectable);
const ensureDir = di.inject(ensureDirInjectable);
getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
writeJsonSync("/minikube-config.yml", {
apiVersion: "v1",
clusters: [{
name: "minikube",
cluster: {
server: clusterServerUrl,
},
}],
"current-context": "minikube",
contexts: [{
context: {
cluster: "minikube",
user: "minikube",
},
name: "minikube",
}],
users: [{
name: "minikube",
}],
kind: "Config",
preferences: {},
});
await ensureDir("/tmp");
spawnMock = jest.fn();
di.override(spawnInjectable, () => spawnMock);
@@ -95,19 +86,10 @@ describe("kube auth proxy tests", () => {
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin");
di.permitSideEffects(getConfigurationFileModelInjectable);
mockFs(mockMinikubeConfig);
createCluster = di.inject(createClusterInjectionToken);
createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable);
});
afterEach(() => {
mockFs.restore();
});
it("calling exit multiple times shouldn't throw", async () => {
const cluster = createCluster({
id: "foobar",
@@ -197,7 +179,7 @@ describe("kube auth proxy tests", () => {
return stdout;
});
spawnMock.mockImplementationOnce((command: string): ChildProcess => {
expect(path.basename(command).split(".")[0]).toBe("lens-k8s-proxy");
expect(getBasenameOfPath(command).split(".")[0]).toBe("lens-k8s-proxy");
return mockedCP;
});
@@ -7,22 +7,6 @@ import type { LensApiRequest, Route } from "../router/route";
import staticFileRouteInjectable from "../routes/files/static-file-route.injectable";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
jest.mock("electron", () => ({
app: {
getVersion: () => "99.99.99",
getName: () => "lens",
setName: jest.fn(),
setPath: jest.fn(),
getPath: () => "tmp",
getLocale: () => "en",
setLoginItemSettings: jest.fn(),
},
ipcMain: {
on: jest.fn(),
handle: jest.fn(),
},
}));
describe("static-file-route", () => {
let handleStaticFileRoute: Route<Buffer, "/{path*}">;
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
import directoryForIntegrationTestingInjectable from "./directory-for-integration-testing.injectable";
export default getGlobalOverride(directoryForIntegrationTestingInjectable, () => undefined);
@@ -3,16 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import environmentVariablesInjectable from "../../../common/utils/environment-variables.injectable";
const directoryForIntegrationTestingInjectable = getInjectable({
id: "directory-for-integration-testing",
instantiate: (di) => {
const environmentVariables = di.inject(environmentVariablesInjectable);
return environmentVariables.CICD;
},
instantiate: () => process.env.CICD,
causesSideEffects: true,
});
export default directoryForIntegrationTestingInjectable;
@@ -3,15 +3,29 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { PathName } from "../../../common/app-paths/app-path-names";
import electronAppInjectable from "../../electron-app/electron-app.injectable";
import { getElectronAppPath } from "./get-electron-app-path";
export type GetElectronAppPath = (name: PathName | "currentApp") => string;
const getElectronAppPathInjectable = getInjectable({
id: "get-electron-app-path",
instantiate: (di) => getElectronAppPath({
app: di.inject(electronAppInjectable),
}),
instantiate: (di): GetElectronAppPath => {
const electronApp = di.inject(electronAppInjectable);
return (name) => {
try {
if (name === "currentApp") {
return electronApp.getAppPath();
}
return electronApp.getPath(name);
} catch (e) {
return "";
}
};
},
});
export default getElectronAppPathInjectable;
@@ -1,28 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { App } from "electron";
import type { PathName } from "../../../common/app-paths/app-path-names";
interface Dependencies {
app: App;
}
export type GetElectronAppPath = (name: PathName | "currentApp") => string;
export const getElectronAppPath = ({
app,
}: Dependencies): GetElectronAppPath => (
(name) => {
try {
if (name === "currentApp") {
return app.getAppPath();
}
return app.getPath(name);
} catch (e) {
return "";
}
}
);
@@ -6,11 +6,16 @@ import { getInjectable } from "@ogre-tools/injectable";
import type { PathName } from "../../../common/app-paths/app-path-names";
import electronAppInjectable from "../../electron-app/electron-app.injectable";
export type SetElectronAppPath = (name: PathName, path: string) => void;
const setElectronAppPathInjectable = getInjectable({
id: "set-electron-app-path",
instantiate: (di) => (name: PathName, path: string) : void =>
di.inject(electronAppInjectable).setPath(name, path),
instantiate: (di): SetElectronAppPath => {
const electronApp = di.inject(electronAppInjectable);
return (name, path) => electronApp.setPath(name, path);
},
});
export default setElectronAppPathInjectable;
@@ -21,7 +21,7 @@ const setupAppPathsInjectable = getInjectable({
instantiate: (di) => {
const setElectronAppPath = di.inject(setElectronAppPathInjectable);
const appName = di.inject(appNameInjectable);
const getAppPath = di.inject(getElectronAppPathInjectable);
const getElectronAppPath = di.inject(getElectronAppPathInjectable);
const appPathsState = di.inject(appPathsStateInjectable);
const directoryForIntegrationTesting = di.inject(directoryForIntegrationTestingInjectable);
const joinPaths = di.inject(joinPathsInjectable);
@@ -33,13 +33,13 @@ const setupAppPathsInjectable = getInjectable({
setElectronAppPath("appData", directoryForIntegrationTesting);
}
const appDataPath = getAppPath("appData");
const appDataPath = getElectronAppPath("appData");
setElectronAppPath("userData", joinPaths(appDataPath, appName));
const appPaths = pipeline(
pathNames,
map(name => [name, getAppPath(name)]),
map(name => [name, getElectronAppPath(name)]),
fromPairs,
) as AppPaths;
-32
View File
@@ -1,32 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { reaction } from "mobx";
import { broadcastMessage, ipcMainOn } from "../common/ipc";
import type { CatalogEntityRegistry } from "./catalog";
import "../common/catalog-entities/kubernetes-cluster";
import { disposer, toJS } from "../common/utils";
import { debounce } from "lodash";
import type { CatalogEntity } from "../common/catalog";
import { catalogInitChannel, catalogItemsChannel } from "../common/ipc/catalog";
const broadcaster = debounce((items: CatalogEntity[]) => {
broadcastMessage(catalogItemsChannel, items);
}, 100, { leading: true, trailing: true });
export function pushCatalogToRenderer(catalog: CatalogEntityRegistry) {
broadcaster(toJS(catalog.items));
}
export function startCatalogSyncToRenderer(catalog: CatalogEntityRegistry) {
return disposer(
ipcMainOn(catalogInitChannel, () => broadcaster(toJS(catalog.items))),
reaction(() => toJS(catalog.items), (items) => {
broadcaster(items);
}, {
fireImmediately: true,
}),
);
}
@@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { debounce } from "lodash";
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import catalogSyncBroadcasterInjectable from "./broadcaster.injectable";
export default getGlobalOverride(catalogSyncBroadcasterInjectable, () => debounce(() => {}));
@@ -0,0 +1,29 @@
/**
* 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 { debounce } from "lodash";
import type { CatalogEntity } from "../../common/catalog";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
import { catalogItemsChannel } from "../../common/ipc/catalog";
const catalogSyncBroadcasterInjectable = getInjectable({
id: "catalog-sync-broadcaster",
instantiate: (di) => {
const broadcastMessage = di.inject(broadcastMessageInjectable);
return debounce(
(items: CatalogEntity[]) => {
broadcastMessage(catalogItemsChannel, items);
},
100,
{
leading: true,
trailing: true,
},
);
},
});
export default catalogSyncBroadcasterInjectable;
@@ -3,23 +3,40 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { startCatalogSyncToRenderer } from "../catalog-pusher";
import { reaction } from "mobx";
import { catalogInitChannel } from "../../common/ipc/catalog";
import { disposer, toJS } from "../../common/utils";
import { getStartableStoppable } from "../../common/utils/get-startable-stoppable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
import ipcMainInjectable from "../utils/channel/ipc-main/ipc-main.injectable";
import catalogSyncBroadcasterInjectable from "./broadcaster.injectable";
const catalogSyncToRendererInjectable = getInjectable({
id: "catalog-sync-to-renderer",
instantiate: (di) => {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const ipcMain = di.inject(ipcMainInjectable);
const catalogSyncBroadcaster = di.inject(catalogSyncBroadcasterInjectable);
return getStartableStoppable(
"catalog-sync",
() => startCatalogSyncToRenderer(catalogEntityRegistry),
() => {
const initChannelHandler = () => catalogSyncBroadcaster(toJS(catalogEntityRegistry.items));
ipcMain.on(catalogInitChannel, initChannelHandler);
return disposer(
() => ipcMain.off(catalogInitChannel, initChannelHandler),
reaction(() => toJS(catalogEntityRegistry.items), (items) => {
catalogSyncBroadcaster(items);
}, {
fireImmediately: true,
}),
);
},
);
},
causesSideEffects: true,
});
export default catalogSyncToRendererInjectable;
@@ -0,0 +1,20 @@
/**
* 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 { toJS } from "../../common/utils";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
import catalogSyncBroadcasterInjectable from "./broadcaster.injectable";
const pushCatalogToRendererInjectable = getInjectable({
id: "push-catalog-to-renderer",
instantiate: (di) => {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const catalogSyncBroadcaster = di.inject(catalogSyncBroadcasterInjectable);
return () => catalogSyncBroadcaster(toJS(catalogEntityRegistry.items));
},
});
export default pushCatalogToRendererInjectable;
@@ -1,12 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import catalogEntityRegistryInjectable from "./entity-registry.injectable";
/**
* @deprecated use `di.inject(catalogEntityRegistryInjectable)` instead
*/
export const catalogEntityRegistry = asLegacyGlobalForExtensionApi(catalogEntityRegistryInjectable);
-1
View File
@@ -3,5 +3,4 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./catalog-entity-registry";
export * from "./entity-registry";
+2
View File
@@ -4,6 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable";
import loggerInjectable from "../../common/logger.injectable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
import clustersThatAreBeingDeletedInjectable from "./are-being-deleted.injectable";
import { ClusterManager } from "./manager";
@@ -17,6 +18,7 @@ const clusterManagerInjectable = getInjectable({
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
clustersThatAreBeingDeleted: di.inject(clustersThatAreBeingDeletedInjectable),
visibleCluster: di.inject(visibleClusterInjectable),
logger: di.inject(loggerInjectable),
}),
});
+13 -35
View File
@@ -4,13 +4,10 @@
*/
import "../../common/ipc/cluster";
import type http from "http";
import type { IObservableValue, ObservableSet } from "mobx";
import { action, makeObservable, observe, reaction, toJS } from "mobx";
import type { Cluster } from "../../common/cluster/cluster";
import logger from "../logger";
import { apiKubePrefix } from "../../common/vars";
import { getClusterIdFromHost, isErrnoException } from "../../common/utils";
import { isErrnoException } from "../../common/utils";
import type { KubernetesClusterPrometheusMetrics } from "../../common/catalog-entities/kubernetes-cluster";
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster";
import { ipcMainOn } from "../../common/ipc";
@@ -18,6 +15,7 @@ import { once } from "lodash";
import type { ClusterStore } from "../../common/cluster-store/cluster-store";
import type { ClusterId } from "../../common/cluster-types";
import type { CatalogEntityRegistry } from "../catalog";
import type { Logger } from "../../common/logger";
const logPrefix = "[CLUSTER-MANAGER]:";
@@ -28,6 +26,7 @@ interface Dependencies {
readonly catalogEntityRegistry: CatalogEntityRegistry;
readonly clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
readonly visibleCluster: IObservableValue<ClusterId | null>;
readonly logger: Logger;
}
export class ClusterManager {
@@ -80,7 +79,7 @@ export class ClusterManager {
@action
protected updateCatalog(clusters: Cluster[]) {
logger.debug("[CLUSTER-MANAGER]: updating catalog from cluster store");
this.dependencies.logger.debug("[CLUSTER-MANAGER]: updating catalog from cluster store");
for (const cluster of clusters) {
this.updateEntityFromCluster(cluster);
@@ -146,31 +145,31 @@ export class ClusterManager {
} else {
entity.status.phase = (() => {
if (!cluster) {
logger.debug(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="no cluster"`);
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="no cluster"`);
return LensKubernetesClusterStatus.DISCONNECTED;
}
if (cluster.accessible) {
logger.debug(`${logPrefix} setting entity ${entity.getName()} to CONNECTED, reason="cluster is accessible"`);
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to CONNECTED, reason="cluster is accessible"`);
return LensKubernetesClusterStatus.CONNECTED;
}
if (!cluster.disconnected) {
logger.debug(`${logPrefix} setting entity ${entity.getName()} to CONNECTING, reason="cluster is not disconnected"`);
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to CONNECTING, reason="cluster is not disconnected"`);
return LensKubernetesClusterStatus.CONNECTING;
}
// Extensions are not allowed to use the Lens specific status phases
if (!lensSpecificClusterStatuses.has(entity?.status?.phase)) {
logger.debug(`${logPrefix} not clearing entity ${entity.getName()} status, reason="custom string"`);
this.dependencies.logger.debug(`${logPrefix} not clearing entity ${entity.getName()} status, reason="custom string"`);
return entity.status.phase;
}
logger.debug(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="fallthrough"`);
this.dependencies.logger.debug(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="fallthrough"`);
return LensKubernetesClusterStatus.DISCONNECTED;
})();
@@ -200,9 +199,9 @@ export class ClusterManager {
this.dependencies.store.addCluster(model);
} catch (error) {
if (isErrnoException(error) && error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) {
logger.warn(`${logPrefix} kubeconfig file disappeared`, model);
this.dependencies.logger.warn(`${logPrefix} kubeconfig file disappeared`, model);
} else {
logger.error(`${logPrefix} failed to add cluster: ${error}`, model);
this.dependencies.logger.error(`${logPrefix} failed to add cluster: ${error}`, model);
}
}
} else {
@@ -235,7 +234,7 @@ export class ClusterManager {
}
protected onNetworkOffline = () => {
logger.info(`${logPrefix} network is offline`);
this.dependencies.logger.info(`${logPrefix} network is offline`);
this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.online = false;
@@ -246,7 +245,7 @@ export class ClusterManager {
};
protected onNetworkOnline = () => {
logger.info(`${logPrefix} network is online`);
this.dependencies.logger.info(`${logPrefix} network is online`);
this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.refreshConnectionStatus().catch((e) => e);
@@ -259,27 +258,6 @@ export class ClusterManager {
cluster.disconnect();
});
}
getClusterForRequest = (req: http.IncomingMessage): Cluster | undefined => {
if (!req.headers.host) {
return undefined;
}
// lens-server is connecting to 127.0.0.1:<port>/<uid>
if (req.url && req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1];
const cluster = this.dependencies.store.getById(clusterId);
if (cluster) {
// we need to swap path prefix so that request is proxied to kube api
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
}
return cluster;
}
return this.dependencies.store.getById(getClusterIdFromHost(req.headers.host));
};
}
export function catalogEntityFromCluster(cluster: Cluster) {
@@ -7,7 +7,7 @@
// convert file path cluster icons to their base64 encoded versions
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import getCustomKubeConfigDirectoryInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import getCustomKubeConfigFilePathInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import type { ClusterModel } from "../../../common/cluster-types";
import readFileSyncInjectable from "../../../common/fs/read-file-sync.injectable";
import { loadConfigFromString } from "../../../common/kube-helpers";
@@ -27,7 +27,7 @@ const v360Beta1ClusterStoreMigrationInjectable = getInjectable({
id: "v3.6.0-beta.1-cluster-store-migration",
instantiate: (di) => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const getCustomKubeConfigDirectory = di.inject(getCustomKubeConfigDirectoryInjectable);
const getCustomKubeConfigDirectory = di.inject(getCustomKubeConfigFilePathInjectable);
const readFileSync = di.inject(readFileSyncInjectable);
const readFileBufferSync = di.inject(readFileBufferSyncInjectable);
const joinPaths = di.inject(joinPathsInjectable);
@@ -44,8 +44,8 @@ const v360Beta1ClusterStoreMigrationInjectable = getInjectable({
for (const clusterModel of storedClusters) {
/**
* migrate kubeconfig
*/
* migrate kubeconfig
*/
try {
const absPath = getCustomKubeConfigDirectory(clusterModel.id);
@@ -80,6 +80,7 @@ const v360Beta1ClusterStoreMigrationInjectable = getInjectable({
delete clusterModel.preferences?.icon;
}
} catch (error) {
console.log(error);
logger.info(`Failed to migrate cluster icon for cluster "${clusterModel.id}"`, error);
delete clusterModel.preferences?.icon;
}
@@ -49,10 +49,17 @@ const v500Beta10ClusterStoreMigrationInjectable = getInjectable({
store.set("clusters", clusters);
} catch (error) {
if (isErrnoException(error) && !(error.code === "ENOENT" && error.path?.endsWith("lens-workspace-store.json"))) {
// ignore lens-workspace-store.json being missing
throw error;
// KLUDGE: remove after https://github.com/streamich/memfs/pull/893 is released
if (process.env.JEST_WORKER_ID && (error as any).code === "ENOENT") {
return;
}
if (isErrnoException(error) && error.code === "ENOENT" && error.path?.endsWith("lens-workspace-store.json")) {
// ignore lens-workspace-store.json being missing
return;
}
throw error;
}
},
};
@@ -3,17 +3,44 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { kebabCase } from "lodash";
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import electronAppInjectable from "./electron-app.injectable";
export default getGlobalOverride(electronAppInjectable, () => ({
getVersion: () => "6.0.0",
setLoginItemSettings: () => {},
commandLine: {
appendArgument: () => {},
appendSwitch: () => {},
getSwitchValue: () => "",
hasSwitch: () => false,
removeSwitch: () => {},
},
} as Partial<Electron.App> as Electron.App));
export default getGlobalOverride(electronAppInjectable, () => {
const commandLineArgs: string[] = [];
const chromiumArgs = new Map<string, string | undefined>();
const appPaths = new Map<string, string>();
const app = ({
getVersion: () => "6.0.0",
setLoginItemSettings: () => { },
on: () => app,
whenReady: async () => {},
getPath: (name) => appPaths.get(name) ?? `/some-directory-for-${kebabCase(name)}`,
setPath: (name, value) => appPaths.set(name, value),
getAppPath: () => "/some-path-to-the-applcation-binary",
focus: () => {},
commandLine: {
appendArgument: (value) => commandLineArgs.push(value),
appendSwitch: (key, value) => chromiumArgs.set(key, value),
getSwitchValue: (key) => chromiumArgs.get(key),
hasSwitch: (key) => chromiumArgs.has(key),
removeSwitch: (key) => chromiumArgs.delete(key),
},
disableHardwareAcceleration: () => {},
requestSingleInstanceLock: () => true,
getLoginItemSettings: () => ({
executableWillLaunchAtLogin: false,
openAtLogin: false,
openAsHidden: false,
wasOpenedAtLogin: false,
wasOpenedAsHidden: false,
restoreState: false,
launchItems: [],
}),
exit: () => {},
} as Partial<Electron.App> as Electron.App);
return app;
});
@@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
import electronDialogInjectable from "./electron-dialog.injectable";
export default getGlobalOverride(electronDialogInjectable, () => ({
showCertificateTrustDialog: async () => {},
showErrorBox: () => {},
showMessageBox: async () => ({
checkboxChecked: false,
response: 0,
}),
showMessageBoxSync: () => 0,
showOpenDialog: async () => ({
canceled: true,
filePaths: [],
}),
showOpenDialogSync: () => [],
showSaveDialog: async () => ({
canceled: true,
}),
showSaveDialogSync: () => "",
}));
@@ -15,8 +15,6 @@ const focusApplicationInjectable = getInjectable({
electronApp.focus({ steal: true });
};
},
causesSideEffects: true,
});
export default focusApplicationInjectable;
@@ -0,0 +1,16 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import EventEmitter from "events";
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
import nativeThemeInjectable from "./native-theme.injectable";
export default getGlobalOverride(nativeThemeInjectable, () => Object.assign(new EventEmitter(), {
shouldUseDarkColors: true,
inForcedColorsMode: true,
shouldUseHighContrastColors: false,
shouldUseInvertedColorScheme: false,
themeSource: "dark" as const,
}));
@@ -7,10 +7,10 @@ import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
import loggerInjectable from "../../../../common/logger.injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
import applicationMenuItemCompositeInjectable from "../../../../features/application-menu/main/application-menu-item-composite.injectable";
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import pushCatalogToRendererInjectable from "../../../catalog-sync-to-renderer/push-catalog-to-renderer.injectable";
const setupIpcMainHandlersInjectable = getInjectable({
id: "setup-ipc-main-handlers",
@@ -18,7 +18,7 @@ const setupIpcMainHandlersInjectable = getInjectable({
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const applicationMenuItemComposite = di.inject(applicationMenuItemCompositeInjectable);
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const pushCatalogToRenderer = di.inject(pushCatalogToRendererInjectable);
const clusterStore = di.inject(clusterStoreInjectable);
const emitAppEvent = di.inject(emitAppEventInjectable);
const getClusterById = di.inject(getClusterByIdInjectable);
@@ -30,7 +30,7 @@ const setupIpcMainHandlersInjectable = getInjectable({
setupIpcMainHandlers({
applicationMenuItemComposite,
catalogEntityRegistry,
pushCatalogToRenderer,
clusterStore,
emitAppEvent,
getClusterById,
@@ -9,8 +9,6 @@ import { clusterActivateHandler, clusterSetFrameIdHandler, clusterDisconnectHand
import type { ClusterId } from "../../../../common/cluster-types";
import type { ClusterStore } from "../../../../common/cluster-store/cluster-store";
import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc";
import type { CatalogEntityRegistry } from "../../../catalog";
import { pushCatalogToRenderer } from "../../../catalog-pusher";
import type { IComputedValue } from "mobx";
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
@@ -22,18 +20,18 @@ import type { EmitAppEvent } from "../../../../common/app-event-bus/emit-event.i
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
interface Dependencies {
applicationMenuItemComposite: IComputedValue<Composite<ApplicationMenuItemTypes | MenuItemRoot>>;
catalogEntityRegistry: CatalogEntityRegistry;
clusterStore: ClusterStore;
emitAppEvent: EmitAppEvent;
getClusterById: GetClusterById;
pushCatalogToRenderer: () => void;
}
export const setupIpcMainHandlers = ({
applicationMenuItemComposite,
catalogEntityRegistry,
clusterStore,
emitAppEvent,
getClusterById,
pushCatalogToRenderer,
}: Dependencies) => {
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
return getClusterById(clusterId)
@@ -45,8 +43,7 @@ export const setupIpcMainHandlers = ({
if (cluster) {
clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId });
pushCatalogToRenderer(catalogEntityRegistry);
pushCatalogToRenderer();
}
});
+13 -127
View File
@@ -3,76 +3,35 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { kebabCase, noop, chunk } from "lodash/fp";
import { chunk } from "lodash/fp";
import type { DiContainer, Injectable } from "@ogre-tools/injectable";
import { createContainer, isInjectable, getInjectable } from "@ogre-tools/injectable";
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
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 loggerInjectable from "../common/logger.injectable";
import spawnInjectable from "./child-process/spawn.injectable";
import commandLineArgumentsInjectable from "./utils/command-line-arguments.injectable";
import initializeExtensionsInjectable from "./start-main-application/runnables/initialize-extensions.injectable";
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
import environmentVariablesInjectable from "../common/utils/environment-variables.injectable";
import setupIpcMainHandlersInjectable from "./electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable";
import setupLensProxyInjectable from "./start-main-application/runnables/setup-lens-proxy.injectable";
import setupShellInjectable from "../features/shell-sync/main/setup-shell.injectable";
import setupSyncingOfWeblinksInjectable from "./start-main-application/runnables/setup-syncing-of-weblinks.injectable";
import stopServicesAndExitAppInjectable from "./stop-services-and-exit-app.injectable";
import isDevelopmentInjectable from "../common/vars/is-development.injectable";
import setupSystemCaInjectable from "./start-main-application/runnables/setup-system-ca.injectable";
import setupDeepLinkingInjectable from "./electron-app/runnables/setup-deep-linking.injectable";
import exitAppInjectable from "./electron-app/features/exit-app.injectable";
import getCommandLineSwitchInjectable from "./electron-app/features/get-command-line-switch.injectable";
import requestSingleInstanceLockInjectable from "./electron-app/features/request-single-instance-lock.injectable";
import disableHardwareAccelerationInjectable from "./electron-app/features/disable-hardware-acceleration.injectable";
import shouldStartHiddenInjectable from "./electron-app/features/should-start-hidden.injectable";
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";
import setupMainWindowVisibilityAfterActivationInjectable from "./electron-app/runnables/setup-main-window-visibility-after-activation.injectable";
import setupDeviceShutdownInjectable from "./electron-app/runnables/setup-device-shutdown.injectable";
import setupApplicationNameInjectable from "./electron-app/runnables/setup-application-name.injectable";
import setupRunnablesBeforeClosingOfApplicationInjectable from "./electron-app/runnables/setup-runnables-before-closing-of-application.injectable";
import showMessagePopupInjectable from "./electron-app/features/show-message-popup.injectable";
import clusterFramesInjectable from "../common/cluster-frames.injectable";
import type { ClusterFrameInfo } from "../common/cluster-frames";
import { observable, runInAction } from "mobx";
import waitForElectronToBeReadyInjectable from "./electron-app/features/wait-for-electron-to-be-ready.injectable";
import setupRunnablesAfterWindowIsOpenedInjectable from "./electron-app/runnables/setup-runnables-after-window-is-opened.injectable";
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
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 electronQuitAndInstallUpdateInjectable from "./electron-app/features/electron-quit-and-install-update.injectable";
import electronUpdaterIsActiveInjectable from "./electron-app/features/electron-updater-is-active.injectable";
import baseBundledBinariesDirectoryInjectable from "../common/vars/base-bundled-binaries-dir.injectable";
import setUpdateOnQuitInjectable from "./electron-app/features/set-update-on-quit.injectable";
import startCatalogSyncInjectable from "./catalog-sync-to-renderer/start-catalog-sync.injectable";
import startKubeConfigSyncInjectable from "./start-main-application/runnables/kube-config-sync/start-kube-config-sync.injectable";
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
import execFileInjectable from "../common/fs/exec-file.injectable";
import normalizedPlatformArchitectureInjectable from "../common/vars/normalized-platform-architecture.injectable";
import getHelmChartVersionsInjectable from "./helm/helm-service/get-helm-chart-versions.injectable";
import getHelmChartValuesInjectable from "./helm/helm-service/get-helm-chart-values.injectable";
import listHelmChartsInjectable from "./helm/helm-service/list-helm-charts.injectable";
import deleteHelmReleaseInjectable from "./helm/helm-service/delete-helm-release.injectable";
import getHelmReleaseHistoryInjectable from "./helm/helm-service/get-helm-release-history.injectable";
import getHelmReleaseValuesInjectable from "./helm/helm-service/get-helm-release-values.injectable";
import installHelmChartInjectable from "./helm/helm-service/install-helm-chart.injectable";
import listHelmReleasesInjectable from "./helm/helm-service/list-helm-releases.injectable";
import rollbackHelmReleaseInjectable from "./helm/helm-service/rollback-helm-release.injectable";
import waitUntilBundledExtensionsAreLoadedInjectable from "./start-main-application/lens-window/application-window/wait-until-bundled-extensions-are-loaded.injectable";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
import focusApplicationInjectable from "./electron-app/features/focus-application.injectable";
import kubectlDownloadingNormalizedArchInjectable from "./kubectl/normalized-arch.injectable";
import initializeClusterManagerInjectable from "./cluster/initialize-manager.injectable";
import addKubeconfigSyncAsEntitySourceInjectable from "./start-main-application/runnables/kube-config-sync/add-source.injectable";
import type { GlobalOverride } from "../common/test-utils/get-global-override";
import applicationInformationInjectable from "../common/vars/application-information-injectable";
import nodeEnvInjectionToken from "../common/vars/node-env-injection-token";
import { getOverrideFsWithFakes } from "../test-utils/override-fs-with-fakes";
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
const {
@@ -83,7 +42,7 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
di.register(getInjectable({
id: "node-env",
instantiate: () => "test",
instantiate: () => "production",
injectionToken: nodeEnvInjectionToken,
}));
@@ -91,13 +50,12 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
di.preventSideEffects();
const injectables: Injectable<any, any, any>[] = (
global as any
).mainInjectablePaths.flatMap((filePath: string) =>
Object.values(require(filePath)).filter(
(maybeInjectable: any) => isInjectable(maybeInjectable),
),
);
const injectables = (
global.injectablePaths.main.paths
.map(path => require(path))
.flatMap(Object.values)
.filter(isInjectable)
) as Injectable<any, any, any>[];
runInAction(() => {
registerMobX(di);
@@ -109,52 +67,24 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
});
if (doGeneralOverrides) {
const globalOverrides: GlobalOverride[] = (global as any).mainGlobalOverridePaths.map(
(filePath: string) => require(filePath).default,
);
for (const globalOverridePath of global.injectablePaths.main.globalOverridePaths) {
const globalOverride = require(globalOverridePath).default as GlobalOverride;
globalOverrides.forEach(globalOverride => {
di.override(globalOverride.injectable, globalOverride.overridingInstantiate);
});
}
di.override(electronInjectable, () => ({}));
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
overrideOperatingSystem(di);
overrideRunnablesHavingSideEffects(di);
overrideElectronFeatures(di);
di.override(isDevelopmentInjectable, () => false);
di.override(environmentVariablesInjectable, () => ({}));
di.override(commandLineArgumentsInjectable, () => []);
getOverrideFsWithFakes()(di);
di.override(clusterFramesInjectable, () => observable.map<string, ClusterFrameInfo>());
di.override(stopServicesAndExitAppInjectable, () => () => {});
di.override(lensResourcesDirInjectable, () => "/irrelevant");
overrideFunctionalInjectables(di, [
getHelmChartVersionsInjectable,
getHelmChartValuesInjectable,
listHelmChartsInjectable,
deleteHelmReleaseInjectable,
getHelmReleaseHistoryInjectable,
getHelmReleaseValuesInjectable,
installHelmChartInjectable,
listHelmReleasesInjectable,
rollbackHelmReleaseInjectable,
writeJsonFileInjectable,
readJsonFileInjectable,
readFileInjectable,
execFileInjectable,
]);
di.override(broadcastMessageInjectable, () => (channel) => {
throw new Error(`Tried to broadcast message to channel "${channel}" over IPC without explicit override.`);
});
di.override(baseBundledBinariesDirectoryInjectable, () => "some-bin-directory");
di.override(spawnInjectable, () => () => {
return {
stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
@@ -162,14 +92,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
on: jest.fn(),
} as never;
});
di.override(loggerInjectable, () => ({
warn: noop,
debug: noop,
error: noop,
info: noop,
silly: noop,
}));
}
return di;
@@ -180,15 +102,9 @@ const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
[
initializeExtensionsInjectable,
initializeClusterManagerInjectable,
addKubeconfigSyncAsEntitySourceInjectable,
setupIpcMainHandlersInjectable,
setupLensProxyInjectable,
setupShellInjectable,
setupSyncingOfWeblinksInjectable,
setupSystemCaInjectable,
setupRunnablesAfterWindowIsOpenedInjectable,
startCatalogSyncInjectable,
startKubeConfigSyncInjectable,
].forEach((injectable) => {
di.override(injectable, () => ({
id: injectable.id,
@@ -197,11 +113,6 @@ const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
});
};
const overrideOperatingSystem = (di: DiContainer) => {
di.override(platformInjectable, () => "darwin");
di.override(normalizedPlatformArchitectureInjectable, () => "arm64");
};
const overrideElectronFeatures = (di: DiContainer) => {
[
setupMainWindowVisibilityAfterActivationInjectable,
@@ -216,32 +127,7 @@ const overrideElectronFeatures = (di: DiContainer) => {
}));
});
di.override(exitAppInjectable, () => () => {});
di.override(getCommandLineSwitchInjectable, () => () => "irrelevant");
di.override(requestSingleInstanceLockInjectable, () => () => true);
di.override(disableHardwareAccelerationInjectable, () => () => {});
di.override(shouldStartHiddenInjectable, () => false);
di.override(showMessagePopupInjectable, () => () => {});
di.override(waitForElectronToBeReadyInjectable, () => () => Promise.resolve());
di.override(getElectronThemeInjectable, () => () => "dark");
di.override(syncThemeFromOperatingSystemInjectable, () => ({ start: () => {}, stop: () => {} }));
di.override(electronQuitAndInstallUpdateInjectable, () => () => {});
di.override(setUpdateOnQuitInjectable, () => () => {});
di.override(focusApplicationInjectable, () => () => {});
di.override(
getElectronAppPathInjectable,
() => (name: string) => `/some-electron-app-path-for-${kebabCase(name)}`,
);
di.override(setElectronAppPathInjectable, () => () => {});
di.override(electronUpdaterIsActiveInjectable, () => false);
};
const overrideFunctionalInjectables = (di: DiContainer, injectables: Injectable<any, any, any>[]) => {
injectables.forEach(injectable => {
di.override(injectable, () => () => {
throw new Error(`Tried to run "${injectable.id}" without explicit override.`);
});
});
};
@@ -0,0 +1,37 @@
/**
* 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 execHelmInjectable from "./exec-helm/exec-helm.injectable";
export interface DeleteHelmReleaseData {
name: string;
namespace: string;
}
export type DeleteHelmRelease = (kubeconfigPath: string, data: DeleteHelmReleaseData) => Promise<string>;
const deleteHelmReleaseInjectable = getInjectable({
id: "delete-helm-release",
instantiate: (di): DeleteHelmRelease => {
const execHelm = di.inject(execHelmInjectable);
return async (kubeconfigPath, { name, namespace }) => {
const result = await execHelm([
"delete",
name,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (result.callWasSuccessful) {
return result.response;
}
throw result.error;
};
},
});
export default deleteHelmReleaseInjectable;
@@ -0,0 +1,39 @@
/**
* 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 type { HelmReleaseRevision } from "../../common/k8s-api/endpoints/helm-releases.api/request-history.injectable";
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
export interface GetHelmReleaseHistoryData {
name: string;
namespace: string;
}
export type GetHelmReleaseHistory = (kubeconfigPath: string, data: GetHelmReleaseHistoryData) => Promise<HelmReleaseRevision[]>;
const getHelmReleaseHistoryInjectable = getInjectable({
id: "get-helm-release-history",
instantiate: (di): GetHelmReleaseHistory => {
const execHelm = di.inject(execHelmInjectable);
return async (kubeconfigPath, { name, namespace }) => {
const result = await execHelm([
"history",
name,
"--output", "json",
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (result.callWasSuccessful) {
return JSON.parse(result.response);
}
throw result.error;
};
},
});
export default getHelmReleaseHistoryInjectable;
@@ -0,0 +1,49 @@
/**
* 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 execHelmInjectable from "./exec-helm/exec-helm.injectable";
export interface GetHelmReleaseValuesData {
name: string;
namespace: string;
all?: boolean;
}
export type GetHelmReleaseValues = (kubeconfigPath: string, data: GetHelmReleaseValuesData) => Promise<string>;
const getHelmReleaseValuesInjectable = getInjectable({
id: "get-helm-release-values",
instantiate: (di): GetHelmReleaseValues => {
const execHelm = di.inject(execHelmInjectable);
return async (kubeconfigPath, { name, namespace, all = false }) => {
const args = [
"get",
"values",
name,
];
if (all) {
args.push("--all");
}
args.push(
"--output", "yaml",
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
);
const result = await execHelm(args);
if (result.callWasSuccessful) {
return result.response;
}
throw result.error;
};
},
});
export default getHelmReleaseValuesInjectable;
-168
View File
@@ -1,168 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import tempy from "tempy";
import fse from "fs-extra";
import * as yaml from "js-yaml";
import { toCamelCase } from "../../common/utils/camelCase";
import type { JsonValue } from "type-fest";
import { isObject, json } from "../../common/utils";
import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
const execHelm = asLegacyGlobalFunctionForExtensionApi(execHelmInjectable);
export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise<Record<string, any>[]> {
const args = [
"ls",
"--all",
"--output", "json",
];
if (namespace) {
args.push("-n", namespace);
} else {
args.push("--all-namespaces");
}
args.push("--kubeconfig", pathToKubeconfig);
const result = await execHelm(args);
if (!result.callWasSuccessful) {
throw result.error;
}
const output = json.parse(result.response);
if (!Array.isArray(output) || output.length == 0) {
return [];
}
return output.filter(isObject).map(toCamelCase);
}
export async function installChart(chart: string, values: JsonValue, name: string | undefined = "", namespace: string, version: string, kubeconfigPath: string) {
const valuesFilePath = tempy.file({ name: "values.yaml" });
await fse.writeFile(valuesFilePath, yaml.dump(values));
const args = ["install"];
if (name) {
args.push(name);
}
args.push(
chart,
"--version", version,
"--values", valuesFilePath,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
);
if (!name) {
args.push("--generate-name");
}
try {
const result = await execHelm(args);
if (!result.callWasSuccessful) {
throw result.error;
}
const output = result.response;
const releaseName = output.split("\n")[0].split(" ")[1].trim();
return {
log: output,
release: {
name: releaseName,
namespace,
},
};
} finally {
await fse.unlink(valuesFilePath);
}
}
export async function deleteRelease(name: string, namespace: string, kubeconfigPath: string): Promise<string> {
const result = await execHelm([
"delete",
name,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (result.callWasSuccessful) {
return result.response;
}
throw result.error;
}
interface GetValuesOptions {
namespace: string;
all?: boolean;
kubeconfigPath: string;
}
export async function getValues(name: string, { namespace, all = false, kubeconfigPath }: GetValuesOptions): Promise<string> {
const args = [
"get",
"values",
name,
];
if (all) {
args.push("--all");
}
args.push(
"--output", "yaml",
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
);
const result = await execHelm(args);
if (result.callWasSuccessful) {
return result.response;
}
throw result.error;
}
export async function getHistory(name: string, namespace: string, kubeconfigPath: string): Promise<JsonValue> {
const result = await execHelm([
"history",
name,
"--output", "json",
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (result.callWasSuccessful) {
return json.parse(result.response);
}
throw result.error;
}
export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string): Promise<void> {
const result = await execHelm([
"rollback",
name,
`${revision}`,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (!result.callWasSuccessful) {
throw result.error;
}
}
@@ -4,25 +4,25 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import { deleteRelease } from "../helm-release-manager";
import loggerInjectable from "../../../common/logger.injectable";
import type { DeleteHelmReleaseData } from "../delete-helm-release.injectable";
import deleteHelmReleaseInjectable from "../delete-helm-release.injectable";
const deleteHelmReleaseInjectable = getInjectable({
id: "delete-helm-release",
const deleteClusterHelmReleaseInjectable = getInjectable({
id: "delete-cluster-helm-release",
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const deleteHelmRelease = di.inject(deleteHelmReleaseInjectable);
return async (cluster: Cluster, releaseName: string, namespace: string) => {
return async (cluster: Cluster, data: DeleteHelmReleaseData) => {
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
logger.debug("Delete release");
logger.debug(`[CLUSTER]: Delete helm release`, data);
return deleteRelease(releaseName, namespace, proxyKubeconfig);
return deleteHelmRelease(proxyKubeconfig, data);
};
},
causesSideEffects: true,
});
export default deleteHelmReleaseInjectable;
export default deleteClusterHelmReleaseInjectable;
@@ -24,8 +24,6 @@ const getHelmChartValuesInjectable = getInjectable({
return getChartManager(repo).getValues(chartName, version);
};
},
causesSideEffects: true,
});
export default getHelmChartValuesInjectable;
@@ -24,8 +24,6 @@ const getHelmChartVersionsInjectable = getInjectable({
return getChartManager(repo).chartVersions(chartName);
};
},
causesSideEffects: true,
});
export default getHelmChartVersionsInjectable;
@@ -4,25 +4,25 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import { getHistory } from "../helm-release-manager";
import loggerInjectable from "../../../common/logger.injectable";
import type { GetHelmReleaseHistoryData } from "../get-helm-release-history.injectable";
import getHelmReleaseHistoryInjectable from "../get-helm-release-history.injectable";
const getHelmReleaseHistoryInjectable = getInjectable({
id: "get-helm-release-history",
const getClusterHelmReleaseHistoryInjectable = getInjectable({
id: "get-cluster-helm-release-history",
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const getHelmReleaseHistory = di.inject(getHelmReleaseHistoryInjectable);
return async (cluster: Cluster, releaseName: string, namespace: string) => {
return async (cluster: Cluster, data: GetHelmReleaseHistoryData) => {
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
logger.debug("Fetch release history");
logger.debug(`[CLUSTER]: Fetch release history for clusterId=${cluster.id}`, data);
return getHistory(releaseName, namespace, proxyKubeconfig);
return getHelmReleaseHistory(proxyKubeconfig, data);
};
},
causesSideEffects: true,
});
export default getHelmReleaseHistoryInjectable;
export default getClusterHelmReleaseHistoryInjectable;
@@ -3,39 +3,26 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { getValues } from "../helm-release-manager";
import loggerInjectable from "../../../common/logger.injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import type { GetHelmReleaseValuesData } from "../get-helm-release-values.injectable";
import getHelmReleaseValuesInjectable from "../get-helm-release-values.injectable";
interface GetReleaseValuesArgs {
cluster: Cluster;
namespace: string;
all: boolean;
}
const getHelmReleaseValuesInjectable = getInjectable({
id: "get-helm-release-values",
const getClusterHelmReleaseValuesInjectable = getInjectable({
id: "get-cluster-helm-release-values",
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const getHelmReleaseValues = di.inject(getHelmReleaseValuesInjectable);
return async (
releaseName: string,
{ cluster, namespace, all }: GetReleaseValuesArgs,
) => {
return async (cluster: Cluster, data: GetHelmReleaseValuesData) => {
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
logger.debug("Fetch release values");
logger.debug(`[CLUSTER]: getting helm release values`, data);
return getValues(releaseName, {
namespace,
all,
kubeconfigPath: pathToKubeconfig,
});
return getHelmReleaseValues(pathToKubeconfig, data);
};
},
causesSideEffects: true,
});
export default getHelmReleaseValuesInjectable;
export default getClusterHelmReleaseValuesInjectable;
@@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import type { JsonObject } from "type-fest";
import type { Cluster } from "../../../common/cluster/cluster";
import { installChart } from "../helm-release-manager";
import installHelmChartInjectable from "../install-helm-chart.injectable";
export interface InstallChartArgs {
chart: string;
@@ -15,16 +15,21 @@ export interface InstallChartArgs {
version: string;
}
const installHelmChartInjectable = getInjectable({
id: "install-helm-chart",
const installClusterHelmChartInjectable = getInjectable({
id: "install-cluster-helm-chart",
instantiate: () => async (cluster: Cluster, data: InstallChartArgs) => {
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
instantiate: (di) => {
const installHelmChart = di.inject(installHelmChartInjectable);
return installChart(data.chart, data.values, data.name, data.namespace, data.version, proxyKubeconfig);
return async (cluster: Cluster, data: InstallChartArgs) => {
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
return installHelmChart({
...data,
kubeconfigPath: proxyKubeconfig,
});
};
},
causesSideEffects: true,
});
export default installHelmChartInjectable;
export default installClusterHelmChartInjectable;
@@ -36,8 +36,6 @@ const listHelmChartsInjectable = getInjectable({
);
};
},
causesSideEffects: true,
});
export default listHelmChartsInjectable;
@@ -5,24 +5,23 @@
import { getInjectable } from "@ogre-tools/injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import loggerInjectable from "../../../common/logger.injectable";
import { listReleases } from "../helm-release-manager";
import listHelmReleasesInjectable from "../list-helm-releases.injectable";
const listHelmReleasesInjectable = getInjectable({
id: "list-helm-releases",
const listClusterHelmReleasesInjectable = getInjectable({
id: "list-cluster-helm-releases",
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const listHelmReleases = di.inject(listHelmReleasesInjectable);
return async (cluster: Cluster, namespace?: string) => {
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
logger.debug("list releases");
logger.debug(`[CLUSTER]: listing helm releases for clusterId=${cluster.id}`, { namespace });
return listReleases(proxyKubeconfig, namespace);
return listHelmReleases(proxyKubeconfig, namespace);
};
},
causesSideEffects: true,
});
export default listHelmReleasesInjectable;
export default listClusterHelmReleasesInjectable;
@@ -5,28 +5,24 @@
import { getInjectable } from "@ogre-tools/injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import loggerInjectable from "../../../common/logger.injectable";
import { rollback } from "../helm-release-manager";
import type { RollbackHelmReleaseData } from "../rollback-helm-release.injectable";
import rollbackHelmReleaseInjectable from "../rollback-helm-release.injectable";
const rollbackHelmReleaseInjectable = getInjectable({
id: "rollback-helm-release",
const rollbackClusterHelmReleaseInjectable = getInjectable({
id: "rollback-cluster-helm-release",
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
const rollbackHelmRelease = di.inject(rollbackHelmReleaseInjectable);
return async (
cluster: Cluster,
releaseName: string,
namespace: string,
revision: number,
) => {
return async (cluster: Cluster, data: RollbackHelmReleaseData) => {
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
logger.debug("Rollback release");
await rollback(releaseName, namespace, revision, proxyKubeconfig);
logger.debug(`[CLUSTER]: rolling back helm release for clusterId=${cluster.id}`, data);
await rollbackHelmRelease(proxyKubeconfig, data);
};
},
causesSideEffects: true,
});
export default rollbackHelmReleaseInjectable;
export default rollbackClusterHelmReleaseInjectable;
@@ -0,0 +1,93 @@
/**
* 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 { dump } from "js-yaml";
import tempy from "tempy";
import type { JsonValue } from "type-fest";
import removePathInjectable from "../../common/fs/remove.injectable";
import writeFileInjectable from "../../common/fs/write-file.injectable";
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
export interface InstallHelmChartData {
chart: string;
values: JsonValue;
name: string;
namespace: string;
version: string;
kubeconfigPath: string;
}
export interface InstallHelmChartResult {
log: string;
release: {
name: string;
namespace: string;
};
}
export type InstallHelmChart = (data: InstallHelmChartData) => Promise<InstallHelmChartResult>;
const installHelmChartInjectable = getInjectable({
id: "install-helm-chart",
instantiate: (di): InstallHelmChart => {
const writeFile = di.inject(writeFileInjectable);
const removePath = di.inject(removePathInjectable);
const execHelm = di.inject(execHelmInjectable);
return async ({
chart,
kubeconfigPath,
name,
namespace,
values,
version,
}) => {
const valuesFilePath = tempy.file({ name: "values.yaml" });
await writeFile(valuesFilePath, dump(values));
const args = ["install"];
if (name) {
args.push(name);
}
args.push(
chart,
"--version", version,
"--values", valuesFilePath,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
);
if (!name) {
args.push("--generate-name");
}
try {
const result = await execHelm(args);
if (!result.callWasSuccessful) {
throw result.error;
}
const output = result.response;
const releaseName = output.split("\n")[0].split(" ")[1].trim();
return {
log: output,
release: {
name: releaseName,
namespace,
},
};
} finally {
await removePath(valuesFilePath);
}
};
},
});
export default installHelmChartInjectable;
@@ -0,0 +1,48 @@
/**
* 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 execHelmInjectable from "./exec-helm/exec-helm.injectable";
import { isObject, json, toCamelCase } from "../../common/utils";
export type ListHelmReleases = (pathToKubeconfig: string, namespace?: string) => Promise<Record<string, any>[]>;
const listHelmReleasesInjectable = getInjectable({
id: "list-helm-releases",
instantiate: (di): ListHelmReleases => {
const execHelm = di.inject(execHelmInjectable);
return async (pathToKubeconfig, namespace) => {
const args = [
"ls",
"--all",
"--output", "json",
];
if (namespace) {
args.push("-n", namespace);
} else {
args.push("--all-namespaces");
}
args.push("--kubeconfig", pathToKubeconfig);
const result = await execHelm(args);
if (!result.callWasSuccessful) {
throw result.error;
}
const output = json.parse(result.response);
if (!Array.isArray(output) || output.length == 0) {
return [];
}
return output.filter(isObject).map(toCamelCase);
};
},
});
export default listHelmReleasesInjectable;
@@ -0,0 +1,37 @@
/**
* 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 execHelmInjectable from "./exec-helm/exec-helm.injectable";
export interface RollbackHelmReleaseData {
name: string;
namespace: string;
revision: number;
}
export type RollbackHelmRelease = (kubeconfigPath: string, data: RollbackHelmReleaseData) => Promise<void>;
const rollbackHelmReleaseInjectable = getInjectable({
id: "rollback-helm-release",
instantiate: (di): RollbackHelmRelease => {
const execHelm = di.inject(execHelmInjectable);
return async (kubeconfigPath, { name, namespace, revision }) => {
const result = await execHelm([
"rollback",
name,
`${revision}`,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (!result.callWasSuccessful) {
throw result.error;
}
};
},
});
export default rollbackHelmReleaseInjectable;
@@ -11,6 +11,7 @@ import kubeAuthProxyCertificateInjectable from "./kube-auth-proxy-certificate.in
import loggerInjectable from "../../common/logger.injectable";
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
import lensK8sProxyPathInjectable from "./lens-k8s-proxy-path.injectable";
import getPortFromStreamInjectable from "../utils/get-port-from-stream.injectable";
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
@@ -23,6 +24,7 @@ const createKubeAuthProxyInjectable = getInjectable({
spawn: di.inject(spawnInjectable),
logger: di.inject(loggerInjectable),
waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable),
getPortFromStream: di.inject(getPortFromStreamInjectable),
};
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
+5 -4
View File
@@ -6,7 +6,7 @@
import type { ChildProcess } from "child_process";
import { randomBytes } from "crypto";
import type { Cluster } from "../../common/cluster/cluster";
import { getPortFrom } from "../utils/get-port";
import type { GetPortFromStream } from "../utils/get-port-from-stream.injectable";
import { makeObservable, observable, when } from "mobx";
import type { SelfSignedCert } from "selfsigned";
import assert from "assert";
@@ -23,9 +23,10 @@ const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"),
export interface KubeAuthProxyDependencies {
readonly proxyBinPath: string;
readonly proxyCert: SelfSignedCert;
readonly spawn: Spawn;
readonly logger: Logger;
readonly waitUntilPortIsUsed: WaitUntilPortIsUsed;
spawn: Spawn;
waitUntilPortIsUsed: WaitUntilPortIsUsed;
getPortFromStream: GetPortFromStream;
}
export class KubeAuthProxy {
@@ -101,7 +102,7 @@ export class KubeAuthProxy {
}
});
this._port = await getPortFrom(this.proxyProcess.stdout, {
this._port = await this.dependencies.getPortFromStream(this.proxyProcess.stdout, {
lineRegex: startingServeRegex,
onFind: () => this.cluster.broadcastConnectUpdate("Authentication proxy started"),
});
@@ -17,6 +17,7 @@ import kubectlVersionMapInjectable from "./version-map.injectable";
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
import joinPathsInjectable from "../../common/path/join-paths.injectable";
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
import loggerInjectable from "../../common/logger.injectable";
const createKubectlInjectable = getInjectable({
id: "create-kubectl",
@@ -32,6 +33,7 @@ const createKubectlInjectable = getInjectable({
baseBundeledBinariesDirectory: di.inject(baseBundledBinariesDirectoryInjectable),
bundledKubectlVersion: di.inject(bundledKubectlVersionInjectable),
kubectlVersionMap: di.inject(kubectlVersionMapInjectable),
logger: di.inject(loggerInjectable),
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
joinPaths: di.inject(joinPathsInjectable),
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
+20 -19
View File
@@ -5,7 +5,6 @@
import fs from "fs";
import { promiseExecFile } from "../../common/utils/promise-exec";
import logger from "../logger";
import { ensureDir, pathExists } from "fs-extra";
import * as lockFile from "proper-lockfile";
import { SemVer, coerce } from "semver";
@@ -18,6 +17,7 @@ import type { JoinPaths } from "../../common/path/join-paths.injectable";
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
import type { NormalizedPlatform } from "../../common/vars/normalized-platform.injectable";
import type { Logger } from "../../common/logger";
const initScriptVersionString = "# lens-initscript v3";
@@ -36,6 +36,7 @@ export interface KubectlDependencies {
};
readonly bundledKubectlVersion: string;
readonly kubectlVersionMap: Map<string, string>;
readonly logger: Logger;
joinPaths: JoinPaths;
getDirnameOfPath: GetDirnameOfPath;
getBasenameOfPath: GetBasenameOfPath;
@@ -67,13 +68,13 @@ export class Kubectl {
*/
if (fromMajorMinor) {
this.kubectlVersion = fromMajorMinor;
logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using version map`);
this.dependencies.logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using version map`);
} else {
/* this is the version (without possible prelease tag) to get from the download mirror */
const ver = coerce(version.format()) ?? bundledVersion;
this.kubectlVersion = ver.format();
logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using fallback`);
this.dependencies.logger.debug(`Set kubectl version ${this.kubectlVersion} for cluster version ${clusterVersion} using fallback`);
}
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${this.dependencies.normalizedDownloadPlatform}/${this.dependencies.normalizedDownloadArch}/${this.dependencies.kubectlBinaryName}`;
@@ -115,14 +116,14 @@ export class Kubectl {
try {
if (!await this.ensureKubectl()) {
logger.error("Failed to ensure kubectl, fallback to the bundled version");
this.dependencies.logger.error("Failed to ensure kubectl, fallback to the bundled version");
return this.getBundledPath();
}
return this.path;
} catch (err) {
logger.error("Failed to ensure kubectl, fallback to the bundled version", err);
this.dependencies.logger.error("Failed to ensure kubectl, fallback to the bundled version", err);
return this.getBundledPath();
}
@@ -135,7 +136,7 @@ export class Kubectl {
return this.dirname;
} catch (err) {
logger.error("Failed to get biniary directory", err);
this.dependencies.logger.error("Failed to get biniary directory", err);
return "";
}
@@ -164,13 +165,13 @@ export class Kubectl {
}
if (version === this.kubectlVersion) {
logger.debug(`Local kubectl is version ${this.kubectlVersion}`);
this.dependencies.logger.debug(`Local kubectl is version ${this.kubectlVersion}`);
return true;
}
logger.error(`Local kubectl is version ${version}, expected ${this.kubectlVersion}, unlinking`);
this.dependencies.logger.error(`Local kubectl is version ${version}, expected ${this.kubectlVersion}, unlinking`);
} catch (error) {
logger.error(`Local kubectl failed to run properly (${error}), unlinking`);
this.dependencies.logger.error(`Local kubectl failed to run properly (${error}), unlinking`);
}
await fs.promises.unlink(this.path);
}
@@ -190,7 +191,7 @@ export class Kubectl {
return true;
} catch (err) {
logger.error(`Could not copy the bundled kubectl to app-data: ${err}`);
this.dependencies.logger.error(`Could not copy the bundled kubectl to app-data: ${err}`);
return false;
}
@@ -205,7 +206,7 @@ export class Kubectl {
}
if (Kubectl.invalidBundle) {
logger.error(`Detected invalid bundle binary, returning ...`);
this.dependencies.logger.error(`Detected invalid bundle binary, returning ...`);
return false;
}
@@ -215,7 +216,7 @@ export class Kubectl {
try {
const release = await lockFile.lock(this.dirname);
logger.debug(`Acquired a lock for ${this.kubectlVersion}`);
this.dependencies.logger.debug(`Acquired a lock for ${this.kubectlVersion}`);
const bundled = await this.checkBundled();
let isValid = await this.checkBinary(this.path, !bundled);
@@ -223,8 +224,8 @@ export class Kubectl {
try {
await this.downloadKubectl();
} catch (error) {
logger.error(`[KUBECTL]: failed to download kubectl`, error);
logger.debug(`[KUBECTL]: Releasing lock for ${this.kubectlVersion}`);
this.dependencies.logger.error(`[KUBECTL]: failed to download kubectl`, error);
this.dependencies.logger.debug(`[KUBECTL]: Releasing lock for ${this.kubectlVersion}`);
await release();
return false;
@@ -234,18 +235,18 @@ export class Kubectl {
}
if (!isValid) {
logger.debug(`[KUBECTL]: Releasing lock for ${this.kubectlVersion}`);
this.dependencies.logger.debug(`[KUBECTL]: Releasing lock for ${this.kubectlVersion}`);
await release();
return false;
}
logger.debug(`[KUBECTL]: Releasing lock for ${this.kubectlVersion}`);
this.dependencies.logger.debug(`[KUBECTL]: Releasing lock for ${this.kubectlVersion}`);
await release();
return true;
} catch (error) {
logger.error(`[KUBECTL]: Failed to get a lock for ${this.kubectlVersion}`, error);
this.dependencies.logger.error(`[KUBECTL]: Failed to get a lock for ${this.kubectlVersion}`, error);
return false;
}
@@ -254,7 +255,7 @@ export class Kubectl {
public async downloadKubectl() {
await ensureDir(this.dependencies.getDirnameOfPath(this.path), 0o755);
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
this.dependencies.logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
const downloadStream = got.stream({ url: this.url, decompress: true });
const fileWriteStream = fs.createWriteStream(this.path, { mode: 0o755 });
@@ -263,7 +264,7 @@ export class Kubectl {
try {
await pipeline(downloadStream, fileWriteStream);
await fs.promises.chmod(this.path, 0o755);
logger.debug("kubectl binary download finished");
this.dependencies.logger.debug("kubectl binary download finished");
} catch (error) {
await fs.promises.unlink(this.path).catch(noop);
throw error;
@@ -3,11 +3,14 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import processArchInjectable from "../../common/vars/process-arch.injectable";
const kubectlDownloadingNormalizedArchInjectable = getInjectable({
id: "kubectl-downloading-normalized-arch",
instantiate: () => {
switch (process.arch) {
instantiate: (di) => {
const processArch = di.inject(processArchInjectable);
switch (processArch) {
case "arm64":
return "arm64";
case "x64":
@@ -18,10 +21,9 @@ const kubectlDownloadingNormalizedArchInjectable = getInjectable({
case "ia32":
return "386";
default:
throw new Error(`arch=${process.arch} is unsupported`);
throw new Error(`arch=${processArch} is unsupported`);
}
},
causesSideEffects: true,
});
export default kubectlDownloadingNormalizedArchInjectable;
@@ -0,0 +1,45 @@
/**
* 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 getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
import { getClusterIdFromHost } from "../../common/utils";
import { apiKubePrefix } from "../../common/vars";
import type { GetClusterForRequest } from "./lens-proxy";
const getClusterForRequestInjectable = getInjectable({
id: "get-cluster-for-request",
instantiate: (di): GetClusterForRequest => {
const getClusterById = di.inject(getClusterByIdInjectable);
return (req) => {
if (!req.headers.host) {
return undefined;
}
// lens-server is connecting to 127.0.0.1:<port>/<uid>
if (req.url && req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1];
const cluster = getClusterById(clusterId);
if (cluster) {
// we need to swap path prefix so that request is proxied to kube api
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
}
return cluster;
}
const clusterId = getClusterIdFromHost(req.headers.host);
if (!clusterId) {
return undefined;
}
return getClusterById(clusterId);
};
},
});
export default getClusterForRequestInjectable;
+3 -3
View File
@@ -7,13 +7,13 @@ import { LensProxy } from "./lens-proxy";
import { kubeApiUpgradeRequest } from "./proxy-functions";
import routerInjectable from "../router/router.injectable";
import httpProxy from "http-proxy";
import clusterManagerInjectable from "../cluster/manager.injectable";
import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable";
import shellApiRequestInjectable from "./proxy-functions/shell-api-request.injectable";
import lensProxyPortInjectable from "./lens-proxy-port.injectable";
import contentSecurityPolicyInjectable from "../../common/vars/content-security-policy.injectable";
import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable";
import loggerInjectable from "../../common/logger.injectable";
import lensProxyCertificateInjectable from "../../common/certificate/lens-proxy-certificate.injectable";
import getClusterForRequestInjectable from "./get-cluster-for-request.injectable";
const lensProxyInjectable = getInjectable({
id: "lens-proxy",
@@ -23,7 +23,7 @@ const lensProxyInjectable = getInjectable({
proxy: httpProxy.createProxy(),
kubeApiUpgradeRequest,
shellApiRequest: di.inject(shellApiRequestInjectable),
getClusterForRequest: di.inject(clusterManagerInjectable).getClusterForRequest,
getClusterForRequest: di.inject(getClusterForRequestInjectable),
lensProxyPort: di.inject(lensProxyPortInjectable),
contentSecurityPolicy: di.inject(contentSecurityPolicyInjectable),
emitAppEvent: di.inject(emitAppEventInjectable),
+4 -4
View File
@@ -19,14 +19,14 @@ import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injecta
import type { Logger } from "../../common/logger";
import type { SelfSignedCert } from "selfsigned";
type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | undefined;
export type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | undefined;
export type ServerIncomingMessage = SetRequired<http.IncomingMessage, "url" | "method">;
export type LensProxyApiRequest = (args: ProxyApiRequestArgs) => void | Promise<void>;
interface Dependencies {
getClusterForRequest: GetClusterForRequest;
shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
shellApiRequest: LensProxyApiRequest;
kubeApiUpgradeRequest: LensProxyApiRequest;
emitAppEvent: EmitAppEvent;
readonly router: Router;
readonly proxy: httpProxy;
@@ -0,0 +1,42 @@
/**
* 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 shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable";
import openShellSessionInjectable from "../../shell-session/create-shell-session.injectable";
import type { LensProxyApiRequest } from "../lens-proxy";
import URLParse from "url-parse";
import { Server as WebSocketServer } from "ws";
import loggerInjectable from "../../../common/logger.injectable";
import getClusterForRequestInjectable from "../get-cluster-for-request.injectable";
const shellApiRequestInjectable = getInjectable({
id: "shell-api-request",
instantiate: (di): LensProxyApiRequest => {
const openShellSession = di.inject(openShellSessionInjectable);
const authenticateRequest = di.inject(shellRequestAuthenticatorInjectable).authenticate;
const getClusterForRequest = di.inject(getClusterForRequestInjectable);
const logger = di.inject(loggerInjectable);
return ({ req, socket, head }) => {
const cluster = getClusterForRequest(req);
const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true);
if (!tabId || !cluster || !authenticateRequest(cluster.id, tabId, shellToken)) {
socket.write("Invalid shell request");
socket.end();
} else {
const ws = new WebSocketServer({ noServer: true });
ws.handleUpgrade(req, socket, head, (websocket) => {
openShellSession({ websocket, cluster, tabId, nodeName })
.catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${nodeName ? "node" : "local"} shell`, error));
});
}
};
},
});
export default shellApiRequestInjectable;
@@ -1,21 +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 { shellApiRequest } from "./shell-api-request";
import shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable";
import clusterManagerInjectable from "../../../cluster/manager.injectable";
import openShellSessionInjectable from "../../../shell-session/create-shell-session.injectable";
const shellApiRequestInjectable = getInjectable({
id: "shell-api-request",
instantiate: (di) => shellApiRequest({
openShellSession: di.inject(openShellSessionInjectable),
authenticateRequest: di.inject(shellRequestAuthenticatorInjectable).authenticate,
clusterManager: di.inject(clusterManagerInjectable),
}),
});
export default shellApiRequestInjectable;
@@ -1,36 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import logger from "../../../logger";
import { Server as WebSocketServer } from "ws";
import type { ProxyApiRequestArgs } from "../types";
import type { ClusterManager } from "../../../cluster/manager";
import URLParse from "url-parse";
import type { ClusterId } from "../../../../common/cluster-types";
import type { OpenShellSession } from "../../../shell-session/create-shell-session.injectable";
interface Dependencies {
authenticateRequest: (clusterId: ClusterId, tabId: string, shellToken: string | undefined) => boolean;
openShellSession: OpenShellSession;
clusterManager: ClusterManager;
}
export const shellApiRequest = ({ openShellSession, authenticateRequest, clusterManager }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => {
const cluster = clusterManager.getClusterForRequest(req);
const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true);
if (!tabId || !cluster || !authenticateRequest(cluster.id, tabId, shellToken)) {
socket.write("Invalid shell request");
return void socket.end();
}
const ws = new WebSocketServer({ noServer: true });
ws.handleUpgrade(req, socket, head, (websocket) => {
openShellSession({ websocket, cluster, tabId, nodeName })
.catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${nodeName ? "node" : "local"} shell`, error));
});
};
@@ -2,9 +2,9 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getOrInsertMap } from "../../../../../common/utils";
import type { ClusterId } from "../../../../../common/cluster-types";
import { ipcMainHandle } from "../../../../../common/ipc";
import { getOrInsertMap } from "../../../../common/utils";
import type { ClusterId } from "../../../../common/cluster-types";
import { ipcMainHandle } from "../../../../common/ipc";
import crypto from "crypto";
import { promisify } from "util";
-8
View File
@@ -1,8 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import logger from "../common/logger";
export default logger;
+1 -10
View File
@@ -13,14 +13,12 @@ import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import parseRequestInjectable from "./parse-request.injectable";
import { contentTypes } from "./router-content-types";
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";
import fsInjectable from "../../common/fs/fs.injectable";
import { runInAction } from "mobx";
describe("router", () => {
@@ -32,14 +30,11 @@ describe("router", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
mockFs();
di.permitSideEffects(fsInjectable);
di.override(parseRequestInjectable, () => () => Promise.resolve({
payload: "some-payload",
mime: "some-mime",
}));
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(kubectlBinaryNameInjectable, () => "kubectl");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin");
@@ -63,10 +58,6 @@ describe("router", () => {
router = di.inject(routerInjectable);
});
afterEach(() => {
mockFs.restore();
});
describe("when navigating to the route", () => {
let actualPromise: Promise<boolean>;
let clusterStub: Cluster;
@@ -5,19 +5,19 @@
import { apiPrefix } from "../../../../common/vars";
import { getRouteInjectable } from "../../../router/router.injectable";
import { clusterRoute } from "../../../router/route";
import deleteHelmReleaseInjectable from "../../../helm/helm-service/delete-helm-release.injectable";
import deleteClusterHelmReleaseInjectable from "../../../helm/helm-service/delete-helm-release.injectable";
const deleteReleaseRouteInjectable = getRouteInjectable({
id: "delete-release-route",
instantiate: (di) => {
const deleteHelmRelease = di.inject(deleteHelmReleaseInjectable);
const deleteHelmRelease = di.inject(deleteClusterHelmReleaseInjectable);
return clusterRoute({
method: "delete",
path: `${apiPrefix}/v2/releases/{namespace}/{release}`,
})(async ({ cluster, params: { release, namespace }}) => ({
response: await deleteHelmRelease(cluster, release, namespace),
path: `${apiPrefix}/v2/releases/{namespace}/{name}`,
})(async ({ cluster, params }) => ({
response: await deleteHelmRelease(cluster, params),
}));
},
});
@@ -5,23 +5,19 @@
import { apiPrefix } from "../../../../common/vars";
import { getRouteInjectable } from "../../../router/router.injectable";
import { clusterRoute } from "../../../router/route";
import getHelmReleaseHistoryInjectable from "../../../helm/helm-service/get-helm-release-history.injectable";
import getClusterHelmReleaseHistoryInjectable from "../../../helm/helm-service/get-helm-release-history.injectable";
const getReleaseRouteHistoryInjectable = getRouteInjectable({
id: "get-release-history-route",
instantiate: (di) => {
const getHelmReleaseHistory = di.inject(getHelmReleaseHistoryInjectable);
const getHelmReleaseHistory = di.inject(getClusterHelmReleaseHistoryInjectable);
return clusterRoute({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace}/{release}/history`,
path: `${apiPrefix}/v2/releases/{namespace}/{name}/history`,
})(async ({ cluster, params }) => ({
response: await getHelmReleaseHistory(
cluster,
params.release,
params.namespace,
),
response: await getHelmReleaseHistory(cluster, params),
}));
},
});
@@ -7,20 +7,20 @@ import { getRouteInjectable } from "../../../router/router.injectable";
import { getBoolean } from "../../../utils/parse-query";
import { contentTypes } from "../../../router/router-content-types";
import { clusterRoute } from "../../../router/route";
import getHelmReleaseValuesInjectable from "../../../helm/helm-service/get-helm-release-values.injectable";
import getClusterHelmReleaseValuesInjectable from "../../../helm/helm-service/get-helm-release-values.injectable";
const getReleaseRouteValuesInjectable = getRouteInjectable({
id: "get-release-values-route",
instantiate: (di) => {
const getHelmReleaseValues = di.inject(getHelmReleaseValuesInjectable);
const getClusterHelmReleaseValues = di.inject(getClusterHelmReleaseValuesInjectable);
return clusterRoute({
method: "get",
path: `${apiPrefix}/v2/releases/{namespace}/{release}/values`,
})(async ({ cluster, params: { namespace, release }, query }) => ({
response: await getHelmReleaseValues(release, {
cluster,
path: `${apiPrefix}/v2/releases/{namespace}/{name}/values`,
})(async ({ cluster, params: { namespace, name }, query }) => ({
response: await getClusterHelmReleaseValues(cluster, {
name,
namespace,
all: getBoolean(query, "all"),
}),
@@ -7,7 +7,7 @@ import { getRouteInjectable } from "../../../router/router.injectable";
import Joi from "joi";
import { payloadValidatedClusterRoute } from "../../../router/route";
import type { InstallChartArgs } from "../../../helm/helm-service/install-helm-chart.injectable";
import installHelmChartInjectable from "../../../helm/helm-service/install-helm-chart.injectable";
import installClusterHelmChartInjectable from "../../../helm/helm-service/install-helm-chart.injectable";
const installChartArgsValidator = Joi.object<InstallChartArgs, true, InstallChartArgs>({
chart: Joi
@@ -31,7 +31,7 @@ const installChartRouteInjectable = getRouteInjectable({
id: "install-chart-route",
instantiate: (di) => {
const installHelmChart = di.inject(installHelmChartInjectable);
const installHelmChart = di.inject(installClusterHelmChartInjectable);
return payloadValidatedClusterRoute({
method: "post",
@@ -5,13 +5,13 @@
import { apiPrefix } from "../../../../common/vars";
import { getRouteInjectable } from "../../../router/router.injectable";
import { clusterRoute } from "../../../router/route";
import listHelmReleasesInjectable from "../../../helm/helm-service/list-helm-releases.injectable";
import listClusterHelmReleasesInjectable from "../../../helm/helm-service/list-helm-releases.injectable";
const listReleasesRouteInjectable = getRouteInjectable({
id: "list-releases-route",
instantiate: (di) => {
const listHelmReleases = di.inject(listHelmReleasesInjectable);
const listHelmReleases = di.inject(listClusterHelmReleasesInjectable);
return clusterRoute({
method: "get",
@@ -6,7 +6,7 @@ import { apiPrefix } from "../../../../common/vars";
import { getRouteInjectable } from "../../../router/router.injectable";
import Joi from "joi";
import { payloadValidatedClusterRoute } from "../../../router/route";
import rollbackHelmReleaseInjectable from "../../../helm/helm-service/rollback-helm-release.injectable";
import rollbackClusterHelmReleaseInjectable from "../../../helm/helm-service/rollback-helm-release.injectable";
interface RollbackReleasePayload {
revision: number;
@@ -22,14 +22,14 @@ const rollbackReleaseRouteInjectable = getRouteInjectable({
id: "rollback-release-route",
instantiate: (di) => {
const rollbackRelease = di.inject(rollbackHelmReleaseInjectable);
const rollbackRelease = di.inject(rollbackClusterHelmReleaseInjectable);
return payloadValidatedClusterRoute({
method: "put",
path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback`,
path: `${apiPrefix}/v2/releases/{namespace}/{name}/rollback`,
payloadValidator: rollbackReleasePayloadValidator,
})(async ({ cluster, params: { release, namespace }, payload }) => {
await rollbackRelease(cluster, release, namespace, payload.revision);
})(async ({ cluster, params, payload }) => {
await rollbackRelease(cluster, { ...params, ...payload });
});
},
});
@@ -3,22 +3,25 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { PortForwardArgs } from "./port-forward";
import type { PortForwardArgs, PortForwardDependencies } from "./port-forward";
import { PortForward } from "./port-forward";
import bundledKubectlInjectable from "../../../kubectl/bundled-kubectl.injectable";
import getPortFromStreamInjectable from "../../../utils/get-port-from-stream.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
export type CreatePortForward = (pathToKubeConfig: string, args: PortForwardArgs) => PortForward;
const createPortForwardInjectable = getInjectable({
id: "create-port-forward",
instantiate: (di) => {
const bundledKubectl = di.inject(bundledKubectlInjectable);
const dependencies = {
getKubectlBinPath: bundledKubectl.getPath,
instantiate: (di): CreatePortForward => {
const dependencies: PortForwardDependencies = {
getKubectlBinPath: di.inject(bundledKubectlInjectable).getPath,
getPortFromStream: di.inject(getPortFromStreamInjectable),
logger: di.inject(loggerInjectable),
};
return (pathToKubeConfig: string, args: PortForwardArgs) =>
new PortForward(dependencies, pathToKubeConfig, args);
return (pathToKubeConfig, args) => new PortForward(dependencies, pathToKubeConfig, args);
},
});
@@ -2,12 +2,12 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import logger from "../../../logger";
import { getPortFrom } from "../../../utils/get-port";
import type { GetPortFromStream } from "../../../utils/get-port-from-stream.injectable";
import type { ChildProcessWithoutNullStreams } from "child_process";
import { spawn } from "child_process";
import * as tcpPortUsed from "tcp-port-used";
import { TypedRegEx } from "typed-regex";
import type { Logger } from "../../../../common/logger";
const internalPortMatcher = "^forwarding from (?<address>.+) ->";
const internalPortRegex = Object.assign(TypedRegEx(internalPortMatcher, "i"), {
@@ -23,8 +23,10 @@ export interface PortForwardArgs {
forwardPort: number;
}
interface Dependencies {
export interface PortForwardDependencies {
readonly logger: Logger;
getKubectlBinPath: (bundled: boolean) => Promise<string>;
getPortFromStream: GetPortFromStream;
}
export class PortForward {
@@ -48,7 +50,7 @@ export class PortForward {
public port: number;
public forwardPort: number;
constructor(private dependencies: Dependencies, public pathToKubeConfig: string, args: PortForwardArgs) {
constructor(private dependencies: PortForwardDependencies, public pathToKubeConfig: string, args: PortForwardArgs) {
this.clusterId = args.clusterId;
this.kind = args.kind;
this.namespace = args.namespace;
@@ -80,10 +82,10 @@ export class PortForward {
});
this.process.stderr.on("data", (data) => {
logger.debug(`[PORT-FORWARD-ROUTE]: kubectl port-forward process stderr: ${data}`);
this.dependencies.logger.debug(`[PORT-FORWARD-ROUTE]: kubectl port-forward process stderr: ${data}`);
});
const internalPort = await getPortFrom(this.process.stdout, {
const internalPort = await this.dependencies.getPortFromStream(this.process.stdout, {
lineRegex: internalPortRegex,
});
@@ -9,9 +9,10 @@ import type { KubeConfig } from "@kubernetes/client-node";
import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session";
import { ShellOpenError, ShellSession } from "../shell-session";
import { get, once } from "lodash";
import { Node, NodeApi } from "../../../common/k8s-api/endpoints";
import { NodeApi } from "../../../common/k8s-api/endpoints";
import { TerminalChannels } from "../../../common/terminal/channels";
import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.injectable";
export interface NodeShellSessionArgs extends ShellSessionArgs {
nodeName: string;
@@ -19,6 +20,7 @@ export interface NodeShellSessionArgs extends ShellSessionArgs {
export interface NodeShellSessionDependencies extends ShellSessionDependencies {
createKubeJsonApiForCluster: CreateKubeJsonApiForCluster;
createKubeApi: CreateKubeApi;
}
export class NodeShellSession extends ShellSession {
@@ -67,8 +69,7 @@ export class NodeShellSession extends ShellSession {
const env = await this.getCachedShellEnv();
const args = ["exec", "-i", "-t", "-n", "kube-system", this.podName, "--"];
const nodeApi = new NodeApi({
objectConstructor: Node,
const nodeApi = this.dependencies.createKubeApi(NodeApi, {
request: this.dependencies.createKubeJsonApiForCluster(this.cluster.id),
});
const node = await nodeApi.get({ name: this.nodeName });
@@ -19,6 +19,7 @@ import appNameInjectable from "../../../common/vars/app-name.injectable";
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
import statInjectable from "../../../common/fs/stat.injectable";
import createKubeApiInjectable from "../../../common/k8s-api/create-kube-api.injectable";
export interface NodeShellSessionArgs {
websocket: WebSocket;
@@ -45,6 +46,7 @@ const openNodeShellSessionInjectable = getInjectable({
spawnPty: di.inject(spawnPtyInjectable),
emitAppEvent: di.inject(emitAppEventInjectable),
stat: di.inject(statInjectable),
createKubeApi: di.inject(createKubeApiInjectable),
};
return async (args) => {
@@ -28,8 +28,6 @@ const startKubeConfigSyncInjectable = getInjectable({
};
},
causesSideEffects: true,
injectionToken: afterApplicationIsLoadedInjectionToken,
});
@@ -3,15 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import environmentVariablesInjectable from "../../../common/utils/environment-variables.injectable";
import disableHardwareAccelerationInjectable from "../../electron-app/features/disable-hardware-acceleration.injectable";
import hardwareAccelerationShouldBeDisabledInjectable from "../../vars/hardware-acceleration-should-be-disabled.injectable";
import { beforeElectronIsReadyInjectionToken } from "../runnable-tokens/before-electron-is-ready-injection-token";
const setupHardwareAccelerationInjectable = getInjectable({
id: "setup-hardware-acceleration",
instantiate: (di) => {
const { LENS_DISABLE_GPU: hardwareAccelerationShouldBeDisabled } = di.inject(environmentVariablesInjectable);
const hardwareAccelerationShouldBeDisabled = di.inject(hardwareAccelerationShouldBeDisabledInjectable);
const disableHardwareAcceleration = di.inject(disableHardwareAccelerationInjectable);
return {
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import commandLineArgumentsInjectable from "./command-line-arguments.injectable";
export default getGlobalOverride(commandLineArgumentsInjectable, () => []);
@@ -0,0 +1,77 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Readable } from "stream";
import URLParse from "url-parse";
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../common/logger.injectable";
export interface GetPortFromStreamArgs {
/**
* Should be case insensitive
* Must have a named matching group called `address`
*/
lineRegex: {
match: (line: string) => {
matched: boolean;
groups?: {
address?: string;
};
raw?: RegExpExecArray;
};
rawMatcher: string;
};
/**
* Called when the port is found
*/
onFind?: () => void;
/**
* Timeout for how long to wait for the port.
* Default: 15s
*/
timeout?: number;
}
export type GetPortFromStream = (stream: Readable, args: GetPortFromStreamArgs) => Promise<number>;
const getPortFromStreamInjectable = getInjectable({
id: "get-port-from-stream",
instantiate: (di): GetPortFromStream => {
const logger = di.inject(loggerInjectable);
return (stream, args) => {
const logLines: string[] = [];
return new Promise<number>((resolve, reject) => {
const handler = (data: unknown) => {
const logItem = String(data);
const match = args.lineRegex.match(logItem);
logLines.push(logItem);
if (match.matched) {
// use unknown protocol so that there is no default port
const addr = new URLParse(`s://${match.groups?.address?.trim()}`);
args.onFind?.();
stream.off("data", handler);
clearTimeout(timeoutID);
resolve(+addr.port);
}
};
const timeoutID = setTimeout(() => {
stream.off("data", handler);
logger.warn(`[getPortFrom]: failed to retrieve port via ${args.lineRegex.rawMatcher}`, logLines);
reject(new Error("failed to retrieve port from stream"));
}, args.timeout ?? 15000);
stream.on("data", handler);
});
};
},
});
export default getPortFromStreamInjectable;
-71
View File
@@ -1,71 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Readable } from "stream";
import URLParse from "url-parse";
import logger from "../logger";
interface GetPortArgs {
/**
* Should be case insensitive
* Must have a named matching group called `address`
*/
lineRegex: {
match: (line: string) => {
matched: boolean;
groups?: {
address?: string;
};
raw?: RegExpExecArray;
};
rawMatcher: string;
};
/**
* Called when the port is found
*/
onFind?: () => void;
/**
* Timeout for how long to wait for the port.
* Default: 15s
*/
timeout?: number;
}
/**
* Parse lines from `stream` (assumes data comes in lines) to find the port
* which the source of the stream is watching on.
* @param stream A readable stream to match lines against
* @param args The args concerning the stream
* @returns A Promise for port number
*/
export function getPortFrom(stream: Readable, args: GetPortArgs): Promise<number> {
const logLines: string[] = [];
return new Promise<number>((resolve, reject) => {
const handler = (data: unknown) => {
const logItem = String(data);
const match = args.lineRegex.match(logItem);
logLines.push(logItem);
if (match.matched) {
// use unknown protocol so that there is no default port
const addr = new URLParse(`s://${match.groups?.address?.trim()}`);
args.onFind?.();
stream.off("data", handler);
clearTimeout(timeoutID);
resolve(+addr.port);
}
};
const timeoutID = setTimeout(() => {
stream.off("data", handler);
logger.warn(`[getPortFrom]: failed to retrieve port via ${args.lineRegex.rawMatcher}`, logLines);
reject(new Error("failed to retrieve port from stream"));
}, args.timeout ?? 15000);
stream.on("data", handler);
});
}
@@ -1,24 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import applicationInformationInjectable from "../../common/vars/application-information-injectable";
export default getGlobalOverride(applicationInformationInjectable, () => ({
name: "some-product-name",
productName: "some-product-name",
version: "6.0.0",
build: {} as any,
config: {
k8sProxyVersion: "0.2.1",
bundledKubectlVersion: "1.23.3",
bundledHelmVersion: "3.7.2",
sentryDsn: "",
contentSecurityPolicy: "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:",
welcomeRoute: "/welcome",
},
copyright: "some-copyright-information",
description: "some-descriptive-text",
}));
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import hardwareAccelerationShouldBeDisabledInjectable from "./hardware-acceleration-should-be-disabled.injectable";
export default getGlobalOverride(hardwareAccelerationShouldBeDisabledInjectable, () => false);
@@ -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 hardwareAccelerationShouldBeDisabledInjectable = getInjectable({
id: "hardware-acceleration-should-be-disabled",
instantiate: () => Boolean(process.env.LENS_DISABLE_GPU),
causesSideEffects: true,
});
export default hardwareAccelerationShouldBeDisabledInjectable;