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 { apiResourceRecord, apiResources } from "../rbac";
import logger from "../../main/logger";
import { VersionDetector } from "../../main/cluster-detectors/version-detector";
import { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
import plimit from "p-limit";
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types";
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
@ -33,6 +33,8 @@ export interface ClusterDependencies {
createKubectl: (clusterVersion: string) => Kubectl;
createAuthorizationReview: (config: KubeConfig) => CanI;
createListNamespaces: (config: KubeConfig) => ListNamespaces;
detectorRegistry: DetectorRegistry;
createVersionDetector: (cluster: Cluster) => VersionDetector;
}
/**
@ -425,7 +427,7 @@ export class Cluster implements ClusterModel, ClusterState {
@action
async refreshMetadata() {
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;
this.metadata = Object.assign(existingMetadata, metadata);
@ -488,7 +490,7 @@ export class Cluster implements ClusterModel, ClusterState {
protected async getConnectionStatus(): Promise<ClusterStatus> {
try {
const versionDetector = new VersionDetector(this);
const versionDetector = this.dependencies.createVersionDetector(this);
const versionData = await versionDetector.detect();
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 { WindowManager } from "../main/window-manager";
import { catalogEntityRegistry } from "../main/catalog";
import type { CatalogEntity } from "../common/catalog";
import type { IObservableArray } from "mobx";
import type { MenuRegistration } from "../main/menu/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 {
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 {
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
*
*
* (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
* 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.
*
*
* @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
*/
terminalShellEnvModifier?: ShellEnvModifier;
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>) {

View File

@ -3,8 +3,17 @@
* 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) {
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 type { Cluster } from "../../common/cluster/cluster";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import prometheusProviderRegistryInjectable
from "../prometheus/prometheus-provider-registry.injectable";
jest.mock("electron", () => ({
app: {
@ -74,6 +76,7 @@ const clusterStub = {
describe("ContextHandler", () => {
let createContextHandler: (cluster: Cluster) => ContextHandler;
let prometheusProviderRegistry: PrometheusProviderRegistry;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
@ -84,13 +87,12 @@ describe("ContextHandler", () => {
di.override(createKubeAuthProxyInjectable, () => ({} as any));
createContextHandler = di.inject(createContextHandlerInjectable);
prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
PrometheusProviderRegistry.createInstance();
createContextHandler = di.inject(createContextHandlerInjectable);
});
afterEach(() => {
PrometheusProviderRegistry.resetInstance();
UserStore.resetInstance();
mockFs.restore();
});
@ -102,17 +104,16 @@ describe("ContextHandler", () => {
[0, 2],
[0, 3],
])("should throw from %d success(es) after %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
for (let i = 0; i < failures; i += 1) {
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) {
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
}
expect(() => {
@ -133,17 +134,16 @@ describe("ContextHandler", () => {
[2, 2],
[2, 3],
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
for (let i = 0; i < failures; i += 1) {
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) {
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
@ -164,17 +164,16 @@ describe("ContextHandler", () => {
[2, 2],
[2, 3],
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
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) {
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
@ -195,23 +194,22 @@ describe("ContextHandler", () => {
[2, 2],
[2, 3],
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
const beforeSuccesses = Math.floor(successes / 2);
const afterSuccesses = successes - beforeSuccesses;
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) {
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) {
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
@ -223,12 +221,11 @@ describe("ContextHandler", () => {
});
it("shouldn't pick the second provider of 2 success(es) after 1 failure(s)", async () => {
const reg = PrometheusProviderRegistry.getInstance();
let count = 0;
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure));
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure));
prometheusProviderRegistry.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
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 mockFs from "mock-fs";
import fs from "fs";
import { ClusterManager } from "../../cluster-manager";
import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
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 appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
import { runSetups } from "../../../common/setupable-injection-token/run-setups";
import clusterManagerInjectable from "../../cluster-manager.injectable";
jest.mock("electron", () => ({
app: {
@ -53,19 +53,17 @@ describe("kubeconfig-sync.source tests", () => {
computeDiff = computeDiffFor({
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
createCluster: di.inject(createClusterInjectionToken),
clusterManager: di.inject(clusterManagerInjectable),
});
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
di.inject(clusterStoreInjectable);
ClusterManager.createInstance();
});
afterEach(() => {
mockFs.restore();
ClusterManager.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 { KubeconfigSyncManager } from "./kubeconfig-sync-manager";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
import clusterManagerInjectable from "../../cluster-manager.injectable";
const kubeconfigSyncManagerInjectable = getInjectable({
id: "kubeconfig-sync-manager",
@ -13,6 +14,7 @@ const kubeconfigSyncManagerInjectable = getInjectable({
instantiate: (di) => new KubeconfigSyncManager({
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
createCluster: di.inject(createClusterInjectionToken),
clusterManager: di.inject(clusterManagerInjectable),
}),
});

View File

@ -53,6 +53,7 @@ const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; //
interface Dependencies {
directoryForKubeConfigs: string;
createCluster: (model: ClusterModel) => Cluster;
clusterManager: ClusterManager;
}
const kubeConfigSyncName = "lens:kube-sync";
@ -164,7 +165,7 @@ type RootSourceValue = [Cluster, CatalogEntity];
type RootSource = ObservableMap<string, RootSourceValue>;
// 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(() => {
try {
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
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
ClusterManager.getInstance().deleting.delete(value[0].id);
clusterManager.deleting.delete(value[0].id);
value[0].disconnect();
source.delete(contextName);

View File

@ -4,7 +4,7 @@
*/
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 { catalogEntityRegistry } from "../catalog";
import got from "got";
@ -29,8 +29,7 @@ async function validateLink(link: WebLink) {
}
export function syncWeblinks() {
const weblinkStore = WeblinkStore.getInstance();
export function syncWeblinks(weblinkStore: WeblinkStore) {
const webLinkEntities = observable.map<string, [WebLink, Disposer]>();
function periodicallyCheckLink(link: WebLink): Disposer {

View File

@ -5,7 +5,7 @@
import type { RequestPromiseOptions } from "request-promise-native";
import type { Cluster } from "../../common/cluster/cluster";
import { k8sRequest } from "../k8s-request";
import type { K8sRequest } from "../k8s-request.injectable";
export interface ClusterDetectionResult {
value: string | number | boolean;
@ -15,7 +15,7 @@ export interface ClusterDetectionResult {
export class BaseClusterDetector {
key: string;
constructor(public cluster: Cluster) {
constructor(public cluster: Cluster, private _k8sRequest: K8sRequest) {
}
detect(): Promise<ClusterDetectionResult> {
@ -23,6 +23,6 @@ export class BaseClusterDetector {
}
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 type { ClusterMetadata } from "../../common/cluster-types";
import { Singleton } from "../../common/utils";
import type { Cluster } from "../../common/cluster/cluster";
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 });
add(detectorClass: typeof BaseClusterDetector): this {
@ -22,7 +28,7 @@ export class DetectorRegistry extends Singleton {
const results: { [key: string]: ClusterDetectionResult } = {};
for (const detectorClass of this.registry) {
const detector = new detectorClass(cluster);
const detector = new detectorClass(cluster, this.dependencies.k8sRequest);
try {
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 logger from "./logger";
import { apiKubePrefix } from "../common/vars";
import { getClusterIdFromHost, Singleton } from "../common/utils";
import { getClusterIdFromHost } from "../common/utils";
import { catalogEntityRegistry } from "./catalog";
import type { KubernetesClusterPrometheusMetrics } from "../common/catalog-entities/kubernetes-cluster";
import { KubernetesCluster, LensKubernetesClusterStatus } from "../common/catalog-entities/kubernetes-cluster";
import { ipcMainOn } from "../common/ipc";
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";
const logPrefix = "[CLUSTER-MANAGER]:";
const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus));
export class ClusterManager extends Singleton {
private store = ClusterStore.getInstance();
interface Dependencies {
store: ClusterStore;
}
export class ClusterManager {
deleting = observable.set<ClusterId>();
@observable visibleCluster: ClusterId | undefined = undefined;
constructor() {
super();
constructor(private dependencies: Dependencies) {
makeObservable(this);
}
init = once(() => {
// reacting to every cluster's state change and total amount of items
reaction(
() => this.store.clustersList.map(c => c.getState()),
() => this.updateCatalog(this.store.clustersList),
() => this.dependencies.store.clustersList.map(c => c.getState()),
() => this.updateCatalog(this.dependencies.store.clustersList),
{ fireImmediately: false },
);
// reacting to every cluster's preferences change and total amount of items
reaction(
() => this.store.clustersList.map(c => toJS(c.preferences)),
() => this.updateCatalog(this.store.clustersList),
() => this.dependencies.store.clustersList.map(c => toJS(c.preferences)),
() => this.updateCatalog(this.dependencies.store.clustersList),
{ fireImmediately: false },
);
@ -180,7 +182,7 @@ export class ClusterManager extends Singleton {
@action
protected syncClustersFromCatalog(entities: KubernetesCluster[]) {
for (const entity of entities) {
const cluster = this.store.getById(entity.getId());
const cluster = this.dependencies.store.getById(entity.getId());
if (!cluster) {
const model = {
@ -195,7 +197,7 @@ export class ClusterManager extends Singleton {
* Add the bare minimum of data to ClusterStore. And especially no
* preferences, as those might be configured by the entity's source
*/
this.store.addCluster(model);
this.dependencies.store.addCluster(model);
} catch (error) {
if (error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) {
logger.warn(`${logPrefix} kubeconfig file disappeared`, model);
@ -234,7 +236,7 @@ export class ClusterManager extends Singleton {
protected onNetworkOffline = () => {
logger.info(`${logPrefix} network is offline`);
this.store.clustersList.forEach((cluster) => {
this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.online = false;
cluster.accessible = false;
@ -245,7 +247,7 @@ export class ClusterManager extends Singleton {
protected onNetworkOnline = () => {
logger.info(`${logPrefix} network is online`);
this.store.clustersList.forEach((cluster) => {
this.dependencies.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.refreshConnectionStatus().catch((e) => e);
}
@ -253,16 +255,16 @@ export class ClusterManager extends Singleton {
};
stop() {
this.store.clusters.forEach((cluster: Cluster) => {
this.dependencies.store.clusters.forEach((cluster: Cluster) => {
cluster.disconnect();
});
}
getClusterForRequest(req: http.IncomingMessage): Cluster {
getClusterForRequest = (req: http.IncomingMessage): Cluster => {
// lens-server is connecting to 127.0.0.1:<port>/<uid>
if (req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1];
const cluster = this.store.getById(clusterId);
const cluster = this.dependencies.store.getById(clusterId);
if (cluster) {
// 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 this.store.getById(getClusterIdFromHost(req.headers.host));
}
return this.dependencies.store.getById(getClusterIdFromHost(req.headers.host));
};
}
export function catalogEntityFromCluster(cluster: Cluster) {

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { PrometheusProvider, PrometheusService } from "../prometheus/provider-registry";
import { PrometheusProviderRegistry } from "../prometheus/provider-registry";
import type { PrometheusProvider, PrometheusService, PrometheusProviderRegistry } from "../prometheus/provider-registry";
import type { ClusterPrometheusPreferences } from "../../common/cluster-types";
import type { Cluster } from "../../common/cluster/cluster";
import type httpProxy from "http-proxy";
@ -30,6 +29,7 @@ interface PrometheusServicePreferences {
interface Dependencies {
createKubeAuthProxy: CreateKubeAuthProxy;
authProxyCa: string;
prometheusProviderRegistry: PrometheusProviderRegistry;
}
export class ContextHandler {
@ -67,11 +67,11 @@ export class ContextHandler {
this.prometheusProvider = service.id;
}
return PrometheusProviderRegistry.getInstance().getByKind(this.prometheusProvider);
return this.dependencies.prometheusProviderRegistry.getByKind(this.prometheusProvider);
}
protected listPotentialProviders(): PrometheusProvider[] {
const registry = PrometheusProviderRegistry.getInstance();
const registry = this.dependencies.prometheusProviderRegistry;
const provider = this.prometheusProvider && registry.getByKind(this.prometheusProvider);
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 { getKubeAuthProxyCertificate } from "../kube-auth-proxy/get-kube-auth-proxy-certificate";
import URLParse from "url-parse";
import prometheusProviderRegistryInjectable from "../prometheus/prometheus-provider-registry.injectable";
const createContextHandlerInjectable = getInjectable({
id: "create-context-handler",
@ -20,6 +21,7 @@ const createContextHandlerInjectable = getInjectable({
const dependencies = {
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
authProxyCa: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate).cert,
prometheusProviderRegistry: di.inject(prometheusProviderRegistryInjectable),
};
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 authorizationReviewInjectable from "../../common/cluster/authorization-review.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({
id: "create-cluster",
@ -24,6 +26,8 @@ const createClusterInjectable = getInjectable({
createContextHandler: di.inject(createContextHandlerInjectable),
createAuthorizationReview: di.inject(authorizationReviewInjectable),
createListNamespaces: di.inject(listNamespacesInjectable),
detectorRegistry: di.inject(detectorRegistryInjectable),
createVersionDetector: di.inject(createVersionDetectorInjectable),
};
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 directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
import { KubeconfigManager } from "./kubeconfig-manager";
import lensProxyPortNumberStateInjectable from "../lens-proxy-port-number-state.injectable";
export interface KubeConfigManagerInstantiationParameter {
cluster: Cluster;
@ -17,6 +18,7 @@ const createKubeconfigManagerInjectable = getInjectable({
instantiate: (di) => {
const dependencies = {
directoryForTemp: di.inject(directoryForTempInjectable),
lensProxyPortNumberState: di.inject(lensProxyPortNumberStateInjectable),
};
return (cluster: Cluster) => new KubeconfigManager(dependencies, cluster);

View File

@ -10,10 +10,10 @@ import path from "path";
import fs from "fs-extra";
import { dumpConfigYaml } from "../../common/kube-helpers";
import logger from "../logger";
import { LensProxy } from "../lens-proxy";
interface Dependencies {
directoryForTemp: string;
lensProxyPortNumberState: { get: () => number };
}
export class KubeconfigManager {
@ -79,7 +79,7 @@ export class KubeconfigManager {
}
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 { ContextHandler } from "./context-handler/context-handler";
import logger from "./logger";
import { Singleton } from "../common/utils";
import type { Cluster } from "../common/cluster/cluster";
import type { ProxyApiRequestArgs } from "./proxy-functions";
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;
export interface LensProxyFunctions {
interface Dependencies {
getClusterForRequest: GetClusterForRequest;
shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
router: Router;
proxy: httpProxy;
lensProxyPortNumberState: { set: (portNumber: number) => void };
}
const watchParam = "watch";
@ -52,21 +54,14 @@ const disallowedPorts = new Set([
10080,
]);
export class LensProxy extends Singleton {
export class LensProxy {
protected origin: string;
protected proxyServer: http.Server;
protected closed = false;
protected retryCounters = new Map<string, number>();
protected getClusterForRequest: GetClusterForRequest;
public port: number;
constructor(protected router: Router, protected proxy: httpProxy, { shellApiRequest, kubeApiUpgradeRequest, getClusterForRequest }: LensProxyFunctions) {
super();
this.configureProxy(proxy);
this.getClusterForRequest = getClusterForRequest;
constructor(private dependencies: Dependencies) {
this.configureProxy(dependencies.proxy);
this.proxyServer = spdy.createServer({
spdy: {
@ -79,14 +74,14 @@ export class LensProxy extends Singleton {
this.proxyServer
.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
const cluster = getClusterForRequest(req);
const cluster = dependencies.getClusterForRequest(req);
if (!cluster) {
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
socket.destroy();
} else {
const isInternal = req.url.startsWith(`${apiPrefix}?`);
const reqHandler = isInternal ? shellApiRequest : kubeApiUpgradeRequest;
const reqHandler = isInternal ? dependencies.shellApiRequest : dependencies.kubeApiUpgradeRequest;
(async () => reqHandler({ req, socket, head, cluster }))()
.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;
this.dependencies.lensProxyPortNumberState.set(port);
logger.info(`[LENS-PROXY]: Proxy server has started at ${address}:${port}`);
this.proxyServer.on("error", (error) => {
logger.info(`[LENS-PROXY]: Subsequent error: ${error}`);
});
this.port = port;
appEventBus.emit({ name: "lens-proxy", action: "listen", params: { port }});
resolve(port);
})
@ -228,15 +224,15 @@ export class LensProxy extends Singleton {
}
protected async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
const cluster = this.getClusterForRequest(req);
const cluster = this.dependencies.getClusterForRequest(req);
if (cluster) {
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
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 { inspect } from "util";
import { Singleton } from "../../common/utils";
export interface PrometheusService {
id: string;
@ -65,7 +64,7 @@ export abstract class PrometheusProvider {
}
}
export class PrometheusProviderRegistry extends Singleton {
export class PrometheusProviderRegistry {
public providers = new Map<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 { LensProtocolRouterMain } from "./lens-protocol-router-main";
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
import windowManagerInjectable from "../../window-manager.injectable";
const lensProtocolRouterMainInjectable = getInjectable({
id: "lens-protocol-router-main",
@ -14,6 +15,7 @@ const lensProtocolRouterMainInjectable = getInjectable({
new LensProtocolRouterMain({
extensionLoader: di.inject(extensionLoaderInjectable),
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 { ProtocolHandlerInvalid } from "../../../common/protocol-handler";
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 { ExtensionsStore } from "../../../extensions/extensions-store/extensions-store";
@ -40,6 +40,7 @@ function checkHost<Query>(url: URLParse<Query>): boolean {
interface Dependencies {
extensionLoader: ExtensionLoader;
extensionsStore: ExtensionsStore;
windowManager: WindowManager;
}
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);
}
WindowManager.getInstance(false)?.ensureMainWindow().catch(noop);
this.dependencies.windowManager.ensureMainWindow().catch(noop);
const routeInternally = checkHost(url);
logger.info(`${proto.LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`);

View File

@ -5,9 +5,9 @@
import { app } from "electron";
const switchValue = app.commandLine.getSwitchValue("proxy-server");
export function mangleProxyEnv() {
const switchValue = app.commandLine.getSwitchValue("proxy-server");
let httpsProxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || "";
delete process.env.HTTPS_PROXY;
@ -20,4 +20,8 @@ export function mangleProxyEnv() {
if (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 { shellApiRequest } from "./shell-api-request";
import createShellSessionInjectable from "../../shell-session/create-shell-session.injectable";
import shellRequestAuthenticatorInjectable
from "./shell-request-authenticator/shell-request-authenticator.injectable";
import shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable";
import clusterManagerInjectable from "../../cluster-manager.injectable";
const shellApiRequestInjectable = getInjectable({
id: "shell-api-request",
@ -14,6 +14,7 @@ const shellApiRequestInjectable = getInjectable({
instantiate: (di) => shellApiRequest({
createShellSession: di.inject(createShellSessionInjectable),
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 { Server as WebSocketServer } from "ws";
import type { ProxyApiRequestArgs } from "../types";
import { ClusterManager } from "../../cluster-manager";
import type { ClusterManager } from "../../cluster-manager";
import URLParse from "url-parse";
import type { Cluster } from "../../../common/cluster/cluster";
import type { ClusterId } from "../../../common/cluster-types";
@ -21,10 +21,12 @@ interface Dependencies {
tabId: string;
nodeName?: string;
}) => { open: () => Promise<void> };
clusterManager: ClusterManager;
}
export const shellApiRequest = ({ createShellSession, authenticateRequest }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => {
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
export const shellApiRequest = ({ createShellSession, authenticateRequest, clusterManager }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => {
const cluster = clusterManager.getClusterForRequest(req);
const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true);
if (!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 logger from "../../logger";
import type { Cluster } from "../../../common/cluster/cluster";
import { getMetrics } from "../../k8s-request";
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.
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 loaders = new Map<string, Promise<any>>();
@ -41,9 +42,11 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
}
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 prometheusMetadata: ClusterPrometheusMetadata = {};
@ -97,11 +100,15 @@ const addMetricsRoute = async ({ cluster, payload, query }: LensApiRequest) => {
const addMetricsRouteInjectable = getInjectable({
id: "add-metrics-route",
instantiate: (): Route<any> => ({
method: "post",
path: `${apiPrefix}/metrics`,
handler: addMetricsRoute,
}),
instantiate: (di): Route<any> => {
const getMetrics = di.inject(getMetricsInjectable);
return {
method: "post",
path: `${apiPrefix}/metrics`,
handler: addMetricsRoute(getMetrics),
};
},
injectionToken: routeInjectionToken,
});

View File

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

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 { WindowManager } from "./window-manager";
import lensProxyPortNumberStateInjectable from "./lens-proxy-port-number-state.injectable";
const windowManagerInjectable = getInjectable({
id: "window-manager",
instantiate: () => {
WindowManager.resetInstance();
return WindowManager.createInstance();
},
instantiate: (di) =>
new WindowManager({
lensProxyPortNumberState: di.inject(lensProxyPortNumberStateInjectable),
}),
causesSideEffects: true,
});

View File

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

View File

@ -14,11 +14,15 @@ const createClusterInjectable = getInjectable({
instantiate: (di) => {
const dependencies: ClusterDependencies = {
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
// TODO: Dismantle wrong abstraction
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.");},
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."); },
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);