mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Get loading the main page to work
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
03a3adf3bc
commit
c615b77ac5
@ -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"
|
||||
|
||||
@ -8,7 +8,7 @@ import Joi from "joi";
|
||||
/**
|
||||
* JSON serializable metadata type
|
||||
*/
|
||||
export type ClusterMetadata = Record<string, string | number | boolean | object>;
|
||||
export type ClusterMetadata = Partial<Record<string, string | number | boolean | object>>;
|
||||
|
||||
/**
|
||||
* Metadata for cluster's prometheus settings
|
||||
|
||||
@ -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<ClusterStatus> {
|
||||
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);
|
||||
|
||||
@ -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,
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
@ -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,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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}`;
|
||||
}
|
||||
@ -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";
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
* A better typed version of `Object.fromEntries` where the keys are known to
|
||||
* be a specific subset
|
||||
*/
|
||||
export function fromEntries<T>(entries: Iterable<readonly [string, T]>): Partial<Record<string, T>>;
|
||||
export function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): Record<Key, T>;
|
||||
|
||||
export function fromEntries<T, Key extends string>(entries: Iterable<readonly [Key, T]>): Record<Key, T> {
|
||||
return Object.fromEntries(entries) as Record<Key, T>;
|
||||
}
|
||||
|
||||
@ -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<K, V>(pair: [K, V | undefined | null]): pair is [K, V] {
|
||||
export function hasDefinedTupleValue<K, V>(pair: readonly [K, V | undefined | null]): pair is [K, V] {
|
||||
return pair[1] != null;
|
||||
}
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
21
src/common/vars/window-file-path.injectable.ts
Normal file
21
src/common/vars/window-file-path.injectable.ts
Normal 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 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;
|
||||
@ -79,7 +79,7 @@ export class LensRendererExtension extends LensExtension<LensRendererExtensionDe
|
||||
toPairs,
|
||||
map(([key, value]) => [
|
||||
key,
|
||||
normalizedParams[key].stringify(value),
|
||||
normalizedParams[key]?.stringify(value),
|
||||
]),
|
||||
fromPairs,
|
||||
);
|
||||
|
||||
@ -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<ClusterDetectionResult | null>;
|
||||
|
||||
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
||||
return this._k8sRequest(this.cluster, path, options);
|
||||
}
|
||||
export interface ClusterMetadataDetector {
|
||||
readonly key: string;
|
||||
detect(cluster: Cluster): Promise<ClusterDetectionResult | null>;
|
||||
}
|
||||
|
||||
export const clusterMetadataDetectorInjectionToken = getInjectionToken<ClusterMetadataDetector>({
|
||||
id: "cluster-metadata-detector-token",
|
||||
});
|
||||
|
||||
@ -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;
|
||||
|
||||
43
src/main/cluster-detectors/cluster-id-detector.injectable.ts
Normal file
43
src/main/cluster-detectors/cluster-id-detector.injectable.ts
Normal file
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
@ -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<ClusterMetadata>;
|
||||
|
||||
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;
|
||||
@ -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;
|
||||
@ -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<DetectorConstructor>([], { deep: false });
|
||||
|
||||
add(detectorClass: DetectorConstructor): this {
|
||||
this.registry.push(detectorClass);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async detectForCluster(cluster: Cluster): Promise<ClusterMetadata> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 };
|
||||
}
|
||||
}
|
||||
@ -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<number> {
|
||||
const response = await this.k8sRequest("/api/v1/nodes");
|
||||
|
||||
return response.items.length;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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:<port>/<uid>
|
||||
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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<unknown>;
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
@ -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<any>;
|
||||
export type K8sRequest = (cluster: Cluster, path: string, options?: AuthenticatedRequestInit) => Promise<unknown>;
|
||||
|
||||
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}`);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
46
src/main/lens-proxy/get-cluster-for-request.injectable.ts
Normal file
46
src/main/lens-proxy/get-cluster-for-request.injectable.ts
Normal file
@ -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:<port>/<uid>
|
||||
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;
|
||||
@ -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),
|
||||
|
||||
@ -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<http.IncomingMessage, "url" | "method">;
|
||||
export type ProxyApiRequest = (args: ProxyApiRequestArgs) => void | Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
getClusterForRequest: GetClusterForRequest;
|
||||
shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
|
||||
kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise<void>;
|
||||
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) {
|
||||
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
});
|
||||
};
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
@ -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";
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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-(?<clusterId>.+)$");
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -92,7 +92,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
? [key, processValue(value)] as const
|
||||
: undefined
|
||||
))
|
||||
.collect(fromEntries);
|
||||
.collect(arg => fromEntries(arg));
|
||||
};
|
||||
|
||||
createSecret = async () => {
|
||||
|
||||
@ -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),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 (
|
||||
<ItemListLayout<K, false>
|
||||
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -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<unknown>] => [
|
||||
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 = <V>(
|
||||
key: string,
|
||||
value: PageParamInit<V>,
|
||||
value: Omit<PageParamInit<V>, "name">,
|
||||
): PageParamInit<V> => ({
|
||||
name: key,
|
||||
defaultValue: value.defaultValue,
|
||||
|
||||
@ -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<string, PageParam<unknown>>;
|
||||
export type GetExtensionPageParameters = (param: ExtensionPageParametersInstantiationParam) => Partial<Record<string, PageParam<unknown>>>;
|
||||
|
||||
const getExtensionPageParametersInjectable = getInjectable({
|
||||
id: "get-extension-page-parameters",
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
50
yarn.lock
50
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==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user