mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fixing Singleton typing to correctly return child class (#1914)
- Add distinction between `getInstance` and `getInstanceOrCreate` since it is not always possible to create an instance (since you might not know the correct arguments) - Remove all the `export const *Store = *Store.getInstance<*Store>();` calls as it defeats the purpose of `Singleton`. Plus with the typing changes the appropriate `*Store.getInstance()` is "short enough". - Special case the two extension export facades to not need to use `getInstanceOrCreate`. Plus since they are just facades it is always possible to create them. - Move some other types to be also `Singleton`'s: ExtensionLoader, ExtensionDiscovery, ThemeStore, LocalizationStore, ... - Fixed dev-run always using the same port with electron inspect - Update Store documentation with new recommendations about creating instances of singletons - Fix all unit tests to create their dependent singletons Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
b2a570ce28
commit
9563ead2e6
114 files changed
+924
-736
No files matched your search
@@ -23,7 +23,6 @@ jest.mock("winston", () => ({
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
jest.mock("../../common/ipc");
|
||||
jest.mock("../context-handler");
|
||||
jest.mock("request");
|
||||
|
||||
@@ -36,6 +36,11 @@ import { bundledKubectlPath, Kubectl } from "../kubectl";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { waitUntilUsed } from "tcp-port-used";
|
||||
import { Readable } from "stream";
|
||||
import { UserStore } from "../../common/user-store";
|
||||
import { Console } from "console";
|
||||
import { stdout, stderr } from "process";
|
||||
|
||||
console = new Console(stdout, stderr);
|
||||
|
||||
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
|
||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
||||
@@ -44,6 +49,8 @@ const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilU
|
||||
describe("kube auth proxy tests", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
UserStore.resetInstance();
|
||||
UserStore.getInstanceOrCreate();
|
||||
});
|
||||
|
||||
it("calling exit multiple times shouldn't throw", async () => {
|
||||
|
||||
@@ -36,10 +36,6 @@ import * as path from "path";
|
||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||
|
||||
describe("kubeconfig manager tests", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const mockOpts = {
|
||||
"minikube-config.yml": JSON.stringify({
|
||||
@@ -76,7 +72,7 @@ describe("kubeconfig manager tests", () => {
|
||||
const cluster = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml"
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
});
|
||||
const contextHandler = new ContextHandler(cluster);
|
||||
const port = await getFreePort();
|
||||
@@ -98,7 +94,7 @@ describe("kubeconfig manager tests", () => {
|
||||
const cluster = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml"
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
});
|
||||
const contextHandler = new ContextHandler(cluster);
|
||||
const port = await getFreePort();
|
||||
|
||||
+14
-14
@@ -2,7 +2,7 @@ import "../common/cluster-ipc";
|
||||
import type http from "http";
|
||||
import { ipcMain } from "electron";
|
||||
import { action, autorun, observable, reaction, toJS } from "mobx";
|
||||
import { clusterStore, getClusterIdFromHost } from "../common/cluster-store";
|
||||
import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store";
|
||||
import { Cluster } from "./cluster";
|
||||
import logger from "./logger";
|
||||
import { apiKubePrefix } from "../common/vars";
|
||||
@@ -21,7 +21,7 @@ export class ClusterManager extends Singleton {
|
||||
|
||||
catalogEntityRegistry.addSource("lens:kubernetes-clusters", this.catalogSource);
|
||||
// auto-init clusters
|
||||
reaction(() => clusterStore.enabledClustersList, (clusters) => {
|
||||
reaction(() => ClusterStore.getInstance().enabledClustersList, (clusters) => {
|
||||
clusters.forEach((cluster) => {
|
||||
if (!cluster.initialized && !cluster.initializing) {
|
||||
logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta());
|
||||
@@ -31,8 +31,8 @@ export class ClusterManager extends Singleton {
|
||||
|
||||
}, { fireImmediately: true });
|
||||
|
||||
reaction(() => toJS(clusterStore.enabledClustersList, { recurseEverything: true }), () => {
|
||||
this.updateCatalogSource(clusterStore.enabledClustersList);
|
||||
reaction(() => toJS(ClusterStore.getInstance().enabledClustersList, { recurseEverything: true }), () => {
|
||||
this.updateCatalogSource(ClusterStore.getInstance().enabledClustersList);
|
||||
}, { fireImmediately: true });
|
||||
|
||||
reaction(() => catalogEntityRegistry.getItemsForApiKind<KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster"), (entities) => {
|
||||
@@ -42,14 +42,14 @@ export class ClusterManager extends Singleton {
|
||||
|
||||
// auto-stop removed clusters
|
||||
autorun(() => {
|
||||
const removedClusters = Array.from(clusterStore.removedClusters.values());
|
||||
const removedClusters = Array.from(ClusterStore.getInstance().removedClusters.values());
|
||||
|
||||
if (removedClusters.length > 0) {
|
||||
const meta = removedClusters.map(cluster => cluster.getMeta());
|
||||
|
||||
logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta);
|
||||
removedClusters.forEach(cluster => cluster.disconnect());
|
||||
clusterStore.removedClusters.clear();
|
||||
ClusterStore.getInstance().removedClusters.clear();
|
||||
}
|
||||
}, {
|
||||
delay: 250
|
||||
@@ -90,10 +90,10 @@ export class ClusterManager extends Singleton {
|
||||
|
||||
@action syncClustersFromCatalog(entities: KubernetesCluster[]) {
|
||||
entities.filter((entity) => entity.metadata.source !== "local").forEach((entity: KubernetesCluster) => {
|
||||
const cluster = clusterStore.getById(entity.metadata.uid);
|
||||
const cluster = ClusterStore.getInstance().getById(entity.metadata.uid);
|
||||
|
||||
if (!cluster) {
|
||||
clusterStore.addCluster({
|
||||
ClusterStore.getInstance().addCluster({
|
||||
id: entity.metadata.uid,
|
||||
enabled: true,
|
||||
ownerRef: clusterOwnerRef,
|
||||
@@ -145,7 +145,7 @@ export class ClusterManager extends Singleton {
|
||||
|
||||
protected onNetworkOffline() {
|
||||
logger.info("[CLUSTER-MANAGER]: network is offline");
|
||||
clusterStore.enabledClustersList.forEach((cluster) => {
|
||||
ClusterStore.getInstance().enabledClustersList.forEach((cluster) => {
|
||||
if (!cluster.disconnected) {
|
||||
cluster.online = false;
|
||||
cluster.accessible = false;
|
||||
@@ -156,7 +156,7 @@ export class ClusterManager extends Singleton {
|
||||
|
||||
protected onNetworkOnline() {
|
||||
logger.info("[CLUSTER-MANAGER]: network is online");
|
||||
clusterStore.enabledClustersList.forEach((cluster) => {
|
||||
ClusterStore.getInstance().enabledClustersList.forEach((cluster) => {
|
||||
if (!cluster.disconnected) {
|
||||
cluster.refreshConnectionStatus().catch((e) => e);
|
||||
}
|
||||
@@ -164,7 +164,7 @@ export class ClusterManager extends Singleton {
|
||||
}
|
||||
|
||||
stop() {
|
||||
clusterStore.clusters.forEach((cluster: Cluster) => {
|
||||
ClusterStore.getInstance().clusters.forEach((cluster: Cluster) => {
|
||||
cluster.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -176,18 +176,18 @@ export class ClusterManager extends Singleton {
|
||||
if (req.headers.host.startsWith("127.0.0.1")) {
|
||||
const clusterId = req.url.split("/")[1];
|
||||
|
||||
cluster = clusterStore.getById(clusterId);
|
||||
cluster = ClusterStore.getInstance().getById(clusterId);
|
||||
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
|
||||
}
|
||||
} else if (req.headers["x-cluster-id"]) {
|
||||
cluster = clusterStore.getById(req.headers["x-cluster-id"].toString());
|
||||
cluster = ClusterStore.getInstance().getById(req.headers["x-cluster-id"].toString());
|
||||
} else {
|
||||
const clusterId = getClusterIdFromHost(req.headers.host);
|
||||
|
||||
cluster = clusterStore.getById(clusterId);
|
||||
cluster = ClusterStore.getInstance().getById(clusterId);
|
||||
}
|
||||
|
||||
return cluster;
|
||||
|
||||
@@ -4,11 +4,12 @@ import logger from "./logger";
|
||||
* Installs Electron developer tools in the development build.
|
||||
* The dependency is not bundled to the production build.
|
||||
*/
|
||||
export const installDeveloperTools = async () => {
|
||||
export const installDeveloperTools = () => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
logger.info("🤓 Installing developer tools");
|
||||
const { default: devToolsInstaller, REACT_DEVELOPER_TOOLS } = await import("electron-devtools-installer");
|
||||
|
||||
return devToolsInstaller([REACT_DEVELOPER_TOOLS]);
|
||||
import("electron-devtools-installer")
|
||||
.then(({ default: devToolsInstaller, REACT_DEVELOPER_TOOLS }) => devToolsInstaller([REACT_DEVELOPER_TOOLS]))
|
||||
.then((name) => logger.info(`[DEVTOOLS-INSTALLER]: installed ${name}`))
|
||||
.catch(error => logger.error(`[DEVTOOLS-INSTALLER]: failed`, { error }));
|
||||
}
|
||||
};
|
||||
@@ -4,10 +4,14 @@ import { appEventBus } from "../common/event-bus";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
import logger from "./logger";
|
||||
|
||||
|
||||
export function exitApp() {
|
||||
const windowManager = WindowManager.getInstance<WindowManager>();
|
||||
const clusterManager = ClusterManager.getInstance<ClusterManager>();
|
||||
console.log("before windowManager");
|
||||
const windowManager = WindowManager.getInstance(false);
|
||||
|
||||
console.log("before clusterManager");
|
||||
const clusterManager = ClusterManager.getInstance(false);
|
||||
|
||||
console.log("after clusterManager");
|
||||
|
||||
appEventBus.emit({ name: "service", action: "close" });
|
||||
windowManager?.hide();
|
||||
|
||||
@@ -14,7 +14,7 @@ interface FSProvisionModel {
|
||||
export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
@observable registeredExtensions = observable.map<LensExtensionId, string>();
|
||||
|
||||
private constructor() {
|
||||
constructor() {
|
||||
super({
|
||||
configName: "lens-filesystem-provisioner-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
@@ -56,5 +56,3 @@ export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const filesystemProvisionerStore = FilesystemProvisionerStore.getInstance<FilesystemProvisionerStore>();
|
||||
@@ -1,17 +1,24 @@
|
||||
import { helmService } from "../helm-service";
|
||||
import { repoManager } from "../helm-repo-manager";
|
||||
import { HelmRepoManager } from "../helm-repo-manager";
|
||||
|
||||
jest.spyOn(repoManager, "init").mockImplementation();
|
||||
const mockHelmRepoManager = jest.spyOn(HelmRepoManager, "getInstance").mockImplementation();
|
||||
|
||||
jest.mock("../helm-chart-manager");
|
||||
|
||||
describe("Helm Service tests", () => {
|
||||
test("list charts without deprecated ones", async () => {
|
||||
jest.spyOn(repoManager, "repositories").mockImplementation(async () => {
|
||||
return [
|
||||
{ name: "stable", url: "stableurl" },
|
||||
{ name: "experiment", url: "experimenturl" }
|
||||
];
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("list charts without deprecated ones", async () => {
|
||||
mockHelmRepoManager.mockReturnValue({
|
||||
init: jest.fn(),
|
||||
repositories: jest.fn().mockImplementation(async () => {
|
||||
return [
|
||||
{ name: "stable", url: "stableurl" },
|
||||
{ name: "experiment", url: "experimenturl" },
|
||||
];
|
||||
}),
|
||||
});
|
||||
|
||||
const charts = await helmService.listCharts();
|
||||
@@ -55,11 +62,14 @@ describe("Helm Service tests", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("list charts sorted by version in descending order", async () => {
|
||||
jest.spyOn(repoManager, "repositories").mockImplementation(async () => {
|
||||
return [
|
||||
{ name: "bitnami", url: "bitnamiurl" }
|
||||
];
|
||||
it("list charts sorted by version in descending order", async () => {
|
||||
mockHelmRepoManager.mockReturnValue({
|
||||
init: jest.fn(),
|
||||
repositories: jest.fn().mockImplementation(async () => {
|
||||
return [
|
||||
{ name: "bitnami", url: "bitnamiurl" },
|
||||
];
|
||||
}),
|
||||
});
|
||||
|
||||
const charts = await helmService.listCharts();
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import * as tempy from "tempy";
|
||||
import fs from "fs";
|
||||
import fse from "fs-extra";
|
||||
import * as yaml from "js-yaml";
|
||||
import { promiseExec} from "../promise-exec";
|
||||
import { helmCli } from "./helm-cli";
|
||||
import { Cluster } from "../cluster";
|
||||
import { toCamelCase } from "../../common/utils/camelCase";
|
||||
|
||||
export class HelmReleaseManager {
|
||||
export async function listReleases(pathToKubeconfig: string, namespace?: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces";
|
||||
|
||||
public async listReleases(pathToKubeconfig: string, namespace?: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces";
|
||||
const { stdout } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
||||
try {
|
||||
const { stdout } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`);
|
||||
const output = JSON.parse(stdout);
|
||||
|
||||
if (output.length == 0) {
|
||||
@@ -22,106 +22,132 @@ export class HelmReleaseManager {
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public async installChart(chart: string, values: any, name: string, namespace: string, version: string, pathToKubeconfig: string){
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({name: "values.yaml"});
|
||||
|
||||
await fs.promises.writeFile(fileName, yaml.safeDump(values));
|
||||
|
||||
try {
|
||||
let generateName = "";
|
||||
|
||||
if (!name) {
|
||||
generateName = "--generate-name";
|
||||
name = "";
|
||||
}
|
||||
const { stdout } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`).catch((error) => { throw(error.stderr);});
|
||||
const releaseName = stdout.split("\n")[0].split(" ")[1].trim();
|
||||
|
||||
return {
|
||||
log: stdout,
|
||||
release: {
|
||||
name: releaseName,
|
||||
namespace
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
await fs.promises.unlink(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public async upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster){
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({name: "values.yaml"});
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
await fs.promises.writeFile(fileName, yaml.safeDump(values));
|
||||
|
||||
try {
|
||||
const { stdout } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`).catch((error) => { throw(error.stderr);});
|
||||
|
||||
return {
|
||||
log: stdout,
|
||||
release: this.getRelease(name, namespace, cluster)
|
||||
};
|
||||
} finally {
|
||||
await fs.promises.unlink(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public async getRelease(name: string, namespace: string, cluster: Cluster) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
const { stdout } = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`).catch((error) => { throw(error.stderr);});
|
||||
const release = JSON.parse(stdout);
|
||||
|
||||
release.resources = await this.getResources(name, namespace, cluster);
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
public async deleteRelease(name: string, namespace: string, pathToKubeconfig: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
||||
|
||||
return stdout;
|
||||
}
|
||||
|
||||
public async getValues(name: string, namespace: string, all: boolean, pathToKubeconfig: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout, } = await promiseExec(`"${helm}" get values ${name} ${all? "--all": ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
||||
|
||||
return stdout;
|
||||
}
|
||||
|
||||
public async getHistory(name: string, namespace: string, pathToKubeconfig: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
||||
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
|
||||
public async rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
||||
|
||||
return stdout;
|
||||
}
|
||||
|
||||
protected async getResources(name: string, namespace: string, cluster: Cluster) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const kubectl = await cluster.kubeCtl.getPath();
|
||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`).catch(() => {
|
||||
return { stdout: JSON.stringify({items: []})};
|
||||
});
|
||||
|
||||
return stdout;
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
}
|
||||
}
|
||||
|
||||
export const releaseManager = new HelmReleaseManager();
|
||||
|
||||
export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string){
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({name: "values.yaml"});
|
||||
|
||||
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||
|
||||
try {
|
||||
let generateName = "";
|
||||
|
||||
if (!name) {
|
||||
generateName = "--generate-name";
|
||||
name = "";
|
||||
}
|
||||
const { stdout } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`);
|
||||
const releaseName = stdout.split("\n")[0].split(" ")[1].trim();
|
||||
|
||||
return {
|
||||
log: stdout,
|
||||
release: {
|
||||
name: releaseName,
|
||||
namespace
|
||||
}
|
||||
};
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
} finally {
|
||||
await fse.unlink(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster) {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const fileName = tempy.file({name: "values.yaml"});
|
||||
|
||||
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||
|
||||
try {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`);
|
||||
|
||||
return {
|
||||
log: stdout,
|
||||
release: getRelease(name, namespace, cluster)
|
||||
};
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
} finally {
|
||||
await fse.unlink(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRelease(name: string, namespace: string, cluster: Cluster) {
|
||||
try {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
const { stdout } = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`);
|
||||
const release = JSON.parse(stdout);
|
||||
|
||||
release.resources = await getResources(name, namespace, cluster);
|
||||
|
||||
return release;
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteRelease(name: string, namespace: string, pathToKubeconfig: string) {
|
||||
try {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||
|
||||
return stdout;
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getValues(name: string, namespace: string, all: boolean, pathToKubeconfig: string) {
|
||||
try {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout, } = await promiseExec(`"${helm}" get values ${name} ${all ? "--all": ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||
|
||||
return stdout;
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getHistory(name: string, namespace: string, pathToKubeconfig: string) {
|
||||
try {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||
|
||||
return JSON.parse(stdout);
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
}
|
||||
}
|
||||
|
||||
export async function rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) {
|
||||
try {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||
|
||||
return stdout;
|
||||
} catch ({ stderr }) {
|
||||
throw stderr;
|
||||
}
|
||||
}
|
||||
|
||||
async function getResources(name: string, namespace: string, cluster: Cluster) {
|
||||
try {
|
||||
const helm = await helmCli.binaryPath();
|
||||
const kubectl = await cluster.kubeCtl.getPath();
|
||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`);
|
||||
|
||||
return stdout;
|
||||
} catch {
|
||||
return { stdout: JSON.stringify({ items: [] }) };
|
||||
}
|
||||
}
|
||||
@@ -77,10 +77,6 @@ export class HelmRepoManager extends Singleton {
|
||||
}
|
||||
|
||||
public async repositories(): Promise<HelmRepo[]> {
|
||||
if (!this.initialized) {
|
||||
await this.init();
|
||||
}
|
||||
|
||||
try {
|
||||
const repoConfigFile = this.helmEnv.HELM_REPOSITORY_CONFIG;
|
||||
const { repositories }: HelmRepoConfig = await readFile(repoConfigFile, "utf8")
|
||||
@@ -160,5 +156,3 @@ export class HelmRepoManager extends Singleton {
|
||||
return stdout;
|
||||
}
|
||||
}
|
||||
|
||||
export const repoManager = HelmRepoManager.getInstance<HelmRepoManager>();
|
||||
@@ -1,23 +1,21 @@
|
||||
import semver from "semver";
|
||||
import { Cluster } from "../cluster";
|
||||
import logger from "../logger";
|
||||
import { repoManager } from "./helm-repo-manager";
|
||||
import { HelmRepoManager } from "./helm-repo-manager";
|
||||
import { HelmChartManager } from "./helm-chart-manager";
|
||||
import { releaseManager } from "./helm-release-manager";
|
||||
import { HelmChartList, RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api";
|
||||
import { deleteRelease, getHistory, getRelease, getValues, installChart, listReleases, rollback, upgradeRelease } from "./helm-release-manager";
|
||||
|
||||
class HelmService {
|
||||
public async installChart(cluster: Cluster, data: { chart: string; values: {}; name: string; namespace: string; version: string }) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
return await releaseManager.installChart(data.chart, data.values, data.name, data.namespace, data.version, proxyKubeconfig);
|
||||
return installChart(data.chart, data.values, data.name, data.namespace, data.version, proxyKubeconfig);
|
||||
}
|
||||
|
||||
public async listCharts() {
|
||||
const charts: HelmChartList = {};
|
||||
|
||||
await repoManager.init();
|
||||
const repositories = await repoManager.repositories();
|
||||
const repositories = await HelmRepoManager.getInstance().repositories();
|
||||
|
||||
for (const repo of repositories) {
|
||||
charts[repo.name] = {};
|
||||
@@ -36,7 +34,7 @@ class HelmService {
|
||||
readme: "",
|
||||
versions: {}
|
||||
};
|
||||
const repo = await repoManager.repository(repoName);
|
||||
const repo = await HelmRepoManager.getInstance().repository(repoName);
|
||||
const chartManager = new HelmChartManager(repo);
|
||||
const chart = await chartManager.chart(chartName);
|
||||
|
||||
@@ -47,23 +45,22 @@ class HelmService {
|
||||
}
|
||||
|
||||
public async getChartValues(repoName: string, chartName: string, version = "") {
|
||||
const repo = await repoManager.repository(repoName);
|
||||
const repo = await HelmRepoManager.getInstance().repository(repoName);
|
||||
const chartManager = new HelmChartManager(repo);
|
||||
|
||||
return chartManager.getValues(chartName, version);
|
||||
}
|
||||
|
||||
public async listReleases(cluster: Cluster, namespace: string = null) {
|
||||
await repoManager.init();
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
return await releaseManager.listReleases(proxyKubeconfig, namespace);
|
||||
return listReleases(proxyKubeconfig, namespace);
|
||||
}
|
||||
|
||||
public async getRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
||||
logger.debug("Fetch release");
|
||||
|
||||
return await releaseManager.getRelease(releaseName, namespace, cluster);
|
||||
return getRelease(releaseName, namespace, cluster);
|
||||
}
|
||||
|
||||
public async getReleaseValues(cluster: Cluster, releaseName: string, namespace: string, all: boolean) {
|
||||
@@ -71,7 +68,7 @@ class HelmService {
|
||||
|
||||
logger.debug("Fetch release values");
|
||||
|
||||
return await releaseManager.getValues(releaseName, namespace, all, proxyKubeconfig);
|
||||
return getValues(releaseName, namespace, all, proxyKubeconfig);
|
||||
}
|
||||
|
||||
public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) {
|
||||
@@ -79,7 +76,7 @@ class HelmService {
|
||||
|
||||
logger.debug("Fetch release history");
|
||||
|
||||
return await releaseManager.getHistory(releaseName, namespace, proxyKubeconfig);
|
||||
return getHistory(releaseName, namespace, proxyKubeconfig);
|
||||
}
|
||||
|
||||
public async deleteRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
||||
@@ -87,20 +84,20 @@ class HelmService {
|
||||
|
||||
logger.debug("Delete release");
|
||||
|
||||
return await releaseManager.deleteRelease(releaseName, namespace, proxyKubeconfig);
|
||||
return deleteRelease(releaseName, namespace, proxyKubeconfig);
|
||||
}
|
||||
|
||||
public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: { chart: string; values: {}; version: string }) {
|
||||
logger.debug("Upgrade release");
|
||||
|
||||
return await releaseManager.upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, cluster);
|
||||
return upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, cluster);
|
||||
}
|
||||
|
||||
public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) {
|
||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||
|
||||
logger.debug("Rollback release");
|
||||
const output = await releaseManager.rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||
const output = rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||
|
||||
return { message: output };
|
||||
}
|
||||
|
||||
+40
-34
@@ -15,15 +15,15 @@ import { getFreePort } from "./port";
|
||||
import { mangleProxyEnv } from "./proxy-env";
|
||||
import { registerFileProtocol } from "../common/register-protocol";
|
||||
import logger from "./logger";
|
||||
import { clusterStore } from "../common/cluster-store";
|
||||
import { userStore } from "../common/user-store";
|
||||
import { ClusterStore } from "../common/cluster-store";
|
||||
import { UserStore } from "../common/user-store";
|
||||
import { appEventBus } from "../common/event-bus";
|
||||
import { extensionLoader } from "../extensions/extension-loader";
|
||||
import { extensionsStore } from "../extensions/extensions-store";
|
||||
import { InstalledExtension, extensionDiscovery } from "../extensions/extension-discovery";
|
||||
import { ExtensionLoader } from "../extensions/extension-loader";
|
||||
import { ExtensionsStore } from "../extensions/extensions-store";
|
||||
import { InstalledExtension, ExtensionDiscovery } from "../extensions/extension-discovery";
|
||||
import type { LensExtensionId } from "../extensions/lens-extension";
|
||||
import { FilesystemProvisionerStore } from "./extension-filesystem";
|
||||
import { installDeveloperTools } from "./developer-tools";
|
||||
import { filesystemProvisionerStore } from "./extension-filesystem";
|
||||
import { LensProtocolRouterMain } from "./protocol-handler";
|
||||
import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
|
||||
import { bindBroadcastHandlers } from "../common/ipc";
|
||||
@@ -31,13 +31,9 @@ import { startUpdateChecking } from "./app-updater";
|
||||
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
||||
import { CatalogPusher } from "./catalog-pusher";
|
||||
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
|
||||
import { hotbarStore } from "../common/hotbar-store";
|
||||
import { HotbarStore } from "../common/hotbar-store";
|
||||
|
||||
const workingDir = path.join(app.getPath("appData"), appName);
|
||||
let proxyPort: number;
|
||||
let proxyServer: LensProxy;
|
||||
let clusterManager: ClusterManager;
|
||||
let windowManager: WindowManager;
|
||||
|
||||
app.setName(appName);
|
||||
|
||||
@@ -66,7 +62,7 @@ if (app.commandLine.getSwitchValue("proxy-server") !== "") {
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.exit();
|
||||
} else {
|
||||
const lprm = LensProtocolRouterMain.getInstance<LensProtocolRouterMain>();
|
||||
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
||||
|
||||
for (const arg of process.argv) {
|
||||
if (arg.toLowerCase().startsWith("lens://")) {
|
||||
@@ -77,7 +73,7 @@ if (!app.requestSingleInstanceLock()) {
|
||||
}
|
||||
|
||||
app.on("second-instance", (event, argv) => {
|
||||
const lprm = LensProtocolRouterMain.getInstance<LensProtocolRouterMain>();
|
||||
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
||||
|
||||
for (const arg of argv) {
|
||||
if (arg.toLowerCase().startsWith("lens://")) {
|
||||
@@ -86,7 +82,7 @@ app.on("second-instance", (event, argv) => {
|
||||
}
|
||||
}
|
||||
|
||||
windowManager?.ensureMainWindow();
|
||||
WindowManager.getInstance(false)?.ensureMainWindow();
|
||||
});
|
||||
|
||||
app.on("ready", async () => {
|
||||
@@ -102,7 +98,11 @@ app.on("ready", async () => {
|
||||
|
||||
registerFileProtocol("static", __static);
|
||||
|
||||
await installDeveloperTools();
|
||||
const userStore = UserStore.getInstanceOrCreate();
|
||||
const clusterStore = ClusterStore.getInstanceOrCreate();
|
||||
const hotbarStore = HotbarStore.getInstanceOrCreate();
|
||||
const extensionsStore = ExtensionsStore.getInstanceOrCreate();
|
||||
const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate();
|
||||
|
||||
logger.info("💾 Loading stores");
|
||||
// preload
|
||||
@@ -111,10 +111,12 @@ app.on("ready", async () => {
|
||||
clusterStore.load(),
|
||||
hotbarStore.load(),
|
||||
extensionsStore.load(),
|
||||
filesystemProvisionerStore.load(),
|
||||
filesystemStore.load(),
|
||||
]);
|
||||
|
||||
// find free port
|
||||
let proxyPort;
|
||||
|
||||
try {
|
||||
logger.info("🔑 Getting free port for LensProxy server");
|
||||
proxyPort = await getFreePort();
|
||||
@@ -125,13 +127,13 @@ app.on("ready", async () => {
|
||||
}
|
||||
|
||||
// create cluster manager
|
||||
clusterManager = ClusterManager.getInstance<ClusterManager>(proxyPort);
|
||||
ClusterManager.getInstanceOrCreate(proxyPort);
|
||||
|
||||
// run proxy
|
||||
try {
|
||||
logger.info("🔌 Starting LensProxy");
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||
proxyServer = LensProxy.create(proxyPort, clusterManager);
|
||||
LensProxy.getInstanceOrCreate(proxyPort).listen();
|
||||
} catch (error) {
|
||||
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error?.message}`);
|
||||
dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error?.message || "unknown error"}`);
|
||||
@@ -151,7 +153,9 @@ app.on("ready", async () => {
|
||||
logger.error("Checking proxy server connection failed", error);
|
||||
}
|
||||
|
||||
extensionLoader.init();
|
||||
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
||||
|
||||
ExtensionLoader.getInstanceOrCreate().init();
|
||||
extensionDiscovery.init();
|
||||
|
||||
// Start the app without showing the main window when auto starting on login
|
||||
@@ -159,7 +163,9 @@ app.on("ready", async () => {
|
||||
const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden);
|
||||
|
||||
logger.info("🖥️ Starting WindowManager");
|
||||
windowManager = WindowManager.getInstance<WindowManager>(proxyPort);
|
||||
const windowManager = WindowManager.getInstanceOrCreate(proxyPort);
|
||||
|
||||
installDeveloperTools();
|
||||
|
||||
if (!startHidden) {
|
||||
windowManager.initMainWindow();
|
||||
@@ -169,13 +175,13 @@ app.on("ready", async () => {
|
||||
CatalogPusher.init(catalogEntityRegistry);
|
||||
startUpdateChecking();
|
||||
LensProtocolRouterMain
|
||||
.getInstance<LensProtocolRouterMain>()
|
||||
.getInstance()
|
||||
.rendererLoaded = true;
|
||||
});
|
||||
|
||||
extensionLoader.whenLoaded.then(() => {
|
||||
ExtensionLoader.getInstance().whenLoaded.then(() => {
|
||||
LensProtocolRouterMain
|
||||
.getInstance<LensProtocolRouterMain>()
|
||||
.getInstance()
|
||||
.extensionsLoaded = true;
|
||||
});
|
||||
|
||||
@@ -189,14 +195,15 @@ app.on("ready", async () => {
|
||||
extensionDiscovery.watchExtensions();
|
||||
|
||||
// Subscribe to extensions that are copied or deleted to/from the extensions folder
|
||||
extensionDiscovery.events.on("add", (extension: InstalledExtension) => {
|
||||
extensionLoader.addExtension(extension);
|
||||
});
|
||||
extensionDiscovery.events.on("remove", (lensExtensionId: LensExtensionId) => {
|
||||
extensionLoader.removeExtension(lensExtensionId);
|
||||
});
|
||||
extensionDiscovery.events
|
||||
.on("add", (extension: InstalledExtension) => {
|
||||
ExtensionLoader.getInstance().addExtension(extension);
|
||||
})
|
||||
.on("remove", (lensExtensionId: LensExtensionId) => {
|
||||
ExtensionLoader.getInstance().removeExtension(lensExtensionId);
|
||||
});
|
||||
|
||||
extensionLoader.initExtensions(extensions);
|
||||
ExtensionLoader.getInstance().initExtensions(extensions);
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("Lens Error", `Could not load extensions${error?.message ? `: ${error.message}` : ""}`);
|
||||
console.error(error);
|
||||
@@ -212,7 +219,7 @@ app.on("activate", (event, hasVisibleWindows) => {
|
||||
logger.info("APP:ACTIVATE", { hasVisibleWindows });
|
||||
|
||||
if (!hasVisibleWindows) {
|
||||
windowManager?.initMainWindow(false);
|
||||
WindowManager.getInstance(false)?.initMainWindow(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -227,8 +234,7 @@ app.on("will-quit", (event) => {
|
||||
// Quit app on Cmd+Q (MacOS)
|
||||
logger.info("APP:QUIT");
|
||||
appEventBus.emit({name: "app", action: "close"});
|
||||
|
||||
clusterManager?.stop(); // close cluster connections
|
||||
ClusterManager.getInstance(false)?.stop(); // close cluster connections
|
||||
|
||||
if (blockQuit) {
|
||||
event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
|
||||
@@ -242,7 +248,7 @@ app.on("open-url", (event, rawUrl) => {
|
||||
event.preventDefault();
|
||||
|
||||
LensProtocolRouterMain
|
||||
.getInstance<LensProtocolRouterMain>()
|
||||
.getInstance()
|
||||
.route(rawUrl)
|
||||
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }));
|
||||
});
|
||||
|
||||
+9
-9
@@ -6,7 +6,7 @@ import logger from "./logger";
|
||||
import { ensureDir, pathExists } from "fs-extra";
|
||||
import * as lockFile from "proper-lockfile";
|
||||
import { helmCli } from "./helm/helm-cli";
|
||||
import { userStore } from "../common/user-store";
|
||||
import { UserStore } from "../common/user-store";
|
||||
import { customRequest } from "../common/request";
|
||||
import { getBundledKubectlVersion } from "../common/utils/app-version";
|
||||
import { isDevelopment, isWindows, isTestEnv } from "../common/vars";
|
||||
@@ -113,12 +113,12 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
public getPathFromPreferences() {
|
||||
return userStore.preferences?.kubectlBinariesPath || this.getBundledPath();
|
||||
return UserStore.getInstance().preferences?.kubectlBinariesPath || this.getBundledPath();
|
||||
}
|
||||
|
||||
protected getDownloadDir() {
|
||||
if (userStore.preferences?.downloadBinariesPath) {
|
||||
return path.join(userStore.preferences.downloadBinariesPath, "kubectl");
|
||||
if (UserStore.getInstance().preferences?.downloadBinariesPath) {
|
||||
return path.join(UserStore.getInstance().preferences.downloadBinariesPath, "kubectl");
|
||||
}
|
||||
|
||||
return Kubectl.kubectlDir;
|
||||
@@ -129,7 +129,7 @@ export class Kubectl {
|
||||
return this.getBundledPath();
|
||||
}
|
||||
|
||||
if (userStore.preferences?.downloadKubectlBinaries === false) {
|
||||
if (UserStore.getInstance().preferences?.downloadKubectlBinaries === false) {
|
||||
return this.getPathFromPreferences();
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
public async ensureKubectl(): Promise<boolean> {
|
||||
if (userStore.preferences?.downloadKubectlBinaries === false) {
|
||||
if (UserStore.getInstance().preferences?.downloadKubectlBinaries === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ export class Kubectl {
|
||||
|
||||
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stream = customRequest({
|
||||
url: this.url,
|
||||
gzip: true,
|
||||
@@ -303,7 +303,7 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
protected async writeInitScripts() {
|
||||
const kubectlPath = userStore.preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences());
|
||||
const kubectlPath = UserStore.getInstance().preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences());
|
||||
const helmPath = helmCli.getBinaryDir();
|
||||
const fsPromises = fs.promises;
|
||||
const bashScriptPath = path.join(this.dirname, ".bash_set_path");
|
||||
@@ -361,7 +361,7 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
protected getDownloadMirror() {
|
||||
const mirror = packageMirrors.get(userStore.preferences?.downloadMirror);
|
||||
const mirror = packageMirrors.get(UserStore.getInstance().preferences?.downloadMirror);
|
||||
|
||||
if (mirror) {
|
||||
return mirror;
|
||||
|
||||
@@ -6,23 +6,22 @@ import url from "url";
|
||||
import * as WebSocket from "ws";
|
||||
import { apiPrefix, apiKubePrefix } from "../common/vars";
|
||||
import { Router } from "./router";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
import { ContextHandler } from "./context-handler";
|
||||
import logger from "./logger";
|
||||
import { NodeShellSession, LocalShellSession } from "./shell-session";
|
||||
import { Singleton } from "../common/utils";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
|
||||
export class LensProxy {
|
||||
export class LensProxy extends Singleton {
|
||||
protected origin: string;
|
||||
protected proxyServer: http.Server;
|
||||
protected router: Router;
|
||||
protected closed = false;
|
||||
protected retryCounters = new Map<string, number>();
|
||||
|
||||
static create(port: number, clusterManager: ClusterManager) {
|
||||
return new LensProxy(port, clusterManager).listen();
|
||||
}
|
||||
constructor(protected port: number) {
|
||||
super();
|
||||
|
||||
private constructor(protected port: number, protected clusterManager: ClusterManager) {
|
||||
this.origin = `http://localhost:${port}`;
|
||||
this.router = new Router();
|
||||
}
|
||||
@@ -66,7 +65,7 @@ export class LensProxy {
|
||||
}
|
||||
|
||||
protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
|
||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
||||
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
|
||||
|
||||
if (cluster) {
|
||||
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "");
|
||||
@@ -171,7 +170,7 @@ export class LensProxy {
|
||||
const ws = new WebSocket.Server({ noServer: true });
|
||||
|
||||
return ws.on("connection", ((socket: WebSocket, req: http.IncomingMessage) => {
|
||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
||||
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
|
||||
const nodeParam = url.parse(req.url, true).query["node"]?.toString();
|
||||
const shell = nodeParam
|
||||
? new NodeShellSession(socket, cluster, nodeParam)
|
||||
@@ -197,7 +196,7 @@ export class LensProxy {
|
||||
}
|
||||
|
||||
protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) {
|
||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
||||
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
|
||||
|
||||
if (cluster) {
|
||||
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LensProtocolRouterMain } from "../router";
|
||||
import { noop } from "../../../common/utils";
|
||||
import { extensionsStore } from "../../../extensions/extensions-store";
|
||||
import { extensionLoader } from "../../../extensions/extension-loader";
|
||||
import { ExtensionsStore } from "../../../extensions/extensions-store";
|
||||
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
||||
import * as uuid from "uuid";
|
||||
import { LensMainExtension } from "../../../extensions/core-api";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
@@ -16,20 +16,28 @@ function throwIfDefined(val: any): void {
|
||||
}
|
||||
|
||||
describe("protocol router tests", () => {
|
||||
let lpr: LensProtocolRouterMain;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(extensionsStore as any).state.clear();
|
||||
(extensionLoader as any).instances.clear();
|
||||
LensProtocolRouterMain.resetInstance();
|
||||
lpr = LensProtocolRouterMain.getInstance<LensProtocolRouterMain>();
|
||||
ExtensionsStore.getInstanceOrCreate();
|
||||
ExtensionLoader.getInstanceOrCreate();
|
||||
|
||||
const lpr = LensProtocolRouterMain.getInstanceOrCreate();
|
||||
|
||||
lpr.extensionsLoaded = true;
|
||||
lpr.rendererLoaded = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
ExtensionsStore.resetInstance();
|
||||
ExtensionLoader.resetInstance();
|
||||
LensProtocolRouterMain.resetInstance();
|
||||
});
|
||||
|
||||
it("should throw on non-lens URLS", async () => {
|
||||
try {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
|
||||
expect(await lpr.route("https://google.ca")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
@@ -38,6 +46,8 @@ describe("protocol router tests", () => {
|
||||
|
||||
it("should throw when host not internal or extension", async () => {
|
||||
try {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
|
||||
expect(await lpr.route("lens://foobar")).toBeUndefined();
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
@@ -57,14 +67,15 @@ describe("protocol router tests", () => {
|
||||
isEnabled: true,
|
||||
absolutePath: "/foo/bar",
|
||||
});
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
|
||||
ext.protocolHandlers.push({
|
||||
pathSchema: "/",
|
||||
handler: noop,
|
||||
});
|
||||
|
||||
(extensionLoader as any).instances.set(extId, ext);
|
||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "@mirantis/minikube" });
|
||||
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@mirantis/minikube" });
|
||||
|
||||
lpr.addInternalHandler("/", noop);
|
||||
|
||||
@@ -86,6 +97,7 @@ describe("protocol router tests", () => {
|
||||
});
|
||||
|
||||
it("should call handler if matches", async () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called = false;
|
||||
|
||||
lpr.addInternalHandler("/page", () => { called = true; });
|
||||
@@ -101,6 +113,7 @@ describe("protocol router tests", () => {
|
||||
});
|
||||
|
||||
it("should call most exact handler", async () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called: any = 0;
|
||||
|
||||
lpr.addInternalHandler("/page", () => { called = 1; });
|
||||
@@ -119,6 +132,7 @@ describe("protocol router tests", () => {
|
||||
it("should call most exact handler for an extension", async () => {
|
||||
let called: any = 0;
|
||||
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
const extId = uuid.v4();
|
||||
const ext = new LensMainExtension({
|
||||
id: extId,
|
||||
@@ -141,8 +155,8 @@ describe("protocol router tests", () => {
|
||||
handler: params => { called = params.pathname.id; },
|
||||
});
|
||||
|
||||
(extensionLoader as any).instances.set(extId, ext);
|
||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
||||
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined();
|
||||
@@ -155,6 +169,7 @@ describe("protocol router tests", () => {
|
||||
});
|
||||
|
||||
it("should work with non-org extensions", async () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called: any = 0;
|
||||
|
||||
{
|
||||
@@ -177,8 +192,8 @@ describe("protocol router tests", () => {
|
||||
handler: params => { called = params.pathname.id; },
|
||||
});
|
||||
|
||||
(extensionLoader as any).instances.set(extId, ext);
|
||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
||||
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
||||
}
|
||||
|
||||
{
|
||||
@@ -201,12 +216,12 @@ describe("protocol router tests", () => {
|
||||
handler: () => { called = 1; },
|
||||
});
|
||||
|
||||
(extensionLoader as any).instances.set(extId, ext);
|
||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "icecream" });
|
||||
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "icecream" });
|
||||
}
|
||||
|
||||
(extensionsStore as any).state.set("@foobar/icecream", { enabled: true, name: "@foobar/icecream" });
|
||||
(extensionsStore as any).state.set("icecream", { enabled: true, name: "icecream" });
|
||||
(ExtensionsStore.getInstance() as any).state.set("@foobar/icecream", { enabled: true, name: "@foobar/icecream" });
|
||||
(ExtensionsStore.getInstance() as any).state.set("icecream", { enabled: true, name: "icecream" });
|
||||
|
||||
try {
|
||||
expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined();
|
||||
@@ -219,10 +234,13 @@ describe("protocol router tests", () => {
|
||||
});
|
||||
|
||||
it("should throw if urlSchema is invalid", () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
|
||||
expect(() => lpr.addInternalHandler("/:@", noop)).toThrowError();
|
||||
});
|
||||
|
||||
it("should call most exact handler with 3 found handlers", async () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called: any = 0;
|
||||
|
||||
lpr.addInternalHandler("/", () => { called = 2; });
|
||||
@@ -241,6 +259,7 @@ describe("protocol router tests", () => {
|
||||
});
|
||||
|
||||
it("should call most exact handler with 2 found handlers", async () => {
|
||||
const lpr = LensProtocolRouterMain.getInstance();
|
||||
let called: any = 0;
|
||||
|
||||
lpr.addInternalHandler("/", () => { called = 2; });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path";
|
||||
import { helmCli } from "../helm/helm-cli";
|
||||
import { userStore } from "../../common/user-store";
|
||||
import { UserStore } from "../../common/user-store";
|
||||
import { ShellSession } from "./shell-session";
|
||||
|
||||
export class LocalShellSession extends ShellSession {
|
||||
@@ -21,8 +21,8 @@ export class LocalShellSession extends ShellSession {
|
||||
|
||||
protected async getShellArgs(shell: string): Promise<string[]> {
|
||||
const helmpath = helmCli.getBinaryDir();
|
||||
const pathFromPreferences = userStore.preferences.kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
||||
const pathFromPreferences = UserStore.getInstance().preferences.kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = UserStore.getInstance().preferences.downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
||||
|
||||
switch(path.basename(shell)) {
|
||||
case "powershell.exe":
|
||||
|
||||
@@ -6,7 +6,7 @@ import { app } from "electron";
|
||||
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
|
||||
import path from "path";
|
||||
import { isWindows } from "../../common/vars";
|
||||
import { userStore } from "../../common/user-store";
|
||||
import { UserStore } from "../../common/user-store";
|
||||
import * as pty from "node-pty";
|
||||
import { appEventBus } from "../../common/event-bus";
|
||||
|
||||
@@ -119,7 +119,7 @@ export abstract class ShellSession {
|
||||
protected async getShellEnv() {
|
||||
const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv())));
|
||||
const pathStr = [...this.getPathEntries(), await this.kubectlBinDirP, process.env.PATH].join(path.delimiter);
|
||||
const shell = userStore.preferences.shell || process.env.SHELL || process.env.PTYSHELL;
|
||||
const shell = UserStore.getInstance().preferences.shell || process.env.SHELL || process.env.PTYSHELL;
|
||||
|
||||
delete env.DEBUG; // don't pass DEBUG into shells
|
||||
|
||||
|
||||
+31
-31
@@ -61,35 +61,34 @@ export class WindowManager extends Singleton {
|
||||
this.windowState.manage(this.mainWindow);
|
||||
|
||||
// open external links in default browser (target=_blank, window.open)
|
||||
this.mainWindow.webContents.on("new-window", (event, url) => {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
this.mainWindow.webContents.on("dom-ready", () => {
|
||||
appEventBus.emit({ name: "app", action: "dom-ready" });
|
||||
});
|
||||
this.mainWindow.on("focus", () => {
|
||||
appEventBus.emit({ name: "app", action: "focus" });
|
||||
});
|
||||
this.mainWindow.on("blur", () => {
|
||||
appEventBus.emit({ name: "app", action: "blur" });
|
||||
});
|
||||
|
||||
// clean up
|
||||
this.mainWindow.on("closed", () => {
|
||||
this.windowState.unmanage();
|
||||
this.mainWindow = null;
|
||||
this.splashWindow = null;
|
||||
app.dock?.hide(); // hide icon in dock (mac-os)
|
||||
});
|
||||
|
||||
this.mainWindow.webContents.on("did-fail-load", (_event, code, desc) => {
|
||||
logger.error(`[WINDOW-MANAGER]: Failed to load Main window`, { code, desc });
|
||||
});
|
||||
|
||||
this.mainWindow.webContents.on("did-finish-load", () => {
|
||||
logger.info("[WINDOW-MANAGER]: Main window loaded");
|
||||
});
|
||||
this.mainWindow
|
||||
.on("focus", () => {
|
||||
appEventBus.emit({ name: "app", action: "focus" });
|
||||
})
|
||||
.on("blur", () => {
|
||||
appEventBus.emit({ name: "app", action: "blur" });
|
||||
})
|
||||
.on("closed", () => {
|
||||
// clean up
|
||||
this.windowState.unmanage();
|
||||
this.mainWindow = null;
|
||||
this.splashWindow = null;
|
||||
app.dock?.hide(); // hide icon in dock (mac-os)
|
||||
})
|
||||
.webContents
|
||||
.on("new-window", (event, url) => {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
})
|
||||
.on("dom-ready", () => {
|
||||
appEventBus.emit({ name: "app", action: "dom-ready" });
|
||||
})
|
||||
.on("did-fail-load", (_event, code, desc) => {
|
||||
logger.error(`[WINDOW-MANAGER]: Failed to load Main window`, { code, desc });
|
||||
})
|
||||
.on("did-finish-load", () => {
|
||||
logger.info("[WINDOW-MANAGER]: Main window loaded");
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -101,8 +100,9 @@ export class WindowManager extends Singleton {
|
||||
setTimeout(() => {
|
||||
appEventBus.emit({ name: "app", action: "start" });
|
||||
}, 1000);
|
||||
} catch (err) {
|
||||
dialog.showErrorBox("ERROR!", err.toString());
|
||||
} catch (error) {
|
||||
logger.error("Showing main window failed", { error });
|
||||
dialog.showErrorBox("ERROR!", error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user