From c615b77ac5858a40e32010bed080f95b5e31ac5c Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 21 Dec 2022 13:05:32 -0500 Subject: [PATCH] Get loading the main page to work Signed-off-by: Sebastian Malton --- package.json | 7 +- src/common/cluster-types.ts | 2 +- src/common/cluster/cluster.ts | 20 +- .../create-kube-api-for-cluster.injectable.ts | 3 +- ...te-kube-json-api-for-cluster.injectable.ts | 5 +- .../__tests__/cluster-id-url-parsing.test.ts | 30 --- src/common/utils/cluster-id-url-parsing.ts | 27 --- src/common/utils/index.ts | 1 - src/common/utils/objects.ts | 3 + src/common/utils/type-narrowing.ts | 2 +- src/common/vars/auth-header.ts | 1 + .../vars/window-file-path.injectable.ts | 21 ++ src/extensions/lens-renderer-extension.ts | 2 +- .../base-cluster-detector.ts | 20 +- ...luster-distribution-detector.injectable.ts | 141 +++++++++++++ .../cluster-id-detector.injectable.ts | 43 ++++ .../cluster-detectors/cluster-id-detector.ts | 31 --- .../cluster-last-seen-detector.injectable.ts | 32 +++ ...cluster-nodes-count-detector.injectable.ts | 40 ++++ .../cluster-version-detector.injectable.ts | 28 +++ .../create-version-detector.injectable.ts | 21 -- .../detect-cluster-metadata.injectable.ts | 51 +++++ .../detector-registry.injectable.ts | 16 -- .../cluster-detectors/detector-registry.ts | 55 ----- .../distribution-detector.ts | 189 ------------------ .../cluster-detectors/last-seen-detector.ts | 19 -- .../cluster-detectors/nodes-count-detector.ts | 24 --- .../request-cluster-version.injectable.ts | 22 ++ .../cluster-detectors/version-detector.ts | 23 --- src/main/cluster/manager.ts | 25 +-- .../create-cluster.injectable.ts | 8 +- src/main/get-metrics.injectable.ts | 13 +- src/main/k8s-request.injectable.ts | 43 ++-- .../get-cluster-for-request.injectable.ts | 46 +++++ src/main/lens-proxy/lens-proxy.injectable.ts | 4 +- src/main/lens-proxy/lens-proxy.ts | 12 +- .../kube-api-upgrade-request.ts | 6 +- .../shell-api-request.injectable.ts | 36 +++- .../shell-api-request/shell-api-request.ts | 36 ---- .../create-application-window.injectable.ts | 6 +- .../setup-detector-registry.injectable.ts | 36 ---- .../get-tray-icon-path.injectable.ts | 4 +- src/renderer/bootstrap.tsx | 1 + .../hosted-cluster-id.injectable.ts | 14 +- .../cluster/create-cluster.injectable.ts | 6 +- .../+config-secrets/add-secret-dialog.tsx | 2 +- .../cluster-frame-handler.injectable.ts | 2 + .../cluster-manager/cluster-frame-handler.ts | 5 +- .../kube-object-list-layout.tsx | 5 +- src/renderer/k8s/api-kube.injectable.ts | 7 +- .../extension-page-parameters.injectable.ts | 13 +- ...et-extension-page-parameters.injectable.ts | 2 +- webpack/vars.ts | 2 +- yarn.lock | 50 +---- 54 files changed, 592 insertions(+), 671 deletions(-) delete mode 100644 src/common/utils/__tests__/cluster-id-url-parsing.test.ts delete mode 100644 src/common/utils/cluster-id-url-parsing.ts create mode 100644 src/common/vars/window-file-path.injectable.ts create mode 100644 src/main/cluster-detectors/cluster-distribution-detector.injectable.ts create mode 100644 src/main/cluster-detectors/cluster-id-detector.injectable.ts delete mode 100644 src/main/cluster-detectors/cluster-id-detector.ts create mode 100644 src/main/cluster-detectors/cluster-last-seen-detector.injectable.ts create mode 100644 src/main/cluster-detectors/cluster-nodes-count-detector.injectable.ts create mode 100644 src/main/cluster-detectors/cluster-version-detector.injectable.ts delete mode 100644 src/main/cluster-detectors/create-version-detector.injectable.ts create mode 100644 src/main/cluster-detectors/detect-cluster-metadata.injectable.ts delete mode 100644 src/main/cluster-detectors/detector-registry.injectable.ts delete mode 100644 src/main/cluster-detectors/detector-registry.ts delete mode 100644 src/main/cluster-detectors/distribution-detector.ts delete mode 100644 src/main/cluster-detectors/last-seen-detector.ts delete mode 100644 src/main/cluster-detectors/nodes-count-detector.ts create mode 100644 src/main/cluster-detectors/request-cluster-version.injectable.ts delete mode 100644 src/main/cluster-detectors/version-detector.ts create mode 100644 src/main/lens-proxy/get-cluster-for-request.injectable.ts delete mode 100644 src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts delete mode 100644 src/main/start-main-application/runnables/setup-detector-registry.injectable.ts diff --git a/package.json b/package.json index 1679398829..e80a5b690d 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "debug-build": "concurrently yarn:compile:main yarn:compile:extension-types", "dev-run": "nodemon --watch ./static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"", "dev:main": "yarn run compile:main --watch --progress", - "dev:renderer": "yarn run ts-node webpack/dev-server.ts", + "dev:renderer": "yarn run compile:renderer --watch --progress", "compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile:main": "yarn run webpack --config webpack/main.ts", "compile:renderer": "yarn run webpack --config webpack/renderer.ts", @@ -252,8 +252,6 @@ "react-router": "^5.3.4", "react-virtualized-auto-sizer": "^1.0.7", "readable-stream": "^3.6.0", - "request": "^2.88.2", - "request-promise-native": "^1.0.9", "rfc6902": "^5.0.1", "selfsigned": "^2.1.1", "semver": "^7.3.8", @@ -320,8 +318,6 @@ "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", "@types/readable-stream": "^2.3.13", - "@types/request": "^2.48.7", - "@types/request-promise-native": "^1.0.18", "@types/semver": "^7.3.13", "@types/sharp": "^0.31.0", "@types/tar": "^6.1.3", @@ -407,7 +403,6 @@ "typescript-plugin-css-modules": "^3.4.0", "webpack": "^5.75.0", "webpack-cli": "^4.9.2", - "webpack-dev-server": "^4.11.1", "webpack-node-externals": "^3.0.0", "xterm": "^4.19.0", "xterm-addon-fit": "^0.5.0" diff --git a/src/common/cluster-types.ts b/src/common/cluster-types.ts index 0cd447f0e2..edf9c87e73 100644 --- a/src/common/cluster-types.ts +++ b/src/common/cluster-types.ts @@ -8,7 +8,7 @@ import Joi from "joi"; /** * JSON serializable metadata type */ -export type ClusterMetadata = Record; +export type ClusterMetadata = Partial>; /** * Metadata for cluster's prometheus settings diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index d60ae07827..a1353474b1 100644 --- a/src/common/cluster/cluster.ts +++ b/src/common/cluster/cluster.ts @@ -11,13 +11,10 @@ import type { Kubectl } from "../../main/kubectl/kubectl"; import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager"; import type { KubeApiResource, KubeApiResourceDescriptor } from "../rbac"; import { formatKubeApiResource } from "../rbac"; -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, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types"; import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types"; import { disposer, isDefined, isRequestError, toJS } from "../utils"; -import type { Response } from "request"; import { clusterListNamespaceForbiddenChannel } from "../ipc/cluster"; import type { CreateAuthorizationReview } from "./create-authorization-review.injectable"; import type { ListNamespaces } from "./list-namespaces.injectable"; @@ -27,11 +24,14 @@ import type { BroadcastMessage } from "../ipc/broadcast-message.injectable"; import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable"; import type { CanListResource, RequestNamespaceListPermissions, RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable"; import type { RequestApiResources } from "../../main/cluster/request-api-resources.injectable"; +import type { ClusterMetadataDetector } from "../../main/cluster-detectors/base-cluster-detector"; +import type { DetectClusterMetadata } from "../../main/cluster-detectors/detect-cluster-metadata.injectable"; export interface ClusterDependencies { readonly directoryForKubeConfigs: string; readonly logger: Logger; - readonly detectorRegistry: DetectorRegistry; + readonly clusterVersionDetector: ClusterMetadataDetector; + detectClusterMetadata: DetectClusterMetadata; createKubeconfigManager: (cluster: Cluster) => KubeconfigManager; createContextHandler: (cluster: Cluster) => ClusterContextHandler; createKubectl: (clusterVersion: string) => Kubectl; @@ -39,7 +39,6 @@ export interface ClusterDependencies { requestApiResources: RequestApiResources; requestNamespaceListPermissionsFor: RequestNamespaceListPermissionsFor; createListNamespaces: (config: KubeConfig) => ListNamespaces; - createVersionDetector: (cluster: Cluster) => VersionDetector; broadcastMessage: BroadcastMessage; loadConfigfromFile: LoadConfigfromFile; } @@ -455,7 +454,7 @@ export class Cluster implements ClusterModel { */ async refreshMetadata() { this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); - const metadata = await this.dependencies.detectorRegistry.detectForCluster(this); + const metadata = await this.dependencies.detectClusterMetadata(this); const existingMetadata = this.metadata; this.metadata = Object.assign(existingMetadata, metadata); @@ -470,6 +469,7 @@ export class Cluster implements ClusterModel { const canI = this.dependencies.createAuthorizationReview(proxyConfig); const requestNamespaceListPermissions = this.dependencies.requestNamespaceListPermissionsFor(proxyConfig); + console.log("before this.isAdmin"); this.isAdmin = await canI({ namespace: "kube-system", resource: "*", @@ -522,13 +522,13 @@ export class Cluster implements ClusterModel { protected async getConnectionStatus(): Promise { try { - const versionDetector = this.dependencies.createVersionDetector(this); - const versionData = await versionDetector.detect(); + const versionData = await this.dependencies.clusterVersionDetector.detect(this); - this.metadata.version = versionData.value; + this.metadata.version = versionData?.value; return ClusterStatus.AccessGranted; } catch (error) { + console.error(error); this.dependencies.logger.error(`[CLUSTER]: Failed to connect to "${this.contextName}": ${error}`); if (isRequestError(error)) { @@ -653,7 +653,7 @@ export class Cluster implements ClusterModel { const namespaceList = [ctx?.namespace].filter(isDefined); if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) { - const { response } = error as HttpError & { response: Response }; + const { response } = error as HttpError & { response: { body: unknown }}; this.dependencies.logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body }); this.dependencies.broadcastMessage(clusterListNamespaceForbiddenChannel, this.id); diff --git a/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts b/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts index eec3752e3a..7bf0c8dc38 100644 --- a/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts +++ b/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts @@ -4,6 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { apiKubePrefix } from "../vars"; +import { lensClusterIdHeader } from "../vars/auth-header"; import isDevelopmentInjectable from "../vars/is-development.injectable"; import apiBaseInjectable from "./api-base.injectable"; import createKubeJsonApiInjectable from "./create-kube-json-api.injectable"; @@ -51,7 +52,7 @@ const createKubeApiForClusterInjectable = getInjectable({ debug: isDevelopment, }, { headers: { - "Host": `${cluster.metadata.uid}.localhost:${new URL(apiBase.config.serverAddress).port}`, + [lensClusterIdHeader]: cluster.metadata.uid, }, }, ), diff --git a/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts b/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts index 799b0bf963..d2da92992d 100644 --- a/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts +++ b/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts @@ -4,8 +4,9 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { apiKubePrefix } from "../vars"; +import { lensClusterIdHeader } from "../vars/auth-header"; import isDebuggingInjectable from "../vars/is-debugging.injectable"; -import { apiBaseHostHeaderInjectionToken, apiBaseServerAddressInjectionToken } from "./api-base-configs"; +import { apiBaseServerAddressInjectionToken } from "./api-base-configs"; import createKubeJsonApiInjectable from "./create-kube-json-api.injectable"; import type { KubeJsonApi } from "./kube-json-api"; @@ -25,7 +26,7 @@ const createKubeJsonApiForClusterInjectable = getInjectable({ }, { headers: { - "Host": `${clusterId}.${di.inject(apiBaseHostHeaderInjectionToken)}`, + [lensClusterIdHeader]: clusterId, }, }, ); diff --git a/src/common/utils/__tests__/cluster-id-url-parsing.test.ts b/src/common/utils/__tests__/cluster-id-url-parsing.test.ts deleted file mode 100644 index 9f60eff8ac..0000000000 --- a/src/common/utils/__tests__/cluster-id-url-parsing.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { getClusterIdFromHost } from "../cluster-id-url-parsing"; - -describe("getClusterIdFromHost", () => { - const clusterFakeId = "fe540901-0bd6-4f6c-b472-bce1559d7c4a"; - - it("should return undefined for non cluster frame hosts", () => { - expect(getClusterIdFromHost("localhost:45345")).toBeUndefined(); - }); - - it("should return ClusterId for cluster frame hosts", () => { - expect(getClusterIdFromHost(`${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - }); - - it("should return ClusterId for cluster frame hosts with additional subdomains", () => { - expect(getClusterIdFromHost(`abc.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.ghi.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.ghi.jkl.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.yz.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId); - }); -}); diff --git a/src/common/utils/cluster-id-url-parsing.ts b/src/common/utils/cluster-id-url-parsing.ts deleted file mode 100644 index cb02f36484..0000000000 --- a/src/common/utils/cluster-id-url-parsing.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { ClusterId } from "../cluster-types"; - -/** - * Grab the `ClusterId` out of a host that was generated by `getClusterFrameUrl`, or nothing - * @param host The host section of a URL - * @returns The `ClusterId` part of the host, or `undefined` - */ -export function getClusterIdFromHost(host: string): ClusterId | undefined { - // e.g host == "%clusterId.localhost:45345" - const subDomains = host.split(":")[0].split("."); - - return subDomains.slice(-2, -1)[0]; // ClusterId or undefined -} - -/** - * Get the OpenLens backend routing host for a given `ClusterId` - * @param clusterId The ID to put in front of the current host - * @returns a new URL host section - */ -export function getClusterFrameUrl(clusterId: ClusterId) { - return `//${clusterId}.${location.host}`; -} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 4857d04418..33a0140b36 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -6,7 +6,6 @@ export * from "./abort-controller"; export * from "./autobind"; export * from "./camelCase"; -export * from "./cluster-id-url-parsing"; export * from "./collection-functions"; export * from "./convertCpu"; export * from "./convertMemory"; diff --git a/src/common/utils/objects.ts b/src/common/utils/objects.ts index 9b6355d3e7..a2dd08080a 100644 --- a/src/common/utils/objects.ts +++ b/src/common/utils/objects.ts @@ -7,6 +7,9 @@ * A better typed version of `Object.fromEntries` where the keys are known to * be a specific subset */ +export function fromEntries(entries: Iterable): Partial>; +export function fromEntries(entries: Iterable): Record; + export function fromEntries(entries: Iterable): Record { return Object.fromEntries(entries) as Record; } diff --git a/src/common/utils/type-narrowing.ts b/src/common/utils/type-narrowing.ts index 41552c3136..68d5265237 100644 --- a/src/common/utils/type-narrowing.ts +++ b/src/common/utils/type-narrowing.ts @@ -130,7 +130,7 @@ export function isFunction(val: unknown): val is (...args: unknown[]) => unknown /** * Checks if the value in the second position is non-nullable */ -export function hasDefinedTupleValue(pair: [K, V | undefined | null]): pair is [K, V] { +export function hasDefinedTupleValue(pair: readonly [K, V | undefined | null]): pair is [K, V] { return pair[1] != null; } diff --git a/src/common/vars/auth-header.ts b/src/common/vars/auth-header.ts index 39dd825f8b..bf6c75a646 100644 --- a/src/common/vars/auth-header.ts +++ b/src/common/vars/auth-header.ts @@ -7,3 +7,4 @@ * This is the header name that we use for request authentication */ export const lensAuthenticationHeader = "Authorization"; +export const lensClusterIdHeader = "Lens-Cluster-Id"; diff --git a/src/common/vars/window-file-path.injectable.ts b/src/common/vars/window-file-path.injectable.ts new file mode 100644 index 0000000000..663c5684d6 --- /dev/null +++ b/src/common/vars/window-file-path.injectable.ts @@ -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 joinPathsInjectable from "../path/join-paths.injectable"; +import appNameInjectable from "./app-name.injectable"; +import staticFilesDirectoryInjectable from "./static-files-directory.injectable"; + +const windowFilePathInjectable = getInjectable({ + id: "window-file-path", + instantiate: (di) => { + const joinPaths = di.inject(joinPathsInjectable); + const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable); + const applicationName = di.inject(appNameInjectable); + + return joinPaths(staticFilesDirectory, "build", `${applicationName}.html`); + }, +}); + +export default windowFilePathInjectable; diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 89cd5dbee5..62bd706361 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -79,7 +79,7 @@ export class LensRendererExtension extends LensExtension [ key, - normalizedParams[key].stringify(value), + normalizedParams[key]?.stringify(value), ]), fromPairs, ); diff --git a/src/main/cluster-detectors/base-cluster-detector.ts b/src/main/cluster-detectors/base-cluster-detector.ts index 1aca321dfe..f869789013 100644 --- a/src/main/cluster-detectors/base-cluster-detector.ts +++ b/src/main/cluster-detectors/base-cluster-detector.ts @@ -3,23 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { RequestPromiseOptions } from "request-promise-native"; +import { getInjectionToken } from "@ogre-tools/injectable"; import type { Cluster } from "../../common/cluster/cluster"; -import type { K8sRequest } from "../k8s-request.injectable"; export interface ClusterDetectionResult { value: string | number | boolean; accuracy: number; } -export abstract class BaseClusterDetector { - abstract readonly key: string; - - constructor(public readonly cluster: Cluster, private _k8sRequest: K8sRequest) {} - - abstract detect(): Promise; - - protected async k8sRequest(path: string, options: RequestPromiseOptions = {}): Promise { - return this._k8sRequest(this.cluster, path, options); - } +export interface ClusterMetadataDetector { + readonly key: string; + detect(cluster: Cluster): Promise; } + +export const clusterMetadataDetectorInjectionToken = getInjectionToken({ + id: "cluster-metadata-detector-token", +}); diff --git a/src/main/cluster-detectors/cluster-distribution-detector.injectable.ts b/src/main/cluster-detectors/cluster-distribution-detector.injectable.ts new file mode 100644 index 0000000000..3356988c1c --- /dev/null +++ b/src/main/cluster-detectors/cluster-distribution-detector.injectable.ts @@ -0,0 +1,141 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { clusterMetadataDetectorInjectionToken } from "./base-cluster-detector"; +import { ClusterMetadataKey } from "../../common/cluster-types"; +import { getInjectable } from "@ogre-tools/injectable"; +import k8SRequestInjectable from "../k8s-request.injectable"; +import type { Cluster } from "../../common/cluster/cluster"; +import requestClusterVersionInjectable from "./request-cluster-version.injectable"; + +const isGKE = (version: string) => version.includes("gke"); +const isEKS = (version: string) => version.includes("eks"); +const isIKS = (version: string) => version.includes("IKS"); +const isAKS = (cluster: Cluster) => cluster.apiUrl.includes("azmk8s.io"); +const isMirantis = (version: string) => version.includes("-mirantis-") || version.includes("-docker-"); +const isDigitalOcean = (cluster: Cluster) => cluster.apiUrl.endsWith("k8s.ondigitalocean.com"); +const isMinikube = (cluster: Cluster) => cluster.contextName.startsWith("minikube"); +const isMicrok8s = (cluster: Cluster) => cluster.contextName.startsWith("microk8s"); +const isKind = (cluster: Cluster) => cluster.contextName.startsWith("kubernetes-admin@kind-"); +const isDockerDesktop = (cluster: Cluster) => cluster.contextName === "docker-desktop"; +const isTke = (version: string) => version.includes("-tke."); +const isCustom = (version: string) => version.includes("+"); +const isVMWare = (version: string) => version.includes("+vmware"); +const isRke = (version: string) => version.includes("-rancher"); +const isRancherDesktop = (cluster: Cluster) => cluster.contextName === "rancher-desktop"; +const isK3s = (version: string) => version.includes("+k3s"); +const isK0s = (version: string) => version.includes("-k0s") || version.includes("+k0s"); +const isAlibaba = (version: string) => version.includes("-aliyun"); +const isHuawei = (version: string) => version.includes("-CCE"); + +const clusterDistributionDetectorInjectable = getInjectable({ + id: "cluster-distribution-detector", + instantiate: (di) => { + const k8sRequest = di.inject(k8SRequestInjectable); + const requestClusterVersion = di.inject(requestClusterVersionInjectable); + const isOpenshift = async (cluster: Cluster) => { + try { + const { paths = [] } = await k8sRequest(cluster, "") as { paths?: string[] }; + + return paths.includes("/apis/project.openshift.io"); + } catch (e) { + return false; + } + }; + + return { + key: ClusterMetadataKey.DISTRIBUTION, + detect: async (cluster) => { + const version = await requestClusterVersion(cluster); + + if (isRke(version)) { + return { value: "rke", accuracy: 80 }; + } + + if (isRancherDesktop(cluster)) { + return { value: "rancher-desktop", accuracy: 80 }; + } + + if (isK3s(version)) { + return { value: "k3s", accuracy: 80 }; + } + + if (isGKE(version)) { + return { value: "gke", accuracy: 80 }; + } + + if (isEKS(version)) { + return { value: "eks", accuracy: 80 }; + } + + if (isIKS(version)) { + return { value: "iks", accuracy: 80 }; + } + + if (isAKS(cluster)) { + return { value: "aks", accuracy: 80 }; + } + + if (isDigitalOcean(cluster)) { + return { value: "digitalocean", accuracy: 90 }; + } + + if (isK0s(version)) { + return { value: "k0s", accuracy: 80 }; + } + + if (isVMWare(version)) { + return { value: "vmware", accuracy: 90 }; + } + + if (isMirantis(version)) { + return { value: "mirantis", accuracy: 90 }; + } + + if (isAlibaba(version)) { + return { value: "alibaba", accuracy: 90 }; + } + + if (isHuawei(version)) { + return { value: "huawei", accuracy: 90 }; + } + + if (isTke(version)) { + return { value: "tencent", accuracy: 90 }; + } + + if (isMinikube(cluster)) { + return { value: "minikube", accuracy: 80 }; + } + + if (isMicrok8s(cluster)) { + return { value: "microk8s", accuracy: 80 }; + } + + if (isKind(cluster)) { + return { value: "kind", accuracy: 70 }; + } + + if (isDockerDesktop(cluster)) { + return { value: "docker-desktop", accuracy: 80 }; + } + + if (isCustom(version)) { + if (await isOpenshift(cluster)) { + return { value: "openshift", accuracy: 90 }; + } + + return { value: "custom", accuracy: 10 }; + } + + return { value: "unknown", accuracy: 10 }; + }, + }; + }, + injectionToken: clusterMetadataDetectorInjectionToken, +}); + +export default clusterDistributionDetectorInjectable; + diff --git a/src/main/cluster-detectors/cluster-id-detector.injectable.ts b/src/main/cluster-detectors/cluster-id-detector.injectable.ts new file mode 100644 index 0000000000..ea63bbac18 --- /dev/null +++ b/src/main/cluster-detectors/cluster-id-detector.injectable.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { clusterMetadataDetectorInjectionToken } from "./base-cluster-detector"; +import { createHash } from "crypto"; +import { ClusterMetadataKey } from "../../common/cluster-types"; +import { getInjectable } from "@ogre-tools/injectable"; +import k8SRequestInjectable from "../k8s-request.injectable"; +import type { Cluster } from "../../common/cluster/cluster"; + +const clusterIdDetectorFactoryInjectable = getInjectable({ + id: "cluster-id-detector-factory", + instantiate: (di) => { + const k8sRequest = di.inject(k8SRequestInjectable); + const getDefaultNamespaceId = async (cluster: Cluster) => { + const { metadata } = await k8sRequest(cluster, "/api/v1/namespaces/default") as { metadata: { uid: string }}; + + return metadata.uid; + }; + + return { + key: ClusterMetadataKey.CLUSTER_ID, + detect: async (cluster) => { + let id: string; + + try { + id = await getDefaultNamespaceId(cluster); + } catch(_) { + id = cluster.apiUrl; + } + const value = createHash("sha256").update(id).digest("hex"); + + return { value, accuracy: 100 }; + }, + }; + }, + injectionToken: clusterMetadataDetectorInjectionToken, +}); + +export default clusterIdDetectorFactoryInjectable; + diff --git a/src/main/cluster-detectors/cluster-id-detector.ts b/src/main/cluster-detectors/cluster-id-detector.ts deleted file mode 100644 index 8f1486fcb1..0000000000 --- a/src/main/cluster-detectors/cluster-id-detector.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { BaseClusterDetector } from "./base-cluster-detector"; -import { createHash } from "crypto"; -import { ClusterMetadataKey } from "../../common/cluster-types"; - -export class ClusterIdDetector extends BaseClusterDetector { - key = ClusterMetadataKey.CLUSTER_ID; - - public async detect() { - let id: string; - - try { - id = await this.getDefaultNamespaceId(); - } catch(_) { - id = this.cluster.apiUrl; - } - const value = createHash("sha256").update(id).digest("hex"); - - return { value, accuracy: 100 }; - } - - protected async getDefaultNamespaceId() { - const response = await this.k8sRequest("/api/v1/namespaces/default"); - - return response.metadata.uid; - } -} diff --git a/src/main/cluster-detectors/cluster-last-seen-detector.injectable.ts b/src/main/cluster-detectors/cluster-last-seen-detector.injectable.ts new file mode 100644 index 0000000000..a4f1d61a13 --- /dev/null +++ b/src/main/cluster-detectors/cluster-last-seen-detector.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { clusterMetadataDetectorInjectionToken } from "./base-cluster-detector"; +import { ClusterMetadataKey } from "../../common/cluster-types"; +import { getInjectable } from "@ogre-tools/injectable"; +import requestClusterVersionInjectable from "./request-cluster-version.injectable"; + +const clusterLastSeenDetectorInjectable = getInjectable({ + id: "cluster-last-seen-detector", + instantiate: (di) => { + const requestClusterVersion = di.inject(requestClusterVersionInjectable); + + return { + key: ClusterMetadataKey.LAST_SEEN, + detect: async (cluster) => { + try { + await requestClusterVersion(cluster); + + return { value: new Date().toJSON(), accuracy: 100 }; + } catch { + return null; + } + }, + }; + }, + injectionToken: clusterMetadataDetectorInjectionToken, +}); + +export default clusterLastSeenDetectorInjectable; diff --git a/src/main/cluster-detectors/cluster-nodes-count-detector.injectable.ts b/src/main/cluster-detectors/cluster-nodes-count-detector.injectable.ts new file mode 100644 index 0000000000..023398d46e --- /dev/null +++ b/src/main/cluster-detectors/cluster-nodes-count-detector.injectable.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { clusterMetadataDetectorInjectionToken } from "./base-cluster-detector"; +import { ClusterMetadataKey } from "../../common/cluster-types"; +import { getInjectable } from "@ogre-tools/injectable"; +import k8SRequestInjectable from "../k8s-request.injectable"; +import type { Cluster } from "../../common/cluster/cluster"; + +const clusterNodeCountDetectorInjectable = getInjectable({ + id: "cluster-node-count-detector", + instantiate: (di) => { + const k8sRequest = di.inject(k8SRequestInjectable); + const requestNodeCount = async (cluster: Cluster) => { + const { items } = await k8sRequest(cluster, "/api/v1/nodes") as { items: unknown[] }; + + return items.length; + }; + + return { + key: ClusterMetadataKey.NODES_COUNT, + detect: async (cluster) => { + try { + return { + value: await requestNodeCount(cluster), + accuracy: 1000, + }; + } catch { + return null; + } + }, + }; + }, + injectionToken: clusterMetadataDetectorInjectionToken, +}); + +export default clusterNodeCountDetectorInjectable; + diff --git a/src/main/cluster-detectors/cluster-version-detector.injectable.ts b/src/main/cluster-detectors/cluster-version-detector.injectable.ts new file mode 100644 index 0000000000..ff36fb6a95 --- /dev/null +++ b/src/main/cluster-detectors/cluster-version-detector.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { clusterMetadataDetectorInjectionToken } from "./base-cluster-detector"; +import { ClusterMetadataKey } from "../../common/cluster-types"; +import { getInjectable } from "@ogre-tools/injectable"; +import requestClusterVersionInjectable from "./request-cluster-version.injectable"; + +const clusterVersionDetectorInjectable = getInjectable({ + id: "cluster-version-detector", + instantiate: (di) => { + const requestClusterVersion = di.inject(requestClusterVersionInjectable); + + return { + key: ClusterMetadataKey.VERSION, + detect: async (cluster) => ({ + value: await requestClusterVersion(cluster), + accuracy: 100, + }), + }; + }, + injectionToken: clusterMetadataDetectorInjectionToken, +}); + +export default clusterVersionDetectorInjectable; + diff --git a/src/main/cluster-detectors/create-version-detector.injectable.ts b/src/main/cluster-detectors/create-version-detector.injectable.ts deleted file mode 100644 index efd59dcfc3..0000000000 --- a/src/main/cluster-detectors/create-version-detector.injectable.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { 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; diff --git a/src/main/cluster-detectors/detect-cluster-metadata.injectable.ts b/src/main/cluster-detectors/detect-cluster-metadata.injectable.ts new file mode 100644 index 0000000000..7a1af1c7be --- /dev/null +++ b/src/main/cluster-detectors/detect-cluster-metadata.injectable.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { pipeline } from "@ogre-tools/fp"; +import { getInjectable } from "@ogre-tools/injectable"; +import { groupBy, reduce } from "lodash"; +import { filter, map } from "lodash/fp"; +import type { ClusterMetadata } from "../../common/cluster-types"; +import type { Cluster } from "../../common/cluster/cluster"; +import { hasDefinedTupleValue, isDefined, object } from "../../common/utils"; +import type { ClusterDetectionResult, ClusterMetadataDetector } from "./base-cluster-detector"; +import { clusterMetadataDetectorInjectionToken } from "./base-cluster-detector"; + +export type DetectClusterMetadata = (cluster: Cluster) => Promise; + +const pickHighestAccuracy = (prev: ClusterDetectionResult, curr: ClusterDetectionResult) => ( + prev.accuracy > curr.accuracy + ? prev + : curr +); + +const detectMetadataWithFor = (cluster: Cluster) => async (clusterMetadataDetector: ClusterMetadataDetector) => { + try { + return await clusterMetadataDetector.detect(cluster); + } catch { + return null; + } +}; + +const detectClusterMetadataInjectable = getInjectable({ + id: "detect-cluster-metadata", + instantiate: (di): DetectClusterMetadata => { + const clusterMetadataDetectors = di.injectMany(clusterMetadataDetectorInjectionToken); + + return async (cluster) => { + const entries = pipeline( + await Promise.all(clusterMetadataDetectors.map(detectMetadataWithFor(cluster))), + filter(isDefined), + (arg) => groupBy(arg, "key"), + (arg) => object.entries(arg), + map(([ key, results ]) => [key, reduce(results, pickHighestAccuracy)] as const), + filter(hasDefinedTupleValue), + ); + + return object.fromEntries(entries); + }; + }, +}); + +export default detectClusterMetadataInjectable; diff --git a/src/main/cluster-detectors/detector-registry.injectable.ts b/src/main/cluster-detectors/detector-registry.injectable.ts deleted file mode 100644 index 344e82003f..0000000000 --- a/src/main/cluster-detectors/detector-registry.injectable.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { 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; diff --git a/src/main/cluster-detectors/detector-registry.ts b/src/main/cluster-detectors/detector-registry.ts deleted file mode 100644 index 395d819850..0000000000 --- a/src/main/cluster-detectors/detector-registry.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { observable } from "mobx"; -import type { ClusterMetadata } from "../../common/cluster-types"; -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 type DetectorConstructor = new (cluster: Cluster, k8sRequest: K8sRequest) => BaseClusterDetector; - -export class DetectorRegistry { - constructor(private dependencies: Dependencies) {} - - registry = observable.array([], { deep: false }); - - add(detectorClass: DetectorConstructor): this { - this.registry.push(detectorClass); - - return this; - } - - async detectForCluster(cluster: Cluster): Promise { - const results: { [key: string]: ClusterDetectionResult } = {}; - - for (const detectorClass of this.registry) { - const detector = new detectorClass(cluster, this.dependencies.k8sRequest); - - try { - const data = await detector.detect(); - - if (!data) continue; - const existingValue = results[detector.key]; - - if (existingValue && existingValue.accuracy > data.accuracy) continue; // previous value exists and is more accurate - results[detector.key] = data; - } catch (e) { - // detector raised error, do nothing - } - } - const metadata: ClusterMetadata = {}; - - for (const [key, result] of Object.entries(results)) { - metadata[key] = result.value; - } - - return metadata; - } -} diff --git a/src/main/cluster-detectors/distribution-detector.ts b/src/main/cluster-detectors/distribution-detector.ts deleted file mode 100644 index dcdacfb253..0000000000 --- a/src/main/cluster-detectors/distribution-detector.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../../common/cluster-types"; - -export class DistributionDetector extends BaseClusterDetector { - key = ClusterMetadataKey.DISTRIBUTION; - - public async detect() { - const version = await this.getKubernetesVersion(); - - if (this.isRke(version)) { - return { value: "rke", accuracy: 80 }; - } - - if (this.isRancherDesktop()) { - return { value: "rancher-desktop", accuracy: 80 }; - } - - if (this.isK3s(version)) { - return { value: "k3s", accuracy: 80 }; - } - - if (this.isGKE(version)) { - return { value: "gke", accuracy: 80 }; - } - - if (this.isEKS(version)) { - return { value: "eks", accuracy: 80 }; - } - - if (this.isIKS(version)) { - return { value: "iks", accuracy: 80 }; - } - - if (this.isAKS()) { - return { value: "aks", accuracy: 80 }; - } - - if (this.isDigitalOcean()) { - return { value: "digitalocean", accuracy: 90 }; - } - - if (this.isK0s(version)) { - return { value: "k0s", accuracy: 80 }; - } - - if (this.isVMWare(version)) { - return { value: "vmware", accuracy: 90 }; - } - - if (this.isMirantis(version)) { - return { value: "mirantis", accuracy: 90 }; - } - - if (this.isAlibaba(version)) { - return { value: "alibaba", accuracy: 90 }; - } - - if (this.isHuawei(version)) { - return { value: "huawei", accuracy: 90 }; - } - - if (this.isTke(version)) { - return { value: "tencent", accuracy: 90 }; - } - - if (this.isMinikube()) { - return { value: "minikube", accuracy: 80 }; - } - - if (this.isMicrok8s()) { - return { value: "microk8s", accuracy: 80 }; - } - - if (this.isKind()) { - return { value: "kind", accuracy: 70 }; - } - - if (this.isDockerDesktop()) { - return { value: "docker-desktop", accuracy: 80 }; - } - - if (this.isCustom(version) && await this.isOpenshift()) { - return { value: "openshift", accuracy: 90 }; - } - - if (this.isCustom(version)) { - return { value: "custom", accuracy: 10 }; - } - - return { value: "unknown", accuracy: 10 }; - } - - public async getKubernetesVersion() { - const response = await this.k8sRequest("/version"); - - return response.gitVersion; - } - - protected isGKE(version: string) { - return version.includes("gke"); - } - - protected isEKS(version: string) { - return version.includes("eks"); - } - - protected isIKS(version: string) { - return version.includes("IKS"); - } - - protected isAKS() { - return this.cluster.apiUrl.includes("azmk8s.io"); - } - - protected isMirantis(version: string) { - return version.includes("-mirantis-") || version.includes("-docker-"); - } - - protected isDigitalOcean() { - return this.cluster.apiUrl.endsWith("k8s.ondigitalocean.com"); - } - - protected isMinikube() { - return this.cluster.contextName.startsWith("minikube"); - } - - protected isMicrok8s() { - return this.cluster.contextName.startsWith("microk8s"); - } - - protected isKind() { - return this.cluster.contextName.startsWith("kubernetes-admin@kind-"); - } - - protected isDockerDesktop() { - return this.cluster.contextName === "docker-desktop"; - } - - protected isTke(version: string) { - return version.includes("-tke."); - } - - protected isCustom(version: string) { - return version.includes("+"); - } - - protected isVMWare(version: string) { - return version.includes("+vmware"); - } - - protected isRke(version: string) { - return version.includes("-rancher"); - } - - protected isRancherDesktop() { - return this.cluster.contextName === "rancher-desktop"; - } - - protected isK3s(version: string) { - return version.includes("+k3s"); - } - - protected isK0s(version: string) { - return version.includes("-k0s") || version.includes("+k0s"); - } - - protected isAlibaba(version: string) { - return version.includes("-aliyun"); - } - - protected isHuawei(version: string) { - return version.includes("-CCE"); - } - - protected async isOpenshift() { - try { - const response = await this.k8sRequest(""); - - return response.paths?.includes("/apis/project.openshift.io"); - } catch (e) { - return false; - } - } -} diff --git a/src/main/cluster-detectors/last-seen-detector.ts b/src/main/cluster-detectors/last-seen-detector.ts deleted file mode 100644 index 087557f083..0000000000 --- a/src/main/cluster-detectors/last-seen-detector.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../../common/cluster-types"; - -export class LastSeenDetector extends BaseClusterDetector { - key = ClusterMetadataKey.LAST_SEEN; - - public async detect() { - if (!this.cluster.accessible) return null; - - await this.k8sRequest("/version"); - - return { value: new Date().toJSON(), accuracy: 100 }; - } -} diff --git a/src/main/cluster-detectors/nodes-count-detector.ts b/src/main/cluster-detectors/nodes-count-detector.ts deleted file mode 100644 index f9b3c02dbf..0000000000 --- a/src/main/cluster-detectors/nodes-count-detector.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../../common/cluster-types"; - -export class NodesCountDetector extends BaseClusterDetector { - key = ClusterMetadataKey.NODES_COUNT; - - public async detect() { - if (!this.cluster.accessible) return null; - const nodeCount = await this.getNodeCount(); - - return { value: nodeCount, accuracy: 100 }; - } - - protected async getNodeCount(): Promise { - const response = await this.k8sRequest("/api/v1/nodes"); - - return response.items.length; - } -} diff --git a/src/main/cluster-detectors/request-cluster-version.injectable.ts b/src/main/cluster-detectors/request-cluster-version.injectable.ts new file mode 100644 index 0000000000..3863e75a0c --- /dev/null +++ b/src/main/cluster-detectors/request-cluster-version.injectable.ts @@ -0,0 +1,22 @@ +/** + * 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 k8SRequestInjectable from "../k8s-request.injectable"; + +const requestClusterVersionInjectable = getInjectable({ + id: "request-cluster-version", + instantiate: (di) => { + const k8sRequest = di.inject(k8SRequestInjectable); + + return async (cluster: Cluster) => { + const { gitVersion } = await k8sRequest(cluster, "/version") as { gitVersion: string }; + + return gitVersion; + }; + }, +}); + +export default requestClusterVersionInjectable; diff --git a/src/main/cluster-detectors/version-detector.ts b/src/main/cluster-detectors/version-detector.ts deleted file mode 100644 index cc228734c5..0000000000 --- a/src/main/cluster-detectors/version-detector.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { BaseClusterDetector } from "./base-cluster-detector"; -import { ClusterMetadataKey } from "../../common/cluster-types"; - -export class VersionDetector extends BaseClusterDetector { - key = ClusterMetadataKey.VERSION; - - public async detect() { - const version = await this.getKubernetesVersion(); - - return { value: version, accuracy: 100 }; - } - - public async getKubernetesVersion() { - const response = await this.k8sRequest("/version"); - - return response.gitVersion; - } -} diff --git a/src/main/cluster/manager.ts b/src/main/cluster/manager.ts index 83270941d4..f67de0d0d7 100644 --- a/src/main/cluster/manager.ts +++ b/src/main/cluster/manager.ts @@ -4,13 +4,11 @@ */ import "../../common/ipc/cluster"; -import type http from "http"; import type { IObservableValue, ObservableSet } from "mobx"; import { action, makeObservable, observe, reaction, toJS } from "mobx"; import type { Cluster } from "../../common/cluster/cluster"; import logger from "../logger"; -import { apiKubePrefix } from "../../common/vars"; -import { getClusterIdFromHost, isErrnoException } from "../../common/utils"; +import { isErrnoException } from "../../common/utils"; import type { KubernetesClusterPrometheusMetrics } from "../../common/catalog-entities/kubernetes-cluster"; import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster"; import { ipcMainOn } from "../../common/ipc"; @@ -259,27 +257,6 @@ export class ClusterManager { cluster.disconnect(); }); } - - getClusterForRequest = (req: http.IncomingMessage): Cluster | undefined => { - if (!req.headers.host) { - return undefined; - } - - // lens-server is connecting to 127.0.0.1:/ - if (req.url && req.headers.host.startsWith("127.0.0.1")) { - const clusterId = req.url.split("/")[1]; - const cluster = this.dependencies.store.getById(clusterId); - - if (cluster) { - // we need to swap path prefix so that request is proxied to kube api - req.url = req.url.replace(`/${clusterId}`, apiKubePrefix); - } - - return cluster; - } - - return this.dependencies.store.getById(getClusterIdFromHost(req.headers.host)); - }; } export function catalogEntityFromCluster(cluster: Cluster) { diff --git a/src/main/create-cluster/create-cluster.injectable.ts b/src/main/create-cluster/create-cluster.injectable.ts index 86ae7011b0..94ab85c936 100644 --- a/src/main/create-cluster/create-cluster.injectable.ts +++ b/src/main/create-cluster/create-cluster.injectable.ts @@ -14,11 +14,11 @@ import createAuthorizationReviewInjectable from "../../common/cluster/create-aut import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; import createListApiResourcesInjectable from "../cluster/request-api-resources.injectable"; import loggerInjectable from "../../common/logger.injectable"; -import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable"; -import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable"; import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; import loadConfigfromFileInjectable from "../../common/kube-helpers/load-config-from-file.injectable"; import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable"; +import clusterVersionDetectorInjectable from "../cluster-detectors/cluster-version-detector.injectable"; +import detectClusterMetadataInjectable from "../cluster-detectors/detect-cluster-metadata.injectable"; const createClusterInjectable = getInjectable({ id: "create-cluster", @@ -34,10 +34,10 @@ const createClusterInjectable = getInjectable({ requestApiResources: di.inject(createListApiResourcesInjectable), createListNamespaces: di.inject(listNamespacesInjectable), logger: di.inject(loggerInjectable), - detectorRegistry: di.inject(detectorRegistryInjectable), - createVersionDetector: di.inject(createVersionDetectorInjectable), broadcastMessage: di.inject(broadcastMessageInjectable), loadConfigfromFile: di.inject(loadConfigfromFileInjectable), + clusterVersionDetector: di.inject(clusterVersionDetectorInjectable), + detectClusterMetadata: di.inject(detectClusterMetadataInjectable), }; return (model, configData) => new Cluster(dependencies, model, configData); diff --git a/src/main/get-metrics.injectable.ts b/src/main/get-metrics.injectable.ts index 88eab26e63..12b45b9880 100644 --- a/src/main/get-metrics.injectable.ts +++ b/src/main/get-metrics.injectable.ts @@ -4,7 +4,9 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { Cluster } from "../common/cluster/cluster"; +import nodeFetchModuleInjectable from "../common/fetch/fetch-module.injectable"; import type { RequestMetricsParams } from "../common/k8s-api/endpoints/metrics.api/request-metrics.injectable"; +import { object } from "../common/utils"; import k8sRequestInjectable from "./k8s-request.injectable"; export type GetMetrics = (cluster: Cluster, prometheusPath: string, queryParams: RequestMetricsParams & { query: string }) => Promise; @@ -14,6 +16,7 @@ const getMetricsInjectable = getInjectable({ instantiate: (di): GetMetrics => { const k8sRequest = di.inject(k8sRequestInjectable); + const { FormData } = di.inject(nodeFetchModuleInjectable); return async ( cluster, @@ -22,13 +25,15 @@ const getMetricsInjectable = getInjectable({ ) => { const prometheusPrefix = cluster.preferences.prometheus?.prefix || ""; const metricsPath = `/api/v1/namespaces/${prometheusPath}/proxy${prometheusPrefix}/api/v1/query_range`; + const body = new FormData(); + + for (const [key, value] of object.entries(queryParams)) { + body.set(key, `${value}`); + } return k8sRequest(cluster, metricsPath, { - timeout: 0, - resolveWithFullResponse: false, - json: true, method: "POST", - form: queryParams, + body, }); }; }, diff --git a/src/main/k8s-request.injectable.ts b/src/main/k8s-request.injectable.ts index 345b9c58d2..8a7799da09 100644 --- a/src/main/k8s-request.injectable.ts +++ b/src/main/k8s-request.injectable.ts @@ -2,38 +2,45 @@ * 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 { Cluster } from "../common/cluster/cluster"; import { getInjectable } from "@ogre-tools/injectable"; import lensProxyPortInjectable from "./lens-proxy/lens-proxy-port.injectable"; -import { lensAuthenticationHeaderValueInjectionToken } from "../common/auth/header-value"; -import { lensAuthenticationHeader } from "../common/vars/auth-header"; +import type { AuthenticatedRequestInit } from "../common/fetch/lens-authed-fetch.injectable"; +import lensAuthenticatedFetchInjectable from "../common/fetch/lens-authed-fetch.injectable"; +import nodeFetchModuleInjectable from "../common/fetch/fetch-module.injectable"; +import { lensClusterIdHeader } from "../common/vars/auth-header"; -export type K8sRequest = (cluster: Cluster, path: string, options?: RequestPromiseOptions) => Promise; +export type K8sRequest = (cluster: Cluster, path: string, options?: AuthenticatedRequestInit) => Promise; const k8sRequestInjectable = getInjectable({ id: "k8s-request", - instantiate: (di) => { + instantiate: (di): K8sRequest => { const lensProxyPort = di.inject(lensProxyPortInjectable); - const lensAuthenticationHeaderValue = di.inject(lensAuthenticationHeaderValueInjectionToken); + const fetch = di.inject(lensAuthenticatedFetchInjectable); + const { Headers } = di.inject(nodeFetchModuleInjectable); - return async ( - cluster: Cluster, - path: string, - options: RequestPromiseOptions = {}, - ) => { + return async (cluster, path, init = {}) => { const kubeProxyUrl = `https://localhost:${lensProxyPort.get()}${apiKubePrefix}`; + const headers = new Headers(init.headers); - options.headers ??= {}; - options.json ??= true; - options.timeout ??= 30000; - options.headers.Host = `${cluster.id}.${new URL(kubeProxyUrl).host}`; // required in ClusterManager.getClusterForRequest() - options.headers[lensAuthenticationHeader] = lensAuthenticationHeaderValue; + headers.set(lensClusterIdHeader, cluster.id); - return request(kubeProxyUrl + path, options); + const response = await fetch(kubeProxyUrl + path, { + ...init, + headers, + }); + + if (200 <= response.status && response.status < 300) { + const body = await response.text(); + + console.log(body); + + return JSON.parse(body); + } + + throw new Error(`${(init.method ?? "GET").toUpperCase()} ${path} failed: ${response.statusText}`); }; }, }); diff --git a/src/main/lens-proxy/get-cluster-for-request.injectable.ts b/src/main/lens-proxy/get-cluster-for-request.injectable.ts new file mode 100644 index 0000000000..8d21f1fe60 --- /dev/null +++ b/src/main/lens-proxy/get-cluster-for-request.injectable.ts @@ -0,0 +1,46 @@ +/** + * 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 { IncomingMessage } from "http"; +import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; +import type { Cluster } from "../../common/cluster/cluster"; +import { apiKubePrefix } from "../../common/vars"; +import { lensClusterIdHeader } from "../../common/vars/auth-header"; + +export type GetClusterForRequest = (req: IncomingMessage) => Cluster | undefined; + +const getClusterForRequestInjectable = getInjectable({ + id: "get-cluster-for-request", + instantiate: (di): GetClusterForRequest => { + const getClusterById = di.inject(getClusterByIdInjectable); + + return (req) => { + const clusterId = req.headers[lensClusterIdHeader.toLowerCase()]; + + console.log(clusterId); + + if (typeof clusterId === "string") { + return getClusterById(clusterId); + } + + // lens-server is connecting to 127.0.0.1:/ + if (req.url && req.headers.host?.startsWith("127.0.0.1")) { + const clusterId = req.url.split("/")[1]; + const cluster = getClusterById(clusterId); + + if (cluster) { + // we need to swap path prefix so that request is proxied to kube api + req.url = req.url.replace(`/${clusterId}`, apiKubePrefix); + } + + return cluster; + } + + return undefined; + }; + }, +}); + +export default getClusterForRequestInjectable; diff --git a/src/main/lens-proxy/lens-proxy.injectable.ts b/src/main/lens-proxy/lens-proxy.injectable.ts index a3ad1b18f8..b4f7ee071a 100644 --- a/src/main/lens-proxy/lens-proxy.injectable.ts +++ b/src/main/lens-proxy/lens-proxy.injectable.ts @@ -7,7 +7,6 @@ import { LensProxy } from "./lens-proxy"; import { kubeApiUpgradeRequest } from "./proxy-functions"; import routeRequestInjectable from "../router/route-request.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 lensProxyPortInjectable from "./lens-proxy-port.injectable"; import contentSecurityPolicyInjectable from "../../common/vars/content-security-policy.injectable"; @@ -15,6 +14,7 @@ import emitAppEventInjectable from "../../common/app-event-bus/emit-event.inject import loggerInjectable from "../../common/logger.injectable"; import authHeaderValueInjectable from "./auth-header-value.injectable"; import lensProxyCertificateInjectable from "./certificate.injectable"; +import getClusterForRequestInjectable from "./get-cluster-for-request.injectable"; const lensProxyInjectable = getInjectable({ id: "lens-proxy", @@ -24,7 +24,7 @@ const lensProxyInjectable = getInjectable({ proxy: httpProxy.createProxy(), kubeApiUpgradeRequest, shellApiRequest: di.inject(shellApiRequestInjectable), - getClusterForRequest: di.inject(clusterManagerInjectable).getClusterForRequest, + getClusterForRequest: di.inject(getClusterForRequestInjectable), lensProxyPort: di.inject(lensProxyPortInjectable), contentSecurityPolicy: di.inject(contentSecurityPolicyInjectable), emitAppEvent: di.inject(emitAppEventInjectable), diff --git a/src/main/lens-proxy/lens-proxy.ts b/src/main/lens-proxy/lens-proxy.ts index 7fa886f4aa..35aa2adb08 100644 --- a/src/main/lens-proxy/lens-proxy.ts +++ b/src/main/lens-proxy/lens-proxy.ts @@ -9,7 +9,6 @@ import https from "https"; import type httpProxy from "http-proxy"; import { apiPrefix, apiKubePrefix } from "../../common/vars"; import type { ClusterContextHandler } from "../context-handler/context-handler"; -import type { Cluster } from "../../common/cluster/cluster"; import type { ProxyApiRequestArgs } from "./proxy-functions"; import { getBoolean } from "../utils/parse-query"; import assert from "assert"; @@ -22,15 +21,15 @@ import { contentTypes } from "../router/router-content-types"; import { writeServerResponseFor } from "../router/write-server-response"; import { URL } from "url"; import type { SelfSignedCert } from "selfsigned"; - -type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | undefined; +import type { GetClusterForRequest } from "./get-cluster-for-request.injectable"; export type ServerIncomingMessage = SetRequired; +export type ProxyApiRequest = (args: ProxyApiRequestArgs) => void | Promise; interface Dependencies { getClusterForRequest: GetClusterForRequest; - shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise; - kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise; + shellApiRequest: ProxyApiRequest; + kubeApiUpgradeRequest: ProxyApiRequest; emitAppEvent: EmitAppEvent; routeRequest: RouteRequest; readonly proxy: httpProxy; @@ -260,6 +259,9 @@ export class LensProxy { protected async handleRequest(req: ServerIncomingMessage, res: http.ServerResponse) { const cluster = this.dependencies.getClusterForRequest(req); + + console.log(cluster?.id, req.url, req.headers); + const writeServerResponse = writeServerResponseFor(res); if (cluster) { diff --git a/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts b/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts index 4484716132..511d1fead6 100644 --- a/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts +++ b/src/main/lens-proxy/proxy-functions/kube-api-upgrade-request.ts @@ -7,11 +7,11 @@ import { chunk } from "lodash"; import type { ConnectionOptions } from "tls"; import { connect } from "tls"; import url from "url"; -import type { ProxyApiRequestArgs } from "./types"; +import type { ProxyApiRequest } from "../lens-proxy"; const skipRawHeaders = new Set(["Host", "Authorization"]); -export async function kubeApiUpgradeRequest({ req, socket, head, cluster }: ProxyApiRequestArgs) { +export const kubeApiUpgradeRequest: ProxyApiRequest = async ({ req, socket, head, cluster }) => { const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url; const proxyCa = cluster.contextHandler.resolveAuthProxyCa(); const apiUrl = url.parse(cluster.apiUrl); @@ -62,4 +62,4 @@ export async function kubeApiUpgradeRequest({ req, socket, head, cluster }: Prox socket.on("error", function () { proxySocket.end(); }); -} +}; diff --git a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts b/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts index 550a8d2560..0bb455553e 100644 --- a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts +++ b/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.injectable.ts @@ -3,19 +3,41 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shellApiRequest } from "./shell-api-request"; import shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable"; -import clusterManagerInjectable from "../../../cluster/manager.injectable"; import openShellSessionInjectable from "../../../shell-session/create-shell-session.injectable"; +import type { ProxyApiRequest } from "../../lens-proxy"; +import getClusterForRequestInjectable from "../../get-cluster-for-request.injectable"; +import URLParse from "url-parse"; +import { Server as WebSocketServer } from "ws"; +import loggerInjectable from "../../../../common/logger.injectable"; const shellApiRequestInjectable = getInjectable({ id: "shell-api-request", - instantiate: (di) => shellApiRequest({ - openShellSession: di.inject(openShellSessionInjectable), - authenticateRequest: di.inject(shellRequestAuthenticatorInjectable).authenticate, - clusterManager: di.inject(clusterManagerInjectable), - }), + instantiate: (di): ProxyApiRequest => { + const openShellSession = di.inject(openShellSessionInjectable); + const authenticateRequest = di.inject(shellRequestAuthenticatorInjectable).authenticate; + const getClusterForRequest = di.inject(getClusterForRequestInjectable); + const logger = di.inject(loggerInjectable); + + return ({ req, socket, head }) => { + const cluster = getClusterForRequest(req); + const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true); + + if (!tabId || !cluster || !authenticateRequest(cluster.id, tabId, shellToken)) { + socket.write("Invalid shell request"); + socket.end(); + } else { + const ws = new WebSocketServer({ noServer: true }); + + ws.handleUpgrade(req, socket, head, (websocket) => { + openShellSession({ websocket, cluster, tabId, nodeName }) + .catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${nodeName ? "node" : "local"} shell`, error)); + }); + } + + }; + }, }); export default shellApiRequestInjectable; diff --git a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts b/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts deleted file mode 100644 index f4492e96f6..0000000000 --- a/src/main/lens-proxy/proxy-functions/shell-api-request/shell-api-request.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import logger from "../../../logger"; -import { Server as WebSocketServer } from "ws"; -import type { ProxyApiRequestArgs } from "../types"; -import type { ClusterManager } from "../../../cluster/manager"; -import URLParse from "url-parse"; -import type { ClusterId } from "../../../../common/cluster-types"; -import type { OpenShellSession } from "../../../shell-session/create-shell-session.injectable"; - -interface Dependencies { - authenticateRequest: (clusterId: ClusterId, tabId: string, shellToken: string | undefined) => boolean; - openShellSession: OpenShellSession; - clusterManager: ClusterManager; -} - -export const shellApiRequest = ({ openShellSession, authenticateRequest, clusterManager }: Dependencies) => ({ req, socket, head }: ProxyApiRequestArgs): void => { - const cluster = clusterManager.getClusterForRequest(req); - const { query: { node: nodeName, shellToken, id: tabId }} = new URLParse(req.url, true); - - if (!tabId || !cluster || !authenticateRequest(cluster.id, tabId, shellToken)) { - socket.write("Invalid shell request"); - - return void socket.end(); - } - - const ws = new WebSocketServer({ noServer: true }); - - ws.handleUpgrade(req, socket, head, (websocket) => { - openShellSession({ websocket, cluster, tabId, nodeName }) - .catch(error => logger.error(`[SHELL-SESSION]: failed to open a ${nodeName ? "node" : "local"} shell`, error)); - }); -}; diff --git a/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts index 038059e099..a580928600 100644 --- a/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts @@ -4,13 +4,13 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import createLensWindowInjectable from "./create-lens-window.injectable"; -import lensProxyPortInjectable from "../../../lens-proxy/lens-proxy-port.injectable"; import isMacInjectable from "../../../../common/vars/is-mac.injectable"; import waitUntilBundledExtensionsAreLoadedInjectable from "./wait-until-bundled-extensions-are-loaded.injectable"; import { applicationWindowInjectionToken } from "./application-window-injection-token"; import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; import { runInAction } from "mobx"; import appNameInjectable from "../../../../common/vars/app-name.injectable"; +import windowFilePathInjectable from "../../../../common/vars/window-file-path.injectable"; const createApplicationWindowInjectable = getInjectable({ id: "create-application-window", @@ -24,8 +24,8 @@ const createApplicationWindowInjectable = getInjectable({ const isMac = di.inject(isMacInjectable); const applicationName = di.inject(appNameInjectable); const waitUntilBundledExtensionsAreLoaded = di.inject(waitUntilBundledExtensionsAreLoadedInjectable); - const lensProxyPort = di.inject(lensProxyPortInjectable); const emitAppEvent = di.inject(emitAppEventInjectable); + const windowFilePath = di.inject(windowFilePathInjectable); return createLensWindow({ id, @@ -33,7 +33,7 @@ const createApplicationWindowInjectable = getInjectable({ defaultHeight: 900, defaultWidth: 1440, getContentSource: () => ({ - url: `https://localhost:${lensProxyPort.get()}`, + file: windowFilePath, }), resizable: true, windowFrameUtilitiesAreShown: isMac, diff --git a/src/main/start-main-application/runnables/setup-detector-registry.injectable.ts b/src/main/start-main-application/runnables/setup-detector-registry.injectable.ts deleted file mode 100644 index 0e8b7ffba2..0000000000 --- a/src/main/start-main-application/runnables/setup-detector-registry.injectable.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { ClusterIdDetector } from "../../cluster-detectors/cluster-id-detector"; -import { LastSeenDetector } from "../../cluster-detectors/last-seen-detector"; -import { VersionDetector } from "../../cluster-detectors/version-detector"; -import { DistributionDetector } from "../../cluster-detectors/distribution-detector"; -import { NodesCountDetector } from "../../cluster-detectors/nodes-count-detector"; -import detectorRegistryInjectable from "../../cluster-detectors/detector-registry.injectable"; -import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token"; - -const setupDetectorRegistryInjectable = getInjectable({ - id: "setup-detector-registry", - - instantiate: (di) => { - const detectorRegistry = di.inject(detectorRegistryInjectable); - - return { - id: "setup-detector-registry", - run: () => { - detectorRegistry - .add(ClusterIdDetector) - .add(LastSeenDetector) - .add(VersionDetector) - .add(DistributionDetector) - .add(NodesCountDetector); - }, - }; - }, - - injectionToken: onLoadOfApplicationInjectionToken, -}); - -export default setupDetectorRegistryInjectable; diff --git a/src/main/tray/menu-icon/get-tray-icon-path.injectable.ts b/src/main/tray/menu-icon/get-tray-icon-path.injectable.ts index 9d3d2d70d6..30d2e58858 100644 --- a/src/main/tray/menu-icon/get-tray-icon-path.injectable.ts +++ b/src/main/tray/menu-icon/get-tray-icon-path.injectable.ts @@ -4,6 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import staticFilesDirectoryInjectable from "../../../common/vars/static-files-directory.injectable"; +import isDevelopmentInjectable from "../../../common/vars/is-development.injectable"; import isMacInjectable from "../../../common/vars/is-mac.injectable"; import { camelCase, flow, upperFirst } from "lodash/fp"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; @@ -15,11 +16,12 @@ const getTrayIconPathInjectable = getInjectable({ instantiate: (di) => { const joinPaths = di.inject(joinPathsInjectable); const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable); + const isDevelopment = di.inject(isDevelopmentInjectable); const isMac = di.inject(isMacInjectable); const baseIconDirectory = joinPaths( staticFilesDirectory, - "build/tray", + isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras ); const fileSuffix = isMac ? "Template.png" : ".png"; diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 34474515fe..3a84b4fc67 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -28,6 +28,7 @@ import assert from "assert"; import startFrameInjectable from "./start-frame/start-frame.injectable"; export async function bootstrap(di: DiContainer) { + console.log(window.frameElement); const startFrame = di.inject(startFrameInjectable); await startFrame(); diff --git a/src/renderer/cluster-frame-context/hosted-cluster-id.injectable.ts b/src/renderer/cluster-frame-context/hosted-cluster-id.injectable.ts index 854e004796..1930f4eae4 100644 --- a/src/renderer/cluster-frame-context/hosted-cluster-id.injectable.ts +++ b/src/renderer/cluster-frame-context/hosted-cluster-id.injectable.ts @@ -3,11 +3,21 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { getClusterIdFromHost } from "../../common/utils"; +import { TypedRegEx } from "typed-regex"; + +const clusterIdMatcher = TypedRegEx("^cluster-frame-(?.+)$"); const hostedClusterIdInjectable = getInjectable({ id: "hosted-cluster-id", - instantiate: () => getClusterIdFromHost(location.host), + instantiate: () => { + const { frameElement } = window; + + if (!frameElement) { + return undefined; + } + + return clusterIdMatcher.match(frameElement.id).groups?.clusterId; + }, causesSideEffects: true, }); diff --git a/src/renderer/cluster/create-cluster.injectable.ts b/src/renderer/cluster/create-cluster.injectable.ts index 385dfe8d66..f3a965ec46 100644 --- a/src/renderer/cluster/create-cluster.injectable.ts +++ b/src/renderer/cluster/create-cluster.injectable.ts @@ -29,9 +29,9 @@ const createClusterInjectable = getInjectable({ createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); }, requestNamespaceListPermissionsFor: () => { 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."); }, - requestApiResources: ()=> { throw new Error("Tried to access back-end feature in front-end."); }, - detectorRegistry: undefined as never, - createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); }, + requestApiResources: () => { throw new Error("Tried to access back-end feature in front-end."); }, + clusterVersionDetector: undefined as never, + detectClusterMetadata: () => { throw new Error("Tried to access back-end feature in front-end."); }, }; return (model, configData) => new Cluster(dependencies, model, configData); diff --git a/src/renderer/components/+config-secrets/add-secret-dialog.tsx b/src/renderer/components/+config-secrets/add-secret-dialog.tsx index c839025f02..c06ed52a74 100644 --- a/src/renderer/components/+config-secrets/add-secret-dialog.tsx +++ b/src/renderer/components/+config-secrets/add-secret-dialog.tsx @@ -92,7 +92,7 @@ export class AddSecretDialog extends React.Component { ? [key, processValue(value)] as const : undefined )) - .collect(fromEntries); + .collect(arg => fromEntries(arg)); }; createSecret = async () => { diff --git a/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts b/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts index 3f40455527..649acf6286 100644 --- a/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts +++ b/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts @@ -5,6 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import windowFilePathInjectable from "../../../common/vars/window-file-path.injectable"; import { ClusterFrameHandler } from "./cluster-frame-handler"; import emitClusterVisibilityInjectable from "./emit-cluster-visibility.injectable"; @@ -14,6 +15,7 @@ const clusterFrameHandlerInjectable = getInjectable({ emitClusterVisibility: di.inject(emitClusterVisibilityInjectable), getClusterById: di.inject(getClusterByIdInjectable), logger: di.inject(loggerInjectable), + windowFilePath: di.inject(windowFilePathInjectable), }), }); diff --git a/src/renderer/components/cluster-manager/cluster-frame-handler.ts b/src/renderer/components/cluster-manager/cluster-frame-handler.ts index cc980eb91d..3b3b5a41c8 100644 --- a/src/renderer/components/cluster-manager/cluster-frame-handler.ts +++ b/src/renderer/components/cluster-manager/cluster-frame-handler.ts @@ -6,7 +6,7 @@ import { action, makeObservable, observable, when } from "mobx"; import type { ClusterId } from "../../../common/cluster-types"; import type { Disposer } from "../../utils"; -import { getClusterFrameUrl, onceDefined } from "../../utils"; +import { onceDefined } from "../../utils"; import assert from "assert"; import type { Logger } from "../../../common/logger"; import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable"; @@ -19,6 +19,7 @@ export interface LensView { interface Dependencies { readonly logger: Logger; + readonly windowFilePath: string; getClusterById: GetClusterById; emitClusterVisibility: EmitClusterVisibility; } @@ -57,7 +58,7 @@ export class ClusterFrameHandler { iframe.id = `cluster-frame-${cluster.id}`; iframe.name = cluster.contextName; - iframe.setAttribute("src", getClusterFrameUrl(clusterId)); + iframe.setAttribute("src", this.dependencies.windowFilePath); iframe.addEventListener("load", action(() => { this.dependencies.logger.info(`[LENS-VIEW]: frame for clusterId=${clusterId} has loaded`); const view = this.views.get(clusterId); diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index bd4fa3d81d..02f4f9e64f 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -132,7 +132,10 @@ class NonInjectedKubeObjectListLayout< onDetails, ...layoutProps } = this.props; - const placeholderString = ResourceNames[ResourceKindMap[store.api.kind]] || store.api.kind; + const kind = ResourceKindMap[store.api.kind]; + const placeholderString = kind + ? ResourceNames[kind] || store.api.kind + : store.api.kind; return ( diff --git a/src/renderer/k8s/api-kube.injectable.ts b/src/renderer/k8s/api-kube.injectable.ts index 1bda8a725c..e13f98e725 100644 --- a/src/renderer/k8s/api-kube.injectable.ts +++ b/src/renderer/k8s/api-kube.injectable.ts @@ -12,7 +12,8 @@ import isDevelopmentInjectable from "../../common/vars/is-development.injectable import showErrorNotificationInjectable from "../components/notifications/show-error-notification.injectable"; import windowLocationInjectable from "../../common/k8s-api/window-location.injectable"; import { lensAuthenticationHeaderValueInjectionToken } from "../../common/auth/header-value"; -import { lensAuthenticationHeader } from "../../common/vars/auth-header"; +import { lensAuthenticationHeader, lensClusterIdHeader } from "../../common/vars/auth-header"; +import hostedClusterIdInjectable from "../cluster-frame-context/hosted-cluster-id.injectable"; const apiKubeInjectable = getInjectable({ id: "api-kube", @@ -23,6 +24,9 @@ const apiKubeInjectable = getInjectable({ const showErrorNotification = di.inject(showErrorNotificationInjectable); const { port, host } = di.inject(windowLocationInjectable); const lensAuthenticationHeaderValue = di.inject(lensAuthenticationHeaderValueInjectionToken); + const hostedClusterId = di.inject(hostedClusterIdInjectable); + + assert(hostedClusterId); const apiKube = createKubeJsonApi({ serverAddress: `https://127.0.0.1:${port}`, @@ -32,6 +36,7 @@ const apiKubeInjectable = getInjectable({ headers: { "Host": host, [lensAuthenticationHeader]: lensAuthenticationHeaderValue, + [lensClusterIdHeader]: hostedClusterId, }, }); diff --git a/src/renderer/routes/extension-page-parameters.injectable.ts b/src/renderer/routes/extension-page-parameters.injectable.ts index 2c9fdb986b..1f55a27ee6 100644 --- a/src/renderer/routes/extension-page-parameters.injectable.ts +++ b/src/renderer/routes/extension-page-parameters.injectable.ts @@ -23,16 +23,15 @@ const extensionPageParametersInjectable = getInjectable({ const createPageParam = di.inject(createPageParamInjectable); return pipeline( - registration.params ?? {}, - Object.entries, + object.entries(registration.params ?? {}), map(([key, value]): [string, PageParamInit] => [ - key, + `${key}`, typeof value === "string" - ? convertStringToPageParamInit(key, value) - : convertPartialPageParamInitToFull(key, value), + ? convertStringToPageParamInit(`${key}`, value) + : convertPartialPageParamInitToFull(`${key}`, value), ]), map(([key, value]) => [key, createPageParam(value)] as const), - object.fromEntries, + arg => object.fromEntries(arg), ); }, @@ -46,7 +45,7 @@ const extensionPageParametersInjectable = getInjectable({ const convertPartialPageParamInitToFull = ( key: string, - value: PageParamInit, + value: Omit, "name">, ): PageParamInit => ({ name: key, defaultValue: value.defaultValue, diff --git a/src/renderer/routes/get-extension-page-parameters.injectable.ts b/src/renderer/routes/get-extension-page-parameters.injectable.ts index fec71c0095..dc829d3d5c 100644 --- a/src/renderer/routes/get-extension-page-parameters.injectable.ts +++ b/src/renderer/routes/get-extension-page-parameters.injectable.ts @@ -7,7 +7,7 @@ import type { PageParam } from "../navigation"; import type { ExtensionPageParametersInstantiationParam } from "./extension-page-parameters.injectable"; import extensionPageParametersInjectable from "./extension-page-parameters.injectable"; -export type GetExtensionPageParameters = (param: ExtensionPageParametersInstantiationParam) => Record>; +export type GetExtensionPageParameters = (param: ExtensionPageParametersInstantiationParam) => Partial>>; const getExtensionPageParametersInjectable = getInjectable({ id: "get-extension-page-parameters", diff --git a/webpack/vars.ts b/webpack/vars.ts index fd0b4bcee9..14d22ade9e 100644 --- a/webpack/vars.ts +++ b/webpack/vars.ts @@ -18,7 +18,7 @@ export const appName = isDevelopment ? `${packageInfo.productName}Dev` : packageInfo.productName; export const htmlTemplate = path.resolve(rendererDir, "template.html"); -export const publicPath = "/build/"; +export const publicPath = `${path.resolve(`${__dirname}/../static/build`)}/`; export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss"); export const webpackDevServerPort = Number(process.env.WEBPACK_DEV_SERVER_PORT) || 9191; diff --git a/yarn.lock b/yarn.lock index 27ab4129b2..95b684dfef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2512,14 +2512,7 @@ resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.29.tgz#68ccecec3d4ffdafb9c577fe764f912afc050fe6" integrity sha512-QSvevZ+IRww2ldtfv1QskYsqVVVwCKQf1XbwtcyyoRvLIQzfyPhj/C+3+PKzSDRdiyejaiLgnq//XTkleorpLg== -"@types/request-promise-native@^1.0.18": - version "1.0.18" - resolved "https://registry.yarnpkg.com/@types/request-promise-native/-/request-promise-native-1.0.18.tgz#437ee2d0b772e01c9691a983b558084b4b3efc2c" - integrity sha512-tPnODeISFc/c1LjWyLuZUY+Z0uLB3+IMfNoQyDEi395+j6kTFTTRAqjENjoPJUid4vHRGEozoTrcTrfZM+AcbA== - dependencies: - "@types/request" "*" - -"@types/request@*", "@types/request@^2.47.1", "@types/request@^2.48.7": +"@types/request@^2.47.1": version "2.48.8" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.8.tgz#0b90fde3b655ab50976cb8c5ac00faca22f5a82c" integrity sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ== @@ -11021,23 +11014,7 @@ repeat-string@^1.5.2, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== -request-promise-core@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" - integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== - dependencies: - lodash "^4.17.19" - -request-promise-native@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" - integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== - dependencies: - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.88.0, request@^2.88.2: +request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -11843,11 +11820,6 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= - stream-buffers@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" @@ -12356,14 +12328,6 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -12373,6 +12337,14 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" @@ -12963,7 +12935,7 @@ webpack-dev-middleware@^5.3.1: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@*, webpack-dev-server@^4.11.1: +webpack-dev-server@*: version "4.11.1" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5" integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==