1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Remove multiple usages of shared global state

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen 2022-04-27 11:42:29 +03:00
parent 8204dc5a9a
commit 4eb4edaed9
No known key found for this signature in database
GPG Key ID: 8C6CFB2FFFE8F68A
48 changed files with 529 additions and 257 deletions

View File

@ -15,8 +15,8 @@ import { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "
import type { KubeApiResource, KubeResource } from "../rbac"; import type { KubeApiResource, KubeResource } from "../rbac";
import { apiResourceRecord, apiResources } from "../rbac"; import { apiResourceRecord, apiResources } from "../rbac";
import logger from "../../main/logger"; import logger from "../../main/logger";
import { VersionDetector } from "../../main/cluster-detectors/version-detector"; import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
import { DetectorRegistry } from "../../main/cluster-detectors/detector-registry"; import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
import plimit from "p-limit"; import plimit from "p-limit";
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types"; import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types";
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types"; import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
@ -33,6 +33,8 @@ export interface ClusterDependencies {
createKubectl: (clusterVersion: string) => Kubectl; createKubectl: (clusterVersion: string) => Kubectl;
createAuthorizationReview: (config: KubeConfig) => CanI; createAuthorizationReview: (config: KubeConfig) => CanI;
createListNamespaces: (config: KubeConfig) => ListNamespaces; createListNamespaces: (config: KubeConfig) => ListNamespaces;
detectorRegistry: DetectorRegistry;
createVersionDetector: (cluster: Cluster) => VersionDetector;
} }
/** /**
@ -425,7 +427,7 @@ export class Cluster implements ClusterModel, ClusterState {
@action @action
async refreshMetadata() { async refreshMetadata() {
logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
const metadata = await DetectorRegistry.getInstance().detectForCluster(this); const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
const existingMetadata = this.metadata; const existingMetadata = this.metadata;
this.metadata = Object.assign(existingMetadata, metadata); this.metadata = Object.assign(existingMetadata, metadata);
@ -488,7 +490,7 @@ export class Cluster implements ClusterModel, ClusterState {
protected async getConnectionStatus(): Promise<ClusterStatus> { protected async getConnectionStatus(): Promise<ClusterStatus> {
try { try {
const versionDetector = new VersionDetector(this); const versionDetector = this.dependencies.createVersionDetector(this);
const versionData = await versionDetector.detect(); const versionData = await versionDetector.detect();
this.metadata.version = versionData.value; this.metadata.version = versionData.value;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
const ensureDirInjectable = getInjectable({
id: "ensure-dir",
// TODO: Remove usages of ensureDir from business logic.
// TODO: Read, Write, Watch etc. operations should do this internally.
instantiate: (di) => di.inject(fsInjectable).ensureDir,
causesSideEffects: true,
});
export default ensureDirInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { WeblinkStore } from "./weblink-store";
const weblinkStoreInjectable = getInjectable({
id: "weblink-store",
instantiate: () => {
WeblinkStore.resetInstance();
return WeblinkStore.createInstance();
},
});
export default weblinkStoreInjectable;

View File

@ -4,13 +4,17 @@
*/ */
import { LensExtension } from "./lens-extension"; import { LensExtension } from "./lens-extension";
import { WindowManager } from "../main/window-manager";
import { catalogEntityRegistry } from "../main/catalog"; import { catalogEntityRegistry } from "../main/catalog";
import type { CatalogEntity } from "../common/catalog"; import type { CatalogEntity } from "../common/catalog";
import type { IObservableArray } from "mobx"; import type { IObservableArray } from "mobx";
import type { MenuRegistration } from "../main/menu/menu-registration"; import type { MenuRegistration } from "../main/menu/menu-registration";
import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration"; import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration";
import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration"; import type { ShellEnvModifier } from "../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
import {
Environments,
getEnvironmentSpecificLegacyGlobalDiForExtensionApi,
} from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import windowManagerInjectable from "../main/window-manager.injectable";
export class LensMainExtension extends LensExtension { export class LensMainExtension extends LensExtension {
appMenus: MenuRegistration[] = []; appMenus: MenuRegistration[] = [];
@ -18,21 +22,25 @@ export class LensMainExtension extends LensExtension {
/** /**
* implement this to modify the shell environment that Lens terminals are opened with. The ShellEnvModifier type has the signature * implement this to modify the shell environment that Lens terminals are opened with. The ShellEnvModifier type has the signature
* *
* (ctx: ShellEnvContext, env: Record<string, string | undefined>) => Record<string, string | undefined> * (ctx: ShellEnvContext, env: Record<string, string | undefined>) => Record<string, string | undefined>
* *
* @param ctx the shell environment context, specifically the relevant catalog entity for the terminal. This can be used, for example, to get * @param ctx the shell environment context, specifically the relevant catalog entity for the terminal. This can be used, for example, to get
* cluster-specific information that can be made available in the shell environment by the implementation of terminalShellEnvModifier * cluster-specific information that can be made available in the shell environment by the implementation of terminalShellEnvModifier
* *
* @param env the current shell environment that the terminal will be opened with. The implementation should modify this as desired. * @param env the current shell environment that the terminal will be opened with. The implementation should modify this as desired.
* *
* @returns the modified shell environment that the terminal will be opened with. The implementation must return env as passed in, if it * @returns the modified shell environment that the terminal will be opened with. The implementation must return env as passed in, if it
* does not modify the shell environment * does not modify the shell environment
*/ */
terminalShellEnvModifier?: ShellEnvModifier; terminalShellEnvModifier?: ShellEnvModifier;
async navigate(pageId?: string, params?: Record<string, any>, frameId?: number) { async navigate(pageId?: string, params?: Record<string, any>, frameId?: number) {
return WindowManager.getInstance().navigateExtension(this.id, pageId, params, frameId); const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.main);
const windowManager = di.inject(windowManagerInjectable);
return windowManager.navigateExtension(this.id, pageId, params, frameId);
} }
addCatalogSource(id: string, source: IObservableArray<CatalogEntity>) { addCatalogSource(id: string, source: IObservableArray<CatalogEntity>) {

View File

@ -3,8 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { WindowManager } from "../../main/window-manager"; import {
Environments,
getEnvironmentSpecificLegacyGlobalDiForExtensionApi,
} from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import windowManagerInjectable from "../../main/window-manager.injectable";
export function navigate(url: string) { export function navigate(url: string) {
return WindowManager.getInstance().navigate(url); const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.main);
const windowManager = di.inject(windowManagerInjectable);
return windowManager.navigate(url);
} }

View File

@ -12,6 +12,8 @@ import { getDiForUnitTesting } from "../getDiForUnitTesting";
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import prometheusProviderRegistryInjectable
from "../prometheus/prometheus-provider-registry.injectable";
jest.mock("electron", () => ({ jest.mock("electron", () => ({
app: { app: {
@ -74,6 +76,7 @@ const clusterStub = {
describe("ContextHandler", () => { describe("ContextHandler", () => {
let createContextHandler: (cluster: Cluster) => ContextHandler; let createContextHandler: (cluster: Cluster) => ContextHandler;
let prometheusProviderRegistry: PrometheusProviderRegistry;
beforeEach(() => { beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting({ doGeneralOverrides: true });
@ -84,13 +87,12 @@ describe("ContextHandler", () => {
di.override(createKubeAuthProxyInjectable, () => ({} as any)); di.override(createKubeAuthProxyInjectable, () => ({} as any));
createContextHandler = di.inject(createContextHandlerInjectable); prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
PrometheusProviderRegistry.createInstance(); createContextHandler = di.inject(createContextHandlerInjectable);
}); });
afterEach(() => { afterEach(() => {
PrometheusProviderRegistry.resetInstance();
UserStore.resetInstance(); UserStore.resetInstance();
mockFs.restore(); mockFs.restore();
}); });
@ -102,17 +104,16 @@ describe("ContextHandler", () => {
[0, 2], [0, 2],
[0, 3], [0, 3],
])("should throw from %d success(es) after %d failure(s)", async (successes, failures) => { ])("should throw from %d success(es) after %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0; let count = 0;
for (let i = 0; i < failures; i += 1) { for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined; const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
} }
for (let i = 0; i < successes; i += 1) { for (let i = 0; i < successes; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
} }
expect(() => { expect(() => {
@ -133,17 +134,16 @@ describe("ContextHandler", () => {
[2, 2], [2, 2],
[2, 3], [2, 3],
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => { ])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0; let count = 0;
for (let i = 0; i < failures; i += 1) { for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined; const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
} }
for (let i = 0; i < successes; i += 1) { for (let i = 0; i < successes; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
} }
// TODO: Unit test shouldn't access protected or private methods // TODO: Unit test shouldn't access protected or private methods
@ -164,17 +164,16 @@ describe("ContextHandler", () => {
[2, 2], [2, 2],
[2, 3], [2, 3],
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => { ])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0; let count = 0;
for (let i = 0; i < successes; i += 1) { for (let i = 0; i < successes; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
} }
for (let i = 0; i < failures; i += 1) { for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined; const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
} }
// TODO: Unit test shouldn't access protected or private methods // TODO: Unit test shouldn't access protected or private methods
@ -195,23 +194,22 @@ describe("ContextHandler", () => {
[2, 2], [2, 2],
[2, 3], [2, 3],
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => { ])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0; let count = 0;
const beforeSuccesses = Math.floor(successes / 2); const beforeSuccesses = Math.floor(successes / 2);
const afterSuccesses = successes - beforeSuccesses; const afterSuccesses = successes - beforeSuccesses;
for (let i = 0; i < beforeSuccesses; i += 1) { for (let i = 0; i < beforeSuccesses; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
} }
for (let i = 0; i < failures; i += 1) { for (let i = 0; i < failures; i += 1) {
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined; const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
} }
for (let i = 0; i < afterSuccesses; i += 1) { for (let i = 0; i < afterSuccesses; i += 1) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
} }
// TODO: Unit test shouldn't access protected or private methods // TODO: Unit test shouldn't access protected or private methods
@ -223,12 +221,11 @@ describe("ContextHandler", () => {
}); });
it("shouldn't pick the second provider of 2 success(es) after 1 failure(s)", async () => { it("shouldn't pick the second provider of 2 success(es) after 1 failure(s)", async () => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0; let count = 0;
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure));
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success)); prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
// TODO: Unit test shouldn't access protected or private methods // TODO: Unit test shouldn't access protected or private methods
const contextHandler = createContextHandler(clusterStub) as any; const contextHandler = createContextHandler(clusterStub) as any;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import electronAppInjectable from "./electron-app.injectable";
const exitAppInjectable = getInjectable({
id: "exit-app",
instantiate: (di) => () => {
const app = di.inject(electronAppInjectable);
app.exit(0);
},
});
export default exitAppInjectable;

View File

@ -10,7 +10,6 @@ import type { Cluster } from "../../../common/cluster/cluster";
import { computeDiff as computeDiffFor, configToModels } from "../kubeconfig-sync-manager/kubeconfig-sync-manager"; import { computeDiff as computeDiffFor, configToModels } from "../kubeconfig-sync-manager/kubeconfig-sync-manager";
import mockFs from "mock-fs"; import mockFs from "mock-fs";
import fs from "fs"; import fs from "fs";
import { ClusterManager } from "../../cluster-manager";
import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable"; import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token"; import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
@ -19,6 +18,7 @@ import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable"; import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
import { runSetups } from "../../../common/setupable-injection-token/run-setups"; import { runSetups } from "../../../common/setupable-injection-token/run-setups";
import clusterManagerInjectable from "../../cluster-manager.injectable";
jest.mock("electron", () => ({ jest.mock("electron", () => ({
app: { app: {
@ -53,19 +53,17 @@ describe("kubeconfig-sync.source tests", () => {
computeDiff = computeDiffFor({ computeDiff = computeDiffFor({
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable), directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
createCluster: di.inject(createClusterInjectionToken), createCluster: di.inject(createClusterInjectionToken),
clusterManager: di.inject(clusterManagerInjectable),
}); });
di.permitSideEffects(getConfigurationFileModelInjectable); di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable); di.permitSideEffects(appVersionInjectable);
di.inject(clusterStoreInjectable); di.inject(clusterStoreInjectable);
ClusterManager.createInstance();
}); });
afterEach(() => { afterEach(() => {
mockFs.restore(); mockFs.restore();
ClusterManager.resetInstance();
ClusterStore.resetInstance(); ClusterStore.resetInstance();
}); });

View File

@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import { KubeconfigSyncManager } from "./kubeconfig-sync-manager"; import { KubeconfigSyncManager } from "./kubeconfig-sync-manager";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token"; import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
import clusterManagerInjectable from "../../cluster-manager.injectable";
const kubeconfigSyncManagerInjectable = getInjectable({ const kubeconfigSyncManagerInjectable = getInjectable({
id: "kubeconfig-sync-manager", id: "kubeconfig-sync-manager",
@ -13,6 +14,7 @@ const kubeconfigSyncManagerInjectable = getInjectable({
instantiate: (di) => new KubeconfigSyncManager({ instantiate: (di) => new KubeconfigSyncManager({
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable), directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
createCluster: di.inject(createClusterInjectionToken), createCluster: di.inject(createClusterInjectionToken),
clusterManager: di.inject(clusterManagerInjectable),
}), }),
}); });

View File

@ -53,6 +53,7 @@ const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; //
interface Dependencies { interface Dependencies {
directoryForKubeConfigs: string; directoryForKubeConfigs: string;
createCluster: (model: ClusterModel) => Cluster; createCluster: (model: ClusterModel) => Cluster;
clusterManager: ClusterManager;
} }
const kubeConfigSyncName = "lens:kube-sync"; const kubeConfigSyncName = "lens:kube-sync";
@ -164,7 +165,7 @@ type RootSourceValue = [Cluster, CatalogEntity];
type RootSource = ObservableMap<string, RootSourceValue>; type RootSource = ObservableMap<string, RootSourceValue>;
// exported for testing // exported for testing
export const computeDiff = ({ directoryForKubeConfigs, createCluster }: Dependencies) => (contents: string, source: RootSource, filePath: string): void => { export const computeDiff = ({ directoryForKubeConfigs, createCluster, clusterManager }: Dependencies) => (contents: string, source: RootSource, filePath: string): void => {
runInAction(() => { runInAction(() => {
try { try {
const { config, error } = loadConfigFromString(contents); const { config, error } = loadConfigFromString(contents);
@ -184,7 +185,7 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster }: Dependen
// remove and disconnect clusters that were removed from the config // remove and disconnect clusters that were removed from the config
if (!model) { if (!model) {
// remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting // remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting
ClusterManager.getInstance().deleting.delete(value[0].id); clusterManager.deleting.delete(value[0].id);
value[0].disconnect(); value[0].disconnect();
source.delete(contextName); source.delete(contextName);

View File

@ -4,7 +4,7 @@
*/ */
import { computed, observable, reaction } from "mobx"; import { computed, observable, reaction } from "mobx";
import { WeblinkStore } from "../../common/weblink-store"; import type { WeblinkStore } from "../../common/weblink-store";
import { WebLink } from "../../common/catalog-entities"; import { WebLink } from "../../common/catalog-entities";
import { catalogEntityRegistry } from "../catalog"; import { catalogEntityRegistry } from "../catalog";
import got from "got"; import got from "got";
@ -29,8 +29,7 @@ async function validateLink(link: WebLink) {
} }
export function syncWeblinks() { export function syncWeblinks(weblinkStore: WeblinkStore) {
const weblinkStore = WeblinkStore.getInstance();
const webLinkEntities = observable.map<string, [WebLink, Disposer]>(); const webLinkEntities = observable.map<string, [WebLink, Disposer]>();
function periodicallyCheckLink(link: WebLink): Disposer { function periodicallyCheckLink(link: WebLink): Disposer {

View File

@ -5,7 +5,7 @@
import type { RequestPromiseOptions } from "request-promise-native"; import type { RequestPromiseOptions } from "request-promise-native";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import { k8sRequest } from "../k8s-request"; import type { K8sRequest } from "../k8s-request.injectable";
export interface ClusterDetectionResult { export interface ClusterDetectionResult {
value: string | number | boolean; value: string | number | boolean;
@ -15,7 +15,7 @@ export interface ClusterDetectionResult {
export class BaseClusterDetector { export class BaseClusterDetector {
key: string; key: string;
constructor(public cluster: Cluster) { constructor(public cluster: Cluster, private _k8sRequest: K8sRequest) {
} }
detect(): Promise<ClusterDetectionResult> { detect(): Promise<ClusterDetectionResult> {
@ -23,6 +23,6 @@ export class BaseClusterDetector {
} }
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> { protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
return k8sRequest(this.cluster, path, options); return this._k8sRequest(this.cluster, path, options);
} }
} }

View File

@ -0,0 +1,21 @@
/**
* 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 { VersionDetector } from "./version-detector";
import k8sRequestInjectable from "../k8s-request.injectable";
import type { Cluster } from "../../common/cluster/cluster";
const createVersionDetectorInjectable = getInjectable({
id: "create-version-detector",
instantiate: (di) => {
const k8sRequest = di.inject(k8sRequestInjectable);
return (cluster: Cluster) =>
new VersionDetector(cluster, k8sRequest);
},
});
export default createVersionDetectorInjectable;

View File

@ -0,0 +1,16 @@
/**
* 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 { DetectorRegistry } from "./detector-registry";
import k8sRequestInjectable from "../k8s-request.injectable";
const detectorRegistryInjectable = getInjectable({
id: "detector-registry",
instantiate: (di) =>
new DetectorRegistry({ k8sRequest: di.inject(k8sRequestInjectable) }),
});
export default detectorRegistryInjectable;

View File

@ -5,11 +5,17 @@
import { observable } from "mobx"; import { observable } from "mobx";
import type { ClusterMetadata } from "../../common/cluster-types"; import type { ClusterMetadata } from "../../common/cluster-types";
import { Singleton } from "../../common/utils";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import type { BaseClusterDetector, ClusterDetectionResult } from "./base-cluster-detector"; import type { BaseClusterDetector, ClusterDetectionResult } from "./base-cluster-detector";
import type { K8sRequest } from "../k8s-request.injectable";
interface Dependencies {
k8sRequest: K8sRequest;
}
export class DetectorRegistry {
constructor(private dependencies: Dependencies) {}
export class DetectorRegistry extends Singleton {
registry = observable.array<typeof BaseClusterDetector>([], { deep: false }); registry = observable.array<typeof BaseClusterDetector>([], { deep: false });
add(detectorClass: typeof BaseClusterDetector): this { add(detectorClass: typeof BaseClusterDetector): this {
@ -22,7 +28,7 @@ export class DetectorRegistry extends Singleton {
const results: { [key: string]: ClusterDetectionResult } = {}; const results: { [key: string]: ClusterDetectionResult } = {};
for (const detectorClass of this.registry) { for (const detectorClass of this.registry) {
const detector = new detectorClass(cluster); const detector = new detectorClass(cluster, this.dependencies.k8sRequest);
try { try {
const data = await detector.detect(); const data = await detector.detect();

View File

@ -0,0 +1,23 @@
/**
* 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 { ClusterManager } from "./cluster-manager";
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
const clusterManagerInjectable = getInjectable({
id: "cluster-manager",
instantiate: (di) => {
const clusterManager = new ClusterManager({
store: di.inject(clusterStoreInjectable),
});
clusterManager.init();
return clusterManager;
},
});
export default clusterManagerInjectable;

View File

@ -9,42 +9,44 @@ import { action, makeObservable, observable, observe, reaction, toJS } from "mob
import type { Cluster } from "../common/cluster/cluster"; import type { Cluster } from "../common/cluster/cluster";
import logger from "./logger"; import logger from "./logger";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { getClusterIdFromHost, Singleton } from "../common/utils"; import { getClusterIdFromHost } from "../common/utils";
import { catalogEntityRegistry } from "./catalog"; import { catalogEntityRegistry } from "./catalog";
import type { KubernetesClusterPrometheusMetrics } from "../common/catalog-entities/kubernetes-cluster"; import type { KubernetesClusterPrometheusMetrics } from "../common/catalog-entities/kubernetes-cluster";
import { KubernetesCluster, LensKubernetesClusterStatus } from "../common/catalog-entities/kubernetes-cluster"; import { KubernetesCluster, LensKubernetesClusterStatus } from "../common/catalog-entities/kubernetes-cluster";
import { ipcMainOn } from "../common/ipc"; import { ipcMainOn } from "../common/ipc";
import { once } from "lodash"; import { once } from "lodash";
import { ClusterStore } from "../common/cluster-store/cluster-store"; import type { ClusterStore } from "../common/cluster-store/cluster-store";
import type { ClusterId } from "../common/cluster-types"; import type { ClusterId } from "../common/cluster-types";
const logPrefix = "[CLUSTER-MANAGER]:"; const logPrefix = "[CLUSTER-MANAGER]:";
const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus)); const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus));
export class ClusterManager extends Singleton { interface Dependencies {
private store = ClusterStore.getInstance(); store: ClusterStore;
}
export class ClusterManager {
deleting = observable.set<ClusterId>(); deleting = observable.set<ClusterId>();
@observable visibleCluster: ClusterId | undefined = undefined; @observable visibleCluster: ClusterId | undefined = undefined;
constructor() { constructor(private dependencies: Dependencies) {
super();
makeObservable(this); makeObservable(this);
} }
init = once(() => { init = once(() => {
// reacting to every cluster's state change and total amount of items // reacting to every cluster's state change and total amount of items
reaction( reaction(
() => this.store.clustersList.map(c => c.getState()), () => this.dependencies.store.clustersList.map(c => c.getState()),
() => this.updateCatalog(this.store.clustersList), () => this.updateCatalog(this.dependencies.store.clustersList),
{ fireImmediately: false }, { fireImmediately: false },
); );
// reacting to every cluster's preferences change and total amount of items // reacting to every cluster's preferences change and total amount of items
reaction( reaction(
() => this.store.clustersList.map(c => toJS(c.preferences)), () => this.dependencies.store.clustersList.map(c => toJS(c.preferences)),
() => this.updateCatalog(this.store.clustersList), () => this.updateCatalog(this.dependencies.store.clustersList),
{ fireImmediately: false }, { fireImmediately: false },
); );
@ -180,7 +182,7 @@ export class ClusterManager extends Singleton {
@action @action
protected syncClustersFromCatalog(entities: KubernetesCluster[]) { protected syncClustersFromCatalog(entities: KubernetesCluster[]) {
for (const entity of entities) { for (const entity of entities) {
const cluster = this.store.getById(entity.getId()); const cluster = this.dependencies.store.getById(entity.getId());
if (!cluster) { if (!cluster) {
const model = { const model = {
@ -195,7 +197,7 @@ export class ClusterManager extends Singleton {
* Add the bare minimum of data to ClusterStore. And especially no * Add the bare minimum of data to ClusterStore. And especially no
* preferences, as those might be configured by the entity's source * preferences, as those might be configured by the entity's source
*/ */
this.store.addCluster(model); this.dependencies.store.addCluster(model);
} catch (error) { } catch (error) {
if (error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) { if (error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) {
logger.warn(`${logPrefix} kubeconfig file disappeared`, model); logger.warn(`${logPrefix} kubeconfig file disappeared`, model);
@ -234,7 +236,7 @@ export class ClusterManager extends Singleton {
protected onNetworkOffline = () => { protected onNetworkOffline = () => {
logger.info(`${logPrefix} network is offline`); logger.info(`${logPrefix} network is offline`);
this.store.clustersList.forEach((cluster) => { this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) { if (!cluster.disconnected) {
cluster.online = false; cluster.online = false;
cluster.accessible = false; cluster.accessible = false;
@ -245,7 +247,7 @@ export class ClusterManager extends Singleton {
protected onNetworkOnline = () => { protected onNetworkOnline = () => {
logger.info(`${logPrefix} network is online`); logger.info(`${logPrefix} network is online`);
this.store.clustersList.forEach((cluster) => { this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) { if (!cluster.disconnected) {
cluster.refreshConnectionStatus().catch((e) => e); cluster.refreshConnectionStatus().catch((e) => e);
} }
@ -253,16 +255,16 @@ export class ClusterManager extends Singleton {
}; };
stop() { stop() {
this.store.clusters.forEach((cluster: Cluster) => { this.dependencies.store.clusters.forEach((cluster: Cluster) => {
cluster.disconnect(); cluster.disconnect();
}); });
} }
getClusterForRequest(req: http.IncomingMessage): Cluster { getClusterForRequest = (req: http.IncomingMessage): Cluster => {
// lens-server is connecting to 127.0.0.1:<port>/<uid> // lens-server is connecting to 127.0.0.1:<port>/<uid>
if (req.headers.host.startsWith("127.0.0.1")) { if (req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1]; const clusterId = req.url.split("/")[1];
const cluster = this.store.getById(clusterId); const cluster = this.dependencies.store.getById(clusterId);
if (cluster) { if (cluster) {
// we need to swap path prefix so that request is proxied to kube api // we need to swap path prefix so that request is proxied to kube api
@ -272,8 +274,8 @@ export class ClusterManager extends Singleton {
return cluster; return cluster;
} }
return this.store.getById(getClusterIdFromHost(req.headers.host)); return this.dependencies.store.getById(getClusterIdFromHost(req.headers.host));
} };
} }
export function catalogEntityFromCluster(cluster: Cluster) { export function catalogEntityFromCluster(cluster: Cluster) {

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { PrometheusProvider, PrometheusService } from "../prometheus/provider-registry"; import type { PrometheusProvider, PrometheusService, PrometheusProviderRegistry } from "../prometheus/provider-registry";
import { PrometheusProviderRegistry } from "../prometheus/provider-registry";
import type { ClusterPrometheusPreferences } from "../../common/cluster-types"; import type { ClusterPrometheusPreferences } from "../../common/cluster-types";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import type httpProxy from "http-proxy"; import type httpProxy from "http-proxy";
@ -30,6 +29,7 @@ interface PrometheusServicePreferences {
interface Dependencies { interface Dependencies {
createKubeAuthProxy: CreateKubeAuthProxy; createKubeAuthProxy: CreateKubeAuthProxy;
authProxyCa: string; authProxyCa: string;
prometheusProviderRegistry: PrometheusProviderRegistry;
} }
export class ContextHandler { export class ContextHandler {
@ -67,11 +67,11 @@ export class ContextHandler {
this.prometheusProvider = service.id; this.prometheusProvider = service.id;
} }
return PrometheusProviderRegistry.getInstance().getByKind(this.prometheusProvider); return this.dependencies.prometheusProviderRegistry.getByKind(this.prometheusProvider);
} }
protected listPotentialProviders(): PrometheusProvider[] { protected listPotentialProviders(): PrometheusProvider[] {
const registry = PrometheusProviderRegistry.getInstance(); const registry = this.dependencies.prometheusProviderRegistry;
const provider = this.prometheusProvider && registry.getByKind(this.prometheusProvider); const provider = this.prometheusProvider && registry.getByKind(this.prometheusProvider);
if (provider) { if (provider) {

View File

@ -9,6 +9,7 @@ import { ContextHandler } from "./context-handler";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import { getKubeAuthProxyCertificate } from "../kube-auth-proxy/get-kube-auth-proxy-certificate"; import { getKubeAuthProxyCertificate } from "../kube-auth-proxy/get-kube-auth-proxy-certificate";
import URLParse from "url-parse"; import URLParse from "url-parse";
import prometheusProviderRegistryInjectable from "../prometheus/prometheus-provider-registry.injectable";
const createContextHandlerInjectable = getInjectable({ const createContextHandlerInjectable = getInjectable({
id: "create-context-handler", id: "create-context-handler",
@ -20,6 +21,7 @@ const createContextHandlerInjectable = getInjectable({
const dependencies = { const dependencies = {
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable), createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
authProxyCa: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate).cert, authProxyCa: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate).cert,
prometheusProviderRegistry: di.inject(prometheusProviderRegistryInjectable),
}; };
return new ContextHandler(dependencies, cluster); return new ContextHandler(dependencies, cluster);

View File

@ -12,6 +12,8 @@ import createContextHandlerInjectable from "../context-handler/create-context-ha
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
const createClusterInjectable = getInjectable({ const createClusterInjectable = getInjectable({
id: "create-cluster", id: "create-cluster",
@ -24,6 +26,8 @@ const createClusterInjectable = getInjectable({
createContextHandler: di.inject(createContextHandlerInjectable), createContextHandler: di.inject(createContextHandlerInjectable),
createAuthorizationReview: di.inject(authorizationReviewInjectable), createAuthorizationReview: di.inject(authorizationReviewInjectable),
createListNamespaces: di.inject(listNamespacesInjectable), createListNamespaces: di.inject(listNamespacesInjectable),
detectorRegistry: di.inject(detectorRegistryInjectable),
createVersionDetector: di.inject(createVersionDetectorInjectable),
}; };
return (model) => new Cluster(dependencies, model); return (model) => new Cluster(dependencies, model);

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import logger from "./logger";
/**
* Installs Electron developer tools in the development build.
* The dependency is not bundled to the production build.
*/
export const installDeveloperTools = () => {
if (process.env.NODE_ENV === "development") {
logger.info("🤓 Installing 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 }));
}
};

View File

@ -1,23 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { app } from "electron";
import { WindowManager } from "./window-manager";
import { appEventBus } from "../common/app-event-bus/event-bus";
import { ClusterManager } from "./cluster-manager";
import logger from "./logger";
export function exitApp() {
const windowManager = WindowManager.getInstance(false);
const clusterManager = ClusterManager.getInstance(false);
appEventBus.emit({ name: "service", action: "close" });
windowManager?.hide();
clusterManager?.stop();
logger.info("SERVICE:QUIT");
setTimeout(() => {
app.exit();
}, 1000);
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Cluster } from "../common/cluster/cluster";
import type { IMetricsReqParams } from "../common/k8s-api/endpoints/metrics.api";
import k8sRequestInjectable from "./k8s-request.injectable";
export type GetMetrics = (cluster: Cluster, prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) => Promise<any>;
const getMetricsInjectable = getInjectable({
id: "get-metrics",
instantiate: (di): GetMetrics => {
const k8sRequest = di.inject(k8sRequestInjectable);
return async (
cluster,
prometheusPath,
queryParams,
) => {
const prometheusPrefix = cluster.preferences.prometheus?.prefix || "";
const metricsPath = `/api/v1/namespaces/${prometheusPath}/proxy${prometheusPrefix}/api/v1/query_range`;
return k8sRequest(cluster, metricsPath, {
timeout: 0,
resolveWithFullResponse: false,
json: true,
method: "POST",
form: queryParams,
});
};
},
});
export default getMetricsInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { HelmRepoManager } from "./helm-repo-manager";
const helmRepoManagerInjectable = getInjectable({
id: "helm-repo-manager",
instantiate: () => {
HelmRepoManager.resetInstance();
return HelmRepoManager.createInstance();
},
});
export default helmRepoManagerInjectable;

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ClusterIdDetector } from "../cluster-detectors/cluster-id-detector";
import { DetectorRegistry } from "../cluster-detectors/detector-registry";
import { DistributionDetector } from "../cluster-detectors/distribution-detector";
import { LastSeenDetector } from "../cluster-detectors/last-seen-detector";
import { NodesCountDetector } from "../cluster-detectors/nodes-count-detector";
import { VersionDetector } from "../cluster-detectors/version-detector";
export function initClusterMetadataDetectors() {
DetectorRegistry.createInstance()
.add(ClusterIdDetector)
.add(LastSeenDetector)
.add(VersionDetector)
.add(DistributionDetector)
.add(NodesCountDetector);
}

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./metrics-providers";
export * from "./cluster-metadata-detectors";

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { PrometheusProviderRegistry } from "../prometheus";
import { PrometheusHelm } from "../prometheus/helm";
import { PrometheusLens } from "../prometheus/lens";
import { PrometheusOperator } from "../prometheus/operator";
import { PrometheusStacklight } from "../prometheus/stacklight";
export function initPrometheusProviderRegistry() {
PrometheusProviderRegistry
.getInstance()
.registerProvider(new PrometheusLens())
.registerProvider(new PrometheusHelm())
.registerProvider(new PrometheusOperator())
.registerProvider(new PrometheusStacklight());
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import request, { RequestPromiseOptions } from "request-promise-native";
import { apiKubePrefix } from "../common/vars";
import type { Cluster } from "../common/cluster/cluster";
import { getInjectable } from "@ogre-tools/injectable";
import lensProxyPortNumberStateInjectable from "./lens-proxy-port-number-state.injectable";
export type K8sRequest = (cluster: Cluster, path: string, options?: RequestPromiseOptions) => Promise<any>;
const k8SRequestInjectable = getInjectable({
id: "k8s-request",
instantiate: (di) => {
const lensProxyPortNumberState = di.inject(lensProxyPortNumberStateInjectable);
return async (
cluster: Cluster,
path: string,
options: RequestPromiseOptions = {},
) => {
const kubeProxyUrl = `http://localhost:${lensProxyPortNumberState.get()}${apiKubePrefix}`;
options.headers ??= {};
options.json ??= true;
options.timeout ??= 30000;
options.headers.Host = `${cluster.id}.${new URL(kubeProxyUrl).host}`; // required in ClusterManager.getClusterForRequest()
return request(kubeProxyUrl + path, options);
};
},
});
export default k8SRequestInjectable;

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RequestPromiseOptions } from "request-promise-native";
import request from "request-promise-native";
import { apiKubePrefix } from "../common/vars";
import type { IMetricsReqParams } from "../common/k8s-api/endpoints/metrics.api";
import { LensProxy } from "./lens-proxy";
import type { Cluster } from "../common/cluster/cluster";
export async function k8sRequest<T = any>(cluster: Cluster, path: string, options: RequestPromiseOptions = {}): Promise<T> {
const kubeProxyUrl = `http://localhost:${LensProxy.getInstance().port}${apiKubePrefix}`;
options.headers ??= {};
options.json ??= true;
options.timeout ??= 30000;
options.headers.Host = `${cluster.id}.${new URL(kubeProxyUrl).host}`; // required in ClusterManager.getClusterForRequest()
return request(kubeProxyUrl + path, options);
}
export async function getMetrics(cluster: Cluster, prometheusPath: string, queryParams: IMetricsReqParams & { query: string }): Promise<any> {
const prometheusPrefix = cluster.preferences.prometheus?.prefix || "";
const metricsPath = `/api/v1/namespaces/${prometheusPath}/proxy${prometheusPrefix}/api/v1/query_range`;
return k8sRequest(cluster, metricsPath, {
timeout: 0,
resolveWithFullResponse: false,
json: true,
method: "POST",
form: queryParams,
});
}

View File

@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import type { Cluster } from "../../common/cluster/cluster"; import type { Cluster } from "../../common/cluster/cluster";
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
import { KubeconfigManager } from "./kubeconfig-manager"; import { KubeconfigManager } from "./kubeconfig-manager";
import lensProxyPortNumberStateInjectable from "../lens-proxy-port-number-state.injectable";
export interface KubeConfigManagerInstantiationParameter { export interface KubeConfigManagerInstantiationParameter {
cluster: Cluster; cluster: Cluster;
@ -17,6 +18,7 @@ const createKubeconfigManagerInjectable = getInjectable({
instantiate: (di) => { instantiate: (di) => {
const dependencies = { const dependencies = {
directoryForTemp: di.inject(directoryForTempInjectable), directoryForTemp: di.inject(directoryForTempInjectable),
lensProxyPortNumberState: di.inject(lensProxyPortNumberStateInjectable),
}; };
return (cluster: Cluster) => new KubeconfigManager(dependencies, cluster); return (cluster: Cluster) => new KubeconfigManager(dependencies, cluster);

View File

@ -10,10 +10,10 @@ import path from "path";
import fs from "fs-extra"; import fs from "fs-extra";
import { dumpConfigYaml } from "../../common/kube-helpers"; import { dumpConfigYaml } from "../../common/kube-helpers";
import logger from "../logger"; import logger from "../logger";
import { LensProxy } from "../lens-proxy";
interface Dependencies { interface Dependencies {
directoryForTemp: string; directoryForTemp: string;
lensProxyPortNumberState: { get: () => number };
} }
export class KubeconfigManager { export class KubeconfigManager {
@ -79,7 +79,7 @@ export class KubeconfigManager {
} }
get resolveProxyUrl() { get resolveProxyUrl() {
return `http://127.0.0.1:${LensProxy.getInstance().port}/${this.cluster.id}`; return `http://127.0.0.1:${this.dependencies.lensProxyPortNumberState.get()}/${this.cluster.id}`;
} }
/** /**

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
const lensProxyPortNumberStateInjectable = getInjectable({
id: "lens-proxy-port-number-state",
instantiate: () => {
let _portNumber: number;
return {
get: () => {
if (!_portNumber) {
throw new Error(
"Tried to access port number of LensProxy while it has not been set yet.",
);
}
return _portNumber;
},
set: (portNumber: number) => {
if (_portNumber) {
throw new Error(
"Tried to set port number for LensProxy when it has already been set.",
);
}
_portNumber = portNumber;
},
};
},
});
export default lensProxyPortNumberStateInjectable;

View File

@ -0,0 +1,14 @@
/**
* 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 lensProxyPortNumberStateInjectable from "./lens-proxy-port-number-state.injectable";
const lensProxyPortNumberInjectable = getInjectable({
id: "lens-proxy-port-number",
instantiate: (di) => di.inject(lensProxyPortNumberStateInjectable).get(),
});
export default lensProxyPortNumberInjectable;

View File

@ -0,0 +1,35 @@
/**
* 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 { LensProxy } from "./lens-proxy";
import { kubeApiUpgradeRequest } from "./proxy-functions";
import routerInjectable from "./router/router.injectable";
import httpProxy from "http-proxy";
import clusterManagerInjectable from "./cluster-manager.injectable";
import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable";
import lensProxyPortNumberStateInjectable from "./lens-proxy-port-number-state.injectable";
const lensProxyInjectable = getInjectable({
id: "lens-proxy",
instantiate: (di) => {
const clusterManager = di.inject(clusterManagerInjectable);
const router = di.inject(routerInjectable);
const shellApiRequest = di.inject(shellApiRequestInjectable);
const proxy = httpProxy.createProxy();
const lensProxyPortNumberState = di.inject(lensProxyPortNumberStateInjectable);
return new LensProxy({
router,
proxy,
kubeApiUpgradeRequest,
shellApiRequest,
getClusterForRequest: clusterManager.getClusterForRequest,
lensProxyPortNumberState,
});
},
});
export default lensProxyInjectable;

View File

@ -11,7 +11,6 @@ import { apiPrefix, apiKubePrefix } from "../common/vars";
import type { Router } from "./router/router"; import type { Router } from "./router/router";
import type { ContextHandler } from "./context-handler/context-handler"; import type { ContextHandler } from "./context-handler/context-handler";
import logger from "./logger"; import logger from "./logger";
import { Singleton } from "../common/utils";
import type { Cluster } from "../common/cluster/cluster"; import type { Cluster } from "../common/cluster/cluster";
import type { ProxyApiRequestArgs } from "./proxy-functions"; import type { ProxyApiRequestArgs } from "./proxy-functions";
import { appEventBus } from "../common/app-event-bus/event-bus"; import { appEventBus } from "../common/app-event-bus/event-bus";
@ -19,10 +18,13 @@ import { getBoolean } from "./utils/parse-query";
type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | null; type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | null;
export interface LensProxyFunctions { interface Dependencies {
getClusterForRequest: GetClusterForRequest; getClusterForRequest: GetClusterForRequest;
shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>; shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise<void>; kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
router: Router;
proxy: httpProxy;
lensProxyPortNumberState: { set: (portNumber: number) => void };
} }
const watchParam = "watch"; const watchParam = "watch";
@ -52,21 +54,14 @@ const disallowedPorts = new Set([
10080, 10080,
]); ]);
export class LensProxy extends Singleton { export class LensProxy {
protected origin: string; protected origin: string;
protected proxyServer: http.Server; protected proxyServer: http.Server;
protected closed = false; protected closed = false;
protected retryCounters = new Map<string, number>(); protected retryCounters = new Map<string, number>();
protected getClusterForRequest: GetClusterForRequest;
public port: number; constructor(private dependencies: Dependencies) {
this.configureProxy(dependencies.proxy);
constructor(protected router: Router, protected proxy: httpProxy, { shellApiRequest, kubeApiUpgradeRequest, getClusterForRequest }: LensProxyFunctions) {
super();
this.configureProxy(proxy);
this.getClusterForRequest = getClusterForRequest;
this.proxyServer = spdy.createServer({ this.proxyServer = spdy.createServer({
spdy: { spdy: {
@ -79,14 +74,14 @@ export class LensProxy extends Singleton {
this.proxyServer this.proxyServer
.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { .on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
const cluster = getClusterForRequest(req); const cluster = dependencies.getClusterForRequest(req);
if (!cluster) { if (!cluster) {
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`); logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
socket.destroy(); socket.destroy();
} else { } else {
const isInternal = req.url.startsWith(`${apiPrefix}?`); const isInternal = req.url.startsWith(`${apiPrefix}?`);
const reqHandler = isInternal ? shellApiRequest : kubeApiUpgradeRequest; const reqHandler = isInternal ? dependencies.shellApiRequest : dependencies.kubeApiUpgradeRequest;
(async () => reqHandler({ req, socket, head, cluster }))() (async () => reqHandler({ req, socket, head, cluster }))()
.catch(error => logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error)); .catch(error => logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error));
@ -110,13 +105,14 @@ export class LensProxy extends Singleton {
const { address, port } = this.proxyServer.address() as net.AddressInfo; const { address, port } = this.proxyServer.address() as net.AddressInfo;
this.dependencies.lensProxyPortNumberState.set(port);
logger.info(`[LENS-PROXY]: Proxy server has started at ${address}:${port}`); logger.info(`[LENS-PROXY]: Proxy server has started at ${address}:${port}`);
this.proxyServer.on("error", (error) => { this.proxyServer.on("error", (error) => {
logger.info(`[LENS-PROXY]: Subsequent error: ${error}`); logger.info(`[LENS-PROXY]: Subsequent error: ${error}`);
}); });
this.port = port;
appEventBus.emit({ name: "lens-proxy", action: "listen", params: { port }}); appEventBus.emit({ name: "lens-proxy", action: "listen", params: { port }});
resolve(port); resolve(port);
}) })
@ -228,15 +224,15 @@ export class LensProxy extends Singleton {
} }
protected async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) { protected async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
const cluster = this.getClusterForRequest(req); const cluster = this.dependencies.getClusterForRequest(req);
if (cluster) { if (cluster) {
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler); const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
if (proxyTarget) { if (proxyTarget) {
return this.proxy.web(req, res, proxyTarget); return this.dependencies.proxy.web(req, res, proxyTarget);
} }
} }
this.router.route(cluster, req, res); this.dependencies.router.route(cluster, req, res);
} }
} }

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { PrometheusProviderRegistry } from "./provider-registry";
const prometheusProviderRegistryInjectable = getInjectable({
id: "prometheus-provider-registry",
instantiate: () => new PrometheusProviderRegistry(),
});
export default prometheusProviderRegistryInjectable;

View File

@ -5,7 +5,6 @@
import type { CoreV1Api } from "@kubernetes/client-node"; import type { CoreV1Api } from "@kubernetes/client-node";
import { inspect } from "util"; import { inspect } from "util";
import { Singleton } from "../../common/utils";
export interface PrometheusService { export interface PrometheusService {
id: string; id: string;
@ -65,7 +64,7 @@ export abstract class PrometheusProvider {
} }
} }
export class PrometheusProviderRegistry extends Singleton { export class PrometheusProviderRegistry {
public providers = new Map<string, PrometheusProvider>(); public providers = new Map<string, PrometheusProvider>();
getByKind(kind: string): PrometheusProvider { getByKind(kind: string): PrometheusProvider {

View File

@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
import { LensProtocolRouterMain } from "./lens-protocol-router-main"; import { LensProtocolRouterMain } from "./lens-protocol-router-main";
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
import windowManagerInjectable from "../../window-manager.injectable";
const lensProtocolRouterMainInjectable = getInjectable({ const lensProtocolRouterMainInjectable = getInjectable({
id: "lens-protocol-router-main", id: "lens-protocol-router-main",
@ -14,6 +15,7 @@ const lensProtocolRouterMainInjectable = getInjectable({
new LensProtocolRouterMain({ new LensProtocolRouterMain({
extensionLoader: di.inject(extensionLoaderInjectable), extensionLoader: di.inject(extensionLoaderInjectable),
extensionsStore: di.inject(extensionsStoreInjectable), extensionsStore: di.inject(extensionsStoreInjectable),
windowManager: di.inject(windowManagerInjectable),
}), }),
}); });

View File

@ -12,7 +12,7 @@ import { observable, when, makeObservable } from "mobx";
import type { RouteAttempt } from "../../../common/protocol-handler"; import type { RouteAttempt } from "../../../common/protocol-handler";
import { ProtocolHandlerInvalid } from "../../../common/protocol-handler"; import { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
import { disposer, noop } from "../../../common/utils"; import { disposer, noop } from "../../../common/utils";
import { WindowManager } from "../../window-manager"; import type { WindowManager } from "../../window-manager";
import type { ExtensionLoader } from "../../../extensions/extension-loader"; import type { ExtensionLoader } from "../../../extensions/extension-loader";
import type { ExtensionsStore } from "../../../extensions/extensions-store/extensions-store"; import type { ExtensionsStore } from "../../../extensions/extensions-store/extensions-store";
@ -40,6 +40,7 @@ function checkHost<Query>(url: URLParse<Query>): boolean {
interface Dependencies { interface Dependencies {
extensionLoader: ExtensionLoader; extensionLoader: ExtensionLoader;
extensionsStore: ExtensionsStore; extensionsStore: ExtensionsStore;
windowManager: WindowManager;
} }
export class LensProtocolRouterMain extends proto.LensProtocolRouter { export class LensProtocolRouterMain extends proto.LensProtocolRouter {
@ -73,7 +74,7 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
throw new proto.RoutingError(proto.RoutingErrorType.INVALID_PROTOCOL, url); throw new proto.RoutingError(proto.RoutingErrorType.INVALID_PROTOCOL, url);
} }
WindowManager.getInstance(false)?.ensureMainWindow().catch(noop); this.dependencies.windowManager.ensureMainWindow().catch(noop);
const routeInternally = checkHost(url); const routeInternally = checkHost(url);
logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`); logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`);

View File

@ -5,9 +5,9 @@
import { app } from "electron"; import { app } from "electron";
const switchValue = app.commandLine.getSwitchValue("proxy-server");
export function mangleProxyEnv() { export function mangleProxyEnv() {
const switchValue = app.commandLine.getSwitchValue("proxy-server");
let httpsProxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || ""; let httpsProxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || "";
delete process.env.HTTPS_PROXY; delete process.env.HTTPS_PROXY;
@ -20,4 +20,8 @@ export function mangleProxyEnv() {
if (httpsProxy !== "") { if (httpsProxy !== "") {
process.env.APP_HTTPS_PROXY = httpsProxy; process.env.APP_HTTPS_PROXY = httpsProxy;
} }
if (app.commandLine.getSwitchValue("proxy-server") !== "") {
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server");
}
} }

View File

@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { shellApiRequest } from "./shell-api-request"; import { shellApiRequest } from "./shell-api-request";
import createShellSessionInjectable from "../../shell-session/create-shell-session.injectable"; import createShellSessionInjectable from "../../shell-session/create-shell-session.injectable";
import shellRequestAuthenticatorInjectable import shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable";
from "./shell-request-authenticator/shell-request-authenticator.injectable"; import clusterManagerInjectable from "../../cluster-manager.injectable";
const shellApiRequestInjectable = getInjectable({ const shellApiRequestInjectable = getInjectable({
id: "shell-api-request", id: "shell-api-request",
@ -14,6 +14,7 @@ const shellApiRequestInjectable = getInjectable({
instantiate: (di) => shellApiRequest({ instantiate: (di) => shellApiRequest({
createShellSession: di.inject(createShellSessionInjectable), createShellSession: di.inject(createShellSessionInjectable),
authenticateRequest: di.inject(shellRequestAuthenticatorInjectable).authenticate, authenticateRequest: di.inject(shellRequestAuthenticatorInjectable).authenticate,
clusterManager: di.inject(clusterManagerInjectable),
}), }),
}); });

View File

@ -7,7 +7,7 @@ import logger from "../../logger";
import type WebSocket from "ws"; import type WebSocket from "ws";
import { Server as WebSocketServer } from "ws"; import { Server as WebSocketServer } from "ws";
import type { ProxyApiRequestArgs } from "../types"; import type { ProxyApiRequestArgs } from "../types";
import { ClusterManager } from "../../cluster-manager"; import type { ClusterManager } from "../../cluster-manager";
import URLParse from "url-parse"; import URLParse from "url-parse";
import type { Cluster } from "../../../common/cluster/cluster"; import type { Cluster } from "../../../common/cluster/cluster";
import type { ClusterId } from "../../../common/cluster-types"; import type { ClusterId } from "../../../common/cluster-types";
@ -21,10 +21,12 @@ interface Dependencies {
tabId: string; tabId: string;
nodeName?: string; nodeName?: string;
}) => { open: () => Promise<void> }; }) => { open: () => Promise<void> };
clusterManager: ClusterManager;
} }
export const shellApiRequest = ({ createShellSession, authenticateRequest }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => { export const shellApiRequest = ({ createShellSession, authenticateRequest, clusterManager }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => {
const cluster = ClusterManager.getInstance().getClusterForRequest(req); const cluster = clusterManager.getClusterForRequest(req);
const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true); const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true);
if (!cluster || !authenticateRequest(cluster.id, tabId, shellToken)) { if (!cluster || !authenticateRequest(cluster.id, tabId, shellToken)) {

View File

@ -11,13 +11,14 @@ import type { ClusterPrometheusMetadata } from "../../../common/cluster-types";
import { ClusterMetadataKey } from "../../../common/cluster-types"; import { ClusterMetadataKey } from "../../../common/cluster-types";
import logger from "../../logger"; import logger from "../../logger";
import type { Cluster } from "../../../common/cluster/cluster"; import type { Cluster } from "../../../common/cluster/cluster";
import { getMetrics } from "../../k8s-request";
import type { IMetricsQuery } from "./metrics-query"; import type { IMetricsQuery } from "./metrics-query";
import type { GetMetrics } from "../../get-metrics.injectable";
import getMetricsInjectable from "../../get-metrics.injectable";
// This is used for backoff retry tracking. // This is used for backoff retry tracking.
const ATTEMPTS = [false, false, false, false, true]; const ATTEMPTS = [false, false, false, false, true];
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> { const loadMetricsFor = (getMetrics: GetMetrics) => async (promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> => {
const queries = promQueries.map(p => p.trim()); const queries = promQueries.map(p => p.trim());
const loaders = new Map<string, Promise<any>>(); const loaders = new Map<string, Promise<any>>();
@ -41,9 +42,11 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
} }
return Promise.all(queries.map(loadMetric)); return Promise.all(queries.map(loadMetric));
} };
const addMetricsRoute = (getMetrics: GetMetrics) => async ({ cluster, payload, query }: LensApiRequest) => {
const loadMetrics = loadMetricsFor(getMetrics);
const addMetricsRoute = async ({ cluster, payload, query }: LensApiRequest) => {
const queryParams: IMetricsQuery = Object.fromEntries(query.entries()); const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
const prometheusMetadata: ClusterPrometheusMetadata = {}; const prometheusMetadata: ClusterPrometheusMetadata = {};
@ -97,11 +100,15 @@ const addMetricsRoute = async ({ cluster, payload, query }: LensApiRequest) => {
const addMetricsRouteInjectable = getInjectable({ const addMetricsRouteInjectable = getInjectable({
id: "add-metrics-route", id: "add-metrics-route",
instantiate: (): Route<any> => ({ instantiate: (di): Route<any> => {
method: "post", const getMetrics = di.inject(getMetricsInjectable);
path: `${apiPrefix}/metrics`,
handler: addMetricsRoute, return {
}), method: "post",
path: `${apiPrefix}/metrics`,
handler: addMetricsRoute(getMetrics),
};
},
injectionToken: routeInjectionToken, injectionToken: routeInjectionToken,
}); });

View File

@ -7,26 +7,34 @@ import { getInjectable } from "@ogre-tools/injectable";
import { apiPrefix } from "../../../common/vars"; import { apiPrefix } from "../../../common/vars";
import type { Route } from "../../router/router"; import type { Route } from "../../router/router";
import { routeInjectionToken } from "../../router/router.injectable"; import { routeInjectionToken } from "../../router/router.injectable";
import { PrometheusProviderRegistry } from "../../prometheus";
import type { MetricProviderInfo } from "../../../common/k8s-api/endpoints/metrics.api"; import type { MetricProviderInfo } from "../../../common/k8s-api/endpoints/metrics.api";
import prometheusProviderRegistryInjectable from "../../prometheus/prometheus-provider-registry.injectable";
const getMetricProvidersRouteInjectable = getInjectable({ const getMetricProvidersRouteInjectable = getInjectable({
id: "get-metric-providers-route", id: "get-metric-providers-route",
instantiate: (): Route<MetricProviderInfo[]> => ({ instantiate: (di): Route<MetricProviderInfo[]> => {
method: "get", const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
path: `${apiPrefix}/metrics/providers`,
handler: () => { return {
const providers: MetricProviderInfo[] = []; method: "get",
path: `${apiPrefix}/metrics/providers`,
for (const { name, id, isConfigurable } of PrometheusProviderRegistry.getInstance().providers.values()) { handler: () => {
providers.push({ name, id, isConfigurable }); const providers: MetricProviderInfo[] = [];
}
return { response: providers }; for (const {
}, name,
}), id,
isConfigurable,
} of prometheusProviderRegistry.providers.values()) {
providers.push({ name, id, isConfigurable });
}
return { response: providers };
},
};
},
injectionToken: routeInjectionToken, injectionToken: routeInjectionToken,
}); });

View File

@ -0,0 +1,32 @@
/**
* 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 exitAppInjectable from "./app-paths/get-electron-app-path/electron-app/exit-app.injectable";
import clusterManagerInjectable from "./cluster-manager.injectable";
import windowManagerInjectable from "./window-manager.injectable";
import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable";
import loggerInjectable from "../common/logger.injectable";
const stopServicesAndExitAppInjectable = getInjectable({
id: "stop-services-and-exit-app",
instantiate: (di) => {
const exitApp = di.inject(exitAppInjectable);
const windowManager = di.inject(windowManagerInjectable);
const clusterManager = di.inject(clusterManagerInjectable);
const appEventBus = di.inject(appEventBusInjectable);
const logger = di.inject(loggerInjectable);
return () => {
appEventBus.emit({ name: "service", action: "close" });
windowManager.hide();
clusterManager.stop();
logger.info("SERVICE:QUIT");
setTimeout(exitApp, 1000);
};
},
});
export default stopServicesAndExitAppInjectable;

View File

@ -4,15 +4,15 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import lensProxyPortNumberStateInjectable from "./lens-proxy-port-number-state.injectable";
const windowManagerInjectable = getInjectable({ const windowManagerInjectable = getInjectable({
id: "window-manager", id: "window-manager",
instantiate: () => { instantiate: (di) =>
WindowManager.resetInstance(); new WindowManager({
lensProxyPortNumberState: di.inject(lensProxyPortNumberStateInjectable),
return WindowManager.createInstance(); }),
},
causesSideEffects: true, causesSideEffects: true,
}); });

View File

@ -9,13 +9,12 @@ import { app, BrowserWindow, dialog, ipcMain, webContents } from "electron";
import windowStateKeeper from "electron-window-state"; import windowStateKeeper from "electron-window-state";
import { appEventBus } from "../common/app-event-bus/event-bus"; import { appEventBus } from "../common/app-event-bus/event-bus";
import { ipcMainOn } from "../common/ipc"; import { ipcMainOn } from "../common/ipc";
import { delay, iter, Singleton, openBrowser } from "../common/utils"; import { delay, iter, openBrowser } from "../common/utils";
import type { ClusterFrameInfo } from "../common/cluster-frames"; import type { ClusterFrameInfo } from "../common/cluster-frames";
import { clusterFrameMap } from "../common/cluster-frames"; import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames";
import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
import logger from "./logger"; import logger from "./logger";
import { isMac, productName } from "../common/vars"; import { isMac, productName } from "../common/vars";
import { LensProxy } from "./lens-proxy";
import { bundledExtensionsLoaded } from "../common/ipc/extension-handling"; import { bundledExtensionsLoaded } from "../common/ipc/extension-handling";
function isHideable(window: BrowserWindow | null): boolean { function isHideable(window: BrowserWindow | null): boolean {
@ -28,9 +27,11 @@ export interface SendToViewArgs {
data?: any[]; data?: any[];
} }
export class WindowManager extends Singleton { interface Dependencies {
public mainContentUrl = `http://localhost:${LensProxy.getInstance().port}`; lensProxyPortNumberState: { get: () => number };
}
export class WindowManager {
protected mainWindow: BrowserWindow; protected mainWindow: BrowserWindow;
protected splashWindow: BrowserWindow; protected splashWindow: BrowserWindow;
protected windowState: windowStateKeeper.State; protected windowState: windowStateKeeper.State;
@ -38,12 +39,15 @@ export class WindowManager extends Singleton {
@observable activeClusterId: ClusterId; @observable activeClusterId: ClusterId;
constructor() { constructor(private dependencies: Dependencies) {
super();
makeObservable(this); makeObservable(this);
this.bindEvents(); this.bindEvents();
} }
get mainContentUrl() {
return `http://localhost:${this.dependencies.lensProxyPortNumberState.get()}`;
}
private async initMainWindow(showSplash: boolean) { private async initMainWindow(showSplash: boolean) {
// Manage main window size and position with state persistence // Manage main window size and position with state persistence
if (!this.windowState) { if (!this.windowState) {

View File

@ -14,11 +14,15 @@ const createClusterInjectable = getInjectable({
instantiate: (di) => { instantiate: (di) => {
const dependencies: ClusterDependencies = { const dependencies: ClusterDependencies = {
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable), directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
// TODO: Dismantle wrong abstraction
createKubeconfigManager: () => { throw new Error("Tried to access back-end feature in front-end."); }, createKubeconfigManager: () => { throw new Error("Tried to access back-end feature in front-end."); },
createKubectl: () => { throw new Error("Tried to access back-end feature in front-end.");}, createKubectl: () => { throw new Error("Tried to access back-end feature in front-end.");},
createContextHandler: () => { throw new Error("Tried to access back-end feature in front-end."); }, createContextHandler: () => { throw new Error("Tried to access back-end feature in front-end."); },
createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); }, createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); },
createListNamespaces: () => { throw new Error("Tried to access back-end feature in front-end."); }, createListNamespaces: () => { throw new Error("Tried to access back-end feature in front-end."); },
detectorRegistry: null,
createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); },
}; };
return (model) => new Cluster(dependencies, model); return (model) => new Cluster(dependencies, model);