mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Making apiBase injectable (#6022)
* Making apiBase injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all of Helm functions to be DI Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make PortForward's use of apiBase fully injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all metric requests to be injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace resource applier with injectables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch KubeJsonApi.forCluster to be injectable but do not use Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert the rest of shell sessions to be DI-ed - This is a prerequesit for using the new createKubeJsonApiForClusterInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Use new createKubeJsonApiForClusterInjectable for openNodeShellSession Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make KubeconfigDialog injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove jest-fetch-mock and make fetch injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests with new global override Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add new injectable for create KubeJsonApi and JsonApi instances Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix showing-details-for-helm-release behavioural tests - Remove HelmChartStore in favour of all injectables - Create a model for UpgradeChartDockTab Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix show details and updating helm releases tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix residual typing issues related to metrics Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash on load due to circular dependency Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix create resource tab not working Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove legacy apiBase global Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use isDebuggingInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use windowLocationInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove global legacy apiKube Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve injectable filenames compared to the injectables inside Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove modifying input in requestActivePortForwardInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use get(Milli)SecondsFromUnixEpochInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch to non-reactive way of gettting possible helm release versions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix typo Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix bug in KubeApi constructor Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all KubeApi related tests to use asyncFn Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests after introducing new injectables that have side effects Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix bad rebase causing tests to fail Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve expects for multiple field values Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash will looking up api refs Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix breaking change on KubeApi.list Signed-off-by: Sebastian Malton <sebastian@malton.name> * Better fix for formatting urls Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove injectable for time since we should just use useMockTime Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add happy path behavioural tests for upgrade chart tab Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove debug message Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix showing-details-for-helm-release tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix installing-helm-chart-from-new-tab tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests relating to hosted cluster id Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots to recent changes in master Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Reupdated upgrade chart new tab test snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix flakiness in unit test when using <Animated> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix flakiness and improve tests for DeleteClusterDialog Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix kubeconfig-sync tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix <Extensions> tests by removing mockFs and making everything injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix build issues Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix getElectronAppPathInjectable override not returning absolute paths - Also fixes the listing-active-helm-repos-in-prefs tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace all uses of getAbsolutePath with joinPaths as it is more correct and less confusing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix opening application window tests by making override properly absolute Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots relating no longer using getAbsolutePath Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix and add behavioural tests for RenderDelay Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension discovery tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test flakiness because of path side effects, propagate uses to as many places Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension-discovery tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add global override to fix some tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Rewrite and fix implementation of KubeconfigManager and its tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by global override pathExists Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests failing on windows by using injectable verions of path functions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Attempt to fix test timeout by using runInAction Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix setupIpcMainHandlers usage 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> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> Co-authored-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
367 files changed
+16701
-5302
No files matched your search
@@ -4,12 +4,7 @@
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
import { KubeconfigManager } from "../kubeconfig-manager/kubeconfig-manager";
|
||||
import mockFs from "mock-fs";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import fse from "fs-extra";
|
||||
import { loadYaml } from "@kubernetes/client-node";
|
||||
import { Console } from "console";
|
||||
import * as path from "path";
|
||||
import createKubeconfigManagerInjectable from "../kubeconfig-manager/create-kubeconfig-manager.injectable";
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||
@@ -18,31 +13,53 @@ import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { parse } from "url";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import assert from "assert";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||
import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
|
||||
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
|
||||
|
||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||
import type { ReadFile } from "../../common/fs/read-file.injectable";
|
||||
import readFileInjectable from "../../common/fs/read-file.injectable";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { WriteFile } from "../../common/fs/write-file.injectable";
|
||||
import writeFileInjectable from "../../common/fs/write-file.injectable";
|
||||
import type { PathExists } from "../../common/fs/path-exists.injectable";
|
||||
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||
import type { DeleteFile } from "../../common/fs/delete-file.injectable";
|
||||
import deleteFileInjectable from "../../common/fs/delete-file.injectable";
|
||||
|
||||
const clusterServerUrl = "https://192.168.64.3:8443";
|
||||
|
||||
describe("kubeconfig manager tests", () => {
|
||||
let clusterFake: Cluster;
|
||||
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager | undefined;
|
||||
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
|
||||
let di: DiContainer;
|
||||
let loggerMock: jest.Mocked<Logger>;
|
||||
let readFileMock: AsyncFnMock<ReadFile>;
|
||||
let deleteFileMock: AsyncFnMock<DeleteFile>;
|
||||
let writeFileMock: AsyncFnMock<WriteFile>;
|
||||
let pathExistsMock: AsyncFnMock<PathExists>;
|
||||
let kubeConfManager: KubeconfigManager;
|
||||
let ensureServerMock: AsyncFnMock<() => Promise<void>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(directoryForTempInjectable, () => "/some-directory-for-temp");
|
||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||
di.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
||||
|
||||
readFileMock = asyncFn();
|
||||
di.override(readFileInjectable, () => readFileMock);
|
||||
writeFileMock = asyncFn();
|
||||
di.override(writeFileInjectable, () => writeFileMock);
|
||||
pathExistsMock = asyncFn();
|
||||
di.override(pathExistsInjectable, () => pathExistsMock);
|
||||
deleteFileMock = asyncFn();
|
||||
di.override(deleteFileInjectable, () => deleteFileMock);
|
||||
|
||||
loggerMock = {
|
||||
warn: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
@@ -53,29 +70,7 @@ describe("kubeconfig manager tests", () => {
|
||||
|
||||
di.override(loggerInjectable, () => loggerMock);
|
||||
|
||||
mockFs({
|
||||
"minikube-config.yml": JSON.stringify({
|
||||
apiVersion: "v1",
|
||||
clusters: [{
|
||||
name: "minikube",
|
||||
cluster: {
|
||||
server: clusterServerUrl,
|
||||
},
|
||||
}],
|
||||
contexts: [{
|
||||
context: {
|
||||
cluster: "minikube",
|
||||
user: "minikube",
|
||||
},
|
||||
name: "minikube",
|
||||
}],
|
||||
users: [{
|
||||
name: "minikube",
|
||||
}],
|
||||
kind: "Config",
|
||||
preferences: {},
|
||||
}),
|
||||
});
|
||||
ensureServerMock = asyncFn();
|
||||
|
||||
di.override(createContextHandlerInjectable, () => (cluster) => ({
|
||||
restartServer: jest.fn(),
|
||||
@@ -86,7 +81,7 @@ describe("kubeconfig manager tests", () => {
|
||||
resolveAuthProxyCa: jest.fn(),
|
||||
resolveAuthProxyUrl: jest.fn(),
|
||||
setupPrometheus: jest.fn(),
|
||||
ensureServer: jest.fn(),
|
||||
ensureServer: ensureServerMock,
|
||||
}));
|
||||
|
||||
const createCluster = di.inject(createClusterInjectionToken);
|
||||
@@ -96,48 +91,222 @@ describe("kubeconfig manager tests", () => {
|
||||
clusterFake = createCluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
kubeConfigPath: "/minikube-config.yml",
|
||||
}, {
|
||||
clusterServerUrl,
|
||||
});
|
||||
|
||||
jest.spyOn(KubeconfigManager.prototype, "resolveProxyUrl", "get").mockReturnValue("http://127.0.0.1:9191/foo");
|
||||
|
||||
kubeConfManager = createKubeconfigManager(clusterFake);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
describe("when calling clear", () => {
|
||||
it("should resolve immediately", async () => {
|
||||
await kubeConfManager.clear();
|
||||
});
|
||||
|
||||
it("being called several times shouldn't throw", async () => {
|
||||
await kubeConfManager.clear();
|
||||
await kubeConfManager.clear();
|
||||
await kubeConfManager.clear();
|
||||
});
|
||||
});
|
||||
|
||||
it("should create 'temp' kube config with proxy", async () => {
|
||||
const kubeConfManager = createKubeconfigManager(clusterFake);
|
||||
describe("when getPath() is called initially", () => {
|
||||
let getPathPromise: Promise<string>;
|
||||
|
||||
assert(kubeConfManager, "should actually create one");
|
||||
beforeEach(async () => {
|
||||
getPathPromise = kubeConfManager.getPath();
|
||||
});
|
||||
|
||||
expect(loggerMock.error).not.toBeCalled();
|
||||
expect(await kubeConfManager.getPath()).toBe(`some-directory-for-temp${path.sep}kubeconfig-foo`);
|
||||
// this causes an intermittent "ENXIO: no such device or address, read" error
|
||||
// const file = await fse.readFile(await kubeConfManager.getPath());
|
||||
const file = fse.readFileSync(await kubeConfManager.getPath());
|
||||
const yml = loadYaml<any>(file.toString());
|
||||
it("should not call pathExists()", () => {
|
||||
expect(pathExistsMock).not.toBeCalled();
|
||||
});
|
||||
|
||||
expect(yml["current-context"]).toBe("minikube");
|
||||
expect(yml["clusters"][0]["cluster"]["server"].endsWith("/foo")).toBe(true);
|
||||
expect(yml["users"][0]["name"]).toBe("proxy");
|
||||
});
|
||||
it("should call ensureServer on the cluster context", () => {
|
||||
expect(ensureServerMock).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
|
||||
const kubeConfManager = createKubeconfigManager(clusterFake);
|
||||
describe("when ensureServer resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await ensureServerMock.resolve();
|
||||
|
||||
assert(kubeConfManager, "should actually create one");
|
||||
// clear state of calls
|
||||
ensureServerMock.mock.calls.length = 0;
|
||||
});
|
||||
|
||||
const configPath = await kubeConfManager.getPath();
|
||||
describe("when reading cluster's kubeconfig resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await readFileMock.resolveSpecific(
|
||||
["/minikube-config.yml"],
|
||||
JSON.stringify({
|
||||
apiVersion: "v1",
|
||||
clusters: [{
|
||||
name: "minikube",
|
||||
cluster: {
|
||||
server: clusterServerUrl,
|
||||
},
|
||||
}],
|
||||
contexts: [{
|
||||
context: {
|
||||
cluster: "minikube",
|
||||
user: "minikube",
|
||||
},
|
||||
name: "minikube",
|
||||
}],
|
||||
users: [{
|
||||
name: "minikube",
|
||||
}],
|
||||
kind: "Config",
|
||||
preferences: {},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(await fse.pathExists(configPath)).toBe(true);
|
||||
await kubeConfManager.clear();
|
||||
expect(await fse.pathExists(configPath)).toBe(false);
|
||||
await kubeConfManager.clear(); // doesn't throw
|
||||
expect(async () => {
|
||||
await kubeConfManager.getPath();
|
||||
}).rejects.toThrow("already unlinked");
|
||||
describe("when writing out new proxy kubeconfig resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await writeFileMock.resolveSpecific(
|
||||
["/some-directory-for-temp/kubeconfig-foo", "apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: minikube\nclusters:\n - name: minikube\n cluster:\n server: http://127.0.0.1:9191/foo\ncontexts:\n - name: minikube\n context:\n cluster: minikube\n user: proxy\nusers:\n - name: proxy\n user: {}\n"],
|
||||
);
|
||||
});
|
||||
|
||||
it("should allow getPath to resolve with the path to the kubeconfig", async () => {
|
||||
expect(await getPathPromise).toBe("/some-directory-for-temp/kubeconfig-foo");
|
||||
});
|
||||
|
||||
describe("when calling clear", () => {
|
||||
let clearPromise: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
clearPromise = kubeConfManager.clear();
|
||||
});
|
||||
|
||||
it("should call deleteFile", () => {
|
||||
expect(deleteFileMock).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("when deleteFile resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await deleteFileMock.resolveSpecific(
|
||||
["/some-directory-for-temp/kubeconfig-foo"],
|
||||
);
|
||||
});
|
||||
|
||||
it("should allow clear to resolve", async () => {
|
||||
await clearPromise;
|
||||
});
|
||||
});
|
||||
|
||||
describe("when deleteFile rejects with ENOENT", () => {
|
||||
beforeEach(async () => {
|
||||
await deleteFileMock.resolveSpecific(
|
||||
["/some-directory-for-temp/kubeconfig-foo"],
|
||||
Promise.reject(Object.assign(new Error("file not found"), {
|
||||
code: "ENOENT",
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
it("should allow clear to resolve", async () => {
|
||||
await clearPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it("when deleteFile rejects with some other error; clear should also reject", async () => {
|
||||
const expectPromise = expect(clearPromise).rejects.toBeDefined();
|
||||
|
||||
await deleteFileMock.reject(new Error("some other error"));
|
||||
await expectPromise;
|
||||
});
|
||||
});
|
||||
|
||||
describe("when calling getPath a second time", () => {
|
||||
let getPathPromise: Promise<string>;
|
||||
|
||||
beforeEach(async () => {
|
||||
getPathPromise = kubeConfManager.getPath();
|
||||
});
|
||||
|
||||
it("should call pathExists", () => {
|
||||
expect(pathExistsMock).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("when pathExists resoves to true", () => {
|
||||
beforeEach(async () => {
|
||||
await pathExistsMock.resolveSpecific(
|
||||
["/some-directory-for-temp/kubeconfig-foo"],
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("always getPath to resolve with path", async () => {
|
||||
expect(await getPathPromise).toBe("/some-directory-for-temp/kubeconfig-foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when pathExists resoves to false", () => {
|
||||
beforeEach(async () => {
|
||||
await pathExistsMock.resolveSpecific(
|
||||
["/some-directory-for-temp/kubeconfig-foo"],
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("should call ensureServer on the cluster context", () => {
|
||||
expect(ensureServerMock).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("when ensureServer resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await ensureServerMock.resolve();
|
||||
});
|
||||
|
||||
describe("when reading cluster's kubeconfig resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await readFileMock.resolveSpecific(
|
||||
["/minikube-config.yml"],
|
||||
JSON.stringify({
|
||||
apiVersion: "v1",
|
||||
clusters: [{
|
||||
name: "minikube",
|
||||
cluster: {
|
||||
server: clusterServerUrl,
|
||||
},
|
||||
}],
|
||||
contexts: [{
|
||||
context: {
|
||||
cluster: "minikube",
|
||||
user: "minikube",
|
||||
},
|
||||
name: "minikube",
|
||||
}],
|
||||
users: [{
|
||||
name: "minikube",
|
||||
}],
|
||||
kind: "Config",
|
||||
preferences: {},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe("when writing out new proxy kubeconfig resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await writeFileMock.resolveSpecific(
|
||||
["/some-directory-for-temp/kubeconfig-foo", "apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: minikube\nclusters:\n - name: minikube\n cluster:\n server: http://127.0.0.1:9191/foo\ncontexts:\n - name: minikube\n context:\n cluster: minikube\n user: proxy\nusers:\n - name: proxy\n user: {}\n"],
|
||||
);
|
||||
});
|
||||
|
||||
it("should allow getPath to resolve with the path to the kubeconfig", async () => {
|
||||
expect(await getPathPromise).toBe("/some-directory-for-temp/kubeconfig-foo");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,8 +6,6 @@ import electronAppInjectable from "../../electron-app/electron-app.injectable";
|
||||
import getElectronAppPathInjectable from "./get-electron-app-path.injectable";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import type { App } from "electron";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import { joinPathsFake } from "../../../common/test-utils/join-paths-fake";
|
||||
|
||||
describe("get-electron-app-path", () => {
|
||||
let getElectronAppPath: (name: string) => string;
|
||||
@@ -31,7 +29,6 @@ describe("get-electron-app-path", () => {
|
||||
} as App;
|
||||
|
||||
di.override(electronAppInjectable, () => appStub);
|
||||
di.override(joinPathsInjectable, () => joinPathsFake);
|
||||
|
||||
getElectronAppPath = di.inject(getElectronAppPathInjectable) as (name: string) => string;
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import loggerInjectable from "../../common/logger.injectable";
|
||||
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
|
||||
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
import loadConfigfromFileInjectable from "../../common/kube-helpers/load-config-from-file.injectable";
|
||||
|
||||
const createClusterInjectable = getInjectable({
|
||||
id: "create-cluster",
|
||||
@@ -32,6 +33,7 @@ const createClusterInjectable = getInjectable({
|
||||
detectorRegistry: di.inject(detectorRegistryInjectable),
|
||||
createVersionDetector: di.inject(createVersionDetectorInjectable),
|
||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||
loadConfigfromFile: di.inject(loadConfigfromFileInjectable),
|
||||
};
|
||||
|
||||
return (model, configData) => new Cluster(dependencies, model, configData);
|
||||
|
||||
-13
@@ -3,37 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import clusterManagerInjectable from "../../../cluster/manager.injectable";
|
||||
import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable";
|
||||
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.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 operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
|
||||
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
|
||||
import askUserForFilePathsInjectable from "../../../ipc/ask-user-for-file-paths.injectable";
|
||||
import clustersThatAreBeingDeletedInjectable from "../../../cluster/are-being-deleted.injectable";
|
||||
|
||||
const setupIpcMainHandlersInjectable = getInjectable({
|
||||
id: "setup-ipc-main-handlers",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
const directoryForLensLocalStorage = di.inject(
|
||||
directoryForLensLocalStorageInjectable,
|
||||
);
|
||||
|
||||
const clusterManager = di.inject(clusterManagerInjectable);
|
||||
const applicationMenuItems = di.inject(applicationMenuItemsInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
|
||||
const askUserForFilePaths = di.inject(askUserForFilePathsInjectable);
|
||||
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||
|
||||
return {
|
||||
id: "setup-ipc-main-handlers",
|
||||
@@ -42,14 +32,11 @@ const setupIpcMainHandlersInjectable = getInjectable({
|
||||
|
||||
setupIpcMainHandlers({
|
||||
applicationMenuItems,
|
||||
getAbsolutePath,
|
||||
directoryForLensLocalStorage,
|
||||
clusterManager,
|
||||
catalogEntityRegistry,
|
||||
clusterStore,
|
||||
operatingSystemTheme,
|
||||
askUserForFilePaths,
|
||||
clustersThatAreBeingDeleted,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import type { IpcMainInvokeEvent } from "electron";
|
||||
import { BrowserWindow, Menu } from "electron";
|
||||
import { clusterFrameMap } from "../../../../common/cluster-frames";
|
||||
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler, clusterSetDeletingHandler, clusterClearDeletingHandler } from "../../../../common/ipc/cluster";
|
||||
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../../../../common/ipc/cluster";
|
||||
import type { ClusterId } from "../../../../common/cluster-types";
|
||||
import { ClusterStore } from "../../../../common/cluster-store/cluster-store";
|
||||
import { appEventBus } from "../../../../common/app-event-bus/event-bus";
|
||||
@@ -14,9 +14,7 @@ import type { CatalogEntityRegistry } from "../../../catalog";
|
||||
import { pushCatalogToRenderer } from "../../../catalog-pusher";
|
||||
import type { ClusterManager } from "../../../cluster/manager";
|
||||
import { ResourceApplier } from "../../../resource-applier";
|
||||
import { remove } from "fs-extra";
|
||||
import type { IComputedValue, ObservableSet } from "mobx";
|
||||
import type { GetAbsolutePath } from "../../../../common/path/get-absolute-path.injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { MenuItemOpts } from "../../../menu/application-menu-items.injectable";
|
||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
|
||||
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
|
||||
@@ -26,27 +24,21 @@ import type { Theme } from "../../../theme/operating-system-theme-state.injectab
|
||||
import type { AskUserForFilePaths } from "../../../ipc/ask-user-for-file-paths.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
directoryForLensLocalStorage: string;
|
||||
getAbsolutePath: GetAbsolutePath;
|
||||
applicationMenuItems: IComputedValue<MenuItemOpts[]>;
|
||||
clusterManager: ClusterManager;
|
||||
catalogEntityRegistry: CatalogEntityRegistry;
|
||||
clusterStore: ClusterStore;
|
||||
operatingSystemTheme: IComputedValue<Theme>;
|
||||
askUserForFilePaths: AskUserForFilePaths;
|
||||
clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
|
||||
}
|
||||
|
||||
export const setupIpcMainHandlers = ({
|
||||
applicationMenuItems,
|
||||
directoryForLensLocalStorage,
|
||||
getAbsolutePath,
|
||||
clusterManager,
|
||||
catalogEntityRegistry,
|
||||
clusterStore,
|
||||
operatingSystemTheme,
|
||||
askUserForFilePaths,
|
||||
clustersThatAreBeingDeleted,
|
||||
}: Dependencies) => {
|
||||
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||
return ClusterStore.getInstance()
|
||||
@@ -85,40 +77,6 @@ export const setupIpcMainHandlers = ({
|
||||
}
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterDeleteHandler, async (event, clusterId: ClusterId) => {
|
||||
appEventBus.emit({ name: "cluster", action: "remove" });
|
||||
|
||||
const clusterStore = ClusterStore.getInstance();
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
|
||||
if (!cluster) {
|
||||
return;
|
||||
}
|
||||
|
||||
cluster.disconnect();
|
||||
clusterFrameMap.delete(cluster.id);
|
||||
|
||||
// Remove from the cluster store as well, this should clear any old settings
|
||||
clusterStore.clusters.delete(cluster.id);
|
||||
|
||||
try {
|
||||
// remove the local storage file
|
||||
const localStorageFilePath = getAbsolutePath(directoryForLensLocalStorage, `${cluster.id}.json`);
|
||||
|
||||
await remove(localStorageFilePath);
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterSetDeletingHandler, (event, clusterId: string) => {
|
||||
clustersThatAreBeingDeleted.add(clusterId);
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterClearDeletingHandler, (event, clusterId: string) => {
|
||||
clustersThatAreBeingDeleted.delete(clusterId);
|
||||
});
|
||||
|
||||
ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
||||
appEventBus.emit({ name: "cluster", action: "kubectl-apply-all" });
|
||||
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../common/cluster/cluster";
|
||||
import type { IMetricsReqParams } from "../common/k8s-api/endpoints/metrics.api";
|
||||
import type { RequestMetricsParams } from "../common/k8s-api/endpoints/metrics.api/request-metrics.injectable";
|
||||
import k8sRequestInjectable from "./k8s-request.injectable";
|
||||
|
||||
export type GetMetrics = (cluster: Cluster, prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) => Promise<any>;
|
||||
export type GetMetrics = (cluster: Cluster, prometheusPath: string, queryParams: RequestMetricsParams & { query: string }) => Promise<any>;
|
||||
|
||||
const getMetricsInjectable = getInjectable({
|
||||
id: "get-metrics",
|
||||
|
||||
@@ -18,10 +18,6 @@ import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader
|
||||
import type { FileSystemProvisionerStore } from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
|
||||
import userStoreInjectable from "../common/user-store/user-store.injectable";
|
||||
import type { UserStore } from "../common/user-store";
|
||||
import getAbsolutePathInjectable from "../common/path/get-absolute-path.injectable";
|
||||
import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake";
|
||||
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
||||
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
||||
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
|
||||
import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable";
|
||||
import { EventEmitter } from "../common/event-emitter";
|
||||
@@ -74,7 +70,7 @@ import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
|
||||
import periodicalCheckForUpdatesInjectable from "./application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
||||
import execFileInjectable from "../common/fs/exec-file.injectable";
|
||||
import normalizedPlatformArchitectureInjectable from "../common/vars/normalized-platform-architecture.injectable";
|
||||
import getHelmChartInjectable from "./helm/helm-service/get-helm-chart.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";
|
||||
@@ -161,7 +157,7 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
di.override(periodicalCheckForUpdatesInjectable, () => ({ start: () => {}, stop: () => {}, started: false }));
|
||||
|
||||
overrideFunctionalInjectables(di, [
|
||||
getHelmChartInjectable,
|
||||
getHelmChartVersionsInjectable,
|
||||
getHelmChartValuesInjectable,
|
||||
listHelmChartsInjectable,
|
||||
deleteHelmReleaseInjectable,
|
||||
@@ -228,8 +224,6 @@ const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
||||
|
||||
const overrideOperatingSystem = (di: DiContainer) => {
|
||||
di.override(platformInjectable, () => "darwin");
|
||||
di.override(getAbsolutePathInjectable, () => getAbsolutePathFake);
|
||||
di.override(joinPathsInjectable, () => joinPathsFake);
|
||||
di.override(normalizedPlatformArchitectureInjectable, () => "arm64");
|
||||
};
|
||||
|
||||
@@ -267,7 +261,7 @@ const overrideElectronFeatures = (di: DiContainer) => {
|
||||
|
||||
di.override(
|
||||
getElectronAppPathInjectable,
|
||||
() => (name: string) => `some-electron-app-path-for-${kebabCase(name)}`,
|
||||
() => (name: string) => `/some-electron-app-path-for-${kebabCase(name)}`,
|
||||
);
|
||||
|
||||
di.override(setElectronAppPathInjectable, () => () => {});
|
||||
|
||||
@@ -8,7 +8,7 @@ import listHelmChartsInjectable from "../helm-service/list-helm-charts.injectabl
|
||||
import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
|
||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import { sortCharts } from "../../../common/utils";
|
||||
import { sortBySemverVersion } from "../../../common/utils";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
describe("Helm Service tests", () => {
|
||||
@@ -203,7 +203,7 @@ describe("Helm Service tests", () => {
|
||||
|
||||
const charts = new Map([
|
||||
["stable", {
|
||||
"invalid-semver": sortCharts([
|
||||
"invalid-semver": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "weird-versioning",
|
||||
@@ -237,7 +237,7 @@ const charts = new Map([
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"apm-server": sortCharts([
|
||||
"apm-server": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
@@ -255,7 +255,7 @@ const charts = new Map([
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"redis": sortCharts([
|
||||
"redis": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "apm-server",
|
||||
@@ -275,7 +275,7 @@ const charts = new Map([
|
||||
]),
|
||||
}],
|
||||
["experiment", {
|
||||
"fairwind": sortCharts([
|
||||
"fairwind": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "fairwind",
|
||||
@@ -296,7 +296,7 @@ const charts = new Map([
|
||||
]),
|
||||
}],
|
||||
["bitnami", {
|
||||
"hotdog": sortCharts([
|
||||
"hotdog": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "hotdog",
|
||||
@@ -314,7 +314,7 @@ const charts = new Map([
|
||||
created: "now",
|
||||
},
|
||||
]),
|
||||
"pretzel": sortCharts([
|
||||
"pretzel": sortBySemverVersion([
|
||||
{
|
||||
apiVersion: "3.0.0",
|
||||
name: "pretzel",
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { getBinaryName } from "../../common/vars";
|
||||
import getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable";
|
||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
const helmBinaryPathInjectable = getInjectable({
|
||||
id: "helm-binary-path",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const normalizedPlatform = di.inject(normalizedPlatformInjectable);
|
||||
const baseBundledBinariesDirectory = di.inject(baseBundledBinariesDirectoryInjectable);
|
||||
|
||||
const helmBinaryName = getBinaryName("helm", { forPlatform: normalizedPlatform });
|
||||
|
||||
return getAbsolutePath(baseBundledBinariesDirectory, helmBinaryName);
|
||||
return joinPaths(baseBundledBinariesDirectory, helmBinaryName);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
import fs from "fs";
|
||||
import * as yaml from "js-yaml";
|
||||
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import { iter, put, sortCharts } from "../../common/utils";
|
||||
import { iter, put, sortBySemverVersion } from "../../common/utils";
|
||||
import { execHelm } from "./exec";
|
||||
import type { SetRequired } from "type-fest";
|
||||
import { assert } from "console";
|
||||
import type { HelmRepo } from "../../common/helm/helm-repo";
|
||||
import type { HelmChartManagerCache } from "./helm-chart-manager-cache.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api/request-charts.injectable";
|
||||
|
||||
export interface HelmCacheFile {
|
||||
apiVersion: string;
|
||||
@@ -118,7 +118,7 @@ function normalizeHelmCharts(repoName: string, entries: RepoHelmChartList): Repo
|
||||
Object.entries(entries),
|
||||
([name, charts]) => [
|
||||
name,
|
||||
sortCharts(
|
||||
sortBySemverVersion(
|
||||
charts.map(chart => ({
|
||||
...chart,
|
||||
created: Date.parse(chart.created).toString(),
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 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 getHelmChartReadmeInjectable from "./get-helm-chart-readme.injectable";
|
||||
|
||||
export default getGlobalOverride(getHelmChartReadmeInjectable, () => () => {
|
||||
throw new Error("tried to get a helm chart's readme without overriding");
|
||||
});
|
||||
+4
-9
@@ -7,8 +7,8 @@ import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-r
|
||||
import type { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
|
||||
const getHelmChartInjectable = getInjectable({
|
||||
id: "get-helm-chart",
|
||||
const getHelmChartReadmeInjectable = getInjectable({
|
||||
id: "get-helm-chart-readme",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
@@ -21,16 +21,11 @@ const getHelmChartInjectable = getInjectable({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const chartManager = getChartManager(repo);
|
||||
|
||||
return {
|
||||
readme: await chartManager.getReadme(chartName, version),
|
||||
versions: await chartManager.chartVersions(chartName),
|
||||
};
|
||||
return getChartManager(repo).getReadme(chartName, version);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmChartInjectable;
|
||||
export default getHelmChartReadmeInjectable;
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 { HelmRepo } from "../../../common/helm/helm-repo";
|
||||
import helmChartManagerInjectable from "../helm-chart-manager.injectable";
|
||||
import getActiveHelmRepositoryInjectable from "../repositories/get-active-helm-repository.injectable";
|
||||
|
||||
const getHelmChartVersionsInjectable = getInjectable({
|
||||
id: "get-helm-chart-versions",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getActiveHelmRepository = di.inject(getActiveHelmRepositoryInjectable);
|
||||
const getChartManager = (repo: HelmRepo) => di.inject(helmChartManagerInjectable, repo);
|
||||
|
||||
return async (repoName: string, chartName: string) => {
|
||||
const repo = await getActiveHelmRepository(repoName);
|
||||
|
||||
if (!repo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getChartManager(repo).chartVersions(chartName);
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default getHelmChartVersionsInjectable;
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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 { apiBaseInjectionToken } from "../../common/k8s-api/api-base";
|
||||
import createJsonApiInjectable from "../../common/k8s-api/create-json-api.injectable";
|
||||
import { apiPrefix } from "../../common/vars";
|
||||
import isDebuggingInjectable from "../../common/vars/is-debugging.injectable";
|
||||
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
|
||||
import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable";
|
||||
|
||||
const apiBaseInjectable = getInjectable({
|
||||
id: "api-base",
|
||||
instantiate: (di) => {
|
||||
const proxyPort = di.inject(lensProxyPortInjectable);
|
||||
const createJsonApi = di.inject(createJsonApiInjectable);
|
||||
const isDebugging = di.inject(isDebuggingInjectable);
|
||||
|
||||
return createJsonApi({
|
||||
serverAddress: `http://127.0.0.1:${proxyPort.get()}`,
|
||||
apiBase: apiPrefix,
|
||||
debug: di.inject(isDevelopmentInjectable) || isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `localhost:${proxyPort.get()}`,
|
||||
},
|
||||
});
|
||||
},
|
||||
injectionToken: apiBaseInjectionToken,
|
||||
});
|
||||
|
||||
export default apiBaseInjectable;
|
||||
@@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
||||
import { KubeAuthProxy } from "./kube-auth-proxy";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import path from "path";
|
||||
import selfsigned from "selfsigned";
|
||||
import { getBinaryName } from "../../common/vars";
|
||||
import spawnInjectable from "../child-process/spawn.injectable";
|
||||
@@ -14,6 +13,7 @@ import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||
|
||||
@@ -22,11 +22,12 @@ const createKubeAuthProxyInjectable = getInjectable({
|
||||
|
||||
instantiate: (di): CreateKubeAuthProxy => {
|
||||
const binaryName = getBinaryName("lens-k8s-proxy");
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
|
||||
const clusterUrl = new URL(cluster.apiUrl);
|
||||
const dependencies: KubeAuthProxyDependencies = {
|
||||
proxyBinPath: path.join(di.inject(baseBundledBinariesDirectoryInjectable), binaryName),
|
||||
proxyBinPath: joinPaths(di.inject(baseBundledBinariesDirectoryInjectable), binaryName),
|
||||
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
|
||||
spawn: di.inject(spawnInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
|
||||
@@ -9,6 +9,11 @@ import type { KubeconfigManagerDependencies } from "./kubeconfig-manager";
|
||||
import { KubeconfigManager } from "./kubeconfig-manager";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||
import deleteFileInjectable from "../../common/fs/delete-file.injectable";
|
||||
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||
import writeFileInjectable from "../../common/fs/write-file.injectable";
|
||||
|
||||
export interface KubeConfigManagerInstantiationParameter {
|
||||
cluster: Cluster;
|
||||
@@ -24,6 +29,11 @@ const createKubeconfigManagerInjectable = getInjectable({
|
||||
directoryForTemp: di.inject(directoryForTempInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
lensProxyPort: di.inject(lensProxyPortInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
deleteFile: di.inject(deleteFileInjectable),
|
||||
pathExists: di.inject(pathExistsInjectable),
|
||||
writeFile: di.inject(writeFileInjectable),
|
||||
};
|
||||
|
||||
return (cluster) => new KubeconfigManager(dependencies, cluster);
|
||||
|
||||
@@ -6,17 +6,25 @@
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import { dumpConfigYaml } from "../../common/kube-helpers";
|
||||
import { isErrnoException } from "../../common/utils";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||
import type { PathExists } from "../../common/fs/path-exists.injectable";
|
||||
import type { DeleteFile } from "../../common/fs/delete-file.injectable";
|
||||
import type { WriteFile } from "../../common/fs/write-file.injectable";
|
||||
|
||||
export interface KubeconfigManagerDependencies {
|
||||
readonly directoryForTemp: string;
|
||||
readonly logger: Logger;
|
||||
lensProxyPort: { get: () => number };
|
||||
readonly lensProxyPort: { get: () => number };
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
pathExists: PathExists;
|
||||
deleteFile: DeleteFile;
|
||||
writeFile: WriteFile;
|
||||
}
|
||||
|
||||
export class KubeconfigManager {
|
||||
@@ -24,10 +32,9 @@ export class KubeconfigManager {
|
||||
* The path to the temp config file
|
||||
*
|
||||
* - if `string` then path
|
||||
* - if `null` then not yet created
|
||||
* - if `undefined` then unlinked by calling `clear()`
|
||||
* - if `null` then not yet created or was cleared
|
||||
*/
|
||||
protected tempFilePath: string | null | undefined = null;
|
||||
protected tempFilePath: string | null = null;
|
||||
|
||||
protected readonly contextHandler: ClusterContextHandler;
|
||||
|
||||
@@ -40,11 +47,7 @@ export class KubeconfigManager {
|
||||
* @returns The path to the temporary kubeconfig
|
||||
*/
|
||||
async getPath(): Promise<string> {
|
||||
if (this.tempFilePath === undefined) {
|
||||
throw new Error("kubeconfig is already unlinked");
|
||||
}
|
||||
|
||||
if (this.tempFilePath === null || !(await fs.pathExists(this.tempFilePath))) {
|
||||
if (this.tempFilePath === null || !(await this.dependencies.pathExists(this.tempFilePath))) {
|
||||
return await this.ensureFile();
|
||||
}
|
||||
|
||||
@@ -62,13 +65,13 @@ export class KubeconfigManager {
|
||||
this.dependencies.logger.info(`[KUBECONFIG-MANAGER]: Deleting temporary kubeconfig: ${this.tempFilePath}`);
|
||||
|
||||
try {
|
||||
await fs.unlink(this.tempFilePath);
|
||||
await this.dependencies.deleteFile(this.tempFilePath);
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
} finally {
|
||||
this.tempFilePath = undefined;
|
||||
this.tempFilePath = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +81,7 @@ export class KubeconfigManager {
|
||||
|
||||
return this.tempFilePath = await this.createProxyKubeconfig();
|
||||
} catch (error) {
|
||||
throw Object.assign(new Error("Failed to creat temp config for auth-proxy"), { cause: error });
|
||||
throw new Error(`Failed to creat temp config for auth-proxy: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +96,7 @@ export class KubeconfigManager {
|
||||
protected async createProxyKubeconfig(): Promise<string> {
|
||||
const { cluster } = this;
|
||||
const { contextName, id } = cluster;
|
||||
const tempFile = path.join(
|
||||
const tempFile = this.dependencies.joinPaths(
|
||||
this.dependencies.directoryForTemp,
|
||||
`kubeconfig-${id}`,
|
||||
);
|
||||
@@ -121,8 +124,7 @@ export class KubeconfigManager {
|
||||
// write
|
||||
const configYaml = dumpConfigYaml(proxyConfig);
|
||||
|
||||
await fs.ensureDir(path.dirname(tempFile));
|
||||
await fs.writeFile(tempFile, configYaml, { mode: 0o600 });
|
||||
await this.dependencies.writeFile(tempFile, configYaml, { mode: 0o600 });
|
||||
this.dependencies.logger.debug(`[KUBECONFIG-MANAGER]: Created temp kubeconfig "${contextName}" at "${tempFile}": \n${configYaml}`);
|
||||
|
||||
return tempFile;
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import kubectlBinaryNameInjectable from "./binary-name.injectable";
|
||||
|
||||
const bundledKubectlBinaryPathInjectable = getInjectable({
|
||||
id: "bundled-kubectl-binary-path",
|
||||
instantiate: (di) => path.join(
|
||||
di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
di.inject(kubectlBinaryNameInjectable),
|
||||
),
|
||||
instantiate: (di) => {
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return joinPaths(
|
||||
di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
di.inject(kubectlBinaryNameInjectable),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default bundledKubectlBinaryPathInjectable;
|
||||
@@ -14,6 +14,9 @@ import bundledKubectlBinaryPathInjectable from "./bundled-binary-path.injectable
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import bundledKubectlVersionInjectable from "../../common/vars/bundled-kubectl-version.injectable";
|
||||
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";
|
||||
|
||||
const createKubectlInjectable = getInjectable({
|
||||
id: "create-kubectl",
|
||||
@@ -29,6 +32,9 @@ const createKubectlInjectable = getInjectable({
|
||||
baseBundeledBinariesDirectory: di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
bundledKubectlVersion: di.inject(bundledKubectlVersionInjectable),
|
||||
kubectlVersionMap: di.inject(kubectlVersionMapInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||
};
|
||||
|
||||
return (clusterVersion: string) => new Kubectl(dependencies, clusterVersion);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||
import logger from "../logger";
|
||||
@@ -15,6 +14,9 @@ import got from "got/dist/source";
|
||||
import { promisify } from "util";
|
||||
import stream from "stream";
|
||||
import { noop } from "lodash/fp";
|
||||
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";
|
||||
|
||||
const initScriptVersionString = "# lens-initscript v3";
|
||||
|
||||
@@ -33,6 +35,9 @@ export interface KubectlDependencies {
|
||||
};
|
||||
readonly bundledKubectlVersion: string;
|
||||
readonly kubectlVersionMap: Map<string, string>;
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
getBasenameOfPath: GetBasenameOfPath;
|
||||
}
|
||||
|
||||
export class Kubectl {
|
||||
@@ -71,8 +76,8 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${this.dependencies.normalizedDownloadPlatform}/${this.dependencies.normalizedDownloadArch}/${this.dependencies.kubectlBinaryName}`;
|
||||
this.dirname = path.normalize(path.join(this.getDownloadDir(), this.kubectlVersion));
|
||||
this.path = path.join(this.dirname, this.dependencies.kubectlBinaryName);
|
||||
this.dirname = this.dependencies.joinPaths(this.getDownloadDir(), this.kubectlVersion);
|
||||
this.path = this.dependencies.joinPaths(this.dirname, this.dependencies.kubectlBinaryName);
|
||||
}
|
||||
|
||||
public getBundledPath() {
|
||||
@@ -85,7 +90,7 @@ export class Kubectl {
|
||||
|
||||
protected getDownloadDir() {
|
||||
if (this.dependencies.userStore.downloadBinariesPath) {
|
||||
return path.join(this.dependencies.userStore.downloadBinariesPath, "kubectl");
|
||||
return this.dependencies.joinPaths(this.dependencies.userStore.downloadBinariesPath, "kubectl");
|
||||
}
|
||||
|
||||
return this.dependencies.directoryForKubectlBinaries;
|
||||
@@ -104,7 +109,7 @@ export class Kubectl {
|
||||
if (!await this.checkBinary(this.getBundledPath(), false)) {
|
||||
Kubectl.invalidBundle = true;
|
||||
|
||||
return path.basename(this.getBundledPath());
|
||||
return this.dependencies.getBasenameOfPath(this.getBundledPath());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -246,7 +251,7 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
public async downloadKubectl() {
|
||||
await ensureDir(path.dirname(this.path), 0o755);
|
||||
await ensureDir(this.dependencies.getDirnameOfPath(this.path), 0o755);
|
||||
|
||||
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
||||
|
||||
@@ -268,9 +273,9 @@ export class Kubectl {
|
||||
const binariesDir = this.dependencies.baseBundeledBinariesDirectory;
|
||||
const kubectlPath = this.dependencies.userStore.downloadKubectlBinaries
|
||||
? this.dirname
|
||||
: path.dirname(this.getPathFromPreferences());
|
||||
: this.dependencies.getDirnameOfPath(this.getPathFromPreferences());
|
||||
|
||||
const bashScriptPath = path.join(this.dirname, ".bash_set_path");
|
||||
const bashScriptPath = this.dependencies.joinPaths(this.dirname, ".bash_set_path");
|
||||
const bashScript = [
|
||||
initScriptVersionString,
|
||||
"tempkubeconfig=\"$KUBECONFIG\"",
|
||||
@@ -292,7 +297,7 @@ export class Kubectl {
|
||||
"unset tempkubeconfig",
|
||||
].join("\n");
|
||||
|
||||
const zshScriptPath = path.join(this.dirname, ".zlogin");
|
||||
const zshScriptPath = this.dependencies.joinPaths(this.dirname, ".zlogin");
|
||||
const zshScript = [
|
||||
initScriptVersionString,
|
||||
"tempkubeconfig=\"$KUBECONFIG\"",
|
||||
|
||||
+2
-2
@@ -4,15 +4,15 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { shellApiRequest } from "./shell-api-request";
|
||||
import createShellSessionInjectable from "../../../shell-session/create-shell-session.injectable";
|
||||
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({
|
||||
createShellSession: di.inject(createShellSessionInjectable),
|
||||
openShellSession: di.inject(openShellSessionInjectable),
|
||||
authenticateRequest: di.inject(shellRequestAuthenticatorInjectable).authenticate,
|
||||
clusterManager: di.inject(clusterManagerInjectable),
|
||||
}),
|
||||
|
||||
@@ -4,28 +4,20 @@
|
||||
*/
|
||||
|
||||
import logger from "../../../logger";
|
||||
import type WebSocket from "ws";
|
||||
import { Server as WebSocketServer } from "ws";
|
||||
import type { ProxyApiRequestArgs } from "../types";
|
||||
import type { ClusterManager } from "../../../cluster/manager";
|
||||
import URLParse from "url-parse";
|
||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
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;
|
||||
|
||||
createShellSession: (args: {
|
||||
webSocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
nodeName?: string;
|
||||
}) => { open: () => Promise<void> };
|
||||
|
||||
openShellSession: OpenShellSession;
|
||||
clusterManager: ClusterManager;
|
||||
}
|
||||
|
||||
export const shellApiRequest = ({ createShellSession, authenticateRequest, clusterManager }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => {
|
||||
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);
|
||||
|
||||
@@ -37,10 +29,8 @@ export const shellApiRequest = ({ createShellSession, authenticateRequest, clust
|
||||
|
||||
const ws = new WebSocketServer({ noServer: true });
|
||||
|
||||
ws.handleUpgrade(req, socket, head, (webSocket) => {
|
||||
const shell = createShellSession({ webSocket, cluster, tabId, nodeName });
|
||||
|
||||
shell.open()
|
||||
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));
|
||||
});
|
||||
};
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import type { Cluster } from "../common/cluster/cluster";
|
||||
import type { KubernetesObject } from "@kubernetes/client-node";
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs-extra";
|
||||
import * as yaml from "js-yaml";
|
||||
@@ -15,6 +14,7 @@ import { appEventBus } from "../common/app-event-bus/event-bus";
|
||||
import { isChildProcessError } from "../common/utils";
|
||||
import type { Patch } from "rfc6902";
|
||||
import { promiseExecFile } from "../common/utils/promise-exec";
|
||||
import type { KubernetesObject } from "@kubernetes/client-node";
|
||||
|
||||
export class ResourceApplier {
|
||||
constructor(protected cluster: Cluster) {}
|
||||
@@ -62,11 +62,12 @@ export class ResourceApplier {
|
||||
}
|
||||
}
|
||||
|
||||
async apply(resource: KubernetesObject | any): Promise<string> {
|
||||
resource = this.sanitizeObject(resource);
|
||||
async create(resource: string): Promise<string> {
|
||||
appEventBus.emit({ name: "resource", action: "apply" });
|
||||
|
||||
return this.kubectlApply(yaml.dump(resource));
|
||||
console.log({ resource });
|
||||
|
||||
return this.kubectlApply(this.sanitizeObject(resource));
|
||||
}
|
||||
|
||||
protected async kubectlApply(content: string): Promise<string> {
|
||||
@@ -149,17 +150,13 @@ export class ResourceApplier {
|
||||
});
|
||||
}
|
||||
|
||||
protected sanitizeObject(resource: KubernetesObject | any) {
|
||||
const res = JSON.parse(JSON.stringify(resource));
|
||||
protected sanitizeObject(resource: string) {
|
||||
const res = yaml.load(resource) as Partial<KubernetesObject> & { status?: object };
|
||||
|
||||
delete res.status;
|
||||
delete res.metadata?.resourceVersion;
|
||||
const annotations = res.metadata?.annotations;
|
||||
delete res.metadata?.annotations?.["kubectl.kubernetes.io/last-applied-configuration"];
|
||||
|
||||
if (annotations) {
|
||||
delete annotations["kubectl.kubernetes.io/last-applied-configuration"];
|
||||
}
|
||||
|
||||
return res;
|
||||
return yaml.dump(res);
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -5,22 +5,22 @@
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { route } from "../../../router/route";
|
||||
import getHelmChartInjectable from "../../../helm/helm-service/get-helm-chart.injectable";
|
||||
import getHelmChartReadmeInjectable from "../../../helm/helm-service/get-helm-chart-readme.injectable";
|
||||
|
||||
const getChartRouteInjectable = getRouteInjectable({
|
||||
id: "get-chart-route",
|
||||
const getHelmChartReadmeRouteInjectable = getRouteInjectable({
|
||||
id: "get-helm-chart-readme-route",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getHelmChart = di.inject(getHelmChartInjectable);
|
||||
const getHelmChartReadme = di.inject(getHelmChartReadmeInjectable);
|
||||
|
||||
return route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts/{repo}/{chart}`,
|
||||
path: `${apiPrefix}/v2/charts/{repo}/{chart}/readme`,
|
||||
})(async ({ params, query }) => {
|
||||
const { repo, chart } = params;
|
||||
|
||||
return {
|
||||
response: await getHelmChart(
|
||||
response: await getHelmChartReadme(
|
||||
repo,
|
||||
chart,
|
||||
query.get("version") ?? undefined,
|
||||
@@ -30,4 +30,4 @@ const getChartRouteInjectable = getRouteInjectable({
|
||||
},
|
||||
});
|
||||
|
||||
export default getChartRouteInjectable;
|
||||
export default getHelmChartReadmeRouteInjectable;
|
||||
+3
-3
@@ -7,8 +7,8 @@ import { apiPrefix } from "../../../../common/vars";
|
||||
import { route } from "../../../router/route";
|
||||
import getHelmChartValuesInjectable from "../../../helm/helm-service/get-helm-chart-values.injectable";
|
||||
|
||||
const getChartRouteValuesInjectable = getRouteInjectable({
|
||||
id: "get-chart-route-values",
|
||||
const getHelmChartRouteValuesInjectable = getRouteInjectable({
|
||||
id: "get-helm-chart-values-route",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getHelmChartValues = di.inject(getHelmChartValuesInjectable);
|
||||
@@ -26,4 +26,4 @@ const getChartRouteValuesInjectable = getRouteInjectable({
|
||||
},
|
||||
});
|
||||
|
||||
export default getChartRouteValuesInjectable;
|
||||
export default getHelmChartRouteValuesInjectable;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getRouteInjectable } from "../../../router/router.injectable";
|
||||
import { apiPrefix } from "../../../../common/vars";
|
||||
import { route } from "../../../router/route";
|
||||
import getHelmChartVersionsInjectable from "../../../helm/helm-service/get-helm-chart-versions.injectable";
|
||||
|
||||
const getHelmChartVersionsRouteInjectable = getRouteInjectable({
|
||||
id: "get-helm-chart-versions-route",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getHelmChartVersions = di.inject(getHelmChartVersionsInjectable);
|
||||
|
||||
return route({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/v2/charts/{repo}/{chart}/versions`,
|
||||
})(async ({ params: { repo, chart }}) => ({
|
||||
response: await getHelmChartVersions(repo, chart),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmChartVersionsRouteInjectable;
|
||||
+3
-3
@@ -7,8 +7,8 @@ import { apiPrefix } from "../../../../common/vars";
|
||||
import { route } from "../../../router/route";
|
||||
import listHelmChartsInjectable from "../../../helm/helm-service/list-helm-charts.injectable";
|
||||
|
||||
const listChartsRouteInjectable = getRouteInjectable({
|
||||
id: "list-charts-route",
|
||||
const listHelmChartsRouteInjectable = getRouteInjectable({
|
||||
id: "list-helm-charts-route",
|
||||
|
||||
instantiate: (di) => {
|
||||
const listHelmCharts = di.inject(listHelmChartsInjectable);
|
||||
@@ -22,4 +22,4 @@ const listChartsRouteInjectable = getRouteInjectable({
|
||||
},
|
||||
});
|
||||
|
||||
export default listChartsRouteInjectable;
|
||||
export default listHelmChartsRouteInjectable;
|
||||
@@ -9,6 +9,7 @@ import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type { V1Secret } from "@kubernetes/client-node";
|
||||
import { CoreV1Api } from "@kubernetes/client-node";
|
||||
import { clusterRoute } from "../../router/route";
|
||||
import { dump } from "js-yaml";
|
||||
|
||||
const getServiceAccountRouteInjectable = getRouteInjectable({
|
||||
id: "get-service-account-route",
|
||||
@@ -50,43 +51,15 @@ const getServiceAccountRouteInjectable = getRouteInjectable({
|
||||
|
||||
export default getServiceAccountRouteInjectable;
|
||||
|
||||
interface ServiceAccountKubeConfig {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
clusters: {
|
||||
name: string;
|
||||
cluster: {
|
||||
server: string;
|
||||
"certificate-authority-data": string;
|
||||
};
|
||||
}[];
|
||||
users: {
|
||||
name: string;
|
||||
user: {
|
||||
token: string;
|
||||
};
|
||||
}[];
|
||||
contexts: {
|
||||
name: string;
|
||||
context: {
|
||||
user: string;
|
||||
cluster: string;
|
||||
namespace: string | undefined;
|
||||
};
|
||||
}[];
|
||||
"current-context": string;
|
||||
}
|
||||
|
||||
function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster): ServiceAccountKubeConfig | undefined {
|
||||
function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster): string | undefined {
|
||||
if (!secret.data || !secret.metadata) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { token, "ca.crt": caCrt } = secret.data;
|
||||
|
||||
const tokenData = Buffer.from(token, "base64");
|
||||
|
||||
return {
|
||||
return dump({
|
||||
"apiVersion": "v1",
|
||||
"kind": "Config",
|
||||
"clusters": [
|
||||
@@ -117,5 +90,5 @@ function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster
|
||||
},
|
||||
],
|
||||
"current-context": cluster.contextName,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import getMetricsInjectable from "../../get-metrics.injectable";
|
||||
// This is used for backoff retry tracking.
|
||||
const ATTEMPTS = [false, false, false, false, true];
|
||||
|
||||
const loadMetricsFor = (getMetrics: GetMetrics) => async (promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> => {
|
||||
const loadMetricsFor = (getMetrics: GetMetrics) => async (promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Partial<Record<string, string>>): Promise<any[]> => {
|
||||
const queries = promQueries.map(p => p.trim());
|
||||
const loaders = new Map<string, Promise<any>>();
|
||||
|
||||
@@ -59,7 +59,7 @@ const addMetricsRouteInjectable = getRouteInjectable({
|
||||
const getMetrics = di.inject(getMetricsInjectable);
|
||||
const loadMetrics = loadMetricsFor(getMetrics);
|
||||
|
||||
const queryParams = Object.fromEntries(query.entries());
|
||||
const queryParams: Partial<Record<string, string>> = Object.fromEntries(query.entries());
|
||||
const prometheusMetadata: ClusterPrometheusMetadata = {};
|
||||
|
||||
try {
|
||||
|
||||
+8
-6
@@ -5,17 +5,19 @@
|
||||
import { getRouteInjectable } from "../../router/router.injectable";
|
||||
import { apiPrefix } from "../../../common/vars";
|
||||
import { ResourceApplier } from "../../resource-applier";
|
||||
import { clusterRoute } from "../../router/route";
|
||||
import { payloadValidatedClusterRoute } from "../../router/route";
|
||||
import Joi from "joi";
|
||||
|
||||
const applyResourceRouteInjectable = getRouteInjectable({
|
||||
id: "apply-resource-route",
|
||||
const createResourceRouteInjectable = getRouteInjectable({
|
||||
id: "create-resource-route",
|
||||
|
||||
instantiate: () => clusterRoute({
|
||||
instantiate: () => payloadValidatedClusterRoute({
|
||||
method: "post",
|
||||
path: `${apiPrefix}/stack`,
|
||||
payloadValidator: Joi.string(),
|
||||
})(async ({ cluster, payload }) => ({
|
||||
response: await new ResourceApplier(cluster).apply(payload),
|
||||
response: await new ResourceApplier(cluster).create(payload),
|
||||
})),
|
||||
});
|
||||
|
||||
export default applyResourceRouteInjectable;
|
||||
export default createResourceRouteInjectable;
|
||||
@@ -11,8 +11,6 @@ import path from "path";
|
||||
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
|
||||
import httpProxy from "http-proxy";
|
||||
import readFileBufferInjectable from "../../common/fs/read-file-buffer.injectable";
|
||||
import type { GetAbsolutePath } from "../../common/path/get-absolute-path.injectable";
|
||||
import getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import { webpackDevServerPort } from "../../../webpack/vars";
|
||||
@@ -20,11 +18,13 @@ import type { LensApiRequest, RouteResponse } from "../router/route";
|
||||
import { route } from "../router/route";
|
||||
import staticFilesDirectoryInjectable from "../../common/vars/static-files-directory.injectable";
|
||||
import appNameInjectable from "../../common/vars/app-name.injectable";
|
||||
import type { GetAbsolutePath } from "../../common/path/get-absolute-path.injectable";
|
||||
import getAbsolutePathInjectable from "../../common/path/get-absolute-path.injectable";
|
||||
|
||||
interface ProductionDependencies {
|
||||
readFileBuffer: (path: string) => Promise<Buffer>;
|
||||
getAbsolutePath: GetAbsolutePath;
|
||||
joinPaths: JoinPaths;
|
||||
getAbsolutePath: GetAbsolutePath;
|
||||
staticFilesDirectory: string;
|
||||
appName: string;
|
||||
}
|
||||
@@ -98,8 +98,8 @@ const staticFileRouteInjectable = getRouteInjectable({
|
||||
instantiate: (di) => {
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const readFileBuffer = di.inject(readFileBufferInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable);
|
||||
const appName = di.inject(appNameInjectable);
|
||||
|
||||
@@ -114,10 +114,10 @@ const staticFileRouteInjectable = getRouteInjectable({
|
||||
})
|
||||
: handleStaticFileInProduction({
|
||||
readFileBuffer,
|
||||
getAbsolutePath,
|
||||
joinPaths,
|
||||
staticFilesDirectory,
|
||||
appName,
|
||||
getAbsolutePath,
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -5,25 +5,30 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import localShellSessionInjectable from "./local-shell-session/local-shell-session.injectable";
|
||||
import nodeShellSessionInjectable from "./node-shell-session/node-shell-session.injectable";
|
||||
import openLocalShellSessionInjectable from "./local-shell-session/open.injectable";
|
||||
import openNodeShellSessionInjectable from "./node-shell-session/open.injectable";
|
||||
|
||||
interface Args {
|
||||
webSocket: WebSocket;
|
||||
export interface OpenShellSessionArgs {
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
nodeName?: string;
|
||||
}
|
||||
|
||||
const createShellSessionInjectable = getInjectable({
|
||||
id: "create-shell-session",
|
||||
export type OpenShellSession = (args: OpenShellSessionArgs) => Promise<void>;
|
||||
|
||||
instantiate:
|
||||
(di) =>
|
||||
({ nodeName, ...rest }: Args) =>
|
||||
!nodeName
|
||||
? di.inject(localShellSessionInjectable, rest)
|
||||
: di.inject(nodeShellSessionInjectable, { nodeName, ...rest }),
|
||||
const openShellSessionInjectable = getInjectable({
|
||||
id: "open-shell-session",
|
||||
|
||||
instantiate: (di): OpenShellSession => {
|
||||
const openLocalShellSession = di.inject(openLocalShellSessionInjectable);
|
||||
|
||||
return ({ nodeName, ...args }) => (
|
||||
nodeName
|
||||
? di.inject(openNodeShellSessionInjectable, { nodeName, ...args })
|
||||
: openLocalShellSession(args)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default createShellSessionInjectable;
|
||||
export default openShellSessionInjectable;
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { LocalShellSession } from "./local-shell-session";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import terminalShellEnvModifiersInjectable from "../shell-env-modifier/terminal-shell-env-modify.injectable";
|
||||
|
||||
interface InstantiationParameter {
|
||||
webSocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
const localShellSessionInjectable = getInjectable({
|
||||
id: "local-shell-session",
|
||||
|
||||
instantiate: (di, { cluster, tabId, webSocket }: InstantiationParameter) => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
const localShellEnvModify = di.inject(terminalShellEnvModifiersInjectable);
|
||||
|
||||
const kubectl = createKubectl(cluster.version);
|
||||
|
||||
return new LocalShellSession(localShellEnvModify, kubectl, webSocket, cluster, tabId);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default localShellSessionInjectable;
|
||||
@@ -3,24 +3,32 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type WebSocket from "ws";
|
||||
import path from "path";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import type { UserStore } from "../../../common/user-store";
|
||||
import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session";
|
||||
import { ShellSession } from "../shell-session";
|
||||
import type { Kubectl } from "../../kubectl/kubectl";
|
||||
import { baseBinariesDir } from "../../../common/vars";
|
||||
import type { ModifyTerminalShellEnv } from "../shell-env-modifier/modify-terminal-shell-env.injectable";
|
||||
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";
|
||||
|
||||
export interface LocalShellSessionDependencies extends ShellSessionDependencies {
|
||||
readonly directoryForBinaries: string;
|
||||
readonly userStore: UserStore;
|
||||
modifyTerminalShellEnv: ModifyTerminalShellEnv;
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
getBasenameOfPath: GetBasenameOfPath;
|
||||
}
|
||||
|
||||
export class LocalShellSession extends ShellSession {
|
||||
ShellType = "shell";
|
||||
|
||||
constructor(protected shellEnvModify: (clusterId: ClusterId, env: Record<string, string | undefined>) => Record<string, string | undefined>, kubectl: Kubectl, websocket: WebSocket, cluster: Cluster, terminalId: string) {
|
||||
super(kubectl, websocket, cluster, terminalId);
|
||||
constructor(protected readonly dependencies: LocalShellSessionDependencies, args: ShellSessionArgs) {
|
||||
super(dependencies, args);
|
||||
}
|
||||
|
||||
protected getPathEntries(): string[] {
|
||||
return [baseBinariesDir.get()];
|
||||
return [this.dependencies.directoryForBinaries];
|
||||
}
|
||||
|
||||
protected get cwd(): string | undefined {
|
||||
@@ -31,7 +39,7 @@ export class LocalShellSession extends ShellSession {
|
||||
let env = await this.getCachedShellEnv();
|
||||
|
||||
// extensions can modify the env
|
||||
env = this.shellEnvModify(this.cluster.id, env);
|
||||
env = this.dependencies.modifyTerminalShellEnv(this.cluster.id, env);
|
||||
|
||||
const shell = env.PTYSHELL;
|
||||
|
||||
@@ -45,16 +53,18 @@ export class LocalShellSession extends ShellSession {
|
||||
}
|
||||
|
||||
protected async getShellArgs(shell: string): Promise<string[]> {
|
||||
const pathFromPreferences = UserStore.getInstance().kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = UserStore.getInstance().downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
||||
const pathFromPreferences = this.dependencies.userStore.kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = this.dependencies.userStore.downloadKubectlBinaries
|
||||
? await this.kubectlBinDirP
|
||||
: this.dependencies.getDirnameOfPath(pathFromPreferences);
|
||||
|
||||
switch(path.basename(shell)) {
|
||||
switch(this.dependencies.getBasenameOfPath(shell)) {
|
||||
case "powershell.exe":
|
||||
return ["-NoExit", "-command", `& {$Env:PATH="${kubectlPathDir};${baseBinariesDir.get()};$Env:PATH"}`];
|
||||
return ["-NoExit", "-command", `& {$Env:PATH="${kubectlPathDir};${this.dependencies.directoryForBinaries};$Env:PATH"}`];
|
||||
case "bash":
|
||||
return ["--init-file", path.join(await this.kubectlBinDirP, ".bash_set_path")];
|
||||
return ["--init-file", this.dependencies.joinPaths(await this.kubectlBinDirP, ".bash_set_path")];
|
||||
case "fish":
|
||||
return ["--login", "--init-command", `export PATH="${kubectlPathDir}:${baseBinariesDir.get()}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`];
|
||||
return ["--login", "--init-command", `export PATH="${kubectlPathDir}:${this.dependencies.directoryForBinaries}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`];
|
||||
case "zsh":
|
||||
return ["--login"];
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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 { LocalShellSessionDependencies } from "./local-shell-session";
|
||||
import { LocalShellSession } from "./local-shell-session";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import modifyTerminalShellEnvInjectable from "../shell-env-modifier/modify-terminal-shell-env.injectable";
|
||||
import directoryForBinariesInjectable from "../../../common/app-paths/directory-for-binaries/directory-for-binaries.injectable";
|
||||
import isMacInjectable from "../../../common/vars/is-mac.injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
|
||||
import type WebSocket from "ws";
|
||||
import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||
|
||||
export interface OpenLocalShellSessionArgs {
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export type OpenLocalShellSession = (args: OpenLocalShellSessionArgs) => Promise<void>;
|
||||
|
||||
const openLocalShellSessionInjectable = getInjectable({
|
||||
id: "open-local-shell-session",
|
||||
|
||||
instantiate: (di): OpenLocalShellSession => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
const dependencies: LocalShellSessionDependencies = {
|
||||
directoryForBinaries: di.inject(directoryForBinariesInjectable),
|
||||
isMac: di.inject(isMacInjectable),
|
||||
modifyTerminalShellEnv: di.inject(modifyTerminalShellEnvInjectable),
|
||||
isWindows: di.inject(isWindowsInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
userStore: di.inject(userStoreInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||
};
|
||||
|
||||
return (args) => {
|
||||
const kubectl = createKubectl(args.cluster.version);
|
||||
const session = new LocalShellSession(dependencies, { kubectl, ...args });
|
||||
|
||||
return session.open();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default openLocalShellSessionInjectable;
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import { NodeShellSession } from "./node-shell-session";
|
||||
|
||||
interface InstantiationParameter {
|
||||
webSocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
const nodeShellSessionInjectable = getInjectable({
|
||||
id: "node-shell-session",
|
||||
|
||||
instantiate: (di, { cluster, tabId, webSocket, nodeName }: InstantiationParameter) => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
|
||||
const kubectl = createKubectl(cluster.version);
|
||||
|
||||
return new NodeShellSession(nodeName, kubectl, webSocket, cluster, tabId);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default nodeShellSessionInjectable;
|
||||
@@ -3,28 +3,34 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type WebSocket from "ws";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { Watch, CoreV1Api } from "@kubernetes/client-node";
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
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 { KubeJsonApi } from "../../../common/k8s-api/kube-json-api";
|
||||
import logger from "../../logger";
|
||||
import type { Kubectl } from "../../kubectl/kubectl";
|
||||
import { TerminalChannels } from "../../../common/terminal/channels";
|
||||
import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
||||
|
||||
export interface NodeShellSessionArgs extends ShellSessionArgs {
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
export interface NodeShellSessionDependencies extends ShellSessionDependencies {
|
||||
createKubeJsonApiForCluster: CreateKubeJsonApiForCluster;
|
||||
}
|
||||
|
||||
export class NodeShellSession extends ShellSession {
|
||||
ShellType = "node-shell";
|
||||
|
||||
protected readonly podName = `node-shell-${uuid()}`;
|
||||
|
||||
protected readonly nodeName: string;
|
||||
protected readonly cwd: string | undefined = undefined;
|
||||
|
||||
constructor(protected nodeName: string, kubectl: Kubectl, socket: WebSocket, cluster: Cluster, terminalId: string) {
|
||||
super(kubectl, socket, cluster, terminalId);
|
||||
constructor(protected readonly dependencies: NodeShellSessionDependencies, { nodeName, ...args }: NodeShellSessionArgs) {
|
||||
super(dependencies, args);
|
||||
this.nodeName = nodeName;
|
||||
}
|
||||
|
||||
public async open() {
|
||||
@@ -35,7 +41,7 @@ export class NodeShellSession extends ShellSession {
|
||||
const cleanup = once(() => {
|
||||
coreApi
|
||||
.deleteNamespacedPod(this.podName, "kube-system")
|
||||
.catch(error => logger.warn(`[NODE-SHELL]: failed to remove pod shell`, error));
|
||||
.catch(error => this.dependencies.logger.warn(`[NODE-SHELL]: failed to remove pod shell`, error));
|
||||
});
|
||||
|
||||
this.websocket.once("close", cleanup);
|
||||
@@ -63,7 +69,7 @@ export class NodeShellSession extends ShellSession {
|
||||
const args = ["exec", "-i", "-t", "-n", "kube-system", this.podName, "--"];
|
||||
const nodeApi = new NodeApi({
|
||||
objectConstructor: Node,
|
||||
request: KubeJsonApi.forCluster(this.cluster.id),
|
||||
request: this.dependencies.createKubeJsonApiForCluster(this.cluster.id),
|
||||
});
|
||||
const node = await nodeApi.get({ name: this.nodeName });
|
||||
|
||||
@@ -75,7 +81,7 @@ export class NodeShellSession extends ShellSession {
|
||||
|
||||
switch (nodeOs) {
|
||||
default:
|
||||
logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
||||
this.dependencies.logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
||||
// fallthrough
|
||||
case "linux":
|
||||
args.push("sh", "-c", "((clear && bash) || (clear && ash) || (clear && sh))");
|
||||
@@ -127,7 +133,7 @@ export class NodeShellSession extends ShellSession {
|
||||
}
|
||||
|
||||
protected waitForRunningPod(kc: KubeConfig): Promise<void> {
|
||||
logger.debug(`[NODE-SHELL]: waiting for ${this.podName} to be running`);
|
||||
this.dependencies.logger.debug(`[NODE-SHELL]: waiting for ${this.podName} to be running`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new Watch(kc)
|
||||
@@ -146,19 +152,19 @@ export class NodeShellSession extends ShellSession {
|
||||
},
|
||||
// done callback is called if the watch terminates normally
|
||||
(err) => {
|
||||
logger.error(`[NODE-SHELL]: ${this.podName} was not created in time`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: ${this.podName} was not created in time`);
|
||||
reject(err);
|
||||
},
|
||||
)
|
||||
.then(req => {
|
||||
setTimeout(() => {
|
||||
logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`);
|
||||
req.abort();
|
||||
reject("Pod creation timed out");
|
||||
}, 2 * 60 * 1000); // 2 * 60 * 1000
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import type { NodeShellSessionDependencies } from "./node-shell-session";
|
||||
import { NodeShellSession } from "./node-shell-session";
|
||||
import isMacInjectable from "../../../common/vars/is-mac.injectable";
|
||||
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import createKubeJsonApiForClusterInjectable from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
||||
|
||||
export interface NodeShellSessionArgs {
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
const openNodeShellSessionInjectable = getInjectable({
|
||||
id: "open-node-shell-session",
|
||||
instantiate: (di, params: NodeShellSessionArgs) => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
const dependencies: NodeShellSessionDependencies = {
|
||||
isMac: di.inject(isMacInjectable),
|
||||
isWindows: di.inject(isWindowsInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
createKubeJsonApiForCluster: di.inject(createKubeJsonApiForClusterInjectable),
|
||||
};
|
||||
const kubectl = createKubectl(params.cluster.version);
|
||||
const session = new NodeShellSession(dependencies, { kubectl, ...params });
|
||||
|
||||
return session.open();
|
||||
},
|
||||
// NOTE: this must be transient to bypass the circular dependency of `createKubeJsonApiForClusterInjectable` on the lens proxy port
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default openNodeShellSessionInjectable;
|
||||
@@ -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 { computed } from "mobx";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import { isDefined } from "../../../common/utils";
|
||||
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
|
||||
|
||||
export type ModifyTerminalShellEnv = (clusterId: ClusterId, env: Partial<Record<string, string>>) => Partial<Record<string, string>>;
|
||||
|
||||
const modifyTerminalShellEnvInjectable = getInjectable({
|
||||
id: "terminal-shell-env-modify",
|
||||
|
||||
instantiate: (di): ModifyTerminalShellEnv => {
|
||||
const extensions = di.inject(mainExtensionsInjectable);
|
||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
const terminalShellEnvModifiers = computed(() => (
|
||||
extensions.get()
|
||||
.map((extension) => extension.terminalShellEnvModifier)
|
||||
.filter(isDefined)
|
||||
));
|
||||
|
||||
return (clusterId, env) => {
|
||||
const modifiers = terminalShellEnvModifiers.get();
|
||||
|
||||
if (modifiers.length === 0) {
|
||||
return env;
|
||||
}
|
||||
|
||||
const entity = catalogEntityRegistry.findById(clusterId);
|
||||
|
||||
if (entity) {
|
||||
const ctx = { catalogEntity: entity };
|
||||
|
||||
// clone it so the passed value is not also modified
|
||||
env = JSON.parse(JSON.stringify(env));
|
||||
env = modifiers.reduce((env, modifier) => modifier(ctx, env), env);
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default modifyTerminalShellEnvInjectable;
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed } from "mobx";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import { isDefined } from "../../../common/utils";
|
||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
import type { CatalogEntityRegistry } from "../../catalog";
|
||||
|
||||
interface Dependencies {
|
||||
extensions: IComputedValue<LensMainExtension[]>;
|
||||
catalogEntityRegistry: CatalogEntityRegistry;
|
||||
}
|
||||
|
||||
export const terminalShellEnvModify = ({ extensions, catalogEntityRegistry }: Dependencies) =>
|
||||
(clusterId: ClusterId, env: Record<string, string | undefined>) => {
|
||||
const terminalShellEnvModifiers = computed(() => (
|
||||
extensions.get()
|
||||
.map((extension) => extension.terminalShellEnvModifier)
|
||||
.filter(isDefined)
|
||||
))
|
||||
.get();
|
||||
|
||||
if (terminalShellEnvModifiers.length === 0) {
|
||||
return env;
|
||||
}
|
||||
|
||||
const entity = catalogEntityRegistry.findById(clusterId);
|
||||
|
||||
if (entity) {
|
||||
const ctx = { catalogEntity: entity };
|
||||
|
||||
// clone it so the passed value is not also modified
|
||||
env = JSON.parse(JSON.stringify(env));
|
||||
env = terminalShellEnvModifiers.reduce((env, modifier) => modifier(ctx, env), env);
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
@@ -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 mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import { terminalShellEnvModify } from "./terminal-shell-env-modifiers";
|
||||
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
|
||||
|
||||
const terminalShellEnvModifyInjectable = getInjectable({
|
||||
id: "terminal-shell-env-modify",
|
||||
|
||||
instantiate: (di) =>
|
||||
terminalShellEnvModify({
|
||||
extensions: di.inject(mainExtensionsInjectable),
|
||||
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default terminalShellEnvModifyInjectable;
|
||||
@@ -11,14 +11,13 @@ import { app } from "electron";
|
||||
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { isMac, isWindows } from "../../common/vars";
|
||||
import { UserStore } from "../../common/user-store";
|
||||
import * as pty from "node-pty";
|
||||
import { appEventBus } from "../../common/app-event-bus/event-bus";
|
||||
import logger from "../logger";
|
||||
import { stat } from "fs/promises";
|
||||
import { getOrInsertWith } from "../../common/utils";
|
||||
import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels";
|
||||
import type { Logger } from "../../common/logger";
|
||||
|
||||
export class ShellOpenError extends Error {
|
||||
constructor(message: string, options?: ErrorOptions) {
|
||||
@@ -104,6 +103,19 @@ export enum WebSocketCloseEvent {
|
||||
TlsHandshake = 1015,
|
||||
}
|
||||
|
||||
export interface ShellSessionDependencies {
|
||||
readonly isWindows: boolean;
|
||||
readonly isMac: boolean;
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export interface ShellSessionArgs {
|
||||
kubectl: Kubectl;
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export abstract class ShellSession {
|
||||
abstract readonly ShellType: string;
|
||||
|
||||
@@ -130,6 +142,9 @@ export abstract class ShellSession {
|
||||
protected readonly kubectlBinDirP: Promise<string>;
|
||||
protected readonly kubeconfigPathP: Promise<string>;
|
||||
protected readonly terminalId: string;
|
||||
protected readonly kubectl: Kubectl;
|
||||
protected readonly websocket: WebSocket;
|
||||
protected readonly cluster: Cluster;
|
||||
|
||||
protected abstract get cwd(): string | undefined;
|
||||
|
||||
@@ -147,12 +162,15 @@ export abstract class ShellSession {
|
||||
})
|
||||
));
|
||||
|
||||
logger.info(`[SHELL-SESSION]: PTY for ${this.terminalId} is ${resume ? "resumed" : "started"} with PID=${shellProcess.pid}`);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: PTY for ${this.terminalId} is ${resume ? "resumed" : "started"} with PID=${shellProcess.pid}`);
|
||||
|
||||
return { shellProcess, resume };
|
||||
}
|
||||
|
||||
constructor(protected readonly kubectl: Kubectl, protected readonly websocket: WebSocket, protected readonly cluster: Cluster, terminalId: string) {
|
||||
constructor(protected readonly dependencies: ShellSessionDependencies, { kubectl, websocket, cluster, tabId: terminalId }: ShellSessionArgs) {
|
||||
this.kubectl = kubectl;
|
||||
this.websocket = websocket;
|
||||
this.cluster = cluster;
|
||||
this.kubeconfigPathP = this.cluster.getProxyKubeconfigPath();
|
||||
this.kubectlBinDirP = this.kubectl.binDir();
|
||||
this.terminalId = `${cluster.id}:${terminalId}`;
|
||||
@@ -165,7 +183,7 @@ export abstract class ShellSession {
|
||||
protected async getCwd(env: Record<string, string | undefined>): Promise<string> {
|
||||
const cwdOptions = [this.cwd];
|
||||
|
||||
if (isWindows) {
|
||||
if (this.dependencies.isWindows) {
|
||||
cwdOptions.push(
|
||||
env.USERPROFILE,
|
||||
os.homedir(),
|
||||
@@ -177,7 +195,7 @@ export abstract class ShellSession {
|
||||
os.homedir(),
|
||||
);
|
||||
|
||||
if (isMac) {
|
||||
if (this.dependencies.isMac) {
|
||||
cwdOptions.push("/Users");
|
||||
} else {
|
||||
cwdOptions.push("/home");
|
||||
@@ -214,7 +232,7 @@ export abstract class ShellSession {
|
||||
this.running = true;
|
||||
shellProcess.onData(data => this.send({ type: TerminalChannels.STDOUT, data }));
|
||||
shellProcess.onExit(({ exitCode }) => {
|
||||
logger.info(`[SHELL-SESSION]: shell has exited for ${this.terminalId} closed with exitcode=${exitCode}`);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: shell has exited for ${this.terminalId} closed with exitcode=${exitCode}`);
|
||||
|
||||
// This might already be false because of the kill() within the websocket.on("close") handler
|
||||
if (this.running) {
|
||||
@@ -232,11 +250,11 @@ export abstract class ShellSession {
|
||||
this.websocket
|
||||
.on("message", (rawData: unknown): void => {
|
||||
if (!this.running) {
|
||||
return void logger.debug(`[SHELL-SESSION]: received message from ${this.terminalId}, but shellProcess isn't running`);
|
||||
return void this.dependencies.logger.debug(`[SHELL-SESSION]: received message from ${this.terminalId}, but shellProcess isn't running`);
|
||||
}
|
||||
|
||||
if (!(rawData instanceof Buffer)) {
|
||||
return void logger.error(`[SHELL-SESSION]: Received message non-buffer message.`, { rawData });
|
||||
return void this.dependencies.logger.error(`[SHELL-SESSION]: Received message non-buffer message.`, { rawData });
|
||||
}
|
||||
|
||||
const data = rawData.toString();
|
||||
@@ -252,18 +270,18 @@ export abstract class ShellSession {
|
||||
shellProcess.resize(message.data.width, message.data.height);
|
||||
break;
|
||||
case TerminalChannels.PING:
|
||||
logger.silly(`[SHELL-SESSION]: ${this.terminalId} ping!`);
|
||||
this.dependencies.logger.silly(`[SHELL-SESSION]: ${this.terminalId} ping!`);
|
||||
break;
|
||||
default:
|
||||
logger.warn(`[SHELL-SESSION]: unknown or unhandleable message type for ${this.terminalId}`, message);
|
||||
this.dependencies.logger.warn(`[SHELL-SESSION]: unknown or unhandleable message type for ${this.terminalId}`, message);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[SHELL-SESSION]: failed to handle message for ${this.terminalId}`, error);
|
||||
this.dependencies.logger.error(`[SHELL-SESSION]: failed to handle message for ${this.terminalId}`, error);
|
||||
}
|
||||
})
|
||||
.once("close", code => {
|
||||
logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${WebSocketCloseEvent[code]}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${WebSocketCloseEvent[code]}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
|
||||
const stopShellSession = this.running
|
||||
&& (
|
||||
@@ -278,11 +296,11 @@ export abstract class ShellSession {
|
||||
this.running = false;
|
||||
|
||||
try {
|
||||
logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
||||
shellProcess.kill();
|
||||
ShellSession.processes.delete(this.terminalId);
|
||||
} catch (error) {
|
||||
logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
||||
this.dependencies.logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -319,7 +337,7 @@ export abstract class ShellSession {
|
||||
|
||||
delete env.DEBUG; // don't pass DEBUG into shells
|
||||
|
||||
if (isWindows) {
|
||||
if (this.dependencies.isWindows) {
|
||||
env.SystemRoot = process.env.SystemRoot;
|
||||
env.PTYSHELL = shell || "powershell.exe";
|
||||
env.PATH = pathStr;
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import createLensWindowInjectable from "../application-window/create-lens-window.injectable";
|
||||
import staticFilesDirectoryInjectable from "../../../../common/vars/static-files-directory.injectable";
|
||||
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||
|
||||
const splashWindowInjectable = getInjectable({
|
||||
id: "splash-window",
|
||||
@@ -13,8 +13,8 @@ const splashWindowInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createLensWindow = di.inject(createLensWindowInjectable);
|
||||
const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const splashWindowFile = getAbsolutePath(staticFilesDirectory, "splash.html");
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const splashWindowFile = joinPaths(staticFilesDirectory, "splash.html");
|
||||
|
||||
return createLensWindow({
|
||||
id: "splash",
|
||||
|
||||
@@ -3,34 +3,33 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import getAbsolutePathInjectable from "../../../common/path/get-absolute-path.injectable";
|
||||
import staticFilesDirectoryInjectable from "../../../common/vars/static-files-directory.injectable";
|
||||
import isDevelopmentInjectable from "../../../common/vars/is-development.injectable";
|
||||
import isMacInjectable from "../../../common/vars/is-mac.injectable";
|
||||
import { camelCase, flow, upperFirst } from "lodash/fp";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
const upperCamelCase = flow(camelCase, upperFirst);
|
||||
|
||||
const getTrayIconPathInjectable = getInjectable({
|
||||
id: "get-tray-icon-path",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable);
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const isMac = di.inject(isMacInjectable);
|
||||
|
||||
const baseIconDirectory = getAbsolutePath(
|
||||
const baseIconDirectory = joinPaths(
|
||||
staticFilesDirectory,
|
||||
isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras
|
||||
);
|
||||
|
||||
const fileSuffix = isMac ? "Template.png" : ".png";
|
||||
|
||||
return (name: string) =>
|
||||
getAbsolutePath(
|
||||
baseIconDirectory,
|
||||
`trayIcon${upperCamelCase(name)}${fileSuffix}`,
|
||||
);
|
||||
return (name: string) => joinPaths(
|
||||
baseIconDirectory,
|
||||
`trayIcon${upperCamelCase(name)}${fileSuffix}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user